From 26e5b5acffa28c7fc9b6d13612ef5077fa35c971 Mon Sep 17 00:00:00 2001
From: Somebody Whoisbored <13044396+shadowninja108@users.noreply.github.com>
Date: Thu, 20 May 2021 16:27:16 -0700
Subject: [PATCH] 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>
---
 .../HOS/Kernel/Process/HleProcessDebugger.cs  | 190 ++++++++++++++----
 Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs    |   2 +
 .../HOS/Kernel/SupervisorCall/Syscall.cs      |   1 +
 Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs   |  12 +-
 4 files changed, 170 insertions(+), 35 deletions(-)

diff --git a/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
index 5ad33154d0..627049bc50 100644
--- a/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
@@ -1,5 +1,6 @@
 using Ryujinx.HLE.HOS.Diagnostics.Demangler;
 using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
 using Ryujinx.HLE.Loaders.Elf;
 using Ryujinx.Memory;
 using System.Collections.Generic;
@@ -18,12 +19,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         private class Image
         {
             public ulong BaseAddress { get; }
+            public ulong Size { get; }
+            public ulong EndAddress => BaseAddress + Size;
 
             public ElfSymbol[] Symbols { get; }
 
-            public Image(ulong baseAddress, ElfSymbol[] symbols)
+            public Image(ulong baseAddress, ulong size, ElfSymbol[] symbols)
             {
                 BaseAddress = baseAddress;
+                Size        = size;
                 Symbols     = symbols;
             }
         }
@@ -39,41 +43,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             _images = new List<Image>();
         }
 
-        public string GetGuestStackTrace(ARMeilleure.State.ExecutionContext context)
+        public string GetGuestStackTrace(KThread thread)
         {
             EnsureLoaded();
 
+            var context = thread.Context;
+
             StringBuilder trace = new StringBuilder();
 
+            trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
+
             void AppendTrace(ulong address)
             {
-                Image image = GetImage(address, out int imageIndex);
-
-                if (image == null || !TryGetSubName(image, address, out string subName))
+                if(AnalyzePointer(out PointerInfo info, address, thread))
                 {
-                    subName = $"Sub{address:x16}";
-                }
-                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}");
+                    trace.AppendLine($"   0x{address:x16}\t{info.ImageDisplay}\t{info.SubDisplay}");
                 }
                 else
                 {
-                    trace.AppendLine($"   ??? {subName}");
+                    trace.AppendLine($"   0x{address:x16}");
                 }
             }
 
-            trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
-
             if (context.IsAarch32)
             {
                 ulong framePointer = context.GetX(11);
@@ -114,7 +105,47 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             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;
 
@@ -127,18 +158,27 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
                 int middle = left + (size >> 1);
 
-                ElfSymbol symbol = image.Symbols[middle];
+                symbol = image.Symbols[middle];
 
                 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;
                 }
 
-                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;
                 }
@@ -148,18 +188,100 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
                 }
             }
 
-            name = null;
+            symbol = default;
 
             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)
         {
             lock (_images)
             {
                 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];
                     }
@@ -226,7 +348,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
                 if (info.State == MemoryState.CodeStatic && info.Permission == KMemoryPermission.ReadAndExecute)
                 {
-                    LoadMod0Symbols(_owner.CpuMemory, info.Address);
+                    LoadMod0Symbols(_owner.CpuMemory, info.Address, info.Size);
                 }
 
                 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);
 
@@ -315,7 +437,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
             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);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 15ab82b8d3..516b53f48e 100644
--- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1073,6 +1073,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         private bool InvalidAccessHandler(ulong va)
         {
             KernelStatic.GetCurrentThread()?.PrintGuestStackTrace();
+            KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
 
             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)
         {
             KernelStatic.GetCurrentThread().PrintGuestStackTrace();
+            KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
 
             throw new UndefinedInstructionException(e.Address, e.OpCode);
         }
diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
index 238f122631..39161f2206 100644
--- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
+++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
@@ -1409,6 +1409,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
             if ((reason & (1UL << 31)) == 0)
             {
                 currentThread.PrintGuestStackTrace();
+                currentThread.PrintGuestRegisterPrintout();
 
                 // As the process is exiting, this is probably caused by emulation termination.
                 if (currentThread.Owner.State == ProcessState.Exiting)
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 7ba9e43a10..3ea03f1662 100644
--- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -991,7 +991,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         public string GetGuestStackTrace()
         {
-            return Owner.Debugger.GetGuestStackTrace(Context);
+            return Owner.Debugger.GetGuestStackTrace(this);
+        }
+
+        public string GetGuestRegisterPrintout()
+        {
+            return Owner.Debugger.GetCpuRegisterPrintout(this);
         }
 
         public void PrintGuestStackTrace()
@@ -999,6 +1004,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             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)
         {
             Interlocked.Add(ref _totalTimeRunning, ticks);