[AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)

* [Core] Add POSIX native execution and PS5 SELF support

Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases.

* [HLE] Expand PS5 service and media compatibility

Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT.

* [AGC/Vulkan] Extend Gen5 shader and presentation support

Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders.

* [Core] Align static TLS reservation across hosts

* [Pad] Align primary user ID with UserService

* [Gpu] Preserve runtime scalar buffers across renderer seam

* [AGC] Restore omitted command helper exports

* [Vulkan] Reuse primary views for promoted MRT targets

* [Vulkan] Preserve scratch storage bindings in compute dispatches
This commit is contained in:
Miguel Cruz
2026-07-15 19:02:34 -04:00
committed by GitHub
parent ad5a7d3799
commit 864cbb0fa0
85 changed files with 36299 additions and 9176 deletions
+513 -108
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -10,16 +11,12 @@ using SharpEmu.Core;
using SharpEmu.Core.Cpu;
using SharpEmu.Core.Memory;
using SharpEmu.HLE;
using SharpEmu.Logging;
namespace SharpEmu.Core.Loader;
public sealed class SelfLoader : ISelfLoader
{
private static readonly SharpEmuLogger Log = SharpEmuLog.For("Loader");
private const uint ElfMagic = 0x7F454C46;
private const uint Ps4SelfMagic = 0x4F153D1D;
private const uint Ps5SelfMagic = 0x5414F5EE;
private const uint SelfMagic = 0x4F153D1D;
private const ulong SelfSegmentFlag = 0x800;
private const int PageSize = 0x1000;
private const ulong ImportStubBaseAddress = 0x0000_7000_0000_0000UL;
@@ -58,11 +55,22 @@ public sealed class SelfLoader : ISelfLoader
private const long DtSceSymTab = 0x61000039;
private const long DtSceSymTabSize = 0x6100003F;
private const uint RelocationTypeNone = 0;
private const uint RelocationTypeAbsolute64 = 1;
private const uint RelocationTypePc32 = 2;
private const uint RelocationTypePlt32 = 4;
private const uint RelocationTypeGlobalData = 6;
private const uint RelocationTypeJumpSlot = 7;
private const uint RelocationTypeRelative = 8;
private const uint RelocationTypeTlsModuleId = 16;
private const uint RelocationTypeUnsigned32 = 10;
private const uint RelocationTypeSigned32 = 11;
private const uint RelocationTypeTlsModuleId = 16; // R_X86_64_DTPMOD64
private const uint RelocationTypeTlsDtpOff64 = 17; // R_X86_64_DTPOFF64
private const uint RelocationTypeTlsTpOff64 = 18; // R_X86_64_TPOFF64
private const uint RelocationTypePc64 = 24;
private const uint RelocationTypeSize32 = 32;
private const uint RelocationTypeSize64 = 33;
private const uint RelocationTypeRelative64 = 38;
private const ulong Ps5MainImageBase = 0x0000000800000000UL;
private const ulong Ps4MainImageBase = 0x0000000000400000UL;
private const ulong Ps5ModuleSearchStart = 0x0000000804000000UL;
@@ -88,6 +96,11 @@ public sealed class SelfLoader : ISelfLoader
private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>();
private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>();
static SelfLoader()
{
RunRelocationSelfChecks();
}
public SelfImage Load(ReadOnlySpan<byte> imageData, IVirtualMemory virtualMemory)
{
return Load(imageData, virtualMemory, fs: null, mountRoot: null);
@@ -156,16 +169,22 @@ public sealed class SelfLoader : ISelfLoader
{
virtualMemory.Clear();
_nextTlsModuleId = 1;
GuestTlsTemplate.Reset();
}
var tlsModuleId = _nextTlsModuleId == 0 ? 1u : _nextTlsModuleId;
Log.Debug($"TLS load_start clear={clearVirtualMemory} next={_nextTlsModuleId} assigned={tlsModuleId}");
var loadContext = ParseLayout(imageData);
var elfHeader = ReadUnmanaged<ElfHeader>(imageData, loadContext.ElfOffset);
ValidateElfHeader(elfHeader);
var programHeaders = ParseProgramHeaders(imageData, loadContext, elfHeader);
var hasTlsSegment = TryGetProgramHeader(programHeaders, ProgramHeaderType.Tls, out var processTlsHeader, out _) &&
processTlsHeader.MemorySize != 0;
var tlsModuleId = hasTlsSegment
? (_nextTlsModuleId == 0 ? 1u : _nextTlsModuleId)
: 0u;
Console.Error.WriteLine(
$"[LOADER][TLS] load_start clear={clearVirtualMemory} next={_nextTlsModuleId} " +
$"assigned={tlsModuleId} has_pt_tls={hasTlsSegment}");
var totalImageSize = CalculateTotalImageSize(programHeaders);
Console.WriteLine($"Total image size needed: 0x{totalImageSize:X} ({totalImageSize} bytes)");
@@ -198,6 +217,13 @@ public sealed class SelfLoader : ISelfLoader
}
MapLoadSegments(imageData, loadContext, programHeaders, virtualMemory, imageBase);
// Register every module before relocations so DTPMOD/DTPOFF/TPOFF use
// the module's real PT_TLS identity and Variant II static offset.
var tlsInfo = RegisterModuleTlsTemplate(
programHeaders,
virtualMemory,
imageBase,
tlsModuleId);
var importStubs = ResolveAndPatchImportStubs(
imageData,
loadContext,
@@ -255,11 +281,11 @@ public sealed class SelfLoader : ISelfLoader
Console.WriteLine($"[LOADER] PH[{i}]: type={ph.HeaderType}, vaddr=0x{ph.VirtualAddress:X16} -> 0x{ph.VirtualAddress + imageBase:X16}, memsz=0x{ph.MemorySize:X}");
}
if (_nextTlsModuleId == tlsModuleId && _nextTlsModuleId < uint.MaxValue)
if (tlsModuleId != 0 && _nextTlsModuleId == tlsModuleId && _nextTlsModuleId < uint.MaxValue)
{
_nextTlsModuleId++;
}
Log.Debug($"TLS load_done assigned={tlsModuleId} next={_nextTlsModuleId}");
Console.Error.WriteLine($"[LOADER][TLS] load_done assigned={tlsModuleId} next={_nextTlsModuleId}");
return new SelfImage(
loadContext.IsSelf,
@@ -276,7 +302,10 @@ public sealed class SelfLoader : ISelfLoader
procParamAddress,
applicationInfo.Title,
applicationInfo.TitleId,
applicationInfo.Version);
applicationInfo.Version,
tlsModuleId,
tlsInfo.MemorySize,
tlsInfo.StaticOffset);
}
private static (string? Title, string? TitleId, string? Version) TryLoadParamJson(
@@ -325,8 +354,7 @@ public sealed class SelfLoader : ISelfLoader
throw new InvalidDataException("Input image is too small to contain an ELF header.");
}
var magic = BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]);
if (magic is Ps4SelfMagic or Ps5SelfMagic)
if (imageData.Length >= sizeof(uint) && BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]) == SelfMagic)
{
var selfHeader = ReadUnmanaged<SelfHeader>(imageData, 0);
if (!selfHeader.HasKnownLayout || selfHeader.Unknown != 0x22)
@@ -348,10 +376,19 @@ public sealed class SelfLoader : ISelfLoader
return new LoadContext(IsSelf: true, elfOffset, selfHeader.FileSize, segments);
}
if (magic != ElfMagic)
// Not a recognized (fake-signed) SELF. Only a bare, decrypted ELF is
// acceptable here; anything else — most commonly a still-encrypted
// retail eboot — must be reported clearly rather than failing later
// with an opaque "not a valid ELF header" message.
const uint ElfMagicBigEndian = 0x7F454C46; // "\x7fELF"
var leadingWord = BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]);
if (leadingWord != ElfMagicBigEndian)
{
throw new InvalidDataException(
$"Unsupported executable signature 0x{magic:X8}");
$"Image is neither a decrypted ELF nor a recognized fake-signed SELF " +
$"(leading bytes 0x{leadingWord:X8}). This is almost certainly a still-encrypted " +
$"retail eboot — SharpEmu has no decryption keys and requires a decrypted / " +
$"fake-signed (fSELF) image.");
}
return new LoadContext(IsSelf: false, ElfOffset: 0, SelfFileSize: 0, Array.Empty<SelfSegment>());
@@ -414,15 +451,15 @@ public sealed class SelfLoader : ISelfLoader
var virtualAddress = header.VirtualAddress + imageBase;
Log.Debug($"Segment {index}: VAddr=0x{virtualAddress:X16}, FileSize=0x{header.FileSize:X}, MemSize=0x{header.MemorySize:X}, Align=0x{header.Alignment:X}");
Console.Error.WriteLine($"[LOADER] Segment {index}: VAddr=0x{virtualAddress:X16}, FileSize=0x{header.FileSize:X}, MemSize=0x{header.MemorySize:X}, Align=0x{header.Alignment:X}");
if (header.Alignment > 1)
{
var vaddrMod = virtualAddress % header.Alignment;
var offsetMod = header.Offset % header.Alignment;
if (vaddrMod != offsetMod)
{
Log.Warning(
$"Segment {index} ELF alignment mismatch! " +
Console.Error.WriteLine(
$"[LOADER] WARNING: Segment {index} ELF alignment mismatch! " +
$"VAddr=0x{virtualAddress:X}, Offset=0x{header.Offset:X}, Align=0x{header.Alignment:X}, " +
$"VAddr%Align=0x{vaddrMod:X}, Offset%Align=0x{offsetMod:X}");
}
@@ -465,6 +502,43 @@ public sealed class SelfLoader : ISelfLoader
return 0;
}
private static ModuleTlsInfo RegisterModuleTlsTemplate(
IReadOnlyList<ProgramHeader> programHeaders,
IVirtualMemory virtualMemory,
ulong imageBase,
uint tlsModuleId)
{
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Tls, out var tlsHeader, out _) ||
tlsHeader.MemorySize == 0)
{
return default;
}
// tdata (initialized) bytes come from the mapped segment; tbss is the
// implicitly-zero remainder up to MemorySize.
var fileSize = (int)Math.Min(tlsHeader.FileSize, tlsHeader.MemorySize);
var initImage = fileSize > 0 ? new byte[fileSize] : [];
if (fileSize > 0 &&
!virtualMemory.TryRead(imageBase + tlsHeader.VirtualAddress, initImage))
{
Console.Error.WriteLine(
$"[LOADER][TLS] Failed to read TLS init image at 0x{imageBase + tlsHeader.VirtualAddress:X}; seeding zeros.");
initImage = [];
}
var staticOffset = GuestTlsTemplate.RegisterModule(
tlsModuleId,
initImage,
tlsHeader.MemorySize,
tlsHeader.Alignment,
tlsHeader.VirtualAddress);
Console.Error.WriteLine(
$"[LOADER][TLS] Module {tlsModuleId} TLS template: memsz=0x{tlsHeader.MemorySize:X} " +
$"filesz=0x{tlsHeader.FileSize:X} align=0x{tlsHeader.Alignment:X} " +
$"static_offset=0x{staticOffset:X} total_static=0x{GuestTlsTemplate.StaticTlsSize:X}");
return new ModuleTlsInfo(tlsHeader.MemorySize, staticOffset);
}
private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
@@ -544,12 +618,17 @@ public sealed class SelfLoader : ISelfLoader
uint maxSymbolIndex = 0;
foreach (var relocation in relocations)
{
if (relocation.Type == RelocationTypeNone)
{
continue;
}
if (!IsSupportedRelocationType(relocation.Type))
{
continue;
}
if (relocation.Type is RelocationTypeRelative or RelocationTypeTlsModuleId)
if (relocation.Type is RelocationTypeNone or RelocationTypeRelative or RelocationTypeRelative64 or RelocationTypeTlsModuleId)
{
continue;
}
@@ -633,13 +712,16 @@ public sealed class SelfLoader : ISelfLoader
importedRelocations = BuildImportedRelocations(descriptors);
var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids);
var stubImportNids = orderedImportNids
.Where(nid => ShouldCreateImportStub(nid, descriptors, moduleManager))
.ToArray();
var stubsByAddress = CreateImportStubMapping(virtualMemory, stubImportNids);
Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs");
int printCount = Math.Min(10, orderedImportNids.Count);
int printCount = Math.Min(10, stubImportNids.Length);
for (int i = 0; i < printCount; i++)
{
var nid = orderedImportNids[i];
var nid = stubImportNids[i];
var addr = stubsByAddress.First(x => x.Value == nid).Key;
}
@@ -663,45 +745,65 @@ public sealed class SelfLoader : ISelfLoader
foreach (var descriptor in descriptors)
{
ulong targetValue;
ulong symbolValue;
if (descriptor.ImportNid is null)
{
targetValue = AddSigned(descriptor.SymbolValue, descriptor.Addend);
symbolValue = descriptor.SymbolValue;
}
else
{
if (!addressesByNid.TryGetValue(descriptor.ImportNid, out var stubAddress))
if (addressesByNid.TryGetValue(descriptor.ImportNid, out var stubAddress))
{
throw new InvalidOperationException($"Import stub not found for NID '{descriptor.ImportNid}'.");
symbolValue = stubAddress;
}
targetValue = AddSigned(stubAddress, descriptor.Addend);
}
if (targetValue < 0x1000)
{
if (descriptor.ValueKind == RelocationValueKind.TlsModuleId)
else if (descriptor.IsWeak)
{
Log.Debug(
$"Patching DTPMOD64 at 0x{descriptor.TargetAddress:X} with module id 0x{targetValue:X}");
// ELF unresolved weak definitions use S=0. They must not
// receive a trap import stub, which would turn a permitted
// null test into a call to an unresolved-import handler.
symbolValue = 0;
}
else
{
Log.Warning($"!!! CRITICAL !!! Patching address 0x{descriptor.TargetAddress:X} with INVALID value 0x{targetValue:X} for NID {descriptor.ImportNid ?? "(null)"}");
Log.Warning($" SymbolValue=0x{descriptor.SymbolValue:X}, Addend=0x{descriptor.Addend:X}, StubAddress=0x{(addressesByNid.TryGetValue(descriptor.ImportNid ?? "", out var sa) ? sa : 0):X}");
throw new InvalidOperationException($"Import stub not found for NID '{descriptor.ImportNid}'.");
}
}
if (!TryWriteUInt64(virtualMemory, descriptor.TargetAddress, targetValue))
var targetValue = ComputeRelocationValue(descriptor, symbolValue);
if (targetValue < 0x1000 && descriptor.ValueKind is
RelocationValueKind.TlsOffset or
RelocationValueKind.PcRelative or
RelocationValueKind.SymbolSize)
{
throw new InvalidDataException($"Failed to patch relocation at 0x{descriptor.TargetAddress:X16}.");
// A TLS offset (TPOFF64/DTPOFF64) is a signed displacement, not a
// mapped address, so a small or negative value here is expected.
}
else if (targetValue < 0x1000 && !descriptor.IsWeak)
{
if (descriptor.ValueKind == RelocationValueKind.TlsModuleId)
{
Console.Error.WriteLine(
$"[LOADER][TLS] Patching DTPMOD64 at 0x{descriptor.TargetAddress:X} with module id 0x{targetValue:X}");
}
else
{
Console.Error.WriteLine($"[LOADER] !!! CRITICAL !!! Patching address 0x{descriptor.TargetAddress:X} with INVALID value 0x{targetValue:X} for NID {descriptor.ImportNid ?? "(null)"}");
Console.Error.WriteLine($"[LOADER] SymbolValue=0x{descriptor.SymbolValue:X}, Addend=0x{descriptor.Addend:X}, StubAddress=0x{(addressesByNid.TryGetValue(descriptor.ImportNid ?? "", out var sa) ? sa : 0):X}");
}
}
if (!TryWriteRelocationValue(virtualMemory, descriptor, targetValue, out var writeError))
{
throw new InvalidDataException(
$"Failed to patch relocation at 0x{descriptor.TargetAddress:X16}: {writeError}");
}
if (descriptor.TargetAddress >= 0x00000008030FC300UL &&
descriptor.TargetAddress <= 0x00000008030FC3F0UL)
{
Log.Debug(
$"[RELOC] target=0x{descriptor.TargetAddress:X16} value=0x{targetValue:X16} addend=0x{descriptor.Addend:X} nid={(descriptor.ImportNid ?? "<sym>")}");
Console.Error.WriteLine(
$"[LOADER][RELOC] target=0x{descriptor.TargetAddress:X16} value=0x{targetValue:X16} addend=0x{descriptor.Addend:X} nid={(descriptor.ImportNid ?? "<sym>")}");
}
}
@@ -794,29 +896,36 @@ public sealed class SelfLoader : ISelfLoader
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Log.Debug(
$"[FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}");
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))
// Surface unsupported relocation types loudly instead of
// skipping silently: TLS-relative (17/18), COPY (5), and
// IRELATIVE/ifunc (37) leave their targets unrelocated, which
// manifests later as reads of zero or calls into address 0.
ReportUnsupportedRelocation(relocation.Type, relocation.Offset, imageBase);
if (relocation.Type is 5 or 37)
{
Log.Debug($"[FOCUS][SKIP] unsupported type={relocation.Type}");
throw new NotSupportedException(
$"Relocation type {relocation.Type} requires deferred runtime-linker processing.");
}
continue;
}
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
var relocationWriteSize = GetRelocationWriteSize(relocation.Type);
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, relocationWriteSize, out var targetAddress))
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Log.Debug("[FOCUS][SKIP] target address not mapped");
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
}
continue;
}
if (relocation.Type == RelocationTypeRelative)
if (relocation.Type is RelocationTypeRelative or RelocationTypeRelative64)
{
descriptors.Add(new RelocationDescriptor(
targetAddress,
@@ -830,7 +939,12 @@ public sealed class SelfLoader : ISelfLoader
if (relocation.Type == RelocationTypeTlsModuleId)
{
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
if (tlsModuleId == 0)
{
throw new InvalidDataException(
$"R_X86_64_DTPMOD64 at 0x{targetAddress:X16} references a module without PT_TLS.");
}
var dtpmodValue = tlsModuleId;
descriptors.Add(new RelocationDescriptor(
targetAddress,
0,
@@ -851,50 +965,100 @@ public sealed class SelfLoader : ISelfLoader
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Log.Debug($"[FOCUS][SKIP] symbol read failed index={symbolIndex}");
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)
if (relocation.Type is RelocationTypeTlsDtpOff64 or RelocationTypeTlsTpOff64)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
// Variant II static TLS: the module block sits at
// [tp - blockSize, tp). DTPOFF64 is the module-relative offset
// (st_value + addend); TPOFF64 is that offset expressed relative
// to the thread pointer, i.e. minus the aligned block size.
var tlsSymbolOffset = AddSigned(symbol.Value, relocation.Addend);
if (!GuestTlsTemplate.TryGetStaticOffset(tlsModuleId, out var moduleStaticOffset))
{
Log.Warning(
$"Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
Console.Error.WriteLine(
$"[LOADER][TLS] Missing PT_TLS registration for module {tlsModuleId}; " +
$"cannot apply relocation {relocation.Type} at 0x{targetAddress:X16}.");
continue;
}
var tlsValue = relocation.Type == RelocationTypeTlsTpOff64
? unchecked(tlsSymbolOffset - moduleStaticOffset)
: tlsSymbolOffset;
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
0,
null,
symbolAddress,
RelocationValueKind.Pointer,
tlsValue,
RelocationValueKind.TlsOffset,
IsDataImport: false));
continue;
}
var symbolBind = GetSymbolBind(symbol.Info);
if (symbolIndex == 0)
{
descriptors.Add(CreateSymbolRelocationDescriptor(
relocation,
targetAddress,
symbol,
symbolAddress: 0,
importNid: null,
isWeak: false));
continue;
}
if (symbolBind == SymbolBindLocal)
{
var symbolAddress = relocation.Type is RelocationTypeSize32 or RelocationTypeSize64
? 0
: ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
if (relocation.Type is not (RelocationTypeSize32 or RelocationTypeSize64))
{
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(CreateSymbolRelocationDescriptor(
relocation,
targetAddress,
symbol,
symbolAddress,
importNid: null,
isWeak: false));
continue;
}
if (symbol.Value != 0)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
var symbolAddress = relocation.Type is RelocationTypeSize32 or RelocationTypeSize64
? 0
: ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Log.Warning(
$"Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
if (relocation.Type is not (RelocationTypeSize32 or RelocationTypeSize64))
{
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(
descriptors.Add(CreateSymbolRelocationDescriptor(
relocation,
targetAddress,
addend,
null,
symbol,
symbolAddress,
RelocationValueKind.Pointer,
IsDataImport: false));
importNid: null,
isWeak: false));
continue;
}
@@ -902,16 +1066,26 @@ public sealed class SelfLoader : ISelfLoader
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Log.Debug($"[FOCUS][SKIP] bind={symbolBind} not importable");
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
}
continue;
}
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
{
if (symbolBind == SymbolBindWeak)
{
descriptors.Add(CreateSymbolRelocationDescriptor(
relocation,
targetAddress,
symbol,
symbolAddress: 0,
importNid: null,
isWeak: true));
}
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Log.Debug($"[FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
}
continue;
}
@@ -919,6 +1093,16 @@ public sealed class SelfLoader : ISelfLoader
var nid = ExtractNid(symbolName);
if (string.IsNullOrWhiteSpace(nid))
{
if (symbolBind == SymbolBindWeak)
{
descriptors.Add(CreateSymbolRelocationDescriptor(
relocation,
targetAddress,
symbol,
symbolAddress: 0,
importNid: null,
isWeak: true));
}
continue;
}
@@ -927,16 +1111,74 @@ public sealed class SelfLoader : ISelfLoader
orderedImportNids.Add(nid);
}
descriptors.Add(new RelocationDescriptor(
descriptors.Add(CreateSymbolRelocationDescriptor(
relocation,
targetAddress,
addend,
nid,
0,
RelocationValueKind.Pointer,
IsDataImport: GetSymbolType(symbol.Info) == SymbolTypeObject));
symbol,
symbolAddress: 0,
importNid: nid,
isWeak: symbolBind == SymbolBindWeak));
}
}
private static RelocationDescriptor CreateSymbolRelocationDescriptor(
ElfRelocation relocation,
ulong targetAddress,
ElfSymbol symbol,
ulong symbolAddress,
string? importNid,
bool isWeak)
{
var valueKind = relocation.Type switch
{
RelocationTypePc32 or RelocationTypePlt32 or RelocationTypePc64 => RelocationValueKind.PcRelative,
RelocationTypeSize32 or RelocationTypeSize64 => RelocationValueKind.SymbolSize,
_ => RelocationValueKind.Pointer,
};
var writeKind = relocation.Type switch
{
RelocationTypePc32 or RelocationTypePlt32 or RelocationTypeSigned32 => RelocationWriteKind.Int32,
RelocationTypeUnsigned32 or RelocationTypeSize32 => RelocationWriteKind.UInt32,
_ => RelocationWriteKind.UInt64,
};
var symbolValue = valueKind == RelocationValueKind.SymbolSize
? symbol.Size
: symbolAddress;
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot
? 0
: relocation.Addend;
return new RelocationDescriptor(
targetAddress,
addend,
importNid,
symbolValue,
valueKind,
IsDataImport: GetSymbolType(symbol.Info) == SymbolTypeObject,
writeKind,
isWeak);
}
private static bool ShouldCreateImportStub(
string nid,
IReadOnlyList<RelocationDescriptor> descriptors,
IModuleManager? moduleManager)
{
for (var i = 0; i < descriptors.Count; i++)
{
var descriptor = descriptors[i];
if (!string.Equals(descriptor.ImportNid, nid, StringComparison.Ordinal))
{
continue;
}
if (!descriptor.IsWeak || moduleManager?.TryGetExport(nid, out _) == true)
{
return true;
}
}
return false;
}
private static void RegisterRuntimeSymbolsAndHooks(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
@@ -959,8 +1201,8 @@ public sealed class SelfLoader : ISelfLoader
if (sectionSymbols > 0 || dynamicSymbols > 0)
{
Log.Info(
$"Runtime symbol index populated: section={sectionSymbols}, dynamic={dynamicSymbols}, total={runtimeSymbols.Count}");
Console.Error.WriteLine(
$"[LOADER] Runtime symbol index populated: section={sectionSymbols}, dynamic={dynamicSymbols}, total={runtimeSymbols.Count}");
}
}
@@ -1034,8 +1276,8 @@ public sealed class SelfLoader : ISelfLoader
if (preInitializers.Count != 0 || initializers.Count != 0)
{
Log.Info(
$"Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
Console.Error.WriteLine(
$"[LOADER] Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
}
}
@@ -1418,8 +1660,8 @@ public sealed class SelfLoader : ISelfLoader
var requiredBytes = checked((ulong)orderedImportNids.Count * ImportStubSlotSize);
var mapSize = AlignUp(Math.Max(requiredBytes, (ulong)PageSize), PageSize);
Log.Debug(
$"CreateImportStubMapping: nids={orderedImportNids.Count}, required=0x{requiredBytes:X}, map_size=0x{mapSize:X}");
Console.Error.WriteLine(
$"[LOADER] CreateImportStubMapping: nids={orderedImportNids.Count}, required=0x{requiredBytes:X}, map_size=0x{mapSize:X}");
if (mapSize > int.MaxValue)
{
throw new NotSupportedException("Import stub mapping exceeds 2 GB and is not supported.");
@@ -1691,11 +1933,51 @@ public sealed class SelfLoader : ISelfLoader
private static bool IsSupportedRelocationType(uint relocationType)
{
return relocationType is
RelocationTypeNone or
RelocationTypeAbsolute64 or
RelocationTypePc32 or
RelocationTypePlt32 or
RelocationTypeGlobalData or
RelocationTypeJumpSlot or
RelocationTypeRelative or
RelocationTypeTlsModuleId;
RelocationTypeUnsigned32 or
RelocationTypeSigned32 or
RelocationTypeTlsModuleId or
RelocationTypeTlsDtpOff64 or
RelocationTypeTlsTpOff64 or
RelocationTypePc64 or
RelocationTypeSize32 or
RelocationTypeSize64 or
RelocationTypeRelative64;
}
private static readonly HashSet<uint> _reportedUnsupportedRelocationTypes = new();
private static void ReportUnsupportedRelocation(uint relocationType, ulong offset, ulong imageBase)
{
// Report each distinct unsupported type once to keep the log useful.
lock (_reportedUnsupportedRelocationTypes)
{
if (!_reportedUnsupportedRelocationTypes.Add(relocationType))
{
if (IsFocusRelocationOffset(offset, imageBase))
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocationType}");
}
return;
}
}
var name = relocationType switch
{
5 => "R_X86_64_COPY",
37 => "R_X86_64_IRELATIVE (ifunc)",
_ => "unknown",
};
Console.Error.WriteLine(
$"[LOADER][ERROR] Unsupported relocation type {relocationType} ({name}) rejected " +
$"(first at off=0x{offset:X16}); COPY requires dependency symbol storage and IRELATIVE requires resolver execution.");
}
private static ulong DetermineRequestedImageBase(
@@ -1851,16 +2133,16 @@ public sealed class SelfLoader : ISelfLoader
tableBytes = GC.AllocateUninitializedArray<byte>((int)size);
var guestAddr = location + imageBase;
Log.Debug($"TryLoadTableBytes: trying guest address 0x{guestAddr:X}");
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: trying guest address 0x{guestAddr:X}");
if (virtualMemory.TryRead(guestAddr, tableBytes))
{
Log.Debug($"TryLoadTableBytes: loaded from guest memory at 0x{guestAddr:X}");
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: loaded from guest memory at 0x{guestAddr:X}");
return true;
}
if (virtualMemory.TryRead(location, tableBytes))
{
Log.Debug($"TryLoadTableBytes: loaded from absolute guest address 0x{location:X}");
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: loaded from absolute guest address 0x{location:X}");
return true;
}
@@ -1868,11 +2150,11 @@ public sealed class SelfLoader : ISelfLoader
{
var slice = elfData.Slice((int)location, (int)size);
tableBytes = slice.ToArray();
Log.Debug($"TryLoadTableBytes: loaded from elfData as file offset at 0x{location:X}");
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: loaded from elfData as file offset at 0x{location:X}");
return true;
}
Log.Warning($"TryLoadTableBytes: FAILED for location 0x{location:X}");
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: FAILED for location 0x{location:X}");
tableBytes = Array.Empty<byte>();
return false;
}
@@ -1929,17 +2211,17 @@ public sealed class SelfLoader : ISelfLoader
private static ulong ResolveMappedAddressOrFallback(IVirtualMemory virtualMemory, ulong address, ulong imageBase)
{
Log.Debug($"ResolveMappedAddressOrFallback addr=0x{address:X} imageBase=0x{imageBase:X16}");
Console.Error.WriteLine($"[LOADER][TEST] ResolveMappedAddressOrFallback addr=0x{address:X} imageBase=0x{imageBase:X16}");
if (address == 0)
{
Log.Debug("-> return 0 (null)");
Console.Error.WriteLine("[LOADER][TEST] -> return 0 (null)");
return 0;
}
if (TryResolveMappedAddress(virtualMemory, address, imageBase, 1, out var resolved))
{
Log.Debug($"-> resolved raw 0x{resolved:X16}");
Console.Error.WriteLine($"[LOADER][TEST] -> resolved raw 0x{resolved:X16}");
return resolved;
}
@@ -1948,18 +2230,18 @@ public sealed class SelfLoader : ISelfLoader
var rebased = address + imageBase;
if (TryResolveMappedAddress(virtualMemory, rebased, imageBase, 1, out var resolvedRebased))
{
Log.Debug($"-> resolved rebased 0x{resolvedRebased:X16}");
Console.Error.WriteLine($"[LOADER][TEST] -> resolved rebased 0x{resolvedRebased:X16}");
return resolvedRebased;
}
}
if (address < 0x10000)
{
Log.Debug($"-> reject small 0x{address:X}");
Console.Error.WriteLine($"[LOADER][TEST] -> reject small 0x{address:X}");
return 0;
}
Log.Debug($"-> fallback raw 0x{address:X}");
Console.Error.WriteLine($"[LOADER][TEST] -> fallback raw 0x{address:X}");
return address;
}
@@ -2049,6 +2331,106 @@ public sealed class SelfLoader : ISelfLoader
return virtualMemory.TryWrite(address, buffer);
}
private static ulong ComputeRelocationValue(RelocationDescriptor descriptor, ulong resolvedSymbolValue)
{
var baseValue = descriptor.ValueKind == RelocationValueKind.SymbolSize
? descriptor.SymbolValue
: resolvedSymbolValue;
var value = AddSigned(baseValue, descriptor.Addend);
return descriptor.ValueKind == RelocationValueKind.PcRelative
? unchecked(value - descriptor.TargetAddress)
: value;
}
private static bool TryWriteRelocationValue(
IVirtualMemory virtualMemory,
RelocationDescriptor descriptor,
ulong value,
out string? error)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
var length = sizeof(ulong);
switch (descriptor.WriteKind)
{
case RelocationWriteKind.UInt64:
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
break;
case RelocationWriteKind.UInt32:
if (value > uint.MaxValue)
{
error = $"value 0x{value:X16} overflows an unsigned 32-bit relocation";
return false;
}
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)value);
length = sizeof(uint);
break;
case RelocationWriteKind.Int32:
var signedValue = unchecked((long)value);
if (signedValue is < int.MinValue or > int.MaxValue)
{
error = $"value {signedValue} overflows a signed 32-bit relocation";
return false;
}
BinaryPrimitives.WriteInt32LittleEndian(buffer, (int)signedValue);
length = sizeof(int);
break;
default:
error = $"unknown relocation write kind {descriptor.WriteKind}";
return false;
}
error = virtualMemory.TryWrite(descriptor.TargetAddress, buffer[..length])
? null
: "target memory is not writable";
return error is null;
}
private static int GetRelocationWriteSize(uint relocationType)
{
return relocationType is
RelocationTypePc32 or
RelocationTypePlt32 or
RelocationTypeUnsigned32 or
RelocationTypeSigned32 or
RelocationTypeSize32
? sizeof(uint)
: sizeof(ulong);
}
[Conditional("DEBUG")]
private static void RunRelocationSelfChecks()
{
var pc32 = new RelocationDescriptor(
TargetAddress: 0x1000,
Addend: -4,
ImportNid: null,
SymbolValue: 0x1800,
RelocationValueKind.PcRelative,
IsDataImport: false,
RelocationWriteKind.Int32);
Debug.Assert(
unchecked((long)ComputeRelocationValue(pc32, pc32.SymbolValue)) == 0x7FC,
"R_X86_64_PC32 did not apply S + A - P.");
var weak = new RelocationDescriptor(
TargetAddress: 0x2000,
Addend: 7,
ImportNid: "weak",
SymbolValue: 0,
RelocationValueKind.Pointer,
IsDataImport: false,
IsWeak: true);
Debug.Assert(
ComputeRelocationValue(weak, resolvedSymbolValue: 0) == 7,
"An unresolved weak relocation did not use S=0.");
Debug.Assert(
!ShouldCreateImportStub("weak", [weak], moduleManager: null),
"An unresolved weak symbol incorrectly received a trap import stub.");
}
private static ulong AlignUp(ulong value, ulong alignment)
{
var mask = alignment - 1;
@@ -2257,6 +2639,16 @@ public sealed class SelfLoader : ISelfLoader
{
throw new InvalidDataException($"Unsupported ELF program header entry size: {header.ProgramHeaderEntrySize}.");
}
// The CPU backend executes guest instructions natively, so a non
// x86-64 image can only fail deep in execution with an opaque SIGILL.
// Catch it here with an actionable message. (EM_X86_64 == 62.)
const ushort ElfMachineX86_64 = 62;
if (header.Machine != ElfMachineX86_64)
{
throw new InvalidDataException(
$"Unsupported ELF machine type 0x{header.Machine:X4}; SharpEmu only runs x86-64 (EM_X86_64) images.");
}
}
private static unsafe T ReadUnmanaged<T>(ReadOnlySpan<byte> source, int offset)
@@ -2337,10 +2729,25 @@ public sealed class SelfLoader : ISelfLoader
public uint Type => (uint)(Info & uint.MaxValue);
}
private readonly record struct ModuleTlsInfo(ulong MemorySize, ulong StaticOffset);
private enum RelocationValueKind : byte
{
Pointer = 0,
TlsModuleId = 1
TlsModuleId = 1,
// A pre-computed TLS offset written verbatim (TPOFF64/DTPOFF64). Unlike
// Pointer it is a signed displacement, not a mapped address, so it is
// patched as-is without the low-address validity warning.
TlsOffset = 2,
PcRelative = 3,
SymbolSize = 4,
}
private enum RelocationWriteKind : byte
{
UInt64 = 0,
UInt32 = 1,
Int32 = 2,
}
private readonly record struct RelocationDescriptor(
@@ -2349,7 +2756,9 @@ public sealed class SelfLoader : ISelfLoader
string? ImportNid,
ulong SymbolValue,
RelocationValueKind ValueKind,
bool IsDataImport);
bool IsDataImport,
RelocationWriteKind WriteKind = RelocationWriteKind.UInt64,
bool IsWeak = false);
private enum SelfSegmentResolveStatus
{
@@ -2389,14 +2798,10 @@ public sealed class SelfLoader : ISelfLoader
public ulong FileSize => _fileSize;
public bool HasKnownLayout =>
((_ident0 == 0x4F &&
_ident1 == 0x15 &&
_ident2 == 0x3D &&
_ident3 == 0x1D) ||
(_ident0 == 0x54 &&
_ident1 == 0x14 &&
_ident2 == 0xF5 &&
_ident3 == 0xEE)) &&
_ident0 == 0x4F &&
_ident1 == 0x15 &&
_ident2 == 0x3D &&
_ident3 == 0x1D &&
_ident4 == 0x00 &&
_ident5 == 0x01 &&
_ident6 == 0x01 &&