
Приветствую!
Товарищи реверсеры, ромхакеры: в основном эта статья будет посвящена вам. В ней я расскажу вам, как написать свой плагин-отладчик для IDA Pro. Да, уже была первая попытка начать рассказ, но, с тех пор много воды утекло, многие принципы пересмотрены. В общем, погнали!
Лирическое вступление
Собственно, из предыдущих статей (раз, два, три), думаю, не будет секретом, что мой любимый процессор — это Motorola 68000. На нём, кстати, работает моя любимая старушка Sega Mega Drive / Genesis. И, так как мне всегда было интересно, как же всё таки устроены сеговские игры, я ещё с первых месяцев владения компьютером решил глубоко и надолго погрязть в дебрях дизассемблирования и реверсинга.
Так появился Smd IDA Tools.
Проект включает в себя различные вспомогательные штуки, которые делают работу по изучению ромов на Сегу значительно проще: загрузчик, отладчик, хелпер по командам VDP. Всё было написано для IDA 6.8, и работало хорошо. Но, когда я решил поведать миру о том, как же я всё таки это сделал, стало понятно, что показывать такой код народу, а, тем более, описывать его, будет очень сложно. Поэтому я так и не смог этого сделать тогда.
А потом вышла IDA 7.0. Желание портировать свой проект под неё появилось сразу, но архитектура эмулятора Gens, на основе которого я писал отладчик, оказалась непригодной для переноса: ассемблерные вставки под x86, костыли, сложный в понимании код, и многое другое. Да и игра Pier Solar and the Great Architects, вышедшая на картриджах в 2010, и которую так хотелось исследовать (а антиэмуляционных трюков там полно), не запускалась в Gens'е.

В поисках подходящего исходника эмулятора, который можно было бы адаптировать под отладчик, я в итоге наткнулся на Genesis Plus GX от EkeEke. Так и появилась данная статья.
Часть первая: ядро отладчика
Эмуляцией инструкций мотороловского процессора в Genesis Plus GX занимается Musashi. В его оригинальном исходнике уже имеется базовый отладочный функционал (хук на выполнение инструкций), но EkeEke решил убрать его за ненадобностью. Возвращаем.


Теперь самое главное: необходимо определиться с архитектурой отладчика. Требования следующие:
- Бряки (точки останова) на исполнение, на чтение и запись в память
- Функционал
Step Into,Step Over - Приостановка (
Pause), продолжение (Resume) эмуляции - Чтение/установка регистров, чтение/запись памяти
Если эти четыре пункта — это работа отладчика изнутри, то необходимо ещё продумать доступ к данному функционалу извне. Добавляем ещё один пункт:
- Протокол общения отладчика-сервера (ядра) с отладчиком-клиентом (GUI, пользователь)
Ядро отладчика: список бряков
Для реализации списка заводим следующую структуру:
typedef struct breakpoint_s {
struct breakpoint_s *next, *prev;
int enabled;
int width;
bpt_type_t type;
unsigned int address;
} breakpoint_t;Поля next и prev будут хранить указатели на следующий и предыдущий элемент соответственно.
Поле enabled будет хранить 0, если данный бряк требуется пропустить в проверках на срабатывание.
width — количество байт начиная от адреса в поле address, которые покрывает бряк.
Ну а в поле type будем хранить тип бряка (исполнение, чтение, запись). Подробнее ниже.
Для работы со списком точек останова я добавил следующие функции:
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;
}
}
}Ядро отладчика: основные переменные
Собственно, данную реализацию я подсмотрел в другом отладчике PCSXR.
Добавляем переменные, которые будут хранить состояние эмуляции:
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;Переменная dbg_first_paused у нас будет отвечать за остановку эмуляции на старте отладки. Если 0 — значит нужно сделать паузу эмуляции и отправить клиенту сообщение о том, что эмуляция начата. После первой паузы устанавливаем в 1.
dbg_trace нам понадобиться для исполнения по одной инструкции (функционал Step Into). Если равно 1, выполняем одну инструкцию, становимся на паузу, и сбрасываем значение в 0.
Переменную dbg_dont_check_bp я завёл для того, чтобы бряки на чтение/запись памяти не срабатывали, если это делает отладчик.
dbg_step_over у нас будет хранить 1, если мы в режиме Step Over до тех пор, пока текущий PC (Program Counter, он же Instruction Pointer) не станет равным адресу в dbg_step_over_addr. После этого обе переменные обнуляем. О подсчёте значения dbg_step_over_addr я расскажу позже.
Я завёл переменную dbg_last_pc для одного конкретного случая: когда мы уже стоим на бряке, и клиент просит Resume. Чтобы бряк не сработал снова, я сравниваю адрес последнего PC в этой переменной с новым, и, если значения разные, можно проверять брейкпоинт на текущем PC.
dbg_active — собственно, хранит состояние 1, когда отладка активна и требуется проверять бряки, обрабатывать запросы от клиента.
С переменной dbg_paused, думаю, всё понятно: 1 — мы на паузе (например, после срабатывания бряка) и ожидаем команд от клиента, 0 — выполняем инструкции.
Пишем функции для работы с этими переменными:
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;
}Видим, что в реализации detach_debugger() я использовал очистку списка бряков. Это нужно для того, чтобы после отсоединения клиента, старые точки останова не продолжали срабатывать.
Ядро отладчика: реализуем хук на инструкции
Собственно, здесь и будет происходить основная работа с паузой, продолжением эмуляции, Step Into, Step Over.
Вот такой получился код для функции 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;
// 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);
}
}Давайте разбираться:
- Если отладка не включена, просто выходим из хука
- Трюк с
setjmp/longjmpнужен был потому, что окно оболочкиRetroArch, для которой была написана своя версияGenesis Plus GX, с помощью который мы и запускаем эмуляцию, подвисает в ожидании выхода из функции рендеринга кадра, которую как раз реализует эмулятор. Вторую часть трюка я покажу позже, т.к. она касается уже оболочки над эмулятором, нежели ядра. - Если это наше первое срабатывание хука, а, соответственно, и начало эмуляции, ставим на паузу и отправляем событие о старте эмуляции клиенту.
- Если клиент ранее отправил команду
Step Into, обнуляем значение переменнойdbg_traceи ставим эмуляцию на паузу. Отправляем клиенту соответствующее событие. - Если мы не на паузе, режим
Step Overвключен, и текущийPCравен адресу назначенияdbg_step_over_addr, обнуляем необходимые переменные и ставим на паузу. - Проверяем брейкпоинт, если мы сейчас не на нём, и, если бряк сработал, ставим на паузу и отправляем клиенту событие о
Step Overили бряке. - Если это не бряк, не
Step Into, и неStep Over, значит клиент попросил паузу. Отправляем событие о сработавшей паузе. - Реализуем трюк с
longjumpв качестве реализации бесконечного цикла ожидания действий от клиента во время паузы.
Код подсчёта адреса для Step Over оказался не таким простым, как можно предположить сначала. У мотороловского процессора бывает разная длина инструкций, поэтому приходится считать адрес следующей вручную, в зависимости от опкода. При том, нужно избегать инструкций типа bra, jmp, rts условных прыжков вперёд, и выполнять их как Step Into. Реализация следующая:
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 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;
}Ядро отладчика: реализация протокола
Протокол общения между сервером-отладчиком и клиентом-пользователем можно смело назвать вторым сердцем процесса отладки, т.к. в нём реализован функционал обработки запросов от клиента, и реакции на них.
Реализовывать было решено на основе Shared Memory, потому как требуется пересылать большие блоки памяти: VRAM, RAM, ROM, а по сети это будет тем ещё удовольствием.
Суть такова: ядро создаёт расшареную память с заранее определённой структурой, и ожидает входящих запросов от клиента. После обработки запроса в ту же память сохраняется ответ, и в список событий отладчика в той же памяти добавляется соответствующая информация.
Прототип был выбран такой:
#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Первым полем в структуре у нас будет тип запроса:
- чтение/установка регистров
- чтение/запись памяти
- работа с брейкпоинтами
- приостановка/продолжение эмуляции, отсоединение/остановка отладчика
Step Into/Step Over
Далее идут регистры M68K, Z80, VDP. Следом — блоки памяти ROM, RAM, VRAM, Z80.
Для добавления/удаления бряка я так же завёл соответствующую структуру. Ну и их список тоже здесь (по большей части, он лишь для отображения в GUI, без необходимости помнить все установленные бряки, как это делает IDA).
Далее идёт список отладочных событий:
- Отладка начата (необходим для
IDA Pro) - Отладка приостановлена (в событии сохраняется
PC, на котором в данный момент приостановлена эмуляция) - Сработал брейкпоинт (так же хранит значение
PC, на котором произошло срабатывание) - Был выполнен
Step IntoилиStep Over(тоже, по сути, нужно только дляIDA, т.к. можно обойтись и одним лишь событием паузы) - Процесс эмуляции был остановлен. После нажатия кнопки
StopвIDAбез получения этого события она будет бесконечно ожидать остановки
Вооружившись идеей протокола, реализуем обработку запросов клиента, получая таким образом следующий код ядра отладчика:
#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;
}#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);Для доступа клиента к расшареной памяти, а также для отправки им запросов, и ожидания отладочных событий сделаем обёртку.
#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(), чтобы когда отладка не на паузе, иметь возможность принимать запросы.
P.S. Затравка для второй части

Update:
Во второй части статьи я расскажу о написании собственно плагина-отладчика для IDA Pro, и дам ссылки на все исходники.
