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

  • Tutorial


Greetings


Comrades reversers, romhackers: basically this article will be devoted to you. In it, I will tell you how to write your own plugin debugger for IDA Pro. Yes, it was already the first attempt to start a story , but since then much water has passed 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 this Motorola 68000. On it, by the way, running is my favorite old Sega Mega Drive/ Genesis. And, since it was always interesting to me how Segov’s games were all arranged, I decided from the first months of owning a computer to plunge deeply and permanently 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 rums for Segou much easier: loader, debugger, helper on commands VDP. Everything was written for IDA 6.8, and 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 came out IDA 7.0. The desire to port your project under it appeared right away, but the emulator architecture Gens, on the basis of which I wrote the debugger, turned out to be unsuitable for transfer: assembler inserts under x86, crutches, difficult to understand code, and much more. And the game Pier Solar and the Great Architects, which was released on the 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, which could be adapted for a debugger, I finally came across a Genesis Plus GX from EkeEke. So this article appeared.


Part One: Debugger Core


Emulation instructions motorolovskogo processor Genesis Plus GXengaged in the Musashi . In its original source code there is already a basic debugging functionality (a hook for executing instructions), but EkeEkedecided to remove it as superfluous. Return.




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


  • Breakpoints (breakpoints) for execution, reading and writing to memory
  • Functional Step Into,Step Over
  • Pause ( Pause), continue ( Resume) emulation
  • Read / set registers, read / write memory

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:


  • Protocol of debugger-server (kernel) communication with debugger-client (GUI, user)

Debugger Kernel: Break List


To implement the list we have the following structure:


typedefstructbreakpoint_s {structbreakpoint_s *next, *prev;int enabled;
    int width;
    bpt_type_t type;
    unsignedint address;
} breakpoint_t;

Fields nextand prevwill store pointers to the next and previous element, respectively.
The field enabledwill be stored 0if this breakpoint is required to be skipped in trigger checks.
width- the number of bytes starting from the address in the field address, which covers bryak.
Well, in the field typewe will keep the type of bribe (execution, reading, writing). More details below.


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


Functions for working with breakpoints
staticbreakpoint_t *first_bp = NULL;
static breakpoint_t *add_bpt(bpt_type_t type, unsignedint 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;
}
staticvoiddelete_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(unsignedint 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;
    }
    return0;
}
staticvoidremove_bpt(unsignedint address, bpt_type_t type){
    breakpoint_t *bpt;
    if ((bpt = find_breakpoint(address, type)))
        delete_breakpoint(bpt);
}
staticintcount_bpt_list(){
    breakpoint_t *p;
    int i = 0;
    for (p = first_bp; p; p = next_breakpoint(p)) {
        ++i;
    }
    return i;
}
staticvoidget_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;
    }
}
staticvoidclear_bpt_list(){
    while (first_bp != NULL) delete_breakpoint(first_bp);
}
staticvoidinit_bpt_list(){
    if (first_bp)
        clear_bpt_list();
}
voidcheck_breakpoint(bpt_type_t type, int width, unsignedint address, unsignedint 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:


staticint dbg_first_paused, dbg_trace, dbg_dont_check_bp;
staticint dbg_step_over;
staticint dbg_last_pc;
staticunsignedint dbg_step_over_addr;
staticint dbg_active, dbg_paused;

The variable dbg_first_pausedwe will have is responsible for stopping the emulation at the start of debugging. If 0it means, you need to pause the emulation and send a message to the client that the emulation has started. After the first pause set in 1.


dbg_tracewe need to execute one instruction at a time (functional Step Into). If it’s equal 1, we execute one instruction, pause, and reset the value to 0.


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


dbg_step_overwe will keep 1it if we are in the mode Step Overuntil the current PC( Program Counter , aka Instruction Pointer ) becomes equal to the address in dbg_step_over_addr. After that both variables are reset. dbg_step_over_addrI will tell you about the calculation of the value later.


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


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


With a variable dbg_paused, 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:


staticvoidpause_debugger(){
    dbg_trace = 1;
    dbg_paused = 1;
}
staticvoidresume_debugger(){
    dbg_trace = 0;
    dbg_paused = 0;
}
staticvoiddetach_debugger(){
    clear_bpt_list();
    resume_debugger();
}
staticvoidactivate_debugger(){
    dbg_active = 1;
}
staticvoiddeactivate_debugger(){
    dbg_active = 0;
}

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


Debugger kernel: implement the hook


Actually, there will be major work with a pause, a continuation of emulation Step Into, Step Over.


This is the code for the function process_breakpoints():


voidprocess_breakpoints(){
    int handled_event = 0;
    int is_step_over = 0;
    int is_step_in = 0;
    if (!dbg_active)
        return;
    unsignedint 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/ longjmpneeded was because the shell window RetroArch, for which its own version was written Genesis Plus GX, with which we start the emulation, hangs in anticipation of leaving 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 a command Step Into, reset the value of the variable dbg_traceand pause the emulation. We send to the client the corresponding event.
  5. If we are not on a pause, the mode is Step Overon, and the current PCis the destination address dbg_step_over_addr, reset the necessary variables and pause.
  6. We check breakpoint if we are not on it now, and if the breakpoint has worked, we pause and send the event to the client Step Overor breakpoint.
  7. If this is not a bryak, 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 longjumpas the implementation of an infinite loop of waiting for actions from the client during a pause.

The code for calculating the address for Step Overwas not as simple as it might be supposed at first. A motorol processor has different length of instructions, so you have to read the address of the next one manually, depending on the opcode. While the need to avoid the type of instruction bra, jmp, rtsconditional jumps forward, and execute them as Step Into. The implementation is as follows:


Calc_step_over () function code
staticunsignedintcalc_step_over(){
    unsignedint pc = m68k_get_reg(M68K_REG_PC);
    unsignedint sp = m68k_get_reg(M68K_REG_SP);
    unsignedint opc = m68ki_read_imm_16();
    unsignedint dest_pc = (unsignedint)(-1);
    // jsrif ((opc & 0xFFF8) == 0x4E90) {
        m68k_op_jsr_32_ai();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFF8) == 0x4EA8) {
        m68k_op_jsr_32_di();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFF8) == 0x4EB0) {
        m68k_op_jsr_32_ix();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EB8) {
        m68k_op_jsr_32_aw();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EB9) {
        m68k_op_jsr_32_al();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EBA) {
        m68k_op_jsr_32_pcdi();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EBB) {
        m68k_op_jsr_32_pcix();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    // bsrelseif ((opc & 0xFFFF) == 0x6100) {
        m68k_op_bsr_16();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x61FF) {
        m68k_op_bsr_32();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFF00) == 0x6100) {
        m68k_op_bsr_8();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    // dbfelseif ((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:


voidstop_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;
}
voidstart_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 based on the Shared the Memory , because it is required to send large blocks of memory: VRAM, RAM, ROM, and the network it will be all the more fun.


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 answer is stored in the same memory, and the corresponding information is added to the list of events of the debugger in the same memory.


The prototype was chosen as follows:


Source code debug_wrap.h
#ifndef _DEBUG_WRAP_H_#define _DEBUG_WRAP_H_#ifdef __cplusplusextern"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)typedefenum {
    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;
typedefenum {
    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;
typedefenum {
    REG_TYPE_M68K = (1 << 0),
    REG_TYPE_S80 = (1 << 1),
    REG_TYPE_Z80 = (1 << 2),
    REG_TYPE_VDP = (1 << 3),
} register_type_t;
typedefenum {
    DBG_EVT_NO_EVENT,
    DBG_EVT_STARTED,
    DBG_EVT_PAUSED,
    DBG_EVT_BREAK,
    DBG_EVT_STEP,
    DBG_EVT_STOPPED,
} dbg_event_type_t;
typedefstruct {dbg_event_type_t type;
    unsignedint pc;
    char msg[256];
} debugger_event_t;
typedefstruct {int index;
    unsignedint val;
} reg_val_t;
typedefstruct {unsignedint d0, d1, d2, d3, d4, d5, d6, d7;
    unsignedint a0, a1, a2, a3, a4, a5, a6, a7;
    unsignedint pc, sr, sp, usp, isp, ppc, ir;
} regs_68k_data_t;
typedefenum {
    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;
typedefstruct {unsignedint pc, sp, af, bc, de, hl, ix, iy, wz;
    unsignedint af2,bc2,de2,hl2;
    unsignedchar r, r2, iff1, iff2, halt, im, i;
} regs_z80_data_t;
typedefstruct {unsignedchar regs_vdp[0x20];
    unsignedshort dma_len;
    unsignedint dma_src, dma_dst;
} vdp_regs_t;
typedefstruct {int type; // register_type_tregs_68k_data_t regs_68k;
    reg_val_t any_reg;
    vdp_regs_t vdp_regs;
    regs_z80_data_t regs_z80;
} register_data_t;
typedefstruct {int size;
    unsignedint address;
    unsignedchar m68k_rom[MAXROMSIZE];
    unsignedchar m68k_ram[0x10000];
    unsignedchar z80_ram[0x2000];
} memory_data_t;
typedefstruct {bpt_type_t type;
    unsignedint address;
    int width;
    int enabled;
} bpt_data_t;
typedefstruct {int count;
    bpt_data_t breaks[MAX_BREAKPOINTS];
} bpt_list_t;
typedefstruct {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();
voidclose_shared_mem(dbg_request_t **request);
intrecv_dbg_event(dbg_request_t *request, int wait);
voidsend_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:


  • reading / setting registers
  • read / write memory
  • work with breakpoints
  • pause / continue emulation, detach / stop debugger
  • Step Into / Step Over

Further there are registers M68K, Z80, VDP. Next - memories 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 it does IDA).


Next comes the list of debug events:


  • Debug started (required for IDA Pro)
  • Debugging is suspended (saved in the event PC, on which the emulation is currently suspended)
  • Breakpoint triggered (also stores the value PCat which the trigger occurred)
  • It was executed Step Intoor Step Over(also, in fact, it is necessary only for IDA, since it is possible to manage with only one pause event)
  • The emulation process has been stopped. After pressing the button Stopin IDAwithout receiving this event, it will wait indefinitely for a stop.

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"staticint dbg_first_paused, dbg_trace, dbg_dont_check_bp;
staticint dbg_step_over;
staticint dbg_last_pc;
staticunsignedint dbg_step_over_addr;
staticdbg_request_t *dbg_req = NULL;
static HANDLE hMapFile = 0;
typedefstructbreakpoint_s {structbreakpoint_s *next, *prev;int enabled;
    int width;
    bpt_type_t type;
    unsignedint address;
} breakpoint_t;
staticbreakpoint_t *first_bp = NULL;
static breakpoint_t *add_bpt(bpt_type_t type, unsignedint 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;
}
staticvoiddelete_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(unsignedint 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;
    }
    return0;
}
staticvoidremove_bpt(unsignedint address, bpt_type_t type){
    breakpoint_t *bpt;
    if ((bpt = find_breakpoint(address, type)))
        delete_breakpoint(bpt);
}
staticintcount_bpt_list(){
    breakpoint_t *p;
    int i = 0;
    for (p = first_bp; p; p = next_breakpoint(p)) {
        ++i;
    }
    return i;
}
staticvoidget_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;
    }
}
staticvoidclear_bpt_list(){
    while (first_bp != NULL) delete_breakpoint(first_bp);
}
staticvoidinit_bpt_list(){
    if (first_bp)
        clear_bpt_list();
}
voidcheck_breakpoint(bpt_type_t type, int width, unsignedint address, unsignedint 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;
        }
    }
}
staticvoidpause_debugger(){
    dbg_trace = 1;
    dbg_req->dbg_paused = 1;
}
staticvoidresume_debugger(){
    dbg_trace = 0;
    dbg_req->dbg_paused = 0;
}
staticvoiddetach_debugger(){
    clear_bpt_list();
    resume_debugger();
}
staticvoidactivate_debugger(){
    dbg_req->dbg_active = 1;
}
staticvoiddeactivate_debugger(){
    dbg_req->dbg_active = 0;
}
intactivate_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));
    return0;
}
voiddeactivate_shared_mem(){
    UnmapViewOfFile(dbg_req);
    CloseHandle(hMapFile);
    hMapFile = NULL;
    dbg_req = NULL;
}
staticunsignedintcalc_step_over(){
    unsignedint pc = m68k_get_reg(M68K_REG_PC);
    unsignedint sp = m68k_get_reg(M68K_REG_SP);
    unsignedint opc = m68ki_read_imm_16();
    unsignedint dest_pc = (unsignedint)(-1);
    // jsrif ((opc & 0xFFF8) == 0x4E90) {
        m68k_op_jsr_32_ai();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFF8) == 0x4EA8) {
        m68k_op_jsr_32_di();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFF8) == 0x4EB0) {
        m68k_op_jsr_32_ix();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EB8) {
        m68k_op_jsr_32_aw();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EB9) {
        m68k_op_jsr_32_al();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EBA) {
        m68k_op_jsr_32_pcdi();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x4EBB) {
        m68k_op_jsr_32_pcix();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    // bsrelseif ((opc & 0xFFFF) == 0x6100) {
        m68k_op_bsr_16();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFFFF) == 0x61FF) {
        m68k_op_bsr_32();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    elseif ((opc & 0xFF00) == 0x6100) {
        m68k_op_bsr_8();
        m68k_op_rts_32();
        dest_pc = m68k_get_reg(M68K_REG_PC);
    }
    // dbfelseif ((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;
}
voidprocess_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 = ((unsignedint *)&Z80.pc)[regs_data->any_reg.index];
            }
            elseif (regs_data->any_reg.index >= 13 && regs_data->any_reg.index <= 19) // R <-> I
            {
                regs_data->any_reg.val = ((unsignedchar *)&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
            {
                ((unsignedint *)&Z80.pc)[regs_data->any_reg.index] = regs_data->any_reg.val;
            }
            elseif (regs_data->any_reg.index >= 13 && regs_data->any_reg.index <= 19) // R <-> I
            {
                ((unsignedchar *)&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)
        {
            unsignedint dest_pc = calc_step_over();
            if (dest_pc != (unsignedint)(-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;
}
voidsend_dbg_event(dbg_event_type_t type){
    dbg_req->dbg_events[dbg_req->dbg_events_count].type = type;
    dbg_req->dbg_events_count += 1;
}
voidstop_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;
}
voidstart_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;
}
intis_debugger_accessible(){
    return (dbg_req != NULL);
}
voidprocess_breakpoints(){
    int handled_event = 0;
    int is_step_over = 0;
    int is_step_in = 0;
    unsignedint 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);
    }
}
intis_debugger_paused(){
    return is_debugger_accessible() && dbg_req->dbg_paused && dbg_first_paused && !dbg_trace;
}

Debug.h source code
#ifndef _DEBUG_H_#define _DEBUG_H_#ifdef __cplusplusextern"C" {
#endif#include<setjmp.h>#include"debug_wrap.h"externvoidstart_debugging();
externvoidstop_debugging();
externintis_debugger_accessible();
externvoidprocess_request();
externintis_debugger_paused();
externintactivate_shared_mem();
externvoiddeactivate_shared_mem();
voidcheck_breakpoint(bpt_type_t type, int width, unsignedint address, unsignedint value);
extern jmp_buf jmp_env;
#ifdef __cplusplus
}
#endif#endif

Now in the function of reading and writing in the memory of the emulator, we need to add the breakpoint check.
Places where to insert a call check_breakpointfor VDPeasy to determine by logging lines under #ifdef LOGVDP. As a result, we insert the following calls into 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);

For RAMit will look like this (file 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);

For client access to shared memory, as well as for sending them requests, and waiting for debug events, we will make a wrapper.


Source code 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)
    {
        returnNULL;
    }
    dbg_request_t *request = (dbg_request_t *)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(dbg_request_t));
    if (request == NULL)
    {
        CloseHandle(hMapFile);
        returnNULL;
    }
    return request;
}
voidclose_shared_mem(dbg_request_t **request){
    UnmapViewOfFile(*request);
    CloseHandle(hMapFile);
    hMapFile = NULL;
    *request = NULL;
}
intrecv_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;
}
voidsend_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);
    }
}

Immediately ask me to forgive the quality of the code. Chukchi more reverser than a programmer. Perhaps, it was worth choosing more adequate methods for synchronization and waiting, but at the time of writing the code they were working.


Debugger kernel: launch


To enable debugging, I added the corresponding item in the option 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" },

A bit about architecture RetroArch:
For frame rendering, the emulator pulls a function each time retro_run(). It is here that the instructions of the processor are executed (and that’s where our hook is triggered), a buffer with a picture is formed. And, until the kernel completes the function retro_run(), the window RetroArchwill hang. I corrected this trick with setjmp()/ longjmp(). So, the first part of the trick I put in the beginning retro_run():


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

Well, at the end of the function, retro_run()I also stuck a call process_request()so that when debugging is not paused, to be able to accept requests.


PS Primer for the second part



Update :
In the second part of the article I will talk about writing the plugin debugger itself IDA Pro, and give links to all sources.


Also popular now: