Модернизация IDA Pro. Отладчик для Sega Mega Drive (часть 1)

Модернизация IDA Pro. Отладчик для Sega Mega Drive (часть 1)

Приветствую!

Товарищи реверсеры, ромхакеры: в основном эта статья будет посвящена вам. В ней я расскажу вам, как написать свой плагин-отладчик для 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);     } }

Давайте разбираться:

  1. Если отладка не включена, просто выходим из хука
  2. Трюк с setjmp/longjmp нужен был, потому, что окно оболочки RetroArch, для которой и был написан Genesis Plus GX, подвисает в ожидании выхода из функции рендеринга кадра, которую как раз реализует эмулятор. Вторую часть трюка я покажу позже, т.к. она касается уже оболочки над эмулятором, нежели ядра.
  3. Если это наше первое срабатывание хука, а, соответственно, и начало эмуляции, ставим на паузу и отправляем событие о старте эмуляции клиенту.
  4. Если клиент ранее отправил команду Step Into, обнуляем значение переменной dbg_trace и ставим эмуляцию на паузу. Отправляем клиенту соответствующее событие.
  5. Если мы не на паузе, режим Step Over включен, и текущий PC равен адресу назначения dbg_step_over_addr, обнуляем необходимые переменные и ставим на паузу.
  6. Проверяем брейкпоинт, если мы сейчас не на нём, и, если бряк сработал, ставим на паузу и отправляем клиенту событие о Step Over или бряке.
  7. Если это не бряк, не Step Into, и не Step Over, значит клиент попросил паузу. Отправляем событие о сработавшей паузе.
  8. Реализуем трюк с longjump в качестве реализации бесконечного цикла ожидания действий от клиента во время паузы.

Код подсчёта адреса для Step Over оказался не таким простым, как можно предположить сначала. У мотороловского процессора бывает разная длина инструкций, поэтому приходится считать адрес следующей вручную, в зависимости от опкода. При том, нужно избегать инструкций типа bra, jmp, rts условных прыжков вперёд, и выполнять их как Step Into. Реализация следующая:

Код функции calc_step_over()

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, а по сети это будет тем ещё удовольствием.

Суть такова: ядро создаёт расшареную память с заранее определённой структурой, и ожидает входящих запросов от клиента. После обработки запроса в ту же память сохраняется ответ, и в список событий отладчика в той же памяти добавляется соответствующая информация.

Прототип был выбран такой:

Исходный код debug_wrap.h

#ifndef _DEBUG_WRAP_H_ #define _DEBUG_WRAP_H_  #ifdef __cplusplus extern "C" { #endif  #include   #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 без получения этого события она будет бесконечно ожидать остановки

Вооружившись идеей протокола, реализуем обработку запросов клиента, получая таким образом следующий код ядра отладчика:

Исходный код 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  #include "debug_wrap.h"  extern void start_debugging(); extern void stop_debugging(); extern int is_debugger_accessible(); extern void process_request(); extern int is_debugger_paused(); extern int activate_shared_mem(); extern void deactivate_shared_mem(); void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value);  extern jmp_buf jmp_env;  #ifdef __cplusplus } #endif  #endif

Теперь в функции чтения и записи в память эмулятора нам необходимо добавить проверку брейкпоинтов.
Места, куда вставлять вызов check_breakpoint для VDP легко определить по строкам логирования под #ifdef LOGVDP. В итоге вставляем следующие вызовы в vdp_ctrl.c:

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

Для RAM это будет выглядеть так (файл m68kcpu.h):

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

Для доступа клиента к расшареной памяти, а также для отправки им запросов, и ожидания отладочных событий сделаем обёртку.

Исходный код debug_wrap.c

#include  #include   #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, и дам ссылки на все исходники.

 
Источник

reverse engineering, sega genesis, sega mega drive, ассемблер, дизассемблер, ненормальное программирование, отладка, реверс-инжиниринг, ретро-игры

Читайте также