another hle implements, especially for mutex calls

This commit is contained in:
ParantezTech
2026-03-12 18:01:42 +03:00
parent 73ae502e09
commit 028494a83b
12 changed files with 1133 additions and 252 deletions
@@ -587,6 +587,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
{ {
"_init_env" or "_init_env" or
"atexit" or "atexit" or
"__cxa_guard_acquire" or
"__cxa_guard_release" or
"__cxa_guard_abort" or
"strlen" or "strlen" or
"strnlen" or "strnlen" or
"strcmp" or "strcmp" or
@@ -0,0 +1,10 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Core.Loader;
public readonly record struct ImportedSymbolRelocation(
ulong TargetAddress,
long Addend,
string Nid,
bool IsData);
+4
View File
@@ -16,6 +16,7 @@ public sealed class SelfImage
IReadOnlyList<VirtualMemoryRegion> mappedRegions, IReadOnlyList<VirtualMemoryRegion> mappedRegions,
IReadOnlyDictionary<ulong, string>? importStubs = null, IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
IReadOnlyList<ImportedSymbolRelocation>? importedRelocations = null,
ulong imageBase = 0, ulong imageBase = 0,
ulong procParamAddress = 0) ulong procParamAddress = 0)
{ {
@@ -28,6 +29,7 @@ public sealed class SelfImage
MappedRegions = mappedRegions; MappedRegions = mappedRegions;
ImportStubs = importStubs ?? new Dictionary<ulong, string>(); ImportStubs = importStubs ?? new Dictionary<ulong, string>();
RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal); RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal);
ImportedRelocations = importedRelocations ?? Array.Empty<ImportedSymbolRelocation>();
_imageBase = imageBase; _imageBase = imageBase;
ProcParamAddress = procParamAddress; ProcParamAddress = procParamAddress;
} }
@@ -44,6 +46,8 @@ public sealed class SelfImage
public IReadOnlyDictionary<string, ulong> RuntimeSymbols { get; } public IReadOnlyDictionary<string, ulong> RuntimeSymbols { get; }
public IReadOnlyList<ImportedSymbolRelocation> ImportedRelocations { get; }
public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase; public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase;
public ulong ProcParamAddress { get; } public ulong ProcParamAddress { get; }
+364 -172
View File
@@ -29,6 +29,7 @@ public sealed class SelfLoader : ISelfLoader
private const int ElfRelocationSize = 24; private const int ElfRelocationSize = 24;
private const int ElfSectionHeaderSize = 64; private const int ElfSectionHeaderSize = 64;
private const uint SectionTypeSymbolTable = 2; private const uint SectionTypeSymbolTable = 2;
private const uint SectionTypeRela = 4;
private const long DtNull = 0; private const long DtNull = 0;
private const long DtPltRelSize = 0x02; private const long DtPltRelSize = 0x02;
@@ -65,6 +66,7 @@ public sealed class SelfLoader : ISelfLoader
private const byte SymbolBindLocal = 0; private const byte SymbolBindLocal = 0;
private const byte SymbolBindGlobal = 1; private const byte SymbolBindGlobal = 1;
private const byte SymbolBindWeak = 2; private const byte SymbolBindWeak = 2;
private const byte SymbolTypeObject = 1;
private IModuleManager? _moduleManager; private IModuleManager? _moduleManager;
private uint _nextTlsModuleId = 1; private uint _nextTlsModuleId = 1;
@@ -190,11 +192,13 @@ public sealed class SelfLoader : ISelfLoader
var importStubs = ResolveAndPatchImportStubs( var importStubs = ResolveAndPatchImportStubs(
imageData, imageData,
loadContext, loadContext,
elfHeader,
programHeaders, programHeaders,
virtualMemory, virtualMemory,
imageBase, imageBase,
_moduleManager, _moduleManager,
tlsModuleId); tlsModuleId,
out var importedRelocations);
var effectiveImportStubs = importStubs.Count == 0 var effectiveImportStubs = importStubs.Count == 0
? new Dictionary<ulong, string>() ? new Dictionary<ulong, string>()
: new Dictionary<ulong, string>(importStubs); : new Dictionary<ulong, string>(importStubs);
@@ -246,6 +250,7 @@ public sealed class SelfLoader : ISelfLoader
virtualMemory.SnapshotRegions(), virtualMemory.SnapshotRegions(),
finalizedImportStubs, finalizedImportStubs,
finalizedRuntimeSymbols, finalizedRuntimeSymbols,
importedRelocations,
imageBase, imageBase,
procParamAddress); procParamAddress);
} }
@@ -426,12 +431,15 @@ public sealed class SelfLoader : ISelfLoader
private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs( private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
ElfHeader elfHeader,
IReadOnlyList<ProgramHeader> programHeaders, IReadOnlyList<ProgramHeader> programHeaders,
IVirtualMemory virtualMemory, IVirtualMemory virtualMemory,
ulong imageBase, ulong imageBase,
IModuleManager? moduleManager, IModuleManager? moduleManager,
uint tlsModuleId) uint tlsModuleId,
out IReadOnlyList<ImportedSymbolRelocation> importedRelocations)
{ {
importedRelocations = Array.Empty<ImportedSymbolRelocation>();
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex)) if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex))
{ {
return EmptyImportStubs; return EmptyImportStubs;
@@ -447,10 +455,18 @@ public sealed class SelfLoader : ISelfLoader
throw new NotSupportedException("Dynamic metadata segments larger than 2 GB are not currently supported."); throw new NotSupportedException("Dynamic metadata segments larger than 2 GB are not currently supported.");
} }
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex); if (!TryLoadDynamicTableBytes(
EnsureRange(imageData.Length, dynamicOffset, dynamicHeader.FileSize); imageData,
loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return EmptyImportStubs;
}
var dynamicTable = imageData.Slice((int)dynamicOffset, (int)dynamicHeader.FileSize);
var elfData = imageData; var elfData = imageData;
var dynamicInfo = ParseDynamicInfo(dynamicTable); var dynamicInfo = ParseDynamicInfo(dynamicTable);
@@ -463,13 +479,6 @@ public sealed class SelfLoader : ISelfLoader
Console.WriteLine($"[LOADER] TLS module id: {tlsModuleId}"); Console.WriteLine($"[LOADER] TLS module id: {tlsModuleId}");
Console.WriteLine($"[LOADER] HasImportMetadata: {dynamicInfo.HasImportMetadata}"); Console.WriteLine($"[LOADER] HasImportMetadata: {dynamicInfo.HasImportMetadata}");
if (!dynamicInfo.HasImportMetadata)
{
Console.WriteLine($"[LOADER] No import metadata found in ELF!");
return EmptyImportStubs;
}
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
var relocations = new List<ElfRelocation>(512); var relocations = new List<ElfRelocation>(512);
if (dynamicInfo.RelaSize != 0 && if (dynamicInfo.RelaSize != 0 &&
@@ -484,13 +493,16 @@ public sealed class SelfLoader : ISelfLoader
CollectRelocations(jmpRelBytes, relocations); CollectRelocations(jmpRelBytes, relocations);
} }
if (relocations.Count == 0) if (!dynamicInfo.HasImportMetadata)
{ {
Console.WriteLine($"[LOADER] No relocations found!"); Console.WriteLine($"[LOADER] No import metadata found in ELF!");
return EmptyImportStubs;
} }
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations..."); if (relocations.Count != 0)
{
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations...");
}
uint maxSymbolIndex = 0; uint maxSymbolIndex = 0;
foreach (var relocation in relocations) foreach (var relocation in relocations)
@@ -544,164 +556,37 @@ public sealed class SelfLoader : ISelfLoader
var descriptors = new List<RelocationDescriptor>(256); var descriptors = new List<RelocationDescriptor>(256);
var orderedImportNids = new List<string>(128); var orderedImportNids = new List<string>(128);
var seenImportNids = new HashSet<string>(StringComparer.Ordinal); var seenImportNids = new HashSet<string>(StringComparer.Ordinal);
var skippedUnsupported = 0; AppendRelocationDescriptors(
var skippedUnmapped = 0; relocations,
var skippedSymbolRead = 0; symbolTable,
var skippedNonImportBind = 0; stringTable,
var skippedNameRead = 0; virtualMemory,
var skippedEmptyNid = 0; imageBase,
foreach (var relocation in relocations) tlsModuleId,
descriptors,
orderedImportNids,
seenImportNids);
if (descriptors.Count == 0)
{ {
if (IsFocusRelocationOffset(relocation.Offset, imageBase)) var sectionFallbackRelocCount = AppendSectionRelocationDescriptors(
imageData,
loadContext,
elfHeader,
virtualMemory,
imageBase,
tlsModuleId,
descriptors,
orderedImportNids,
seenImportNids);
if (sectionFallbackRelocCount != 0)
{ {
Console.Error.WriteLine( Console.WriteLine(
$"[LOADER][FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}"); $"[LOADER] Section relocation fallback recovered {sectionFallbackRelocCount} relocation entries, {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
} }
if (!IsSupportedRelocationType(relocation.Type))
{
skippedUnsupported++;
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocation.Type}");
}
continue;
}
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
{
skippedUnmapped++;
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
}
continue;
}
if (relocation.Type == RelocationTypeRelative)
{
descriptors.Add(new RelocationDescriptor(
targetAddress,
relocation.Addend,
null,
imageBase,
RelocationValueKind.Pointer));
continue;
}
if (relocation.Type == RelocationTypeTlsModuleId)
{
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
descriptors.Add(new RelocationDescriptor(
targetAddress,
0,
null,
dtpmodValue,
RelocationValueKind.TlsModuleId));
continue;
}
var symbolIndex = relocation.SymbolIndex;
ElfSymbol symbol;
if (symbolIndex == 0)
{
symbol = default;
}
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
{
skippedSymbolRead++;
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
}
continue;
}
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
var symbolBind = GetSymbolBind(symbol.Info);
if (symbolBind == SymbolBindLocal)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer));
continue;
}
if (symbol.Value != 0)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer));
continue;
}
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
{
skippedNonImportBind++;
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
}
continue;
}
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
{
skippedNameRead++;
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
}
continue;
}
var nid = ExtractNid(symbolName);
if (string.IsNullOrWhiteSpace(nid))
{
skippedEmptyNid++;
continue;
}
if (seenImportNids.Add(nid))
{
orderedImportNids.Add(nid);
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
nid,
0,
RelocationValueKind.Pointer));
} }
Console.WriteLine($"[LOADER] Found {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors"); Console.WriteLine($"[LOADER] Found {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
Console.WriteLine(
$"[LOADER] Reloc skip stats: unsupported={skippedUnsupported}, unmapped={skippedUnmapped}, symbolRead={skippedSymbolRead}, nonImportBind={skippedNonImportBind}, nameRead={skippedNameRead}, emptyNid={skippedEmptyNid}");
if (descriptors.Count == 0) if (descriptors.Count == 0)
{ {
@@ -709,6 +594,8 @@ public sealed class SelfLoader : ISelfLoader
return EmptyImportStubs; return EmptyImportStubs;
} }
importedRelocations = BuildImportedRelocations(descriptors);
var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids); var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids);
Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs"); Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs");
@@ -784,6 +671,237 @@ public sealed class SelfLoader : ISelfLoader
return stubsByAddress; return stubsByAddress;
} }
private static int AppendSectionRelocationDescriptors(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
ElfHeader elfHeader,
IVirtualMemory virtualMemory,
ulong imageBase,
uint tlsModuleId,
ICollection<RelocationDescriptor> descriptors,
IList<string> orderedImportNids,
ISet<string> seenImportNids)
{
if (elfHeader.SectionHeaderOffset == 0 ||
elfHeader.SectionHeaderCount == 0 ||
elfHeader.SectionHeaderEntrySize < ElfSectionHeaderSize)
{
return 0;
}
var appendedRelocations = 0;
for (var sectionIndex = 0; sectionIndex < elfHeader.SectionHeaderCount; sectionIndex++)
{
if (!TryReadSectionHeader(imageData, loadContext, elfHeader, sectionIndex, out var relocationHeader) ||
relocationHeader.Type != SectionTypeRela ||
relocationHeader.Size == 0 ||
relocationHeader.EntrySize < ElfRelocationSize)
{
continue;
}
if (!TryReadElfRelativeSlice(imageData, loadContext, relocationHeader.Offset, relocationHeader.Size, out var relocationTable))
{
continue;
}
var relocations = new List<ElfRelocation>(checked((int)(relocationHeader.Size / relocationHeader.EntrySize)));
CollectRelocations(relocationTable, relocations);
if (relocations.Count == 0)
{
continue;
}
ReadOnlySpan<byte> symbolTable = ReadOnlySpan<byte>.Empty;
ReadOnlySpan<byte> stringTable = ReadOnlySpan<byte>.Empty;
if (relocationHeader.Link < elfHeader.SectionHeaderCount &&
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)relocationHeader.Link, out var symbolHeader) &&
symbolHeader.Size != 0 &&
symbolHeader.EntrySize >= ElfSymbolSize &&
TryReadElfRelativeSlice(imageData, loadContext, symbolHeader.Offset, symbolHeader.Size, out symbolTable) &&
symbolHeader.Link < elfHeader.SectionHeaderCount &&
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)symbolHeader.Link, out var stringHeader) &&
stringHeader.Size != 0 &&
TryReadElfRelativeSlice(imageData, loadContext, stringHeader.Offset, stringHeader.Size, out stringTable))
{
}
AppendRelocationDescriptors(
relocations,
symbolTable,
stringTable,
virtualMemory,
imageBase,
tlsModuleId,
descriptors,
orderedImportNids,
seenImportNids);
appendedRelocations += relocations.Count;
}
return appendedRelocations;
}
private static void AppendRelocationDescriptors(
IReadOnlyList<ElfRelocation> relocations,
ReadOnlySpan<byte> symbolTable,
ReadOnlySpan<byte> stringTable,
IVirtualMemory virtualMemory,
ulong imageBase,
uint tlsModuleId,
ICollection<RelocationDescriptor> descriptors,
IList<string> orderedImportNids,
ISet<string> seenImportNids)
{
foreach (var relocation in relocations)
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine(
$"[LOADER][FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}");
}
if (!IsSupportedRelocationType(relocation.Type))
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocation.Type}");
}
continue;
}
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
}
continue;
}
if (relocation.Type == RelocationTypeRelative)
{
descriptors.Add(new RelocationDescriptor(
targetAddress,
relocation.Addend,
null,
imageBase,
RelocationValueKind.Pointer,
IsDataImport: false));
continue;
}
if (relocation.Type == RelocationTypeTlsModuleId)
{
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
descriptors.Add(new RelocationDescriptor(
targetAddress,
0,
null,
dtpmodValue,
RelocationValueKind.TlsModuleId,
IsDataImport: false));
continue;
}
var symbolIndex = relocation.SymbolIndex;
ElfSymbol symbol;
if (symbolIndex == 0)
{
symbol = default;
}
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
}
continue;
}
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
var symbolBind = GetSymbolBind(symbol.Info);
if (symbolBind == SymbolBindLocal)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer,
IsDataImport: false));
continue;
}
if (symbol.Value != 0)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer,
IsDataImport: false));
continue;
}
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
}
continue;
}
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
}
continue;
}
var nid = ExtractNid(symbolName);
if (string.IsNullOrWhiteSpace(nid))
{
continue;
}
if (seenImportNids.Add(nid))
{
orderedImportNids.Add(nid);
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
nid,
0,
RelocationValueKind.Pointer,
IsDataImport: GetSymbolType(symbol.Info) == SymbolTypeObject));
}
}
private static void RegisterRuntimeSymbolsAndHooks( private static void RegisterRuntimeSymbolsAndHooks(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
@@ -811,6 +929,34 @@ public sealed class SelfLoader : ISelfLoader
} }
} }
private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations(
IReadOnlyList<RelocationDescriptor> descriptors)
{
if (descriptors.Count == 0)
{
return Array.Empty<ImportedSymbolRelocation>();
}
var importedRelocations = new List<ImportedSymbolRelocation>(descriptors.Count);
foreach (var descriptor in descriptors)
{
if (descriptor.ImportNid is null || descriptor.ValueKind != RelocationValueKind.Pointer)
{
continue;
}
importedRelocations.Add(new ImportedSymbolRelocation(
descriptor.TargetAddress,
descriptor.Addend,
descriptor.ImportNid,
descriptor.IsDataImport));
}
return importedRelocations.Count == 0
? Array.Empty<ImportedSymbolRelocation>()
: importedRelocations;
}
private static int RegisterSectionRuntimeSymbols( private static int RegisterSectionRuntimeSymbols(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
@@ -909,9 +1055,18 @@ public sealed class SelfLoader : ISelfLoader
return 0; return 0;
} }
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex); if (!TryLoadDynamicTableBytes(
EnsureRange(imageData.Length, dynamicOffset, dynamicHeader.FileSize); imageData,
var dynamicTable = imageData.Slice((int)dynamicOffset, (int)dynamicHeader.FileSize); loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return 0;
}
var dynamicInfo = ParseDynamicInfo(dynamicTable); var dynamicInfo = ParseDynamicInfo(dynamicTable);
if (dynamicInfo.SymTabOffset == 0 || dynamicInfo.StrTabOffset == 0) if (dynamicInfo.SymTabOffset == 0 || dynamicInfo.StrTabOffset == 0)
{ {
@@ -1044,6 +1199,37 @@ public sealed class SelfLoader : ISelfLoader
return true; return true;
} }
private static bool TryLoadDynamicTableBytes(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
IVirtualMemory virtualMemory,
ulong imageBase,
ProgramHeader dynamicHeader,
int dynamicHeaderIndex,
out ReadOnlySpan<byte> dynamicTable)
{
if (TryLoadTableBytes(
imageData,
virtualMemory,
imageBase,
dynamicHeader.VirtualAddress,
dynamicHeader.FileSize,
out var loadedDynamicTable))
{
dynamicTable = loadedDynamicTable;
return true;
}
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex);
if (!TrySlice(imageData, dynamicOffset, dynamicHeader.FileSize, out dynamicTable))
{
dynamicTable = default;
return false;
}
return true;
}
private static bool TryReadElfRelativeSlice( private static bool TryReadElfRelativeSlice(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
@@ -1512,6 +1698,11 @@ public sealed class SelfLoader : ISelfLoader
return (byte)(info >> 4); return (byte)(info >> 4);
} }
private static byte GetSymbolType(byte info)
{
return (byte)(info & 0x0F);
}
private static bool IsFocusRelocationOffset(ulong relocationOffset, ulong imageBase) private static bool IsFocusRelocationOffset(ulong relocationOffset, ulong imageBase)
{ {
if (relocationOffset >= FocusRelocGuestStart && relocationOffset <= FocusRelocGuestEnd) if (relocationOffset >= FocusRelocGuestStart && relocationOffset <= FocusRelocGuestEnd)
@@ -1947,7 +2138,8 @@ public sealed class SelfLoader : ISelfLoader
long Addend, long Addend,
string? ImportNid, string? ImportNid,
ulong SymbolValue, ulong SymbolValue,
RelocationValueKind ValueKind); RelocationValueKind ValueKind,
bool IsDataImport);
private enum SelfSegmentResolveStatus private enum SelfSegmentResolveStatus
{ {
+87 -5
View File
@@ -138,7 +138,8 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4; var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4;
var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs); var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs);
var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal); var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal);
LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols); var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols);
var processImageName = Path.GetFileName(ebootPath); var processImageName = Path.GetFileName(ebootPath);
if (string.IsNullOrWhiteSpace(processImageName)) if (string.IsNullOrWhiteSpace(processImageName))
{ {
@@ -328,15 +329,16 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
return result; return result;
} }
private void LoadAdjacentSceModules( private List<SelfImage> LoadAdjacentSceModules(
string ebootPath, string ebootPath,
IDictionary<ulong, string> importStubs, IDictionary<ulong, string> importStubs,
IDictionary<string, ulong> runtimeSymbols) IDictionary<string, ulong> runtimeSymbols)
{ {
var loadedImages = new List<SelfImage>();
var ebootDirectory = Path.GetDirectoryName(ebootPath); var ebootDirectory = Path.GetDirectoryName(ebootPath);
if (string.IsNullOrWhiteSpace(ebootDirectory)) if (string.IsNullOrWhiteSpace(ebootDirectory))
{ {
return; return loadedImages;
} }
var moduleDirectories = new[] var moduleDirectories = new[]
@@ -350,7 +352,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
if (moduleDirectories.Length == 0) if (moduleDirectories.Length == 0)
{ {
return; return loadedImages;
} }
var allModulePaths = moduleDirectories var allModulePaths = moduleDirectories
@@ -380,7 +382,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
if (modulePaths.Length == 0) if (modulePaths.Length == 0)
{ {
return; return loadedImages;
} }
Console.Error.WriteLine($"[RUNTIME] Module search directories: {string.Join(", ", moduleDirectories)}"); Console.Error.WriteLine($"[RUNTIME] Module search directories: {string.Join(", ", moduleDirectories)}");
@@ -416,6 +418,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath); mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath);
mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols); mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols);
RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false); RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false);
loadedImages.Add(moduleImage);
loadedModules++; loadedModules++;
Console.Error.WriteLine( Console.Error.WriteLine(
@@ -430,6 +433,67 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
Console.Error.WriteLine( Console.Error.WriteLine(
$"[RUNTIME] Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}"); $"[RUNTIME] Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}");
return loadedImages;
}
private void RebindImportedDataSymbols(
SelfImage mainImage,
IReadOnlyList<SelfImage> loadedModuleImages,
IReadOnlyDictionary<string, ulong> runtimeSymbols)
{
var rebound = 0;
var unresolved = 0;
rebound += RebindImportedDataSymbols(mainImage, runtimeSymbols, ref unresolved);
for (var i = 0; i < loadedModuleImages.Count; i++)
{
rebound += RebindImportedDataSymbols(loadedModuleImages[i], runtimeSymbols, ref unresolved);
}
if (rebound != 0 || unresolved != 0)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data rebind: rebound={rebound}, unresolved={unresolved}");
}
}
private int RebindImportedDataSymbols(
SelfImage image,
IReadOnlyDictionary<string, ulong> runtimeSymbols,
ref int unresolved)
{
if (image.ImportedRelocations.Count == 0)
{
return 0;
}
var rebound = 0;
for (var i = 0; i < image.ImportedRelocations.Count; i++)
{
var relocation = image.ImportedRelocations[i];
if (!relocation.IsData)
{
continue;
}
if (!runtimeSymbols.TryGetValue(relocation.Nid, out var symbolAddress) ||
!IsUsableRuntimeSymbolAddress(symbolAddress))
{
unresolved++;
continue;
}
var reboundValue = AddSigned(symbolAddress, relocation.Addend);
if (!TryWriteUInt64(_virtualMemory, relocation.TargetAddress, reboundValue))
{
unresolved++;
continue;
}
rebound++;
}
return rebound;
} }
private static int MergeImportStubs( private static int MergeImportStubs(
@@ -498,6 +562,24 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
return address >= 0x10000 && !IsUnresolvedRuntimeSentinel(address); return address >= 0x10000 && !IsUnresolvedRuntimeSentinel(address);
} }
private static bool TryWriteUInt64(IVirtualMemory virtualMemory, ulong address, ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
return virtualMemory.TryWrite(address, bytes);
}
private static ulong AddSigned(ulong value, long addend)
{
if (addend >= 0)
{
return unchecked(value + (ulong)addend);
}
var magnitude = unchecked((ulong)(-(addend + 1))) + 1;
return unchecked(value - magnitude);
}
private static bool IsUnresolvedRuntimeSentinel(ulong value) private static bool IsUnresolvedRuntimeSentinel(ulong value)
{ {
return value == 0xFFFEUL || return value == 0xFFFEUL ||
+20
View File
@@ -15,6 +15,11 @@ public enum OrbisGen2Result : int
/// </summary> /// </summary>
ORBIS_GEN2_OK = 0, ORBIS_GEN2_OK = 0,
/// <summary>
/// Indicates that the operation is not permitted for the calling thread.
/// </summary>
ORBIS_GEN2_ERROR_PERMISSION_DENIED = unchecked((int)0x80020001),
/// <summary> /// <summary>
/// Indicates that the requested export was not found. /// Indicates that the requested export was not found.
/// </summary> /// </summary>
@@ -30,11 +35,26 @@ public enum OrbisGen2Result : int
/// </summary> /// </summary>
ORBIS_GEN2_ERROR_ALREADY_EXISTS = unchecked((int)0x80020004), ORBIS_GEN2_ERROR_ALREADY_EXISTS = unchecked((int)0x80020004),
/// <summary>
/// Indicates that completing the operation would deadlock.
/// </summary>
ORBIS_GEN2_ERROR_DEADLOCK = unchecked((int)0x8002000B),
/// <summary>
/// Indicates that the target resource is busy.
/// </summary>
ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010),
/// <summary> /// <summary>
/// Indicates that behavior is recognized but not implemented yet. /// Indicates that behavior is recognized but not implemented yet.
/// </summary> /// </summary>
ORBIS_GEN2_ERROR_NOT_IMPLEMENTED = unchecked((int)0x8002FFFF), ORBIS_GEN2_ERROR_NOT_IMPLEMENTED = unchecked((int)0x8002FFFF),
/// <summary>
/// Indicates that the operation timed out.
/// </summary>
ORBIS_GEN2_ERROR_TIMED_OUT = unchecked((int)0x8002003C),
/// <summary> /// <summary>
/// Indicates that memory access failed. /// Indicates that memory access failed.
/// </summary> /// </summary>
+16
View File
@@ -13,6 +13,7 @@ public static class CxaGuardExports
private sealed class GuardState private sealed class GuardState
{ {
public int OwnerThreadId { get; set; } public int OwnerThreadId { get; set; }
public int RecursionDepth { get; set; }
} }
private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new(); private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new();
@@ -53,6 +54,7 @@ public static class CxaGuardExports
var newState = new GuardState var newState = new GuardState
{ {
OwnerThreadId = currentThreadId, OwnerThreadId = currentThreadId,
RecursionDepth = 1,
}; };
if (_inProgress.TryAdd(guardPtr, newState)) if (_inProgress.TryAdd(guardPtr, newState))
{ {
@@ -101,6 +103,20 @@ public static class CxaGuardExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
if (state is not null)
{
lock (state)
{
if (state.RecursionDepth > 1)
{
state.RecursionDepth--;
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_release", guardPtr, result: 0, initialized: false, inProgress: true, ownerThreadId: state.OwnerThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
}
if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true)) if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true))
{ {
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
+27 -3
View File
@@ -8,7 +8,6 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelExports public static class KernelExports
{ {
private static long _nextThreadId = 1;
private static int _nextFileDescriptor = 2; private static int _nextFileDescriptor = 2;
private static readonly object _cxaGate = new(); private static readonly object _cxaGate = new();
private static readonly List<CxaDestructorEntry> _cxaDestructors = new(); private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
@@ -175,12 +174,24 @@ public static class KernelExports
public static int PthreadCreate(CpuContext ctx) public static int PthreadCreate(CpuContext ctx)
{ {
var threadIdAddress = ctx[CpuRegister.Rdi]; var threadIdAddress = ctx[CpuRegister.Rdi];
var nextThreadId = unchecked((ulong)Interlocked.Increment(ref _nextThreadId)); var attrAddress = ctx[CpuRegister.Rsi];
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, nextThreadId)) var entryAddress = ctx[CpuRegister.Rdx];
var argument = ctx[CpuRegister.Rcx];
var nameAddress = ctx[CpuRegister.R8];
var name = nameAddress == 0 ? string.Empty : ReadCString(ctx, nameAddress, 256);
var threadHandle = KernelPthreadState.CreateThreadHandle(name);
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, threadHandle))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
if (ShouldTracePthread())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -201,12 +212,20 @@ public static class KernelExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadJoin(CpuContext ctx) public static int PthreadJoin(CpuContext ctx)
{ {
var threadId = ctx[CpuRegister.Rdi];
var returnValueAddress = ctx[CpuRegister.Rsi]; var returnValueAddress = ctx[CpuRegister.Rsi];
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0)) if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
if (ShouldTracePthread())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -370,4 +389,9 @@ public static class KernelExports
try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); } try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); }
catch { return System.Text.Encoding.ASCII.GetString(buf.Slice(0, len)); } catch { return System.Text.Encoding.ASCII.GetString(buf.Slice(0, len)); }
} }
private static bool ShouldTracePthread()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
}
} }
@@ -35,13 +35,18 @@ public static class KernelMemoryCompatExports
private const uint PageExecuteReadWrite = 0x40; private const uint PageExecuteReadWrite = 0x40;
private const uint PageExecuteWriteCopy = 0x80; private const uint PageExecuteWriteCopy = 0x80;
private const uint PageGuard = 0x100; private const uint PageGuard = 0x100;
private const int Enomem = 12;
private const int Einval = 22;
private const nuint DefaultLibcHeapAlignment = 16;
private static readonly object _fdGate = new(); private static readonly object _fdGate = new();
private static readonly Dictionary<int, FileStream> _openFiles = new(); private static readonly Dictionary<int, FileStream> _openFiles = new();
private static readonly Dictionary<int, OpenDirectory> _openDirectories = new(); private static readonly Dictionary<int, OpenDirectory> _openDirectories = new();
private static readonly object _libcAllocGate = new();
private static readonly object _memoryGate = new(); private static readonly object _memoryGate = new();
private static readonly object _tlsGate = new(); private static readonly object _tlsGate = new();
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new(); private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new(); private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new(); private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new();
private static long _nextFileDescriptor = 2; private static long _nextFileDescriptor = 2;
@@ -78,7 +83,9 @@ public static class KernelMemoryCompatExports
public required string[] Entries { get; init; } public required string[] Entries { get; init; }
public int NextIndex { get; set; } public int NextIndex { get; set; }
} }
private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType); private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType);
private readonly record struct LibcHeapAllocation(nint BaseAddress, nuint Size, nuint Alignment);
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart); private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart);
[SysAbiExport( [SysAbiExport(
@@ -310,7 +317,7 @@ public static class KernelMemoryCompatExports
var payload = new byte[bytes.Length + 1]; var payload = new byte[bytes.Length + 1];
bytes.CopyTo(payload.AsSpan()); bytes.CopyTo(payload.AsSpan());
if (!ctx.Memory.TryWrite(destination, payload)) if (!TryWriteCompat(ctx, destination, payload))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
@@ -339,7 +346,7 @@ public static class KernelMemoryCompatExports
var copied = 0; var copied = 0;
while (copied < count) while (copied < count)
{ {
if (!ctx.Memory.TryRead(source + (ulong)copied, one)) if (!TryReadCompat(ctx, source + (ulong)copied, one))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
@@ -352,7 +359,7 @@ public static class KernelMemoryCompatExports
} }
} }
if (!ctx.Memory.TryWrite(destination, payload)) if (!TryWriteCompat(ctx, destination, payload))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
@@ -377,7 +384,7 @@ public static class KernelMemoryCompatExports
} }
var payload = GC.AllocateUninitializedArray<byte>(count); var payload = GC.AllocateUninitializedArray<byte>(count);
if (count > 0 && (!ctx.Memory.TryRead(source, payload) || !ctx.Memory.TryWrite(destination, payload))) if (count > 0 && (!TryReadCompat(ctx, source, payload) || !TryWriteCompat(ctx, destination, payload)))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
@@ -396,6 +403,161 @@ public static class KernelMemoryCompatExports
return Memcpy(ctx); return Memcpy(ctx);
} }
[SysAbiExport(
Nid = "gQX+4GDQjpM",
ExportName = "malloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Malloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateLibcHeap(ctx[CpuRegister.Rdi], DefaultLibcHeapAlignment, zeroFill: false, out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "tIhsqj0qsFE",
ExportName = "free",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Free(CpuContext ctx)
{
FreeLibcHeap(ctx[CpuRegister.Rdi]);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2X5agFjKxMc",
ExportName = "calloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Calloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryMultiplyAllocationSize(ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], out var totalSize) &&
TryAllocateLibcHeapCore(totalSize, DefaultLibcHeapAlignment, zeroFill: true, out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Y7aJ1uydPMo",
ExportName = "realloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Realloc(CpuContext ctx)
{
var existingAddress = ctx[CpuRegister.Rdi];
var requestedSize = ctx[CpuRegister.Rsi];
if (existingAddress == 0)
{
ctx[CpuRegister.Rax] =
TryAllocateLibcHeap(requestedSize, DefaultLibcHeapAlignment, zeroFill: false, out var freshAddress)
? freshAddress
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (requestedSize == 0)
{
FreeLibcHeap(existingAddress);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] =
TryReallocateLibcHeap(existingAddress, requestedSize, out var resizedAddress)
? resizedAddress
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ujf3KzMvRmI",
ExportName = "memalign",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memalign(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateAlignedLibcHeap(
alignmentValue: ctx[CpuRegister.Rdi],
requestedSize: ctx[CpuRegister.Rsi],
requireSizeMultiple: false,
out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2Btkg8k24Zg",
ExportName = "aligned_alloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int AlignedAlloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateAlignedLibcHeap(
alignmentValue: ctx[CpuRegister.Rdi],
requestedSize: ctx[CpuRegister.Rsi],
requireSizeMultiple: true,
out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "cVSk9y8URbc",
ExportName = "posix_memalign",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int PosixMemalign(CpuContext ctx)
{
var outPointerAddress = ctx[CpuRegister.Rdi];
if (outPointerAddress == 0)
{
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryValidateAlignedAllocation(
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
requireSizeMultiple: false,
requirePointerSizedAlignment: true,
out var alignment,
out var requestedSize))
{
_ = TryWriteUInt64Compat(ctx, outPointerAddress, 0);
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryAllocateLibcHeapCore(requestedSize, alignment, zeroFill: false, out var address))
{
_ = TryWriteUInt64Compat(ctx, outPointerAddress, 0);
ctx[CpuRegister.Rax] = Enomem;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryWriteUInt64Compat(ctx, outPointerAddress, address))
{
FreeLibcHeap(address);
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "DfivPArhucg", Nid = "DfivPArhucg",
ExportName = "memcmp", ExportName = "memcmp",
@@ -415,8 +577,8 @@ public static class KernelMemoryCompatExports
Span<byte> rightByte = stackalloc byte[1]; Span<byte> rightByte = stackalloc byte[1];
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
if (!ctx.Memory.TryRead(left + (ulong)i, leftByte) || if (!TryReadCompat(ctx, left + (ulong)i, leftByte) ||
!ctx.Memory.TryRead(right + (ulong)i, rightByte)) !TryReadCompat(ctx, right + (ulong)i, rightByte))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
@@ -2318,6 +2480,236 @@ public static class KernelMemoryCompatExports
} }
} }
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
return TryConvertAllocationSize(requestedSize, out var size) &&
TryAllocateLibcHeapCore(size, alignment, zeroFill, out address);
}
private static unsafe bool TryAllocateLibcHeapCore(nuint requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
alignment = NormalizeLibcAlignment(alignment);
var actualSize = requestedSize == 0 ? 1u : requestedSize;
nuint totalSize;
try
{
checked
{
totalSize = actualSize + alignment - 1 + (nuint)IntPtr.Size;
}
}
catch (OverflowException)
{
return false;
}
nint baseAddress;
try
{
baseAddress = Marshal.AllocHGlobal(checked((nint)totalSize));
}
catch (OutOfMemoryException)
{
return false;
}
catch (OverflowException)
{
return false;
}
if (baseAddress == 0)
{
return false;
}
var alignedAddress = AlignUp(unchecked((ulong)baseAddress) + (ulong)IntPtr.Size, (ulong)alignment);
lock (_libcAllocGate)
{
_libcAllocations[alignedAddress] = new LibcHeapAllocation(baseAddress, actualSize, alignment);
}
try
{
if (zeroFill)
{
NativeMemory.Clear((void*)alignedAddress, actualSize);
}
}
catch
{
FreeLibcHeap(alignedAddress);
return false;
}
address = alignedAddress;
return true;
}
private static unsafe bool TryReallocateLibcHeap(ulong existingAddress, ulong requestedSize, out ulong resizedAddress)
{
resizedAddress = 0;
if (existingAddress == 0)
{
return TryAllocateLibcHeap(requestedSize, DefaultLibcHeapAlignment, zeroFill: false, out resizedAddress);
}
if (requestedSize == 0)
{
FreeLibcHeap(existingAddress);
return true;
}
LibcHeapAllocation allocation;
lock (_libcAllocGate)
{
if (!_libcAllocations.TryGetValue(existingAddress, out allocation))
{
return false;
}
}
if (!TryAllocateLibcHeap(requestedSize, allocation.Alignment, zeroFill: false, out resizedAddress))
{
return false;
}
var bytesToCopy = Math.Min(allocation.Size, (nuint)requestedSize);
Buffer.MemoryCopy(
source: (void*)existingAddress,
destination: (void*)resizedAddress,
destinationSizeInBytes: checked((long)Math.Max(bytesToCopy, 1u)),
sourceBytesToCopy: checked((long)bytesToCopy));
FreeLibcHeap(existingAddress);
return true;
}
private static bool TryAllocateAlignedLibcHeap(ulong alignmentValue, ulong requestedSize, bool requireSizeMultiple, out ulong address)
{
address = 0;
return TryValidateAlignedAllocation(
alignmentValue,
requestedSize,
requireSizeMultiple,
requirePointerSizedAlignment: false,
out var alignment,
out var size) &&
TryAllocateLibcHeapCore(size, alignment, zeroFill: false, out address);
}
private static bool TryValidateAlignedAllocation(
ulong alignmentValue,
ulong requestedSize,
bool requireSizeMultiple,
bool requirePointerSizedAlignment,
out nuint alignment,
out nuint size)
{
alignment = 0;
size = 0;
if (!TryConvertAllocationSize(requestedSize, out size) ||
alignmentValue == 0 ||
alignmentValue > (ulong)nint.MaxValue)
{
return false;
}
alignment = (nuint)alignmentValue;
if (!IsPowerOfTwo(alignment))
{
return false;
}
if (requirePointerSizedAlignment && alignment % (nuint)IntPtr.Size != 0)
{
return false;
}
if (alignment < (nuint)IntPtr.Size)
{
alignment = (nuint)IntPtr.Size;
}
if (requireSizeMultiple && size % alignment != 0)
{
return false;
}
return true;
}
private static void FreeLibcHeap(ulong address)
{
if (address == 0)
{
return;
}
LibcHeapAllocation allocation;
lock (_libcAllocGate)
{
if (!_libcAllocations.Remove(address, out allocation))
{
return;
}
}
Marshal.FreeHGlobal(allocation.BaseAddress);
}
private static bool TryMultiplyAllocationSize(ulong left, ulong right, out nuint size)
{
size = 0;
if (!TryConvertAllocationSize(left, out var leftSize) ||
!TryConvertAllocationSize(right, out var rightSize))
{
return false;
}
try
{
checked
{
size = leftSize * rightSize;
}
}
catch (OverflowException)
{
return false;
}
return true;
}
private static bool TryConvertAllocationSize(ulong requestedSize, out nuint size)
{
size = 0;
if (requestedSize > (ulong)nint.MaxValue)
{
return false;
}
size = (nuint)requestedSize;
return true;
}
private static nuint NormalizeLibcAlignment(nuint alignment)
{
if (alignment < DefaultLibcHeapAlignment)
{
return DefaultLibcHeapAlignment;
}
return alignment;
}
private static bool IsPowerOfTwo(nuint value)
{
return value != 0 && (value & (value - 1)) == 0;
}
private static bool TryWriteHostMemory(ulong address, ReadOnlySpan<byte> source) private static bool TryWriteHostMemory(ulong address, ReadOnlySpan<byte> source)
{ {
if (source.IsEmpty || !IsHostRangeAccessible(address, (ulong)source.Length, writeAccess: true)) if (source.IsEmpty || !IsHostRangeAccessible(address, (ulong)source.Length, writeAccess: true))
@@ -8,8 +8,10 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelPthreadCompatExports public static class KernelPthreadCompatExports
{ {
private const int MutexTypeNormal = 0; private const int MutexTypeDefault = 1;
private const int MutexTypeRecursive = 1; private const int MutexTypeErrorCheck = 1;
private const int MutexTypeRecursive = 2;
private const int MutexTypeNormal = 4;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000; private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000; private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
@@ -18,24 +20,22 @@ public static class KernelPthreadCompatExports
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new(); private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new(); private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
private static readonly HashSet<ulong> _condAttrStates = new(); private static readonly HashSet<ulong> _condAttrStates = new();
private static long _nextSyntheticThreadId = 1;
private static long _nextSyntheticMutexHandleId = 1; private static long _nextSyntheticMutexHandleId = 1;
private static long _nextSyntheticMutexAttrHandleId = 1; private static long _nextSyntheticMutexAttrHandleId = 1;
[ThreadStatic]
private static ulong _currentThreadId;
private sealed class PthreadMutexState private sealed class PthreadMutexState
{ {
public SemaphoreSlim Semaphore { get; } = new(1, 1); public SemaphoreSlim Semaphore { get; } = new(1, 1);
public ulong OwnerThreadId { get; set; } public ulong OwnerThreadId { get; set; }
public int RecursionCount { get; set; } public int RecursionCount { get; set; }
public int Type { get; set; } = MutexTypeNormal; public int Type { get; set; } = MutexTypeDefault;
public int Protocol { get; set; } public int Protocol { get; set; }
} }
private sealed class PthreadCondState private sealed class PthreadCondState
{ {
public int PendingSignals { get; set; } public object SyncRoot { get; } = new();
public ulong SignalEpoch { get; set; }
public int Waiters { get; set; } public int Waiters { get; set; }
} }
@@ -48,9 +48,9 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadSelf(CpuContext ctx) public static int PthreadSelf(CpuContext ctx)
{ {
var currentThreadId = GetCurrentThreadId(); var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
ctx[CpuRegister.Rax] = currentThreadId; ctx[CpuRegister.Rax] = currentThreadHandle;
TracePthreadSelf(ctx, currentThreadId); TracePthreadSelf(ctx, currentThreadHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -86,7 +86,7 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadGetthreadid(CpuContext ctx) public static int PthreadGetthreadid(CpuContext ctx)
{ {
var currentThreadId = GetCurrentThreadId(); var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
var outAddress = ctx[CpuRegister.Rdi]; var outAddress = ctx[CpuRegister.Rdi];
if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId)) if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId))
{ {
@@ -216,6 +216,13 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi])); public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "5txKfcMUAok",
ExportName = "pthread_mutexattr_setprotocol",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSetprotocol(CpuContext ctx) => PthreadMutexattrSetprotocolCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport( [SysAbiExport(
Nid = "2Tb92quprl0", Nid = "2Tb92quprl0",
ExportName = "scePthreadCondInit", ExportName = "scePthreadCondInit",
@@ -242,7 +249,7 @@ public static class KernelPthreadCompatExports
ExportName = "scePthreadCondTimedwait", ExportName = "scePthreadCondTimedwait",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true); public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true, timeoutUsec: unchecked((uint)ctx[CpuRegister.Rdx]));
[SysAbiExport( [SysAbiExport(
Nid = "kDh-NfxgMtE", Nid = "kDh-NfxgMtE",
@@ -390,7 +397,7 @@ public static class KernelPthreadCompatExports
} }
} }
var currentThreadId = GetCurrentThreadId(); var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
lock (state) lock (state)
{ {
if (state.OwnerThreadId == currentThreadId) if (state.OwnerThreadId == currentThreadId)
@@ -402,8 +409,11 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS); var ownedResult = tryOnly
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS; ? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, ownedResult);
return ownedResult;
} }
} }
@@ -418,8 +428,8 @@ public static class KernelPthreadCompatExports
} }
if (!acquired) if (!acquired)
{ {
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS); TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
} }
lock (state) lock (state)
@@ -448,11 +458,11 @@ public static class KernelPthreadCompatExports
if (state is null) if (state is null)
{ {
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, GetCurrentThreadId(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
var currentThreadId = GetCurrentThreadId(); var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
var shouldRelease = false; var shouldRelease = false;
lock (state) lock (state)
{ {
@@ -464,8 +474,8 @@ public static class KernelPthreadCompatExports
if (requireOwner && state.OwnerThreadId != currentThreadId) if (requireOwner && state.OwnerThreadId != currentThreadId)
{ {
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
} }
state.RecursionCount--; state.RecursionCount--;
@@ -503,8 +513,8 @@ public static class KernelPthreadCompatExports
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId); var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
lock (_stateGate) lock (_stateGate)
{ {
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeNormal, 0); _mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeDefault, 0);
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeNormal, 0); _mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle); _ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
@@ -543,10 +553,10 @@ public static class KernelPthreadCompatExports
{ {
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state)) if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{ {
state = new PthreadMutexAttrState(MutexTypeNormal, 0); state = new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
_mutexAttrStates[resolvedAddress] = state with { Type = type }; _mutexAttrStates[resolvedAddress] = state with { Type = NormalizeMutexType(type) };
if (resolvedAddress != attrAddress) if (resolvedAddress != attrAddress)
{ {
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress]; _mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
@@ -568,7 +578,7 @@ public static class KernelPthreadCompatExports
{ {
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state)) if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{ {
state = new PthreadMutexAttrState(MutexTypeNormal, 0); state = new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol }; _mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
@@ -651,7 +661,7 @@ public static class KernelPthreadCompatExports
{ {
return _mutexAttrStates.TryGetValue(resolvedAddress, out var state) return _mutexAttrStates.TryGetValue(resolvedAddress, out var state)
? state ? state
: default; : new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
} }
@@ -691,55 +701,66 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed) private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed, uint timeoutUsec = 0)
{ {
if (condAddress == 0 || mutexAddress == 0) if (condAddress == 0 || mutexAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
PthreadCondState state;
lock (_stateGate) lock (_stateGate)
{ {
if (!_condStates.TryGetValue(condAddress, out var state)) if (!_condStates.TryGetValue(condAddress, out state!))
{ {
state = new PthreadCondState(); state = new PthreadCondState();
_condStates[condAddress] = state; _condStates[condAddress] = state;
} }
}
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
lock (state.SyncRoot)
{
state.Waiters++; state.Waiters++;
if (state.PendingSignals > 0) var observedEpoch = state.SignalEpoch;
TracePthreadCond("wait-enter", condAddress, mutexAddress, state, timed, waitResult);
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{ {
state.PendingSignals--;
state.Waiters--; state.Waiters--;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; TracePthreadCond("wait-unlock-fail", condAddress, mutexAddress, state, timed, unlockResult);
return unlockResult;
} }
}
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true); while (state.SignalEpoch == observedEpoch)
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) {
{ if (!timed)
return unlockResult; {
} Monitor.Wait(state.SyncRoot);
continue;
}
if (timed) if (!Monitor.Wait(state.SyncRoot, GetCondWaitTimeout(timeoutUsec)))
{ {
Thread.Sleep(1); waitResult = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
} break;
else }
{ }
Thread.Yield();
state.Waiters = Math.Max(0, state.Waiters - 1);
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout", condAddress, mutexAddress, state, timed, waitResult);
} }
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false); var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
lock (_stateGate) if (lockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{ {
if (_condStates.TryGetValue(condAddress, out var state)) TracePthreadCond("wait-relock-fail", condAddress, mutexAddress, state, timed, lockResult);
{ return lockResult;
state.Waiters = Math.Max(0, state.Waiters - 1);
}
} }
return lockResult; TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout", condAddress, mutexAddress, state, timed, waitResult);
return waitResult;
} }
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast) private static int PthreadCondSignalCore(ulong condAddress, bool broadcast)
@@ -749,47 +770,70 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
PthreadCondState state;
lock (_stateGate) lock (_stateGate)
{ {
if (!_condStates.TryGetValue(condAddress, out var state)) if (!_condStates.TryGetValue(condAddress, out state!))
{ {
state = new PthreadCondState(); state = new PthreadCondState();
_condStates[condAddress] = state; _condStates[condAddress] = state;
} }
}
if (broadcast) lock (state.SyncRoot)
{
if (state.Waiters > 0)
{ {
state.PendingSignals += Math.Max(1, state.Waiters); state.SignalEpoch++;
} if (broadcast)
else {
{ Monitor.PulseAll(state.SyncRoot);
state.PendingSignals++; }
else
{
Monitor.Pulse(state.SyncRoot);
}
} }
TracePthreadCond(broadcast ? "broadcast" : "signal", condAddress, mutexAddress: 0, state, timed: false, (int)OrbisGen2Result.ORBIS_GEN2_OK);
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static ulong GetCurrentThreadId() private static TimeSpan GetCondWaitTimeout(uint timeoutUsec)
{ {
if (_currentThreadId != 0) if (timeoutUsec == 0)
{ {
return _currentThreadId; return TimeSpan.Zero;
} }
_currentThreadId = unchecked((ulong)Interlocked.Increment(ref _nextSyntheticThreadId)); return TimeSpan.FromTicks((long)timeoutUsec * 10L);
return _currentThreadId;
} }
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadId) private static int NormalizeMutexType(int type)
{
return type switch
{
0 => MutexTypeDefault,
1 => MutexTypeErrorCheck,
2 => MutexTypeRecursive,
3 => MutexTypeNormal,
4 => MutexTypeNormal,
_ => MutexTypeDefault,
};
}
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadHandle)
{ {
if (!ShouldTracePthread()) if (!ShouldTracePthread())
{ {
return; return;
} }
var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadId:X16}"); $"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadHandle:X16} tid=0x{currentThreadId:X16}");
} }
private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result) private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result)
@@ -808,6 +852,18 @@ public static class KernelPthreadCompatExports
$"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}"); $"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}");
} }
private static void TracePthreadCond(string operation, ulong condAddress, ulong mutexAddress, PthreadCondState? state, bool timed, int result)
{
if (!ShouldTracePthread())
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_cond_{operation}: cond=0x{condAddress:X16} mutex=0x{mutexAddress:X16} " +
$"waiters={(state?.Waiters ?? 0)} epoch=0x{(state?.SignalEpoch ?? 0):X} timed={timed} result=0x{unchecked((uint)result):X8}");
}
private static bool ShouldTracePthread() private static bool ShouldTracePthread()
{ {
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal); return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
@@ -841,9 +841,13 @@ public static class KernelPthreadExtendedCompatExports
return state; return state;
} }
var name = KernelPthreadState.TryGetThreadIdentity(thread, out var identity)
? identity.Name
: $"Thread-{thread:X}";
state = new ThreadState state = new ThreadState
{ {
Name = $"Thread-{thread:X}", Name = name,
Priority = DefaultThreadPriority, Priority = DefaultThreadPriority,
AffinityMask = DefaultThreadAffinityMask, AffinityMask = DefaultThreadAffinityMask,
DetachState = DefaultDetachState, DetachState = DefaultDetachState,
@@ -0,0 +1,78 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
internal static class KernelPthreadState
{
private const int ThreadObjectSize = 0x1000;
private static readonly object Gate = new();
private static readonly Dictionary<ulong, ThreadIdentity> Threads = new();
private static readonly byte[] ZeroThreadObject = new byte[ThreadObjectSize];
private static long _nextUniqueThreadId = 1;
[ThreadStatic]
private static ulong _currentThreadHandle;
[ThreadStatic]
private static ulong _currentThreadUniqueId;
internal readonly record struct ThreadIdentity(ulong UniqueId, string Name);
internal static ulong GetCurrentThreadHandle()
{
EnsureCurrentThreadRegistered();
return _currentThreadHandle;
}
internal static ulong GetCurrentThreadUniqueId()
{
EnsureCurrentThreadRegistered();
return _currentThreadUniqueId;
}
internal static ulong CreateThreadHandle(string name)
{
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
return AllocateThreadHandle(uniqueId, name);
}
internal static bool TryGetThreadIdentity(ulong threadHandle, out ThreadIdentity identity)
{
lock (Gate)
{
return Threads.TryGetValue(threadHandle, out identity);
}
}
private static void EnsureCurrentThreadRegistered()
{
if (_currentThreadHandle != 0)
{
return;
}
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
var name = $"Thread-{uniqueId:X}";
_currentThreadHandle = AllocateThreadHandle(uniqueId, name);
_currentThreadUniqueId = uniqueId;
}
private static ulong AllocateThreadHandle(ulong uniqueId, string name)
{
var pointer = Marshal.AllocHGlobal(ThreadObjectSize);
Marshal.Copy(ZeroThreadObject, 0, pointer, ThreadObjectSize);
var handle = unchecked((ulong)pointer.ToInt64());
lock (Gate)
{
Threads[handle] = new ThreadIdentity(uniqueId, string.IsNullOrWhiteSpace(name) ? $"Thread-{uniqueId:X}" : name);
}
return handle;
}
}