mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a24db567f | |||
| a9b06974be | |||
| 19added142 | |||
| 8a40251a1c | |||
| edb4eb86a2 |
@@ -16,6 +16,11 @@ body:
|
||||
Example title:
|
||||
Demon’s Souls (PPSA01341)
|
||||
|
||||
**Before reporting:**
|
||||
- Test the game on a **fresh build from `main`**, not an old release.
|
||||
- Copy the **exact commit SHA** shown at the top of the log.
|
||||
- `DEBUG` and `TRACE` lines are diagnostic context, not errors by themselves; report the last milestone or the first `WARNING`, `ERROR`, or `CRITICAL` line connected to the stop.
|
||||
|
||||
- type: input
|
||||
id: game_name
|
||||
attributes:
|
||||
@@ -73,11 +78,20 @@ body:
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log File
|
||||
description: Drag and drop your log file here, or click to browse
|
||||
description: Attach the **full** log file (`.log`, `.txt`, or `.zip`). Do not paste only a fragment — the maintainer needs the complete log from launch to crash.
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
accept: ".log,.txt,.zip"
|
||||
|
||||
- type: input
|
||||
id: last_milestone_first_error
|
||||
attributes:
|
||||
label: Last Milestone / First Error
|
||||
description: Paste the **last useful milestone line** or the **first `WARNING`, `ERROR`, or `CRITICAL` line connected to the stop** from the log. `DEBUG`/`TRACE` lines do not count unless a real error follows them.
|
||||
placeholder: e.g. CpuEngine: native-only OR [ERROR] Native backend FAILED: ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: operating_system
|
||||
attributes:
|
||||
@@ -119,7 +133,8 @@ body:
|
||||
id: emulator_version
|
||||
attributes:
|
||||
label: Emulator Version / Commit
|
||||
placeholder: e.g. v0.0.1 / a1b2c3d
|
||||
description: Copy the **exact short SHA** from the top of the log. Do not enter just `0.0.1` without a commit hash.
|
||||
placeholder: e.g. a1b2c3d
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Logging;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
@@ -43,7 +44,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return;
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Recent import calls ({_recentImportTraceCount}):");
|
||||
Log.Info($" Recent import calls ({_recentImportTraceCount}):");
|
||||
int num = (_recentImportTraceWriteIndex - _recentImportTraceCount + _recentImportTrace.Length) % _recentImportTrace.Length;
|
||||
for (int i = 0; i < _recentImportTraceCount; i++)
|
||||
{
|
||||
@@ -51,8 +52,8 @@ public sealed partial class DirectExecutionBackend
|
||||
var entry = _recentImportTrace[num2];
|
||||
if (!string.IsNullOrEmpty(entry.Nid))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] #{entry.DispatchIndex} nid={entry.Nid} ret=0x{entry.ReturnRip:X16} " +
|
||||
Log.Info(
|
||||
$" #{entry.DispatchIndex} nid={entry.Nid} ret=0x{entry.ReturnRip:X16} " +
|
||||
$"rdi=0x{entry.Arg0:X16} rsi=0x{entry.Arg1:X16} rdx=0x{entry.Arg2:X16}");
|
||||
}
|
||||
}
|
||||
@@ -91,12 +92,12 @@ public sealed partial class DirectExecutionBackend
|
||||
list.Add(num7);
|
||||
if (num2 < 32)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Suspicious unresolved pointer: slot=0x{num7:X16} value=0x{value2:X16}");
|
||||
Log.Info($"Suspicious unresolved pointer: slot=0x{num7:X16} value=0x{value2:X16}");
|
||||
num2++;
|
||||
}
|
||||
if (num >= 16384)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Suspicious unresolved pointer scan reached cap ({16384}); truncating.");
|
||||
Log.Warning($"Suspicious unresolved pointer scan reached cap ({16384}); truncating.");
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
if (num != 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Suspicious unresolved pointer hits: {num}");
|
||||
Log.Warning($"Suspicious unresolved pointer hits: {num}");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -121,18 +122,18 @@ public sealed partial class DirectExecutionBackend
|
||||
Span<byte> destination = stackalloc byte[128];
|
||||
if (!cpuContext.Memory.TryRead(returnRip, destination))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip probe: unreadable @0x{returnRip:X16}");
|
||||
Log.Debug($"Import#{dispatchIndex} return-rip probe: unreadable @0x{returnRip:X16}");
|
||||
return;
|
||||
}
|
||||
string value = BitConverter.ToString(destination.ToArray()).Replace("-", " ");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip bytes @0x{returnRip:X16}: {value}");
|
||||
Log.Debug($"Import#{dispatchIndex} return-rip bytes @0x{returnRip:X16}: {value}");
|
||||
if (destination[0] == byte.MaxValue && (destination[1] == 21 || destination[1] == 37))
|
||||
{
|
||||
int num = BitConverter.ToInt32(destination.Slice(2, 4));
|
||||
ulong num2 = returnRip + 6 + (ulong)num;
|
||||
if (cpuContext.TryReadUInt64(num2, out var value2))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip slot: [0x{num2:X16}] = 0x{value2:X16}");
|
||||
Log.Debug($"Import#{dispatchIndex} return-rip slot: [0x{num2:X16}] = 0x{value2:X16}");
|
||||
}
|
||||
}
|
||||
if (destination[0] == 72 && destination[1] == 139 && destination[2] == 5)
|
||||
@@ -141,7 +142,7 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong num4 = returnRip + 7 + (ulong)num3;
|
||||
if (cpuContext.TryReadUInt64(num4, out var value3))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip mov-slot: [0x{num4:X16}] = 0x{value3:X16}");
|
||||
Log.Debug($"Import#{dispatchIndex} return-rip mov-slot: [0x{num4:X16}] = 0x{value3:X16}");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i + 6 <= destination.Length; i++)
|
||||
@@ -153,7 +154,7 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong num7 = num6 + 6 + (ulong)num5;
|
||||
if (cpuContext.TryReadUInt64(num7, out var value4))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} near-indirect @{num6:X16}: slot=0x{num7:X16} val=0x{value4:X16}");
|
||||
Log.Debug($"Import#{dispatchIndex} near-indirect @{num6:X16}: slot=0x{num7:X16} val=0x{value4:X16}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +169,7 @@ public sealed partial class DirectExecutionBackend
|
||||
int rel32 = BitConverter.ToInt32(destination.Slice(i + 1, 4));
|
||||
ulong callRip = returnRip + (ulong)i;
|
||||
ulong target = unchecked((ulong)((long)(callRip + 5) + rel32));
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
|
||||
Log.Debug($"Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
|
||||
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||
{
|
||||
if (_importEntries[importIndex].Address != target)
|
||||
@@ -179,21 +180,21 @@ public sealed partial class DirectExecutionBackend
|
||||
string nid = _importEntries[importIndex].Nid;
|
||||
if (_moduleManager.TryGetExport(nid, out var export))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call import: index={importIndex} {export.LibraryName}:{export.Name} ({nid})");
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call import: index={importIndex} {export.LibraryName}:{export.Name} ({nid})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call import: index={importIndex} nid={nid}");
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call import: index={importIndex} nid={nid}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpuContext.Memory.TryRead(target, targetBytes))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call target bytes @0x{target:X16}: " +
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call target bytes @0x{target:X16}: " +
|
||||
BitConverter.ToString(targetBytes.ToArray()).Replace("-", " "));
|
||||
if (targetBytes[0] == 0xFF && targetBytes[1] == 0x25)
|
||||
{
|
||||
@@ -201,8 +202,8 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong slot = unchecked((ulong)((long)(target + 6) + slotRel32));
|
||||
if (cpuContext.TryReadUInt64(slot, out var slotTarget))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
|
||||
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||
{
|
||||
if (_importEntries[importIndex].Address != slotTarget)
|
||||
@@ -213,13 +214,13 @@ public sealed partial class DirectExecutionBackend
|
||||
string nid = _importEntries[importIndex].Nid;
|
||||
if (_moduleManager.TryGetExport(nid, out var export))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call PLT import: index={importIndex} {export.LibraryName}:{export.Name} ({nid})");
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call PLT import: index={importIndex} {export.LibraryName}:{export.Name} ({nid})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} near-call PLT import: index={importIndex} nid={nid}");
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call PLT import: index={importIndex} nid={nid}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ using SharpEmu.Core.Cpu;
|
||||
using SharpEmu.Core.Loader;
|
||||
using SharpEmu.Core.Memory;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Logging;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IGuestThreadScheduler, IDisposable
|
||||
{
|
||||
private static readonly SharpEmuLogger Log = SharpEmuLog.For("Native");
|
||||
private const int ImportLoopHistoryLength = 2048;
|
||||
|
||||
private const int ImportLoopWideDiversityWindow = 768;
|
||||
|
||||
@@ -10,11 +10,13 @@ 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 SelfMagic = 0x4F153D1D;
|
||||
private const ulong SelfSegmentFlag = 0x800;
|
||||
private const int PageSize = 0x1000;
|
||||
@@ -155,7 +157,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
}
|
||||
|
||||
var tlsModuleId = _nextTlsModuleId == 0 ? 1u : _nextTlsModuleId;
|
||||
Console.Error.WriteLine($"[LOADER][TLS] load_start clear={clearVirtualMemory} next={_nextTlsModuleId} assigned={tlsModuleId}");
|
||||
Log.Debug($"TLS load_start clear={clearVirtualMemory} next={_nextTlsModuleId} assigned={tlsModuleId}");
|
||||
|
||||
var loadContext = ParseLayout(imageData);
|
||||
var elfHeader = ReadUnmanaged<ElfHeader>(imageData, loadContext.ElfOffset);
|
||||
@@ -255,7 +257,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
_nextTlsModuleId++;
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][TLS] load_done assigned={tlsModuleId} next={_nextTlsModuleId}");
|
||||
Log.Debug($"TLS load_done assigned={tlsModuleId} next={_nextTlsModuleId}");
|
||||
|
||||
return new SelfImage(
|
||||
loadContext.IsSelf,
|
||||
@@ -403,15 +405,15 @@ public sealed class SelfLoader : ISelfLoader
|
||||
|
||||
var virtualAddress = header.VirtualAddress + imageBase;
|
||||
|
||||
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}");
|
||||
Log.Debug($"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)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] WARNING: Segment {index} ELF alignment mismatch! " +
|
||||
Log.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}");
|
||||
}
|
||||
@@ -671,13 +673,13 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
if (descriptor.ValueKind == RelocationValueKind.TlsModuleId)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TLS] Patching DTPMOD64 at 0x{descriptor.TargetAddress:X} with module id 0x{targetValue:X}");
|
||||
Log.Debug(
|
||||
$"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}");
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,8 +691,8 @@ public sealed class SelfLoader : ISelfLoader
|
||||
if (descriptor.TargetAddress >= 0x00000008030FC300UL &&
|
||||
descriptor.TargetAddress <= 0x00000008030FC3F0UL)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][RELOC] target=0x{descriptor.TargetAddress:X16} value=0x{targetValue:X16} addend=0x{descriptor.Addend:X} nid={(descriptor.ImportNid ?? "<sym>")}");
|
||||
Log.Debug(
|
||||
$"[RELOC] target=0x{descriptor.TargetAddress:X16} value=0x{targetValue:X16} addend=0x{descriptor.Addend:X} nid={(descriptor.ImportNid ?? "<sym>")}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,15 +785,15 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
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}");
|
||||
Log.Debug(
|
||||
$"[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}");
|
||||
Log.Debug($"[FOCUS][SKIP] unsupported type={relocation.Type}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -800,7 +802,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
|
||||
Log.Debug("[FOCUS][SKIP] target address not mapped");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -840,7 +842,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
|
||||
Log.Debug($"[FOCUS][SKIP] symbol read failed index={symbolIndex}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -852,10 +854,9 @@ public sealed class SelfLoader : ISelfLoader
|
||||
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} " +
|
||||
Log.Warning(
|
||||
$"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(
|
||||
@@ -873,10 +874,9 @@ public sealed class SelfLoader : ISelfLoader
|
||||
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
||||
if (symbolAddress == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
|
||||
Log.Warning(
|
||||
$"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(
|
||||
@@ -893,7 +893,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
|
||||
Log.Debug($"[FOCUS][SKIP] bind={symbolBind} not importable");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -902,7 +902,7 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
|
||||
Log.Debug($"[FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -950,8 +950,8 @@ public sealed class SelfLoader : ISelfLoader
|
||||
|
||||
if (sectionSymbols > 0 || dynamicSymbols > 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Runtime symbol index populated: section={sectionSymbols}, dynamic={dynamicSymbols}, total={runtimeSymbols.Count}");
|
||||
Log.Info(
|
||||
$"Runtime symbol index populated: section={sectionSymbols}, dynamic={dynamicSymbols}, total={runtimeSymbols.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,8 +1025,8 @@ public sealed class SelfLoader : ISelfLoader
|
||||
|
||||
if (preInitializers.Count != 0 || initializers.Count != 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
|
||||
Log.Info(
|
||||
$"Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1409,8 +1409,8 @@ public sealed class SelfLoader : ISelfLoader
|
||||
|
||||
var requiredBytes = checked((ulong)orderedImportNids.Count * ImportStubSlotSize);
|
||||
var mapSize = AlignUp(Math.Max(requiredBytes, (ulong)PageSize), PageSize);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER] CreateImportStubMapping: nids={orderedImportNids.Count}, required=0x{requiredBytes:X}, map_size=0x{mapSize:X}");
|
||||
Log.Debug(
|
||||
$"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.");
|
||||
@@ -1842,16 +1842,16 @@ public sealed class SelfLoader : ISelfLoader
|
||||
tableBytes = GC.AllocateUninitializedArray<byte>((int)size);
|
||||
|
||||
var guestAddr = location + imageBase;
|
||||
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: trying guest address 0x{guestAddr:X}");
|
||||
Log.Debug($"TryLoadTableBytes: trying guest address 0x{guestAddr:X}");
|
||||
if (virtualMemory.TryRead(guestAddr, tableBytes))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: loaded from guest memory at 0x{guestAddr:X}");
|
||||
Log.Debug($"TryLoadTableBytes: loaded from guest memory at 0x{guestAddr:X}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (virtualMemory.TryRead(location, tableBytes))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: loaded from absolute guest address 0x{location:X}");
|
||||
Log.Debug($"TryLoadTableBytes: loaded from absolute guest address 0x{location:X}");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1859,11 +1859,11 @@ public sealed class SelfLoader : ISelfLoader
|
||||
{
|
||||
var slice = elfData.Slice((int)location, (int)size);
|
||||
tableBytes = slice.ToArray();
|
||||
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: loaded from elfData as file offset at 0x{location:X}");
|
||||
Log.Debug($"TryLoadTableBytes: loaded from elfData as file offset at 0x{location:X}");
|
||||
return true;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER] TryLoadTableBytes: FAILED for location 0x{location:X}");
|
||||
Log.Warning($"TryLoadTableBytes: FAILED for location 0x{location:X}");
|
||||
tableBytes = Array.Empty<byte>();
|
||||
return false;
|
||||
}
|
||||
@@ -1920,17 +1920,17 @@ public sealed class SelfLoader : ISelfLoader
|
||||
|
||||
private static ulong ResolveMappedAddressOrFallback(IVirtualMemory virtualMemory, ulong address, ulong imageBase)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TEST] ResolveMappedAddressOrFallback addr=0x{address:X} imageBase=0x{imageBase:X16}");
|
||||
Log.Debug($"ResolveMappedAddressOrFallback addr=0x{address:X} imageBase=0x{imageBase:X16}");
|
||||
|
||||
if (address == 0)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][TEST] -> return 0 (null)");
|
||||
Log.Debug("-> return 0 (null)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (TryResolveMappedAddress(virtualMemory, address, imageBase, 1, out var resolved))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TEST] -> resolved raw 0x{resolved:X16}");
|
||||
Log.Debug($"-> resolved raw 0x{resolved:X16}");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -1939,18 +1939,18 @@ public sealed class SelfLoader : ISelfLoader
|
||||
var rebased = address + imageBase;
|
||||
if (TryResolveMappedAddress(virtualMemory, rebased, imageBase, 1, out var resolvedRebased))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TEST] -> resolved rebased 0x{resolvedRebased:X16}");
|
||||
Log.Debug($"-> resolved rebased 0x{resolvedRebased:X16}");
|
||||
return resolvedRebased;
|
||||
}
|
||||
}
|
||||
|
||||
if (address < 0x10000)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TEST] -> reject small 0x{address:X}");
|
||||
Log.Debug($"-> reject small 0x{address:X}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][TEST] -> fallback raw 0x{address:X}");
|
||||
Log.Debug($"-> fallback raw 0x{address:X}");
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,13 @@ public enum OrbisGen2Result : int
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_DEADLOCK = unchecked((int)0x8002000B),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the waited-on object was deleted while the caller was
|
||||
/// blocked on it. Matches the SCE kernel EACCES code that waiters of a
|
||||
/// deleted semaphore observe.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_DELETED = unchecked((int)0x8002000D),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the target resource is busy.
|
||||
/// </summary>
|
||||
@@ -50,6 +57,13 @@ public enum OrbisGen2Result : int
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_TRY_AGAIN = unchecked((int)0x80020023),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a blocked wait was canceled (e.g. sceKernelCancelSema).
|
||||
/// Matches the SCE kernel ECANCELED code that canceled semaphore waiters
|
||||
/// observe.
|
||||
/// </summary>
|
||||
ORBIS_GEN2_ERROR_CANCELED = unchecked((int)0x80020055),
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that behavior is recognized but not implemented yet.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,8 @@ public static class KernelExports
|
||||
private static readonly object _coredumpGate = new();
|
||||
private static ulong _coredumpHandler;
|
||||
private static ulong _coredumpHandlerContext;
|
||||
private const uint Gen4CompiledSdkVersion = 0x05000000;
|
||||
private const uint Gen5CompiledSdkVersion = 0x09000000;
|
||||
|
||||
private readonly record struct CxaDestructorEntry(
|
||||
ulong Function,
|
||||
@@ -26,7 +28,24 @@ public static class KernelExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelGetCompiledSdkVersion(CpuContext ctx)
|
||||
{
|
||||
_ = ctx;
|
||||
var versionAddress = ctx[CpuRegister.Rdi];
|
||||
if (versionAddress == 0)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
var sdkVersion = ctx.TargetGeneration == Generation.Gen5
|
||||
? Gen5CompiledSdkVersion
|
||||
: Gen4CompiledSdkVersion;
|
||||
|
||||
if (!ctx.TryWriteUInt32(versionAddress, sdkVersion))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelRuntimeCompatExports
|
||||
{
|
||||
private const int Efault = 14;
|
||||
private const ulong TlsErrnoOffset = 0x40;
|
||||
private const ulong TlsStackChkGuardBaseOffset = 0x800;
|
||||
private const ulong StackChkGuardFieldOffset = 0x10;
|
||||
@@ -226,8 +227,11 @@ public static class KernelRuntimeCompatExports
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var seconds = now.ToUnixTimeSeconds();
|
||||
var microseconds = (now.Ticks % TimeSpan.TicksPerSecond) / 10;
|
||||
if (!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
|
||||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)microseconds)))
|
||||
|
||||
Span<byte> timevalBuffer = stackalloc byte[16];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(timevalBuffer, seconds);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(timevalBuffer[sizeof(long)..], microseconds);
|
||||
if (!ctx.Memory.TryWrite(timeAddress, timevalBuffer))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
@@ -249,18 +253,28 @@ public static class KernelRuntimeCompatExports
|
||||
var seconds = now.ToUnixTimeSeconds();
|
||||
var microseconds = (now.Ticks % TimeSpan.TicksPerSecond) / 10;
|
||||
|
||||
if (timeAddress != 0 &&
|
||||
(!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
|
||||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)microseconds))))
|
||||
if (timeAddress != 0)
|
||||
{
|
||||
return -1;
|
||||
Span<byte> timevalBuffer = stackalloc byte[16];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(timevalBuffer, seconds);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(timevalBuffer[sizeof(long)..], microseconds);
|
||||
if (!ctx.Memory.TryWrite(timeAddress, timevalBuffer))
|
||||
{
|
||||
TrySetErrno(ctx, Efault);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (timezoneAddress != 0 &&
|
||||
(!ctx.TryWriteInt32(timezoneAddress, 0) ||
|
||||
!ctx.TryWriteInt32(timezoneAddress + sizeof(int), 0)))
|
||||
if (timezoneAddress != 0)
|
||||
{
|
||||
return -1;
|
||||
Span<byte> timezoneBuffer = stackalloc byte[8];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(timezoneBuffer, 0);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(timezoneBuffer[sizeof(int)..], 0);
|
||||
if (!ctx.Memory.TryWrite(timezoneAddress, timezoneBuffer))
|
||||
{
|
||||
TrySetErrno(ctx, Efault);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
|
||||
@@ -19,9 +19,22 @@ public static class KernelSemaphoreCompatExports
|
||||
public required int MaxCount { get; init; }
|
||||
public int Count { get; set; }
|
||||
public int WaitingThreads { get; set; }
|
||||
public int CancelEpoch { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
public object Gate { get; } = new();
|
||||
}
|
||||
|
||||
private sealed class SemaphoreWaiter
|
||||
{
|
||||
public required int NeedCount { get; init; }
|
||||
public required int CancelEpochAtBlock { get; init; }
|
||||
|
||||
// Written and read only under the owning semaphore's Gate.
|
||||
public int? Result { get; set; }
|
||||
}
|
||||
|
||||
private static string GetSemaphoreWakeKey(uint handle) => $"kernel_sema:0x{handle:X8}";
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "188x57JYp0g",
|
||||
ExportName = "sceKernelCreateSema",
|
||||
@@ -58,17 +71,27 @@ public static class KernelSemaphoreCompatExports
|
||||
handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
|
||||
}
|
||||
|
||||
_semaphores[handle] = new KernelSemaphoreState
|
||||
var state = new KernelSemaphoreState
|
||||
{
|
||||
Name = name,
|
||||
InitialCount = initialCount,
|
||||
MaxCount = maxCount,
|
||||
Count = initialCount,
|
||||
};
|
||||
_semaphores[handle] = state;
|
||||
|
||||
if (!ctx.TryWriteUInt32(semaphoreAddress, handle))
|
||||
{
|
||||
_semaphores.TryRemove(handle, out _);
|
||||
// Handles are sequential and guest-predictable, so a hostile guest can
|
||||
// race a WaitSema onto the handle between publication above and this
|
||||
// rollback. Strand-proof that waiter exactly like DeleteSema does.
|
||||
lock (state.Gate)
|
||||
{
|
||||
state.Deleted = true;
|
||||
}
|
||||
|
||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
@@ -118,7 +141,17 @@ public static class KernelSemaphoreCompatExports
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
||||
}
|
||||
|
||||
if (!GuestThreadExecution.RequestCurrentThreadBlock(ctx, "sceKernelWaitSema"))
|
||||
var waiter = new SemaphoreWaiter
|
||||
{
|
||||
NeedCount = needCount,
|
||||
CancelEpochAtBlock = semaphore.CancelEpoch,
|
||||
};
|
||||
if (!GuestThreadExecution.RequestCurrentThreadBlock(
|
||||
ctx,
|
||||
"sceKernelWaitSema",
|
||||
GetSemaphoreWakeKey(handle),
|
||||
resumeHandler: () => CompleteBlockedSemaWait(semaphore, waiter),
|
||||
wakeHandler: () => TryConsumeBlockedSemaWait(semaphore, waiter)))
|
||||
{
|
||||
TraceSemaphore($"wait-would-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
||||
@@ -193,8 +226,14 @@ public static class KernelSemaphoreCompatExports
|
||||
|
||||
semaphore.Count += signalCount;
|
||||
TraceSemaphore($"signal handle=0x{handle:X8} name='{semaphore.Name}' signal={signalCount} count={semaphore.Count} waiters={semaphore.WaitingThreads}");
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
// Wake after releasing the gate (lock order: scheduler gate -> semaphore gate).
|
||||
// Wake everyone; the wake handler consumes the count per waiter, so a waiter
|
||||
// whose needCount exceeds the remaining count stays parked while a smaller
|
||||
// waiter can proceed.
|
||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
@@ -226,10 +265,16 @@ public static class KernelSemaphoreCompatExports
|
||||
}
|
||||
|
||||
semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount;
|
||||
semaphore.WaitingThreads = 0;
|
||||
semaphore.CancelEpoch++;
|
||||
// WaitingThreads is NOT zeroed here: each canceled waiter decrements it
|
||||
// exactly once in its wake handler. Zeroing here as well would double-count
|
||||
// and silently absorb the increment of a waiter that parks between this
|
||||
// gate release and the wake-all below.
|
||||
TraceSemaphore($"cancel handle=0x{handle:X8} name='{semaphore.Name}' set={setCount} count={semaphore.Count}");
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
@@ -245,10 +290,84 @@ public static class KernelSemaphoreCompatExports
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Delete succeeds even with blocked waiters; they wake with the deleted
|
||||
// result (the SCE kernel wakes waiters with the EACCES-class code).
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
semaphore.Deleted = true;
|
||||
}
|
||||
|
||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
|
||||
TraceSemaphore($"delete handle=0x{handle:X8} name='{semaphore.Name}'");
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
// Wake handler: runs under the scheduler's guest-thread gate (lock order:
|
||||
// scheduler gate -> semaphore gate). Returns true iff the waiter has a final
|
||||
// result and should be re-readied; false leaves it parked.
|
||||
private static bool TryConsumeBlockedSemaWait(KernelSemaphoreState semaphore, SemaphoreWaiter waiter)
|
||||
{
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
return TryConsumeBlockedSemaWaitLocked(semaphore, waiter);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConsumeBlockedSemaWaitLocked(KernelSemaphoreState semaphore, SemaphoreWaiter waiter)
|
||||
{
|
||||
if (waiter.Result is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (semaphore.Deleted)
|
||||
{
|
||||
waiter.Result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DELETED;
|
||||
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
|
||||
TraceSemaphore($"wake-deleted name='{semaphore.Name}' need={waiter.NeedCount}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (semaphore.CancelEpoch != waiter.CancelEpochAtBlock)
|
||||
{
|
||||
waiter.Result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_CANCELED;
|
||||
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
|
||||
TraceSemaphore($"wake-canceled name='{semaphore.Name}' need={waiter.NeedCount}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (semaphore.Count >= waiter.NeedCount)
|
||||
{
|
||||
semaphore.Count -= waiter.NeedCount;
|
||||
waiter.Result = (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
|
||||
TraceSemaphore($"wake-consume name='{semaphore.Name}' need={waiter.NeedCount} count={semaphore.Count} waiters={semaphore.WaitingThreads}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resume handler: runs on the woken guest thread outside the scheduler gate;
|
||||
// its return value becomes the guest's RAX for the resumed sceKernelWaitSema.
|
||||
private static int CompleteBlockedSemaWait(KernelSemaphoreState semaphore, SemaphoreWaiter waiter)
|
||||
{
|
||||
lock (semaphore.Gate)
|
||||
{
|
||||
if (waiter.Result is null && !TryConsumeBlockedSemaWaitLocked(semaphore, waiter))
|
||||
{
|
||||
// Nothing readies a parked semaphore waiter without the wake handler
|
||||
// resolving it, so reaching here means the scheduler contract changed.
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][GAP] sema.resume-no-outcome name='{semaphore.Name}' need={waiter.NeedCount} count={semaphore.Count}");
|
||||
waiter.Result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
|
||||
}
|
||||
|
||||
return waiter.Result!.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TraceSemaphore(string message)
|
||||
{
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SEMA"), "1", StringComparison.Ordinal))
|
||||
|
||||
Reference in New Issue
Block a user