Extend info printed when guest crashes/breaks execution (#1845)
* Add CPU register printout when guest crashes/breaks execution * Print out registers when undefined instruction is hit * Apply suggestions from code review Co-authored-by: Ac_K <Acoustik666@gmail.com> * Fixes after rebase * Address gdkchan's comments Co-authored-by: Ac_K <Acoustik666@gmail.com> Co-authored-by: Mary <me@thog.eu>
This commit is contained in:
parent
2c3dab6986
commit
26e5b5acff
4 changed files with 170 additions and 35 deletions
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.HLE.HOS.Diagnostics.Demangler;
|
using Ryujinx.HLE.HOS.Diagnostics.Demangler;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.HLE.Loaders.Elf;
|
using Ryujinx.HLE.Loaders.Elf;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -18,12 +19,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
private class Image
|
private class Image
|
||||||
{
|
{
|
||||||
public ulong BaseAddress { get; }
|
public ulong BaseAddress { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
public ulong EndAddress => BaseAddress + Size;
|
||||||
|
|
||||||
public ElfSymbol[] Symbols { get; }
|
public ElfSymbol[] Symbols { get; }
|
||||||
|
|
||||||
public Image(ulong baseAddress, ElfSymbol[] symbols)
|
public Image(ulong baseAddress, ulong size, ElfSymbol[] symbols)
|
||||||
{
|
{
|
||||||
BaseAddress = baseAddress;
|
BaseAddress = baseAddress;
|
||||||
|
Size = size;
|
||||||
Symbols = symbols;
|
Symbols = symbols;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,41 +43,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
_images = new List<Image>();
|
_images = new List<Image>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetGuestStackTrace(ARMeilleure.State.ExecutionContext context)
|
public string GetGuestStackTrace(KThread thread)
|
||||||
{
|
{
|
||||||
EnsureLoaded();
|
EnsureLoaded();
|
||||||
|
|
||||||
|
var context = thread.Context;
|
||||||
|
|
||||||
StringBuilder trace = new StringBuilder();
|
StringBuilder trace = new StringBuilder();
|
||||||
|
|
||||||
|
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
|
||||||
|
|
||||||
void AppendTrace(ulong address)
|
void AppendTrace(ulong address)
|
||||||
{
|
{
|
||||||
Image image = GetImage(address, out int imageIndex);
|
if(AnalyzePointer(out PointerInfo info, address, thread))
|
||||||
|
|
||||||
if (image == null || !TryGetSubName(image, address, out string subName))
|
|
||||||
{
|
{
|
||||||
subName = $"Sub{address:x16}";
|
trace.AppendLine($" 0x{address:x16}\t{info.ImageDisplay}\t{info.SubDisplay}");
|
||||||
}
|
|
||||||
else if (subName.StartsWith("_Z"))
|
|
||||||
{
|
|
||||||
subName = Demangler.Parse(subName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image != null)
|
|
||||||
{
|
|
||||||
ulong offset = address - image.BaseAddress;
|
|
||||||
|
|
||||||
string imageName = GetGuessedNsoNameFromIndex(imageIndex);
|
|
||||||
|
|
||||||
trace.AppendLine($" {imageName}:0x{offset:x8} {subName}");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace.AppendLine($" ??? {subName}");
|
trace.AppendLine($" 0x{address:x16}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
|
|
||||||
|
|
||||||
if (context.IsAarch32)
|
if (context.IsAarch32)
|
||||||
{
|
{
|
||||||
ulong framePointer = context.GetX(11);
|
ulong framePointer = context.GetX(11);
|
||||||
|
@ -114,7 +105,47 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
return trace.ToString();
|
return trace.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetSubName(Image image, ulong address, out string name)
|
public string GetCpuRegisterPrintout(KThread thread)
|
||||||
|
{
|
||||||
|
EnsureLoaded();
|
||||||
|
|
||||||
|
var context = thread.Context;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
string GetReg(int x)
|
||||||
|
{
|
||||||
|
var v = x == 32 ? (ulong)thread.LastPc : context.GetX(x);
|
||||||
|
if (!AnalyzePointer(out PointerInfo info, v, thread))
|
||||||
|
{
|
||||||
|
return $"0x{v:x16}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(info.ImageName))
|
||||||
|
{
|
||||||
|
return $"0x{v:x16} ({info.ImageDisplay})\t=> {info.SubDisplay}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"0x{v:x16} ({info.SpDisplay})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i <= 28; i++)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"\tX[{i:d2}]:\t{GetReg(i)}");
|
||||||
|
}
|
||||||
|
sb.AppendLine($"\tFP:\t{GetReg(29)}");
|
||||||
|
sb.AppendLine($"\tLR:\t{GetReg(30)}");
|
||||||
|
sb.AppendLine($"\tSP:\t{GetReg(31)}");
|
||||||
|
sb.AppendLine($"\tPC:\t{GetReg(32)}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetSubName(Image image, ulong address, out ElfSymbol symbol)
|
||||||
{
|
{
|
||||||
address -= image.BaseAddress;
|
address -= image.BaseAddress;
|
||||||
|
|
||||||
|
@ -127,18 +158,27 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
|
||||||
int middle = left + (size >> 1);
|
int middle = left + (size >> 1);
|
||||||
|
|
||||||
ElfSymbol symbol = image.Symbols[middle];
|
symbol = image.Symbols[middle];
|
||||||
|
|
||||||
ulong endAddr = symbol.Value + symbol.Size;
|
ulong endAddr = symbol.Value + symbol.Size;
|
||||||
|
|
||||||
if ((ulong)address >= symbol.Value && (ulong)address < endAddr)
|
if (address >= symbol.Value && address < endAddr)
|
||||||
{
|
{
|
||||||
name = symbol.Name;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ulong)address < (ulong)symbol.Value)
|
if (middle + 1 < image.Symbols.Length)
|
||||||
|
{
|
||||||
|
ElfSymbol next = image.Symbols[middle + 1];
|
||||||
|
|
||||||
|
// If our symbol points inbetween two symbols, we can *guess* that it's referring to the first one
|
||||||
|
if (address >= symbol.Value && address < next.Value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address < symbol.Value)
|
||||||
{
|
{
|
||||||
right = middle - 1;
|
right = middle - 1;
|
||||||
}
|
}
|
||||||
|
@ -148,18 +188,100 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
name = null;
|
symbol = default;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PointerInfo
|
||||||
|
{
|
||||||
|
public string ImageName;
|
||||||
|
public string SubName;
|
||||||
|
|
||||||
|
public ulong Offset;
|
||||||
|
public ulong SubOffset;
|
||||||
|
|
||||||
|
public string ImageDisplay => $"{ImageName}:0x{Offset:x4}";
|
||||||
|
public string SubDisplay => SubOffset == 0 ? SubName : $"{SubName}:0x{SubOffset:x4}";
|
||||||
|
public string SpDisplay => SubOffset == 0 ? "SP" : $"SP:-0x{SubOffset:x4}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AnalyzePointer(out PointerInfo info, ulong address, KThread thread)
|
||||||
|
{
|
||||||
|
if (AnalyzePointerFromImages(out info, address))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AnalyzePointerFromStack(out info, address, thread))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AnalyzePointerFromImages(out PointerInfo info, ulong address)
|
||||||
|
{
|
||||||
|
info = default;
|
||||||
|
|
||||||
|
Image image = GetImage(address, out int imageIndex);
|
||||||
|
|
||||||
|
if (image == null)
|
||||||
|
{
|
||||||
|
// Value isn't a pointer to a known image...
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Offset = address - image.BaseAddress;
|
||||||
|
|
||||||
|
// Try to find what this pointer is referring to
|
||||||
|
if (TryGetSubName(image, address, out ElfSymbol symbol))
|
||||||
|
{
|
||||||
|
info.SubName = symbol.Name;
|
||||||
|
|
||||||
|
// Demangle string if possible
|
||||||
|
if (info.SubName.StartsWith("_Z"))
|
||||||
|
{
|
||||||
|
info.SubName = Demangler.Parse(info.SubName);
|
||||||
|
}
|
||||||
|
info.SubOffset = info.Offset - symbol.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info.SubName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
info.ImageName = GetGuessedNsoNameFromIndex(imageIndex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AnalyzePointerFromStack(out PointerInfo info, ulong address, KThread thread)
|
||||||
|
{
|
||||||
|
info = default;
|
||||||
|
|
||||||
|
ulong sp = thread.Context.GetX(31);
|
||||||
|
var memoryInfo = _owner.MemoryManager.QueryMemory(address);
|
||||||
|
MemoryState memoryState = memoryInfo.State;
|
||||||
|
|
||||||
|
if (!memoryState.HasFlag(MemoryState.Stack)) // Is this pointer within the stack?
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.SubOffset = address - sp;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private Image GetImage(ulong address, out int index)
|
private Image GetImage(ulong address, out int index)
|
||||||
{
|
{
|
||||||
lock (_images)
|
lock (_images)
|
||||||
{
|
{
|
||||||
for (index = _images.Count - 1; index >= 0; index--)
|
for (index = _images.Count - 1; index >= 0; index--)
|
||||||
{
|
{
|
||||||
if (address >= _images[index].BaseAddress)
|
if (address >= _images[index].BaseAddress && address < _images[index].EndAddress)
|
||||||
{
|
{
|
||||||
return _images[index];
|
return _images[index];
|
||||||
}
|
}
|
||||||
|
@ -226,7 +348,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
|
||||||
if (info.State == MemoryState.CodeStatic && info.Permission == KMemoryPermission.ReadAndExecute)
|
if (info.State == MemoryState.CodeStatic && info.Permission == KMemoryPermission.ReadAndExecute)
|
||||||
{
|
{
|
||||||
LoadMod0Symbols(_owner.CpuMemory, info.Address);
|
LoadMod0Symbols(_owner.CpuMemory, info.Address, info.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
oldAddress = address;
|
oldAddress = address;
|
||||||
|
@ -235,7 +357,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadMod0Symbols(IVirtualMemoryManager memory, ulong textOffset)
|
private void LoadMod0Symbols(IVirtualMemoryManager memory, ulong textOffset, ulong textSize)
|
||||||
{
|
{
|
||||||
ulong mod0Offset = textOffset + memory.Read<uint>(textOffset + 4);
|
ulong mod0Offset = textOffset + memory.Read<uint>(textOffset + 4);
|
||||||
|
|
||||||
|
@ -315,7 +437,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
|
||||||
lock (_images)
|
lock (_images)
|
||||||
{
|
{
|
||||||
_images.Add(new Image(textOffset, symbols.OrderBy(x => x.Value).ToArray()));
|
_images.Add(new Image(textOffset, textSize, symbols.OrderBy(x => x.Value).ToArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,4 +473,4 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
|
return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1073,6 +1073,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
private bool InvalidAccessHandler(ulong va)
|
private bool InvalidAccessHandler(ulong va)
|
||||||
{
|
{
|
||||||
KernelStatic.GetCurrentThread()?.PrintGuestStackTrace();
|
KernelStatic.GetCurrentThread()?.PrintGuestStackTrace();
|
||||||
|
KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
|
||||||
|
|
||||||
Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
|
Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
|
||||||
|
|
||||||
|
@ -1082,6 +1083,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e)
|
private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e)
|
||||||
{
|
{
|
||||||
KernelStatic.GetCurrentThread().PrintGuestStackTrace();
|
KernelStatic.GetCurrentThread().PrintGuestStackTrace();
|
||||||
|
KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
|
||||||
|
|
||||||
throw new UndefinedInstructionException(e.Address, e.OpCode);
|
throw new UndefinedInstructionException(e.Address, e.OpCode);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1409,6 +1409,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
if ((reason & (1UL << 31)) == 0)
|
if ((reason & (1UL << 31)) == 0)
|
||||||
{
|
{
|
||||||
currentThread.PrintGuestStackTrace();
|
currentThread.PrintGuestStackTrace();
|
||||||
|
currentThread.PrintGuestRegisterPrintout();
|
||||||
|
|
||||||
// As the process is exiting, this is probably caused by emulation termination.
|
// As the process is exiting, this is probably caused by emulation termination.
|
||||||
if (currentThread.Owner.State == ProcessState.Exiting)
|
if (currentThread.Owner.State == ProcessState.Exiting)
|
||||||
|
|
|
@ -991,7 +991,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
|
|
||||||
public string GetGuestStackTrace()
|
public string GetGuestStackTrace()
|
||||||
{
|
{
|
||||||
return Owner.Debugger.GetGuestStackTrace(Context);
|
return Owner.Debugger.GetGuestStackTrace(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetGuestRegisterPrintout()
|
||||||
|
{
|
||||||
|
return Owner.Debugger.GetCpuRegisterPrintout(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintGuestStackTrace()
|
public void PrintGuestStackTrace()
|
||||||
|
@ -999,6 +1004,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
Logger.Info?.Print(LogClass.Cpu, $"Guest stack trace:\n{GetGuestStackTrace()}\n");
|
Logger.Info?.Print(LogClass.Cpu, $"Guest stack trace:\n{GetGuestStackTrace()}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrintGuestRegisterPrintout()
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Cpu, $"Guest CPU registers:\n{GetGuestRegisterPrintout()}\n");
|
||||||
|
}
|
||||||
|
|
||||||
public void AddCpuTime(long ticks)
|
public void AddCpuTime(long ticks)
|
||||||
{
|
{
|
||||||
Interlocked.Add(ref _totalTimeRunning, ticks);
|
Interlocked.Add(ref _totalTimeRunning, ticks);
|
||||||
|
|
Loading…
Reference in a new issue