Commits

spencercw committed 6545931

Make the stack trace more resistant to programs that do strange and unnatural things.

Comments (0)

Files changed (3)

gb_emulator/include/gb_emulator/gb_debugger.h

 	void handleCall(bool condition);
 	void handleReturn(bool condition);
 	void handleJumpHl();
+	void handleJump(bool condition);
+	void handleRst(uint16_t addr);
 
 	void doReturn();
 

gb_emulator/src/gb_debugger.cpp

 
 #include <gb_emulator/gb.hpp>
 
+#include "gb_cpu_opcodes.hpp"
+
 namespace fs = boost::filesystem;
 using boost::indeterminate;
 using boost::lock_guard;
 
 	if (gb_.cpu_.pc == 0x100)
 	{
-		// We are entering the ROM code so replace the current stack with a stack frame for the ROM
+		// We are entering the ROM code
 		assert(!gb_.romFilename_.empty());
-		stack_.clear();
 
 		shared_ptr<GbStackFrame> frame(new GbStackFrame);
 		frame->isRom = true;
 	// Update the stack trace
 	switch (gb_.mem_.read(gb_.cpu_.pc))
 	{
-	// ld sp,nn
-	case 0x31:
-		// We are discarding the previous stack so clear it and create a new one
-		{
-			uint16_t address = gb_.mem_.read16(gb_.cpu_.pc + 1);
-			if (address != gb_.cpu_.sp)
-			{
-				stack_.clear();
-
-				shared_ptr<GbStackFrame> frame(new GbStackFrame);
-				frame->module = getModule(&frame->isRom);
-				frame->isInterrupt = false;
-				frame->stackAddrMin = address;
-				frame->stackAddrMax = address;
-				frame->functionAddr = frame->instructionAddr = getCodeAddr(gb_.cpu_.pc);
-				frame->returnAddr = 0;
-
-				stack_.push_back(frame);
-			}
-		}
-		break;
-
 	// PUSH
 	case 0xf5:
 	case 0xc5:
 	case 0xe9:
 		handleJumpHl();
 		break;
+
+	case JP_NN:
+		handleJump(true);
+		break;
+	case JP_NZ_NN:
+		handleJump((gb_.cpu_.r.r8[F] & Z) == 0);
+		break;
+	case JP_Z_NN:
+		handleJump((gb_.cpu_.r.r8[F] & Z) != 0);
+		break;
+	case JP_NC_NN:
+		handleJump((gb_.cpu_.r.r8[F] & CF) == 0);
+		break;
+	case JR_C_N:
+		handleJump((gb_.cpu_.r.r8[F] & CF) != 0);
+		break;
+
+	case RST_00:
+		handleRst(0x00);
+		break;
+	case RST_08:
+		handleRst(0x08);
+		break;
+	case RST_10:
+		handleRst(0x10);
+		break;
+	case RST_18:
+		handleRst(0x18);
+		break;
+	case RST_20:
+		handleRst(0x20);
+		break;
+	case RST_28:
+		handleRst(0x28);
+		break;
 	}
 
 	return true;
 		}
 
 		// Compare the return address of the current stack frame against the return addrress on the
-		// stack. If they differ either something has gone wrong so just discard the stack and start
-		// again
+		// stack. If they differ the program is probably doing something strange
 		shared_ptr<GbStackFrame> &frame = stack_.back();
 		uint16_t returnAddr = gb_.mem_.read16(gb_.cpu_.sp);
 		if (frame->returnAddr == returnAddr)
 		}
 		else
 		{
-			stack_.clear();
+			// Check for fake calls. These are used when the function address is not known at
+			// compile time and are done by pushing the address of the next instruction (the return
+			// address), or leaving the return address for this function, followed by the function
+			// address then returning. This achieves the same as a normal function call
+			bool ok = false;
+			uint16_t fakeCallReturnAddr = gb_.mem_.read16(gb_.cpu_.sp + 2);
+			if (fakeCallReturnAddr == gb_.cpu_.pc + 1)
+			{
+				ok = true;
 
-			shared_ptr<GbStackFrame> frame(new GbStackFrame);
-			frame->module = getModule(&frame->isRom);
-			frame->isInterrupt = false;
-			frame->stackAddrMin = gb_.cpu_.sp + 2;
-			frame->stackAddrMax = gb_.cpu_.sp + 2;
-			frame->functionAddr = frame->instructionAddr = getCodeAddr(returnAddr);
-			frame->returnAddr = 0;
+				// Add four to the stack pointer. The two bytes at the top of the stack are the call
+				// address which is popped off and the next two are the return address which belongs
+				// to the stack frame for the function being called 
+				frame->stackAddrMin = gb_.cpu_.sp + 4;
+				frame->instructionAddr = getCodeAddr(fakeCallReturnAddr);
+				frame->registers = getRegisters();
 
-			stack_.push_back(frame);
+				// Create a new stack frame
+				shared_ptr<GbStackFrame> newFrame(new GbStackFrame);
+				newFrame->module = getModule(&newFrame->isRom);
+				newFrame->isInterrupt = false;
+				newFrame->stackAddrMin = gb_.cpu_.sp + 2;
+				newFrame->stackAddrMax = gb_.cpu_.sp + 2;
+				// Note that returnAddr here actually contains the call address
+				newFrame->functionAddr = newFrame->instructionAddr = getCodeAddr(returnAddr);
+				newFrame->returnAddr = fakeCallReturnAddr;
+
+				stack_.push_back(newFrame);
+
+				// Update the disassembly
+				if (newFrame->isRom)
+				{
+					rom_.update(newFrame->functionAddr);
+				}
+			}
+			else if (fakeCallReturnAddr == frame->returnAddr)
+			{
+				// This is more of a jump than a fake call. I'm not sure why programs do this
+				// instead of just jumping..
+				ok = true;
+				if (gb_.mem_.ioPorts[BLCK] && !gb_.romFilename_.empty())
+				{
+					// Update the disassembly. returnAddr contains the call/jump address
+					rom_.update(getCodeAddr(returnAddr));
+				}
+			}
+			else if (frame->stackAddrMin == frame->stackAddrMax + 2)
+			{
+				// Looks like the function has popped off its own return address; check if the
+				// return address matches that of the parent frame
+				if (stack_.size() > 1)
+				{
+					shared_ptr<GbStackFrame> &parent = stack_[stack_.size() - 2];
+					if (parent->returnAddr == returnAddr)
+					{
+						// Got it
+						ok = true;
+						doReturn();
+						doReturn();
+					}
+				}
+			}
+			
+			if (!ok)
+			{
+				// Something has gone wrong so just discard the stack and start again
+				stack_.clear();
+
+				shared_ptr<GbStackFrame> frame(new GbStackFrame);
+				frame->module = getModule(&frame->isRom);
+				frame->isInterrupt = false;
+				frame->stackAddrMin = gb_.cpu_.sp + 2;
+				frame->stackAddrMax = gb_.cpu_.sp + 2;
+				frame->functionAddr = frame->instructionAddr = getCodeAddr(returnAddr);
+				frame->returnAddr = 0;
+
+				stack_.push_back(frame);
+			}
 		}
 	}
 }
 	//   Look at the address on the top of the stack
 	//   If it points to the instruction after the jump assume it is a fake call
 
+	// Update the disassembly
+	uint32_t targetAddr = getCodeAddr(gb_.cpu_.r.r16[HL]);
+	if (gb_.mem_.ioPorts[BLCK] && !gb_.romFilename_.empty())
+	{
+		rom_.update(targetAddr);
+	}
+
 	if (stack_.empty())
 	{
 		return;
 		newFrame->isInterrupt = false;
 		newFrame->stackAddrMin = gb_.cpu_.sp;
 		newFrame->stackAddrMax = gb_.cpu_.sp;
-		newFrame->functionAddr = newFrame->instructionAddr = getCodeAddr(gb_.cpu_.r.r16[HL]);
+		newFrame->functionAddr = newFrame->instructionAddr = targetAddr;
 		newFrame->returnAddr = returnAddr;
 
 		stack_.push_back(newFrame);
-
-		// Update the disassembly
-		if (newFrame->isRom)
-		{
-			rom_.update(newFrame->functionAddr);
-		}
 	}
 }
 
 	stack_.pop_back();
 }
 
+void GbDebugger::handleJump(bool condition)
+{
+	if (condition)
+	{
+		// Update the disassembly
+		if (gb_.mem_.ioPorts[BLCK] && !gb_.romFilename_.empty())
+		{
+			rom_.update(getCodeAddr(gb_.mem_.read16(gb_.cpu_.pc + 1)));
+		}
+	}
+}
+
+void GbDebugger::handleRst(uint16_t addr)
+{
+	// Update the parameters of the current stack frame
+	if (!stack_.empty())
+	{
+		shared_ptr<GbStackFrame> &frame = stack_.back();
+		frame->stackAddrMin = gb_.cpu_.sp;
+		frame->instructionAddr = getCodeAddr(gb_.cpu_.pc + 1);
+		frame->registers = getRegisters();
+	}
+
+	// Create a new stack frame
+	{
+		shared_ptr<GbStackFrame> frame(new GbStackFrame);
+		frame->module = getModule(&frame->isRom);
+		frame->isInterrupt = false;
+		frame->stackAddrMin = gb_.cpu_.sp - 2;
+		frame->stackAddrMax = gb_.cpu_.sp - 2;
+		frame->functionAddr = frame->instructionAddr = addr;
+		frame->returnAddr = gb_.cpu_.pc + 1;
+
+		stack_.push_back(frame);
+
+		// Update the disassembly
+		rom_.update(addr);
+	}
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Interface functions. These just post jobs to the I/O service so the actual work is done on the
 // correct thread

gb_emulator/src/gb_disassembler.cpp

 	case JP_NN:
 	case JP_MHL:
 		stop = true;
+		break;
+
 	case JP_NZ_NN:
 	case JP_Z_NN:
 	case JP_NC_NN: