📜 ⬆️ ⬇️

IDA Pro Upgrade. Debugger for Sega Mega Drive (part 1)


Greetings


Comrades reversers, romhackers: basically this article will be devoted to you. In it, I will tell you how to write your plugin debugger for IDA Pro . Yes, it was already the first attempt to start a story , but since then much water has flowed under the bridge, many principles have been revised. In general, drove!


Lyrical introduction


Actually, from the previous articles ( one , two , three ), I think it will not be a secret that my favorite processor is the Motorola 68000 . By the way, my favorite old woman Sega Mega Drive / Genesis works on it. And, since it was always interesting to me, how did the Segov games work, I had decided from the first months of owning a computer deeply and for a long time to get into the wilds of disassembling and reversing.


This is how Smd IDA Tools appeared .
The project includes various auxiliary things that make the work on the study of roms on Segou much easier: bootloader, debugger, helper on the commands of VDP . Everything was written for IDA 6.8 , and it worked well. But when I decided to tell the world how I did it all the same, it became clear that it would be very difficult to show such code to people, and, moreover, to describe it. Therefore, I could not do it then.


And then IDA 7.0 came out. The desire to port your project under it appeared right away, but the architecture of the Gens emulator, on the basis of which I wrote the debugger, turned out to be unsuitable for porting: assembler inserts for x86 , crutches, difficult to understand code, and much more. And the game Pier Solar and the Great Architects , released on cartridges in 2010, and which I wanted to explore so much (and there are plenty of anti-emulation tricks), did not start in Gens ' e.



In search of a suitable emulator source code, which could be adapted for a debugger, I finally came across EkeEke Genesis Plus GX . So this article appeared.


Part One: Debugger Core


Musashi handles emulation of motorola processor instructions in the Genesis Plus GX . In its original source code there is already a basic debugging functionality (a hook for executing instructions), but EkeEke decided to remove it as superfluous. Return.




Now the most important thing: you need to decide on the debugger architecture. The requirements are as follows:



If these four points are the work of the debugger from the inside, then you need to think about access to this functionality from the outside. Add one more item:



Debugger Kernel: Break List


To implement the list we have the following structure:


 typedef struct breakpoint_s { struct breakpoint_s *next, *prev; int enabled; int width; bpt_type_t type; unsigned int address; } breakpoint_t; 

The next and prev fields will store pointers to the next and previous element, respectively.
The enabled field will store 0 if you want to skip this breakpoint in the triggers.
width - the number of bytes starting from the address in the address field that the breakpoint covers.
Well, in the type field we will keep the type of the breach (execution, reading, writing). More details below.


To work with the list of breakpoints, I added the following functions:


Functions for working with breakpoints
 static breakpoint_t *first_bp = NULL; static breakpoint_t *add_bpt(bpt_type_t type, unsigned int address, int width) { breakpoint_t *bp = (breakpoint_t *)malloc(sizeof(breakpoint_t)); bp->type = type; bp->address = address; bp->width = width; bp->enabled = 1; if (first_bp) { bp->next = first_bp; bp->prev = first_bp->prev; first_bp->prev = bp; bp->prev->next = bp; } else { first_bp = bp; bp->next = bp; bp->prev = bp; } return bp; } static void delete_breakpoint(breakpoint_t * bp) { if (bp == first_bp) { if (bp->next == bp) { first_bp = NULL; } else { first_bp = bp->next; } } bp->next->prev = bp->prev; bp->prev->next = bp->next; free(bp); } static breakpoint_t *next_breakpoint(breakpoint_t *bp) { return bp->next != first_bp ? bp->next : 0; } static breakpoint_t *find_breakpoint(unsigned int address, bpt_type_t type) { breakpoint_t *p; for (p = first_bp; p; p = next_breakpoint(p)) { if ((p->address == address) && ((p->type == BPT_ANY) || (p->type & type))) return p; } return 0; } static void remove_bpt(unsigned int address, bpt_type_t type) { breakpoint_t *bpt; if ((bpt = find_breakpoint(address, type))) delete_breakpoint(bpt); } static int count_bpt_list() { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { ++i; } return i; } static void get_bpt_data(int index, bpt_data_t *data) { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { if (i == index) { data->address = p->address; data->width = p->width; data->type = p->type; data->enabled = p->enabled; break; } ++i; } } static void clear_bpt_list() { while (first_bp != NULL) delete_breakpoint(first_bp); } static void init_bpt_list() { if (first_bp) clear_bpt_list(); } void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value) { if (!dbg_req || !dbg_req->dbg_active || dbg_dont_check_bp) return; breakpoint_t *bp; for (bp = first_bp; bp; bp = next_breakpoint(bp)) { if (!(bp->type & type) || !bp->enabled) continue; if ((address <= (bp->address + bp->width)) && ((address + width) >= bp->address)) { dbg_req->dbg_paused = 1; break; } } } 

Debugger Core: Main Variables


Actually, I spied this implementation in another PCSXR debugger.


Add variables that will store the emulation state:


 static int dbg_first_paused, dbg_trace, dbg_dont_check_bp; static int dbg_step_over; static int dbg_last_pc; static unsigned int dbg_step_over_addr; static int dbg_active, dbg_paused; 

The dbg_first_paused variable will be responsible for stopping the emulation at the start of debugging. If 0 - then you need to pause the emulation and send a message to the client that the emulation has started. After the first pause, set to 1 .


dbg_trace we need to execute one instruction at a time ( Step Into functionality). If it is equal to 1 , we execute one instruction, we pause, and reset the value to 0 .


I started the variable dbg_dont_check_bp so that the read / write memory breaks do not work if the debugger does.


We will store dbg_step_over 1 if we are in Step Over mode until the current PC ( Program Counter , also Instruction Pointer ) becomes equal to the address in dbg_step_over_addr . After that both variables are reset. I’ll tell you about the calculation of the dbg_step_over_addr value later.


I dbg_last_pc variable for one specific case: when we are already on the breakpoint and the client requests Resume . To prevent the bryak from working again, I compare the address of the last PC in this variable with the new one, and if the values ​​are different, you can check the breakpoint on the current PC .


dbg_active - in fact, it stores state 1 when debugging is active and you need to check the breakpoints, process requests from the client.


With the dbg_paused variable, I think everything is clear: 1 - we are paused (for example, after the breakpoint has triggered) and we expect commands from the client, 0 - we follow the instructions.


We write functions for working with these variables:


 static void pause_debugger() { dbg_trace = 1; dbg_paused = 1; } static void resume_debugger() { dbg_trace = 0; dbg_paused = 0; } static void detach_debugger() { clear_bpt_list(); resume_debugger(); } static void activate_debugger() { dbg_active = 1; } static void deactivate_debugger() { dbg_active = 0; } 

We see that in the implementation of detach_debugger() I used to clear the list of bryaks. This is to ensure that after disconnecting the client, the old breakpoints do not continue to operate.


Debugger kernel: implement the instructions hook


Actually, the main work with pause, continuation of emulation, Step Into , Step Over will take place here.


This is the code for the process_breakpoints() function:


 void process_breakpoints() { int handled_event = 0; int is_step_over = 0; int is_step_in = 0; if (!dbg_active) return; unsigned int pc = m68k_get_reg(M68K_REG_PC); if (dbg_paused && dbg_first_paused && !dbg_trace) longjmp(jmp_env, 1); if (!dbg_first_paused) { dbg_first_paused = 1; dbg_paused = 1; // TODO: Send emulation started event } if (dbg_trace) { is_step_in = 1; dbg_trace = 0; dbg_paused = 1; // TODO: Send event that Step Into has been triggered handled_event = 1; } if (!dbg_paused) { if (dbg_step_over && pc == dbg_step_over_addr) { is_step_over = 1; dbg_step_over = 0; dbg_step_over_addr = 0; dbg_paused = 1; } if (dbg_last_pc != pc) check_breakpoint(BPT_M68K_E, 1, pc, pc); if (dbg_paused) { // TODO: Send event about Step Over or breakpoint has been triggered handled_event = 1; } } if (dbg_first_paused && (!handled_event) && dbg_paused) { // TODO: Send paused event } dbg_last_pc = pc; if (dbg_paused && (!is_step_in || is_step_over)) { longjmp(jmp_env, 1); } } 

Let's understand:


  1. If debugging is not enabled, just exit the hook
  2. The trick with setjmp / longjmp needed because the RetroArch shell RetroArch , for which its own version of Genesis Plus GX was written, with which we start the emulation, hangs waiting for the output from the frame rendering function, which the emulator implements. I will show the second part of the trick later, because it concerns already the shell over the emulator, rather than the core.
  3. If this is our first triggering of the hook, and, accordingly, the beginning of the emulation, pause and send the event to start the emulation to the client.
  4. If the client previously sent the Step Into command, reset the value of the dbg_trace variable and pause the emulation. We send to the client the corresponding event.
  5. If we are not on a pause, the Step Over mode is enabled, and the current PC is equal to the destination address dbg_step_over_addr , dbg_step_over_addr necessary variables and paused.
  6. We check breakpoint if we are not on it now, and if the breakpoint has worked, we pause and send a Step Over or Breakpoint event to the client.
  7. If this is not a breakpoint, not Step Into , and not Step Over , then the client has asked for a pause. We send event about the pause.
  8. We implement a trick with longjump as the implementation of an infinite loop of waiting for actions from the client during a pause.

The code for calculating the address for Step Over was not as simple as it might have been supposed at first. A motorol processor has different lengths of instructions, so you have to read the address of the next one manually, depending on the opcode. In addition, one should avoid instructions like bra , jmp , rts conditional jumps forward, and follow them as Step Into . The implementation is as follows:


Calc_step_over () function code
 static unsigned int calc_step_over() { unsigned int pc = m68k_get_reg(M68K_REG_PC); unsigned int sp = m68k_get_reg(M68K_REG_SP); unsigned int opc = m68ki_read_imm_16(); unsigned int dest_pc = (unsigned int)(-1); // jsr if ((opc & 0xFFF8) == 0x4E90) { m68k_op_jsr_32_ai(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFF8) == 0x4EA8) { m68k_op_jsr_32_di(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFF8) == 0x4EB0) { m68k_op_jsr_32_ix(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EB8) { m68k_op_jsr_32_aw(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EB9) { m68k_op_jsr_32_al(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EBA) { m68k_op_jsr_32_pcdi(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EBB) { m68k_op_jsr_32_pcix(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } // bsr else if ((opc & 0xFFFF) == 0x6100) { m68k_op_bsr_16(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x61FF) { m68k_op_bsr_32(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFF00) == 0x6100) { m68k_op_bsr_8(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } // dbf else if ((opc & 0xfff8) == 0x51C8) { dest_pc = m68k_get_reg(M68K_REG_PC) + 2; } m68k_set_reg(M68K_REG_PC, pc); m68k_set_reg(M68K_REG_SP, sp); return dest_pc; 

Debugger kernel: initialize and stop debugging


Everything is simple:


 void stop_debugging() { // TODO: Send Stopped event to client detach_debugger(); deactivate_debugger(); dbg_first_paused = dbg_paused = dbg_trace = dbg_dont_check_bp = dbg_step_over = dbg_step_over_addr = dbg_last_pc = 0; } void start_debugging() { if (dbg_active) return; activate_debugger(); init_bpt_list(); dbg_first_paused = dbg_paused = dbg_trace = dbg_dont_check_bp = dbg_step_over = dbg_step_over_addr = dbg_last_pc = 0; } 

Debugger kernel: protocol implementation


The communication protocol between the debug server and the client user can be called the second heart of the debugging process, since it implements the functionality of processing requests from the client, and reactions to them.
It was decided to implement on the basis of Shared Memory , because you need to send large blocks of memory: VRAM , RAM , ROM , and over the network it will be all the more enjoyable.


The essence is this: the kernel creates shared memory with a predetermined structure, and waits for incoming requests from the client. After processing the request, the response is stored in the same memory, and the corresponding information is added to the debugger's event list in the same memory.


The prototype was chosen as follows:


Source code debug_wrap.h
 #ifndef _DEBUG_WRAP_H_ #define _DEBUG_WRAP_H_ #ifdef __cplusplus extern "C" { #endif #include <Windows.h> #define SHARED_MEM_NAME "GX_PLUS_SHARED_MEM" #define MAX_BREAKPOINTS 1000 #define MAX_DBG_EVENTS 20 #ifndef MAXROMSIZE #define MAXROMSIZE ((unsigned int)0xA00000) #endif #pragma pack(push, 4) typedef enum { BPT_ANY = (0 << 0), // M68K BPT_M68K_E = (1 << 0), BPT_M68K_R = (1 << 1), BPT_M68K_W = (1 << 2), BPT_M68K_RW = BPT_M68K_R | BPT_M68K_W, // VDP BPT_VRAM_R = (1 << 3), BPT_VRAM_W = (1 << 4), BPT_VRAM_RW = BPT_VRAM_R | BPT_VRAM_W, BPT_CRAM_R = (1 << 5), BPT_CRAM_W = (1 << 6), BPT_CRAM_RW = BPT_CRAM_R | BPT_CRAM_W, BPT_VSRAM_R = (1 << 7), BPT_VSRAM_W = (1 << 8), BPT_VSRAM_RW = BPT_VSRAM_R | BPT_VSRAM_W, // Z80 BPT_Z80_E = (1 << 11), BPT_Z80_R = (1 << 12), BPT_Z80_W = (1 << 13), BPT_Z80_RW = BPT_Z80_R | BPT_Z80_W, // REGS BPT_VDP_REG = (1 << 9), BPT_M68K_REG = (1 << 10), } bpt_type_t; typedef enum { REQ_NO_REQUEST, REQ_GET_REGS, REQ_SET_REGS, REQ_GET_REG, REQ_SET_REG, REQ_READ_68K_ROM, REQ_READ_68K_RAM, REQ_WRITE_68K_ROM, REQ_WRITE_68K_RAM, REQ_READ_Z80, REQ_WRITE_Z80, REQ_ADD_BREAK, REQ_TOGGLE_BREAK, REQ_DEL_BREAK, REQ_CLEAR_BREAKS, REQ_LIST_BREAKS, REQ_ATTACH, REQ_PAUSE, REQ_RESUME, REQ_STOP, REQ_STEP_INTO, REQ_STEP_OVER, } request_type_t; typedef enum { REG_TYPE_M68K = (1 << 0), REG_TYPE_S80 = (1 << 1), REG_TYPE_Z80 = (1 << 2), REG_TYPE_VDP = (1 << 3), } register_type_t; typedef enum { DBG_EVT_NO_EVENT, DBG_EVT_STARTED, DBG_EVT_PAUSED, DBG_EVT_BREAK, DBG_EVT_STEP, DBG_EVT_STOPPED, } dbg_event_type_t; typedef struct { dbg_event_type_t type; unsigned int pc; char msg[256]; } debugger_event_t; typedef struct { int index; unsigned int val; } reg_val_t; typedef struct { unsigned int d0, d1, d2, d3, d4, d5, d6, d7; unsigned int a0, a1, a2, a3, a4, a5, a6, a7; unsigned int pc, sr, sp, usp, isp, ppc, ir; } regs_68k_data_t; typedef enum { REG_68K_D0, REG_68K_D1, REG_68K_D2, REG_68K_D3, REG_68K_D4, REG_68K_D5, REG_68K_D6, REG_68K_D7, REG_68K_A0, REG_68K_A1, REG_68K_A2, REG_68K_A3, REG_68K_A4, REG_68K_A5, REG_68K_A6, REG_68K_A7, REG_68K_PC, REG_68K_SR, REG_68K_SP, REG_68K_USP, REG_68K_ISP, REG_68K_PPC, REG_68K_IR, REG_VDP_00, REG_VDP_01, REG_VDP_02, REG_VDP_03, REG_VDP_04, REG_VDP_05, REG_VDP_06, REG_VDP_07, REG_VDP_08, REG_VDP_09, REG_VDP_0A, REG_VDP_0B, REG_VDP_0C, REG_VDP_0D, REG_VDP_0E, REG_VDP_0F, REG_VDP_10, REG_VDP_11, REG_VDP_12, REG_VDP_13, REG_VDP_14, REG_VDP_15, REG_VDP_16, REG_VDP_17, REG_VDP_18, REG_VDP_19, REG_VDP_1A, REG_VDP_1B, REG_VDP_1C, REG_VDP_1D, REG_VDP_1E, REG_VDP_1F, REG_VDP_DMA_LEN, REG_VDP_DMA_SRC, REG_VDP_DMA_DST, REG_Z80_PC, REG_Z80_SP, REG_Z80_AF, REG_Z80_BC, REG_Z80_DE, REG_Z80_HL, REG_Z80_IX, REG_Z80_IY, REG_Z80_WZ, REG_Z80_AF2, REG_Z80_BC2, REG_Z80_DE2, REG_Z80_HL2, REG_Z80_R, REG_Z80_R2, REG_Z80_IFFI1, REG_Z80_IFFI2, REG_Z80_HALT, REG_Z80_IM, REG_Z80_I, } regs_all_t; typedef struct { unsigned int pc, sp, af, bc, de, hl, ix, iy, wz; unsigned int af2,bc2,de2,hl2; unsigned char r, r2, iff1, iff2, halt, im, i; } regs_z80_data_t; typedef struct { unsigned char regs_vdp[0x20]; unsigned short dma_len; unsigned int dma_src, dma_dst; } vdp_regs_t; typedef struct { int type; // register_type_t regs_68k_data_t regs_68k; reg_val_t any_reg; vdp_regs_t vdp_regs; regs_z80_data_t regs_z80; } register_data_t; typedef struct { int size; unsigned int address; unsigned char m68k_rom[MAXROMSIZE]; unsigned char m68k_ram[0x10000]; unsigned char z80_ram[0x2000]; } memory_data_t; typedef struct { bpt_type_t type; unsigned int address; int width; int enabled; } bpt_data_t; typedef struct { int count; bpt_data_t breaks[MAX_BREAKPOINTS]; } bpt_list_t; typedef struct { request_type_t req_type; register_data_t regs_data; memory_data_t mem_data; bpt_data_t bpt_data; int dbg_events_count; debugger_event_t dbg_events[MAX_DBG_EVENTS]; bpt_list_t bpt_list; int dbg_active, dbg_paused; int is_ida; } dbg_request_t; #pragma pack(pop) dbg_request_t *open_shared_mem(); void close_shared_mem(dbg_request_t **request); int recv_dbg_event(dbg_request_t *request, int wait); void send_dbg_request(dbg_request_t *request, request_type_t type); #ifdef __cplusplus } #endif #endif 

The first field in the structure will be the query type:



Next come the registers M68K , Z80 , VDP . Next are the memory blocks ROM , RAM , VRAM , Z80 .


To add / remove breakpoints, I also started the corresponding structure. Well, their list is also here (for the most part, it is only for display in the GUI, without having to remember all the installed bryaks, as IDA does).


Next comes the list of debug events:



Armed with the idea of ​​a protocol, we implement the processing of client requests, thus obtaining the following debugger kernel code:


Debug.c source code
 #include "debug.h" #include "shared.h" #define m68ki_cpu m68k #define MUL (7) #ifndef BUILD_TABLES #include "m68ki_cycles.h" #endif #include "m68kconf.h" #include "m68kcpu.h" #include "m68kops.h" #include "vdp_ctrl.h" #include "Z80.h" static int dbg_first_paused, dbg_trace, dbg_dont_check_bp; static int dbg_step_over; static int dbg_last_pc; static unsigned int dbg_step_over_addr; static dbg_request_t *dbg_req = NULL; static HANDLE hMapFile = 0; typedef struct breakpoint_s { struct breakpoint_s *next, *prev; int enabled; int width; bpt_type_t type; unsigned int address; } breakpoint_t; static breakpoint_t *first_bp = NULL; static breakpoint_t *add_bpt(bpt_type_t type, unsigned int address, int width) { breakpoint_t *bp = (breakpoint_t *)malloc(sizeof(breakpoint_t)); bp->type = type; bp->address = address; bp->width = width; bp->enabled = 1; if (first_bp) { bp->next = first_bp; bp->prev = first_bp->prev; first_bp->prev = bp; bp->prev->next = bp; } else { first_bp = bp; bp->next = bp; bp->prev = bp; } return bp; } static void delete_breakpoint(breakpoint_t * bp) { if (bp == first_bp) { if (bp->next == bp) { first_bp = NULL; } else { first_bp = bp->next; } } bp->next->prev = bp->prev; bp->prev->next = bp->next; free(bp); } static breakpoint_t *next_breakpoint(breakpoint_t *bp) { return bp->next != first_bp ? bp->next : 0; } static breakpoint_t *find_breakpoint(unsigned int address, bpt_type_t type) { breakpoint_t *p; for (p = first_bp; p; p = next_breakpoint(p)) { if ((p->address == address) && ((p->type == BPT_ANY) || (p->type & type))) return p; } return 0; } static void remove_bpt(unsigned int address, bpt_type_t type) { breakpoint_t *bpt; if ((bpt = find_breakpoint(address, type))) delete_breakpoint(bpt); } static int count_bpt_list() { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { ++i; } return i; } static void get_bpt_data(int index, bpt_data_t *data) { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { if (i == index) { data->address = p->address; data->width = p->width; data->type = p->type; data->enabled = p->enabled; break; } ++i; } } static void clear_bpt_list() { while (first_bp != NULL) delete_breakpoint(first_bp); } static void init_bpt_list() { if (first_bp) clear_bpt_list(); } void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value) { if (!dbg_req || !dbg_req->dbg_active || dbg_dont_check_bp) return; breakpoint_t *bp; for (bp = first_bp; bp; bp = next_breakpoint(bp)) { if (!(bp->type & type) || !bp->enabled) continue; if ((address <= (bp->address + bp->width)) && ((address + width) >= bp->address)) { dbg_req->dbg_paused = 1; break; } } } static void pause_debugger() { dbg_trace = 1; dbg_req->dbg_paused = 1; } static void resume_debugger() { dbg_trace = 0; dbg_req->dbg_paused = 0; } static void detach_debugger() { clear_bpt_list(); resume_debugger(); } static void activate_debugger() { dbg_req->dbg_active = 1; } static void deactivate_debugger() { dbg_req->dbg_active = 0; } int activate_shared_mem() { hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(dbg_request_t), SHARED_MEM_NAME); if (hMapFile == 0) { return -1; } dbg_req = (dbg_request_t*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(dbg_request_t)); if (dbg_req == 0) { CloseHandle(hMapFile); return -1; } memset(dbg_req, 0, sizeof(dbg_request_t)); return 0; } void deactivate_shared_mem() { UnmapViewOfFile(dbg_req); CloseHandle(hMapFile); hMapFile = NULL; dbg_req = NULL; } static unsigned int calc_step_over() { unsigned int pc = m68k_get_reg(M68K_REG_PC); unsigned int sp = m68k_get_reg(M68K_REG_SP); unsigned int opc = m68ki_read_imm_16(); unsigned int dest_pc = (unsigned int)(-1); // jsr if ((opc & 0xFFF8) == 0x4E90) { m68k_op_jsr_32_ai(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFF8) == 0x4EA8) { m68k_op_jsr_32_di(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFF8) == 0x4EB0) { m68k_op_jsr_32_ix(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EB8) { m68k_op_jsr_32_aw(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EB9) { m68k_op_jsr_32_al(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EBA) { m68k_op_jsr_32_pcdi(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EBB) { m68k_op_jsr_32_pcix(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } // bsr else if ((opc & 0xFFFF) == 0x6100) { m68k_op_bsr_16(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x61FF) { m68k_op_bsr_32(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFF00) == 0x6100) { m68k_op_bsr_8(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } // dbf else if ((opc & 0xfff8) == 0x51C8) { dest_pc = m68k_get_reg(M68K_REG_PC) + 2; } m68k_set_reg(M68K_REG_PC, pc); m68k_set_reg(M68K_REG_SP, sp); return dest_pc; } void process_request() { if (!dbg_req || !dbg_req->dbg_active) return; if (dbg_req->req_type == REQ_NO_REQUEST) return; switch (dbg_req->req_type) { case REQ_GET_REG: { register_data_t *regs_data = &dbg_req->regs_data; if (regs_data->type & REG_TYPE_M68K) regs_data->any_reg.val = m68k_get_reg(regs_data->any_reg.index); if (regs_data->type & REG_TYPE_VDP) regs_data->any_reg.val = reg[regs_data->any_reg.index]; if (regs_data->type & REG_TYPE_Z80) { if (regs_data->any_reg.index >= 0 && regs_data->any_reg.index <= 12) // PC <-> HL2 { regs_data->any_reg.val = ((unsigned int *)&Z80.pc)[regs_data->any_reg.index]; } else if (regs_data->any_reg.index >= 13 && regs_data->any_reg.index <= 19) // R <-> I { regs_data->any_reg.val = ((unsigned char *)&Z80.r)[regs_data->any_reg.index - 13]; } } } break; case REQ_SET_REG: { register_data_t *regs_data = &dbg_req->regs_data; if (regs_data->type & REG_TYPE_M68K) m68k_set_reg(regs_data->any_reg.index, regs_data->any_reg.val); if (regs_data->type & REG_TYPE_VDP) reg[regs_data->any_reg.index] = regs_data->any_reg.val; if (regs_data->type & REG_TYPE_Z80) { if (regs_data->any_reg.index >= 0 && regs_data->any_reg.index <= 12) // PC <-> HL2 { ((unsigned int *)&Z80.pc)[regs_data->any_reg.index] = regs_data->any_reg.val; } else if (regs_data->any_reg.index >= 13 && regs_data->any_reg.index <= 19) // R <-> I { ((unsigned char *)&Z80.r)[regs_data->any_reg.index - 13] = regs_data->any_reg.val & 0xFF; } } } break; case REQ_GET_REGS: case REQ_SET_REGS: { register_data_t *regs_data = &dbg_req->regs_data; if (regs_data->type & REG_TYPE_M68K) { regs_68k_data_t *m68kr = &regs_data->regs_68k; if (dbg_req->req_type == REQ_GET_REGS) { m68kr->d0 = m68k_get_reg(M68K_REG_D0); m68kr->d1 = m68k_get_reg(M68K_REG_D1); m68kr->d2 = m68k_get_reg(M68K_REG_D2); m68kr->d3 = m68k_get_reg(M68K_REG_D3); m68kr->d4 = m68k_get_reg(M68K_REG_D4); m68kr->d5 = m68k_get_reg(M68K_REG_D5); m68kr->d6 = m68k_get_reg(M68K_REG_D6); m68kr->d7 = m68k_get_reg(M68K_REG_D7); m68kr->a0 = m68k_get_reg(M68K_REG_A0); m68kr->a1 = m68k_get_reg(M68K_REG_A1); m68kr->a2 = m68k_get_reg(M68K_REG_A2); m68kr->a3 = m68k_get_reg(M68K_REG_A3); m68kr->a4 = m68k_get_reg(M68K_REG_A4); m68kr->a5 = m68k_get_reg(M68K_REG_A5); m68kr->a6 = m68k_get_reg(M68K_REG_A6); m68kr->a7 = m68k_get_reg(M68K_REG_A7); m68kr->pc = m68k_get_reg(M68K_REG_PC); m68kr->sr = m68k_get_reg(M68K_REG_SR); m68kr->sp = m68k_get_reg(M68K_REG_SP); m68kr->usp = m68k_get_reg(M68K_REG_USP); m68kr->isp = m68k_get_reg(M68K_REG_ISP); m68kr->ppc = m68k_get_reg(M68K_REG_PPC); m68kr->ir = m68k_get_reg(M68K_REG_IR); } else { m68k_set_reg(M68K_REG_D0, m68kr->d0); m68k_set_reg(M68K_REG_D1, m68kr->d1); m68k_set_reg(M68K_REG_D2, m68kr->d2); m68k_set_reg(M68K_REG_D3, m68kr->d3); m68k_set_reg(M68K_REG_D4, m68kr->d4); m68k_set_reg(M68K_REG_D5, m68kr->d5); m68k_set_reg(M68K_REG_D6, m68kr->d6); m68k_set_reg(M68K_REG_D7, m68kr->d7); m68k_set_reg(M68K_REG_A0, m68kr->a0); m68k_set_reg(M68K_REG_A1, m68kr->a1); m68k_set_reg(M68K_REG_A2, m68kr->a2); m68k_set_reg(M68K_REG_A3, m68kr->a3); m68k_set_reg(M68K_REG_A4, m68kr->a4); m68k_set_reg(M68K_REG_A5, m68kr->a5); m68k_set_reg(M68K_REG_A6, m68kr->a6); m68k_set_reg(M68K_REG_A7, m68kr->a7); m68k_set_reg(M68K_REG_PC, m68kr->pc); m68k_set_reg(M68K_REG_SR, m68kr->sr); m68k_set_reg(M68K_REG_SP, m68kr->sp); m68k_set_reg(M68K_REG_USP, m68kr->usp); m68k_set_reg(M68K_REG_ISP, m68kr->isp); } } if (regs_data->type & REG_TYPE_VDP) { vdp_regs_t *vdp_regs = &regs_data->vdp_regs; for (int i = 0; i < (sizeof(vdp_regs) / sizeof(vdp_regs->regs_vdp[0])); ++i) { if (dbg_req->req_type == REQ_GET_REGS) vdp_regs->regs_vdp[i] = reg[i]; else reg[i] = vdp_regs->regs_vdp[i]; } if (dbg_req->req_type == REQ_GET_REGS) { vdp_regs->dma_len = (reg[20] << 8) | reg[19]; if (!vdp_regs->dma_len) vdp_regs->dma_len = 0x10000; vdp_regs->dma_src = vdp_dma_calc_src(); vdp_regs->dma_dst = vdp_dma_get_dst(); } } if (regs_data->type & REG_TYPE_Z80) { regs_z80_data_t *z80r = &regs_data->regs_z80; if (dbg_req->req_type == REQ_GET_REGS) { z80r->pc = Z80.pc.d; z80r->sp = Z80.sp.d; z80r->af = Z80.af.d; z80r->bc = Z80.bc.d; z80r->de = Z80.de.d; z80r->hl = Z80.hl.d; z80r->ix = Z80.ix.d; z80r->iy = Z80.iy.d; z80r->wz = Z80.wz.d; z80r->af2 = Z80.af2.d; z80r->bc2 = Z80.bc2.d; z80r->de2 = Z80.de2.d; z80r->hl2 = Z80.hl2.d; z80r->r = Z80.r; z80r->r2 = Z80.r2; z80r->iff1 = Z80.iff1; z80r->iff2 = Z80.iff2; z80r->halt = Z80.halt; z80r->im = Z80.im; z80r->i = Z80.i; } else { Z80.pc.d = z80r->pc; Z80.sp.d = z80r->sp; Z80.af.d = z80r->af; Z80.bc.d = z80r->bc; Z80.de.d = z80r->de; Z80.hl.d = z80r->hl; Z80.ix.d = z80r->ix; Z80.iy.d = z80r->iy; Z80.wz.d = z80r->wz; Z80.af2.d = z80r->af2; Z80.bc2.d = z80r->bc2; Z80.de2.d = z80r->de2; Z80.hl2.d = z80r->hl2; Z80.r = z80r->r; Z80.r2 = z80r->r2; Z80.iff1 = z80r->iff1; Z80.iff2 = z80r->iff2; Z80.halt = z80r->halt; Z80.im = z80r->im; Z80.i = z80r->i; } } } break; case REQ_READ_68K_ROM: case REQ_READ_68K_RAM: case REQ_READ_Z80: { dbg_dont_check_bp = 1; memory_data_t *mem_data = &dbg_req->mem_data; for (int i = 0; i < mem_data->size; ++i) { switch (dbg_req->req_type) { case REQ_READ_68K_ROM: mem_data->m68k_rom[mem_data->address + i] = m68ki_read_8(mem_data->address + i); break; case REQ_READ_68K_RAM: mem_data->m68k_ram[(mem_data->address + i) & 0xFFFF] = m68ki_read_8(mem_data->address + i); break; case REQ_READ_Z80: mem_data->z80_ram[(mem_data->address + i) & 0x1FFF] = z80_readmem(mem_data->address + i); break; default: break; } } dbg_dont_check_bp = 0; } break; case REQ_WRITE_68K_ROM: case REQ_WRITE_68K_RAM: case REQ_WRITE_Z80: { dbg_dont_check_bp = 1; memory_data_t *mem_data = &dbg_req->mem_data; for (int i = 0; i < mem_data->size; ++i) { switch (dbg_req->req_type) { case REQ_WRITE_68K_ROM: m68ki_write_8(mem_data->address + i, mem_data->m68k_rom[mem_data->address + i]); break; case REQ_WRITE_68K_RAM: m68ki_write_8(0xFF0000 | ((mem_data->address + i) & 0xFFFF), mem_data->m68k_ram[(mem_data->address + i) & 0xFFFF]); break; case REQ_WRITE_Z80: z80_writemem(mem_data->address + i, mem_data->z80_ram[(mem_data->address + i) & 0x1FFF]); break; default: break; } } dbg_dont_check_bp = 0; } break; case REQ_ADD_BREAK: { bpt_data_t *bpt_data = &dbg_req->bpt_data; if (!find_breakpoint(bpt_data->address, bpt_data->type)) add_bpt(bpt_data->type, bpt_data->address, bpt_data->width); } break; case REQ_TOGGLE_BREAK: { bpt_data_t *bpt_data = &dbg_req->bpt_data; breakpoint_t *bp = find_breakpoint(bpt_data->address, bpt_data->type); if (bp != NULL) bp->enabled = !bp->enabled; } break; case REQ_DEL_BREAK: { bpt_data_t *bpt_data = &dbg_req->bpt_data; remove_bpt(bpt_data->address, bpt_data->type); } break; case REQ_CLEAR_BREAKS: clear_bpt_list(); case REQ_LIST_BREAKS: { bpt_list_t *bpt_list = &dbg_req->bpt_list; bpt_list->count = count_bpt_list(); for (int i = 0; i < bpt_list->count; ++i) get_bpt_data(i, &bpt_list->breaks[i]); } break; case REQ_ATTACH: activate_debugger(); dbg_first_paused = 0; break; case REQ_PAUSE: pause_debugger(); break; case REQ_RESUME: resume_debugger(); break; case REQ_STOP: stop_debugging(); break; case REQ_STEP_INTO: { if (dbg_req->dbg_paused) { dbg_trace = 1; dbg_req->dbg_paused = 0; } } break; case REQ_STEP_OVER: { if (dbg_req->dbg_paused) { unsigned int dest_pc = calc_step_over(); if (dest_pc != (unsigned int)(-1)) { dbg_step_over = 1; dbg_step_over_addr = dest_pc; } else { dbg_step_over = 0; dbg_step_over_addr = 0; dbg_trace = 1; } dbg_req->dbg_paused = 0; } } break; default: break; } dbg_req->req_type = REQ_NO_REQUEST; } void send_dbg_event(dbg_event_type_t type) { dbg_req->dbg_events[dbg_req->dbg_events_count].type = type; dbg_req->dbg_events_count += 1; } void stop_debugging() { send_dbg_event(DBG_EVT_STOPPED); detach_debugger(); deactivate_debugger(); dbg_first_paused = dbg_req->dbg_paused = dbg_trace = dbg_dont_check_bp = dbg_step_over = dbg_step_over_addr = dbg_last_pc = 0; } void start_debugging() { if (dbg_req != NULL && dbg_req->dbg_active) return; activate_debugger(); init_bpt_list(); dbg_first_paused = dbg_req->dbg_paused = dbg_trace = dbg_dont_check_bp = dbg_step_over = dbg_step_over_addr = dbg_last_pc = 0; } int is_debugger_accessible() { return (dbg_req != NULL); } void process_breakpoints() { int handled_event = 0; int is_step_over = 0; int is_step_in = 0; unsigned int pc = m68k_get_reg(M68K_REG_PC); if (!dbg_req || !dbg_req->dbg_active) return; if (dbg_req->dbg_paused && dbg_first_paused && !dbg_trace) longjmp(jmp_env, 1); if (!dbg_first_paused) { dbg_first_paused = 1; dbg_req->dbg_paused = 1; dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; strncpy(dbg_req->dbg_events[dbg_req->dbg_events_count].msg, "gpgx", sizeof(dbg_req->dbg_events[dbg_req->dbg_events_count].msg)); send_dbg_event(DBG_EVT_STARTED); } if (dbg_trace) { is_step_in = 1; dbg_trace = 0; dbg_req->dbg_paused = 1; dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; send_dbg_event(DBG_EVT_STEP); handled_event = 1; } if (!dbg_req->dbg_paused) { if (dbg_step_over && pc == dbg_step_over_addr) { is_step_over = 1; dbg_step_over = 0; dbg_step_over_addr = 0; dbg_req->dbg_paused = 1; } if (dbg_last_pc != pc) check_breakpoint(BPT_M68K_E, 1, pc, pc); if (dbg_req->dbg_paused) { dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; send_dbg_event(is_step_over ? DBG_EVT_STEP : DBG_EVT_BREAK); handled_event = 1; } } if (dbg_first_paused && (!handled_event) && dbg_req->dbg_paused) { dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; send_dbg_event(DBG_EVT_PAUSED); } dbg_last_pc = pc; if (dbg_req->dbg_paused && (!is_step_in || is_step_over)) { longjmp(jmp_env, 1); } } int is_debugger_paused() { return is_debugger_accessible() && dbg_req->dbg_paused && dbg_first_paused && !dbg_trace; } 

debug.h
 #ifndef _DEBUG_H_ #define _DEBUG_H_ #ifdef __cplusplus extern "C" { #endif #include <setjmp.h> #include "debug_wrap.h" extern void start_debugging(); extern void stop_debugging(); extern int is_debugger_accessible(); extern void process_request(); extern int is_debugger_paused(); extern int activate_shared_mem(); extern void deactivate_shared_mem(); void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value); extern jmp_buf jmp_env; #ifdef __cplusplus } #endif #endif 

.
, check_breakpoint VDP #ifdef LOGVDP . vdp_ctrl.c :


 check_breakpoint(BPT_VRAM_W, 2, addr, data); ... check_breakpoint(BPT_CRAM_W, 2, addr, data); ... check_breakpoint(BPT_VSRAM_W, 2, addr, data); ... check_breakpoint(BPT_VRAM_R, 2, addr, data); ... check_breakpoint(BPT_CRAM_R, 2, addr, data); ... check_breakpoint(BPT_VSRAM_R, 2, addr, data); 

RAM ( m68kcpu.h ):


 // m68ki_read_8 check_breakpoint(BPT_M68K_R, 1, address, val); // m68ki_read_16 check_breakpoint(BPT_M68K_R, 2, address, val); // m68ki_read_32 check_breakpoint(BPT_M68K_R, 4, address, val); // m68ki_write_8 check_breakpoint(BPT_M68K_W, 1, address, val); // m68ki_write_16 check_breakpoint(BPT_M68K_W, 2, address, val); // m68ki_write_32 check_breakpoint(BPT_M68K_W, 4, address, val); 

, , .


debug_wrap.c
 #include <Windows.h> #include <process.h> #include "debug_wrap.h" static HANDLE hMapFile = NULL, hStartFunc = NULL; dbg_request_t *open_shared_mem() { hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEM_NAME); if (hMapFile == NULL) { return NULL; } dbg_request_t *request = (dbg_request_t *)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(dbg_request_t)); if (request == NULL) { CloseHandle(hMapFile); return NULL; } return request; } void close_shared_mem(dbg_request_t **request) { UnmapViewOfFile(*request); CloseHandle(hMapFile); hMapFile = NULL; *request = NULL; } int recv_dbg_event(dbg_request_t *request, int wait) { while (request->dbg_active || request->dbg_events_count) { for (int i = 0; i < MAX_DBG_EVENTS; ++i) { if (request->dbg_events[i].type != DBG_EVT_NO_EVENT) { request->dbg_events_count -= 1; return i; } } if (!wait) return -1; Sleep(10); } return -1; } void send_dbg_request(dbg_request_t *request, request_type_t type) { if (!request) return; request->req_type = type; while (request->dbg_active && request->req_type != REQ_NO_REQUEST) { Sleep(10); } } 

. , . , , , .


:


Genesis Plus GX :


  var.key = "genesis_plus_gx_debugger"; environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var); { if (!var.value || !strcmp(var.value, "disabled")) { if (is_debugger_accessible()) { stop_debugging(); stop_gui(); deactivate_shared_mem(); } } else { activate_shared_mem(); start_debugging(); run_gui(); } } ... { "genesis_plus_gx_debugger", "Debugger; disabled|enabled" }, 

RetroArch :
, retro_run() . ( ), . , retro_run() , RetroArch . setjmp() / longjmp() . , retro_run() :


  if (is_debugger_paused()) { longjmp(jmp_env, 1); } int is_paused = setjmp(jmp_env); if (is_paused) { process_request(); return; } 

retro_run() process_request() , , .


PS



Update :
- IDA Pro , .


')

Source: https://habr.com/ru/post/434992/


All Articles