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:
Somebody Whoisbored 2021-05-20 16:27:16 -07:00 committed by GitHub
parent 2c3dab6986
commit 26e5b5acff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 35 deletions

View file

@ -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()));
} }
} }

View file

@ -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);
} }

View file

@ -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)

View file

@ -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);