mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-29 06:00:58 +08:00
another hle implements, especially for mutex calls
This commit is contained in:
@@ -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);
|
||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user