Debugger: Improved callstack reliability

This commit is contained in:
Sour 2018-03-05 21:18:29 -05:00
parent 23c85a447b
commit cdbef431d6
6 changed files with 122 additions and 89 deletions

View File

@ -355,55 +355,75 @@ int32_t Debugger::EvaluateExpression(string expression, EvalResultType &resultTy
return _watchExpEval.Evaluate(expression, state, resultType, operationInfo);
}
void Debugger::RemoveExcessCallstackEntries()
{
while(_callstackRelative.size() >= 1022) {
//Ensure callstack stays below 512 entries - some games never call RTI, causing an infinite stack
_callstackRelative.pop_front();
_callstackRelative.pop_front();
_callstackAbsolute.pop_front();
_callstackAbsolute.pop_front();
}
}
void Debugger::UpdateCallstack(uint8_t instruction, uint32_t addr)
{
if((instruction == 0x60 || instruction == 0x40) && !_callstackRelative.empty()) {
//RTS & RTI
_callstackRelative.pop_back();
_callstackRelative.pop_back();
_callstackAbsolute.pop_back();
_callstackAbsolute.pop_back();
if((instruction == 0x60 || instruction == 0x40) && !_callstack.empty()) {
//RTS & RTI
uint16_t expectedReturnAddress = _callstack[_callstack.size() - 1].JumpSource;
_callstack.pop_back();
_subReturnAddresses.pop_back();
int spOffset = instruction == 0x40 ? 2 : 1; //RTI has an extra byte on the stack (flags)
uint16_t targetAddr = _memoryManager->DebugReadWord(0x100 + ((_debugState.CPU.SP + spOffset) & 0xFF));
if(targetAddr < expectedReturnAddress || targetAddr - expectedReturnAddress > 3) {
//Mismatch, pop that stack frame and add the new one
if(!_callstack.empty()) {
bool foundMatch = false;
for(int i = (int)_callstack.size() - 1; i >= 0; i--) {
if(targetAddr > _callstack[i].JumpSource && targetAddr < _callstack[i].JumpSource + 3) {
//Found a matching stack frame, unstack until that point
foundMatch = true;
for(int j = (int)_callstack.size() - i - 1; j >= 0; j--) {
_callstack.pop_back();
_subReturnAddresses.pop_back();
}
break;
}
}
if(!foundMatch) {
//Couldn't find a matching frame, replace the current one
AddCallstackFrame(expectedReturnAddress, targetAddr, StackFrameFlags::None);
_subReturnAddresses.push_back(expectedReturnAddress + 3);
}
}
}
_profiler->UnstackFunction();
} else if(instruction == 0x20) {
//JSR
RemoveExcessCallstackEntries();
uint16_t targetAddr = _memoryManager->DebugRead(addr + 1) | (_memoryManager->DebugRead(addr + 2) << 8);
_callstackRelative.push_back(addr);
_callstackRelative.push_back(targetAddr);
_callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(addr));
_callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(targetAddr));
_profiler->StackFunction(_mapper->ToAbsoluteAddress(addr), _mapper->ToAbsoluteAddress(targetAddr));
AddCallstackFrame(addr, targetAddr, StackFrameFlags::None);
_subReturnAddresses.push_back(addr + 3);
_profiler->StackFunction(_mapper->ToAbsoluteAddress(addr), _mapper->ToAbsoluteAddress(targetAddr));
}
}
void Debugger::AddCallstackFrame(uint16_t source, uint16_t target, StackFrameFlags flags)
{
if(_callstack.size() >= 511) {
//Ensure callstack stays below 512 entries - games can use various tricks that could keep making the callstack grow
_callstack.pop_front();
_subReturnAddresses.pop_front();
}
StackFrameInfo stackFrame;
stackFrame.JumpSource = source;
stackFrame.JumpSourceAbsolute = _mapper->ToAbsoluteAddress(source);
stackFrame.JumpTarget = target;
stackFrame.JumpTargetAbsolute = _mapper->ToAbsoluteAddress(target);
stackFrame.Flags = flags;
_callstack.push_back(stackFrame);
}
void Debugger::PrivateProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, bool forNmi)
{
RemoveExcessCallstackEntries();
_callstackRelative.push_back(cpuAddr | (forNmi ? 0x40000 : 0x20000));
_callstackRelative.push_back(destCpuAddr);
_callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(cpuAddr));
_callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(destCpuAddr));
AddCallstackFrame(cpuAddr, destCpuAddr, forNmi ? StackFrameFlags::Nmi : StackFrameFlags::Irq);
_subReturnAddresses.push_back(cpuAddr);
_profiler->StackFunction(-1, _mapper->ToAbsoluteAddress(destCpuAddr));
@ -963,21 +983,15 @@ void Debugger::ProcessVramWriteOperation(uint16_t addr, uint8_t &value)
#endif
}
void Debugger::GetCallstack(int32_t* callstackAbsolute, int32_t* callstackRelative)
void Debugger::GetCallstack(StackFrameInfo* callstackArray, uint32_t &callstackSize)
{
int callstackSize = std::min(1022, (int)_callstackRelative.size());
for(int i = 0; i < callstackSize; i++) {
callstackAbsolute[i] = _callstackAbsolute[i];
int32_t relativeAddr = _callstackRelative[i];
if(_mapper->FromAbsoluteAddress(_callstackAbsolute[i]) == -1) {
//Mark address as an unmapped memory addr
relativeAddr |= 0x10000;
}
callstackRelative[i] = relativeAddr;
DebugBreakHelper helper(this);
int i = 0;
for(StackFrameInfo &info : _callstack) {
callstackArray[i] = info;
i++;
}
callstackAbsolute[callstackSize] = -2;
callstackRelative[callstackSize] = -2;
callstackSize = i;
}
int32_t Debugger::GetFunctionEntryPointCount()

View File

@ -73,8 +73,7 @@ private:
vector<uint8_t> _frozenAddresses;
deque<uint32_t> _callstackAbsolute;
deque<uint32_t> _callstackRelative;
deque<StackFrameInfo> _callstack;
deque<int32_t> _subReturnAddresses;
int32_t _stepOutReturnAddress;
@ -131,14 +130,13 @@ private:
void PrivateProcessVramWriteOperation(uint16_t addr, uint8_t &value);
void ProcessBreakpoints(BreakpointType type, OperationInfo &operationInfo, bool allowBreak = true);
void AddCallstackFrame(uint16_t source, uint16_t target, StackFrameFlags flags);
void UpdateCallstack(uint8_t currentInstruction, uint32_t addr);
void PrivateProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, bool forNmi);
void ProcessStepConditions(uint32_t addr);
bool SleepUntilResume(BreakSource source = BreakSource::Break);
void RemoveExcessCallstackEntries();
void AddDebugEvent(DebugEventType type, uint16_t address = -1, uint8_t value = 0, int16_t breakpointId = -1, int8_t ppuLatch = -1);
public:
@ -155,7 +153,7 @@ public:
void GetFunctionEntryPoints(int32_t* entryPoints, int32_t maxCount);
int32_t GetFunctionEntryPointCount();
void GetCallstack(int32_t* callstackAbsolute, int32_t* callstackRelative);
void GetCallstack(StackFrameInfo* callstackArray, uint32_t &callstackSize);
void GetApuState(ApuState *state);
__forceinline void GetState(DebugState *state, bool includeMapperInfo = true);

View File

@ -147,4 +147,20 @@ enum class EventType
InputPolled = 8,
SpriteZeroHit = 9,
EventTypeSize
};
enum class StackFrameFlags : uint8_t
{
None = 0,
Nmi = 1,
Irq = 2
};
struct StackFrameInfo
{
int32_t JumpSourceAbsolute;
int32_t JumpTargetAbsolute;
uint16_t JumpSource;
uint16_t JumpTarget;
StackFrameFlags Flags;
};

View File

@ -23,8 +23,7 @@ namespace Mesen.GUI.Debugger.Controls
public event EventHandler FunctionSelected;
private Int32[] _absoluteCallstack;
private Int32[] _relativeCallstack;
private StackFrameInfo[] _stackFrames;
private Int32 _programCounter;
public ctrlCallstack()
@ -43,7 +42,7 @@ namespace Mesen.GUI.Debugger.Controls
int nmiHandler = InteropEmu.DebugGetMemoryValue(DebugMemoryType.CpuMemory, 0xFFFA) | (InteropEmu.DebugGetMemoryValue(DebugMemoryType.CpuMemory, 0xFFFB) << 8);
int irqHandler = InteropEmu.DebugGetMemoryValue(DebugMemoryType.CpuMemory, 0xFFFE) | (InteropEmu.DebugGetMemoryValue(DebugMemoryType.CpuMemory, 0xFFFF) << 8);
InteropEmu.DebugGetCallstack(out _absoluteCallstack, out _relativeCallstack);
_stackFrames = InteropEmu.DebugGetCallstack();
DebugState state = new DebugState();
InteropEmu.DebugGetState(ref state);
_programCounter = state.CPU.DebugPC;
@ -51,23 +50,19 @@ namespace Mesen.GUI.Debugger.Controls
int relDestinationAddr = -1, absDestinationAddr = -1;
List<StackInfo> stack = new List<StackInfo>();
for(int i = 0, len = _relativeCallstack.Length; i < len; i+=2) {
if(_relativeCallstack[i] == -2) {
break;
}
int relSubEntryAddr = i == 0 ? -1 : _relativeCallstack[i-1] & 0xFFFF;
int absSubEntryAddr = i == 0 ? -1 : _absoluteCallstack[i-1];
for(int i = 0, len = _stackFrames.Length; i < len; i++) {
int relSubEntryAddr = i == 0 ? -1 : _stackFrames[i-1].JumpTarget;
int absSubEntryAddr = i == 0 ? -1 : _stackFrames[i-1].JumpTargetAbsolute;
stack.Add(new StackInfo() {
SubName = this.GetFunctionName(relSubEntryAddr, absSubEntryAddr, nmiHandler, irqHandler),
IsMapped = (_relativeCallstack[i] & 0x10000) != 0x10000,
CurrentRelativeAddr = _relativeCallstack[i] & 0xFFFF,
CurrentAbsoluteAddr = _absoluteCallstack[i]
IsMapped = _stackFrames[i].JumpSourceAbsolute < 0 ? false : InteropEmu.DebugGetRelativeAddress((uint)_stackFrames[i].JumpSourceAbsolute, AddressType.PrgRom) >= 0,
CurrentRelativeAddr = _stackFrames[i].JumpSource,
CurrentAbsoluteAddr = _stackFrames[i].JumpSourceAbsolute
});
relDestinationAddr = _relativeCallstack[i+1] & 0xFFFF;
absDestinationAddr = _absoluteCallstack[i+1];
relDestinationAddr = _stackFrames[i].JumpTarget;
absDestinationAddr = _stackFrames[i].JumpTargetAbsolute;
}
//Add current location
@ -109,12 +104,7 @@ namespace Mesen.GUI.Debugger.Controls
}
this.lstCallstack.EndUpdate();
}
private void UpdateList()
{
}
private string GetFunctionName(int relSubEntryAddr, int absSubEntryAddr, int nmiHandler, int irqHandler)
{
if(relSubEntryAddr < 0) {
@ -122,7 +112,7 @@ namespace Mesen.GUI.Debugger.Controls
}
string funcName;
CodeLabel label = LabelManager.GetLabel((UInt32)absSubEntryAddr, AddressType.PrgRom);
CodeLabel label = absSubEntryAddr >= 0 ? LabelManager.GetLabel((UInt32)absSubEntryAddr, AddressType.PrgRom) : null;
if(label != null) {
funcName = label.Label + (relSubEntryAddr >= 0 ? (" ($" + relSubEntryAddr.ToString("X4") + ")") : "");
} else {
@ -143,10 +133,8 @@ namespace Mesen.GUI.Debugger.Controls
if(this.lstCallstack.SelectedIndices[0] == 0) {
this.FunctionSelected(_programCounter, null);
} else {
Int32 address = _relativeCallstack[(this.lstCallstack.Items.Count - 1 - this.lstCallstack.SelectedIndices[0]) * 2];
if((address & 0x10000) == 0) {
this.FunctionSelected?.Invoke(address & 0xFFFF, null);
}
StackFrameInfo stackFrameInfo = _stackFrames[(this.lstCallstack.Items.Count - 1 - this.lstCallstack.SelectedIndices[0])];
this.FunctionSelected?.Invoke((int)stackFrameInfo.JumpSource, null);
}
}
}

View File

@ -579,20 +579,21 @@ namespace Mesen.GUI
return cdlData;
}
[DllImport(DLLPath, EntryPoint = "DebugGetCallstack")] private static extern void DebugGetCallstackWrapper(IntPtr callstackAbsolute, IntPtr callstackRelative);
public static void DebugGetCallstack(out Int32[] callstackAbsolute, out Int32[] callstackRelative)
[DllImport(DLLPath, EntryPoint = "DebugGetCallstack")] private static extern void DebugGetCallstackWrapper(IntPtr callstackArray, ref UInt32 callstackSize);
public static StackFrameInfo[] DebugGetCallstack()
{
callstackAbsolute = new Int32[1024];
callstackRelative = new Int32[1024];
StackFrameInfo[] callstack = new StackFrameInfo[512];
UInt32 callstackSize = 0;
GCHandle hAbsolute = GCHandle.Alloc(callstackAbsolute, GCHandleType.Pinned);
GCHandle hRelative = GCHandle.Alloc(callstackRelative, GCHandleType.Pinned);
GCHandle hCallstack = GCHandle.Alloc(callstack, GCHandleType.Pinned);
try {
InteropEmu.DebugGetCallstackWrapper(hAbsolute.AddrOfPinnedObject(), hRelative.AddrOfPinnedObject());
InteropEmu.DebugGetCallstackWrapper(hCallstack.AddrOfPinnedObject(), ref callstackSize);
} finally {
hAbsolute.Free();
hRelative.Free();
hCallstack.Free();
}
Array.Resize(ref callstack, (int)callstackSize);
return callstack;
}
[DllImport(DLLPath)] private static extern Int32 DebugGetFunctionEntryPointCount();
[DllImport(DLLPath, EntryPoint = "DebugGetFunctionEntryPoints")] private static extern void DebugGetFunctionEntryPointsWrapper(IntPtr callstackAbsolute, Int32 maxCount);
@ -1124,6 +1125,22 @@ namespace Mesen.GUI
public SByte PpuLatch;
}
public enum StackFrameFlags : byte
{
None = 0,
Nmi = 1,
Irq = 2
}
public struct StackFrameInfo
{
public Int32 JumpSourceAbsolute;
public Int32 JumpTargetAbsolute;
public UInt16 JumpSource;
public UInt16 JumpTarget;
public StackFrameFlags Flags;
};
public struct DebugState
{
public CPUState CPU;

View File

@ -63,7 +63,7 @@ extern "C"
DllExport void __stdcall DebugGetSprites(uint32_t *frameBuffer) { GetDebugger()->GetMemoryDumper()->GetSprites(frameBuffer); }
DllExport void __stdcall DebugGetPalette(uint32_t *frameBuffer) { GetDebugger()->GetMemoryDumper()->GetPalette(frameBuffer); }
DllExport void __stdcall DebugGetCallstack(int32_t *callstackAbsolute, int32_t *callstackRelative) { GetDebugger()->GetCallstack(callstackAbsolute, callstackRelative); }
DllExport void __stdcall DebugGetCallstack(StackFrameInfo *callstackArray, uint32_t &callstackSize) { GetDebugger()->GetCallstack(callstackArray, callstackSize); }
DllExport int32_t __stdcall DebugGetFunctionEntryPointCount() { return GetDebugger()->GetFunctionEntryPointCount(); }
DllExport void __stdcall DebugGetFunctionEntryPoints(int32_t *entryPoints, int32_t maxCount) { GetDebugger()->GetFunctionEntryPoints(entryPoints, maxCount); }