
Salam!
Kamerad pembalik, romhackers: pada dasarnya artikel ini akan dikhususkan untuk Anda. Di dalamnya, saya akan memberi tahu Anda cara menulis plugin debugger Anda sendiri untuk IDA Pro
. Ya, sudah ada upaya pertama untuk memulai cerita , tetapi sejak itu banyak air telah mengalir, banyak prinsip telah direvisi. Secara umum, mereka mengemudi!
Pengantar liris
Sebenarnya, dari artikel sebelumnya ( satu , dua , tiga ), saya pikir itu tidak akan menjadi rahasia bahwa prosesor favorit saya adalah Motorola 68000
. Ngomong-ngomong, wanita tua favorit saya Sega Mega Drive
/ Genesis
mengerjakannya. Dan, karena saya selalu tertarik pada bagaimana permainan Segov diatur, dari bulan-bulan pertama penggunaan komputer saya, saya memutuskan untuk pergi jauh ke dalam hutan pembongkaran dan membalikkan untuk waktu yang lama.
Inilah bagaimana Smd IDA Tools muncul.
Proyek ini mencakup berbagai hal tambahan yang membuat pekerjaan mempelajari ROM di Sega jauh lebih mudah: loader, debugger, penolong untuk perintah VDP
. Semuanya ditulis untuk IDA 6.8
, dan berfungsi dengan baik. Tetapi, ketika saya memutuskan untuk memberi tahu dunia bagaimana saya melakukan semua hal yang sama, menjadi jelas bahwa akan sangat sulit untuk menunjukkan kode seperti itu kepada orang-orang, dan bahkan lebih untuk menggambarkannya. Karena itu, saya tidak bisa melakukan ini.
Dan kemudian IDA 7.0
keluar. Keinginan untuk mem-porting proyek saya langsung muncul, tetapi arsitektur emulator Gens
, atas dasar yang saya tuliskan debuggernya, ternyata tidak cocok untuk porting: sisipan perakitan x86
, kruk, kode yang sulit dipahami, dan banyak lagi. Dan permainan Pier Solar and the Great Architects
, yang dirilis pada kartrid pada tahun 2010, dan yang saya benar-benar ingin jelajahi (dan ada banyak trik anti-emulasi di sana), tidak dimulai pada Gens
.

Mencari sumber emulator yang cocok yang dapat diadaptasi untuk debugger, saya akhirnya menemukan EkeEke
Genesis Plus GX EkeEke
. Jadi artikel ini muncul.
Bagian Satu: Inti Debugger
Musashi menangani persaingan instruksi prosesor Motorola dalam Genesis Plus GX
. Sumber aslinya sudah memiliki fungsi debugging dasar (sebuah kait untuk menjalankan instruksi), tetapi EkeEke
memutuskan untuk menghapusnya sebagai tidak perlu. Kami kembali.


Sekarang yang paling penting: Anda perlu memutuskan arsitektur debugger. Persyaratannya adalah sebagai berikut:
- Breaks (breakpoints) untuk eksekusi, untuk membaca dan menulis ke memori
Step Into
Fungsi Step Into
, Step Over
- Jeda,
Resume
Emulasi - Baca / atur register, baca / tulis memori
Jika keempat poin ini adalah pekerjaan debugger dari dalam, maka Anda masih perlu mempertimbangkan akses ke fungsi ini dari luar. Tambahkan item lain:
- Protokol komunikasi dari debugger-server (kernel) dengan debugger-client (GUI, pengguna)
Inti debugger: daftar istirahat
Untuk mengimplementasikan daftar, kami memulai struktur berikut:
typedef struct breakpoint_s { struct breakpoint_s *next, *prev; int enabled; int width; bpt_type_t type; unsigned int address; } breakpoint_t;
Bidang next
dan next
akan masing-masing menyimpan pointer ke elemen berikutnya dan sebelumnya.
Kolom yang enabled
akan menyimpan 0
jika breakpoint ini perlu dilewati dalam tes operasi.
width
- jumlah byte yang dimulai dari alamat di bidang address
yang dicakup pemutus.
Nah, di bidang type
kita akan menyimpan tipe breakpoint (eksekusi, membaca, menulis). Lebih detail di bawah ini.
Untuk bekerja dengan daftar breakpoint, saya menambahkan fungsi-fungsi berikut:
Fungsi Breakpoint 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; } } }
Inti debugger: variabel inti
Sebenarnya, saya melihat implementasi ini di debugger PCSXR lain.
Tambahkan variabel yang akan menyimpan status emulasi:
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;
Variabel dbg_first_paused
akan bertanggung jawab untuk menghentikan emulasi pada awal debugging. Jika 0
- maka Anda perlu menjeda emulasi dan mengirim pesan ke klien bahwa emulasi dimulai. Setelah jeda pertama, atur ke 1
.
Kami membutuhkan dbg_trace
untuk dieksekusi sesuai dengan satu instruksi ( Step Into
fungsionalitas). Jika sama dengan 1
, kami menjalankan satu instruksi, jeda, dan setel ulang nilainya ke 0
.
Saya mengatur variabel dbg_dont_check_bp
sehingga memori baca / tulis tidak akan berfungsi jika debugger melakukannya.
dbg_step_over
akan disimpan pada 1
jika kita berada dalam mode Step Over
sampai PC
saat ini ( Program Counter , alias Instruction Pointer ) menjadi sama dengan alamat di dbg_step_over_addr
. Setelah itu, kedua variabel diatur ulang. Saya akan dbg_step_over_addr
perhitungan nilai dbg_step_over_addr
nanti.
Saya mengatur variabel dbg_last_pc
untuk satu kasus tertentu: ketika kita sudah berdiri pada istirahat, dan klien meminta untuk Resume
. Agar pemutus tidak berfungsi lagi, saya membandingkan alamat PC
terakhir dalam variabel ini dengan yang baru, dan jika nilainya berbeda, Anda dapat memeriksa breakpoint pada PC
saat ini.
dbg_active
- pada kenyataannya, ia menyimpan status 1
ketika debugging aktif dan Anda perlu memeriksa jeda, memproses permintaan dari klien.
Dengan variabel dbg_paused
, saya pikir semuanya jelas: 1
- kita sedang jeda (misalnya, setelah istirahat), dan kami menunggu perintah dari klien, 0
- kami mengikuti instruksi.
Kami menulis fungsi untuk bekerja dengan variabel-variabel ini:
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; }
Kita melihat bahwa dalam implementasi detach_debugger()
saya digunakan untuk menghapus daftar istirahat. Ini diperlukan agar setelah memutuskan hubungan klien, breakpoint lama tidak terus bekerja.
Inti Debugger: kami menerapkan hook pada instruksi
Sebenarnya, di sini pekerjaan utama akan berlangsung dengan jeda, melanjutkan emulasi, Step Into
, Step Over
.
Berikut adalah kode untuk fungsi process_breakpoints()
:
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;
Mari kita pahami:
- Jika debugging tidak diaktifkan, keluar saja dari kail
- Trik dengan
setjmp
/ longjmp
diperlukan karena RetroArch
shell RetroArch
, di mana kami menulis versi kami sendiri Genesis Plus GX
, yang dengannya kami menjalankan emulasi, hang menunggu emulator untuk keluar dari fungsi rendering bingkai. Saya akan menunjukkan bagian kedua dari trik ini nanti, karena itu menyentuh shell di atas emulator, bukan inti. - Jika ini adalah operasi pertama kami dari pengait, dan, dengan demikian, awal dari persaingan, kami menghentikan sebentar dan mengirimkan peristiwa dimulainya persaingan ke klien.
- Jika klien sebelumnya mengirim perintah
Step Into
, kami dbg_trace
nilai variabel dbg_trace
dan mengatur emulasi menjadi jeda. Kami mengirimkan acara yang sesuai kepada klien. - Jika kita tidak jeda, mode
Step Over
dihidupkan, dan PC
saat ini sama dengan alamat tujuan dbg_step_over_addr
, kita dbg_step_over_addr
variabel yang diperlukan dan jeda. - Kami memeriksa breakpoint jika kami tidak menggunakannya sekarang, dan jika break telah bekerja, kami menjeda dan mengirimkan klien acara tentang
Step Over
atau break. - Jika ini bukan gangguan, bukan
Step Into
, dan bukan Step Over
, maka klien meminta istirahat. Kami mengirim acara tentang jeda yang dipicu. - Kami menerapkan trik dengan
longjump
sebagai implementasi dari loop tak terbatas yang menunggu tindakan dari klien selama jeda.
Kode untuk menghitung alamat untuk Step Over
tidak sesederhana yang Anda duga. Prosesor Motorola memiliki panjang instruksi yang berbeda, jadi Anda harus mempertimbangkan alamat selanjutnya secara manual, tergantung pada opcode. Selain itu, Anda perlu menghindari instruksi seperti bra
, jmp
, rts
conditional jumps forward, dan jalankan sebagai Step Into
. Implementasinya adalah sebagai berikut:
Calc_step_over () kode fungsi 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);
Kernel Debugger: Menginisialisasi dan Menghentikan Debugging
Semuanya sederhana di sini:
void stop_debugging() {
Debugger Kernel: Implementasi Protokol
Protokol komunikasi antara server debug dan klien klien dapat dengan aman disebut jantung kedua dari proses debugging, karena itu mengimplementasikan fungsionalitas pemrosesan permintaan dari klien, dan reaksi terhadapnya.
Diputuskan untuk mengimplementasikan berdasarkan Memori Bersama , karena perlu mengirim blok memori besar: VRAM
, RAM
, ROM
, dan melalui jaringan ini akan lebih menyenangkan.
Intinya adalah ini: kernel menciptakan memori bersama dengan struktur yang telah ditentukan, dan mengharapkan permintaan masuk dari klien. Setelah memproses permintaan, jawabannya disimpan dalam memori yang sama, dan informasi yang sesuai ditambahkan ke daftar peristiwa debugger di memori yang sama.
Prototipe dipilih sebagai berikut:
Unduh Paket Sumber 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
Bidang pertama dalam struktur yang akan kita miliki adalah jenis permintaan:
- baca / atur register
- baca / tulis memori
- bekerja dengan breakpoints
- jeda / lanjutkan emulasi, putuskan / hentikan debugger
Step Into
/ Step Over
Berikutnya adalah register M68K
, Z80
, VDP
. Berikut ini adalah blok memori ROM
, RAM
, VRAM
, Z80
.
Untuk menambah / menghapus celah, saya juga membuat struktur yang sesuai. Nah, daftar mereka juga ada di sini (sebagian besar, itu hanya untuk tampilan di GUI, tanpa perlu mengingat semua jeda yang diinstal, seperti yang dilakukan IDA
).
Berikut ini adalah daftar acara debug:
- Debugging dimulai (diperlukan untuk
IDA Pro
) - Debugging dijeda (
PC
di mana emulasi saat ini dijeda disimpan dalam acara tersebut) - Breakpoint berfungsi (juga menyimpan nilai
PC
tempat operasi terjadi) Step Into
atau Step Over
dilakukan (juga, sebenarnya, hanya diperlukan untuk IDA
, karena Anda dapat melakukannya hanya dengan satu jeda acara)- Proses emulasi telah dihentikan. Setelah mengklik tombol
Stop
di IDA
tanpa menerima acara ini, ia akan menunggu tanpa henti untuk berhenti
Berbekal gagasan protokol, kami mengimplementasikan pemrosesan permintaan klien, sehingga mendapatkan kode kernel debugger berikut:
Unduh Paket Sumber debug.c #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 = ®s_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 = ®s_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 = ®s_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
):
, , .
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
, .