
Hola a todos
En el artículo anterior, modificamos con éxito el núcleo del emulador de juegos en Sega Mega Drive
/ Genesis
, agregando capacidades de depuración. Ahora es el turno de escribir el complemento del depurador para IDA Pro
, versión 7.0
. Empecemos
Segunda parte: el complemento del depurador
Primero, cree un nuevo proyecto DLL
vacío: File
-> New
-> Project
-> Windows Desktop Wizard
-> Dynamic link library (.dll)
, también marcando la casilla Empty Project
y desmarcando el resto:

Desempaquete el IDA SDK
y escríbalo en las macros de Visual Studio
(usaré la 2017 Community
) para que en el futuro pueda consultarlo fácilmente. Al mismo tiempo, agregaremos una macro para la ruta a IDA Pro
.
Vaya a View
-> Other Windows
-> Property Manager
:

Porque estamos trabajando con SDK 7.0
versión SDK 7.0
, la compilación será por compilador x64
. Por lo tanto, seleccione Debug | x64
Debug | x64
-> Microsoft.Cpp.x64.user
-> Properties
:

Haga clic en el botón Add Macro
en la sección User Macros
y escriba IDA_SDK
macro IDA_SDK
con la ruta donde desempaquetó el SDK
:

Hacemos lo mismo con IDA_DIR
(la ruta a su IDA Pro
):

Observo que la IDA
está configurada de manera predeterminada en %Program Files%
, lo que requiere derechos de administrador.
Eliminemos también la configuración de Win32
(en este artículo no afectaré la compilación en un sistema x86
), dejando solo la opción x64
.
Ahora tome la plantilla de clase de cola de eventos del depurador:
Descargar el paquete fuente ida_debmod.h #pragma once #include <deque> #include <ida.hpp> #include <idd.hpp> //-------------------------------------------------------------------------- // Very simple class to store pending events enum queue_pos_t { IN_FRONT, IN_BACK }; struct eventlist_t : public std::deque<debug_event_t> { private: bool synced; public: // save a pending event void enqueue(const debug_event_t &ev, queue_pos_t pos) { if (pos != IN_BACK) push_front(ev); else push_back(ev); } // retrieve a pending event bool retrieve(debug_event_t *event) { if (empty()) return false; // get the first event and return it *event = front(); pop_front(); return true; } };
Ahora el proyecto de estudio tendrá la oportunidad de establecer definiciones para el compilador, por lo que agregamos lo siguiente:
__NT__ __IDP__ __X64__
Agregue un nuevo archivo ida_debug.cpp
vacío y pegue la siguiente plantilla en él:
Descargar el paquete fuente ida_debug.cpp #include <ida.hpp> #include <idd.hpp> #include <auto.hpp> #include <funcs.hpp> #include <idp.hpp> #include <dbg.hpp> #include "ida_debmod.h" #include "debug_wrap.h" static dbg_request_t *dbg_req = NULL; static void pause_execution() { send_dbg_request(dbg_req, REQ_PAUSE); } static void continue_execution() { send_dbg_request(dbg_req, REQ_RESUME); } static void stop_debugging() { send_dbg_request(dbg_req, REQ_STOP); } eventlist_t g_events; static qthread_t events_thread = NULL; // TODO: Implement status register bits mask static const char *const SRReg[] = { }; #define RC_GENERAL (1 << 0) // TODO: define different register types register_info_t registers[] = { // TODO: Implement registers description }; static const char *register_classes[] = { "General Registers", // TODO: Add other register group names NULL }; static void finish_execution() { if (events_thread != NULL) { qthread_join(events_thread); qthread_free(events_thread); qthread_kill(events_thread); events_thread = NULL; } } static bool idaapi init_debugger(const char *hostname, int portnum, const char *password) { set_processor_type(ph.psnames[0], SETPROC_LOADER); // reset proc to "M68000" return true; } static bool idaapi term_debugger(void) { dbg_req->is_ida = 0; close_shared_mem(&dbg_req); return true; } static int idaapi process_get_info(procinfo_vec_t *procs) { return 0; } static int idaapi check_debugger_events(void *ud) { while (dbg_req->dbg_active || dbg_req->dbg_events_count) { dbg_req->is_ida = 1; int event_index = recv_dbg_event(dbg_req, 0); if (event_index == -1) { qsleep(10); continue; } debugger_event_t *dbg_event = &dbg_req->dbg_events[event_index]; debug_event_t ev; switch (dbg_event->type) { case DBG_EVT_STARTED: ev.eid = PROCESS_START; ev.pid = 1; ev.tid = 1; ev.ea = BADADDR; ev.handled = true; ev.modinfo.name[0] = 'E'; ev.modinfo.name[1] = 'M'; ev.modinfo.name[2] = 'U'; ev.modinfo.name[3] = 'L'; ev.modinfo.name[4] = '\0'; ev.modinfo.base = 0; ev.modinfo.size = 0; ev.modinfo.rebase_to = BADADDR; g_events.enqueue(ev, IN_FRONT); break; case DBG_EVT_PAUSED: ev.pid = 1; ev.tid = 1; ev.ea = dbg_event->pc; ev.handled = true; ev.eid = PROCESS_SUSPEND; g_events.enqueue(ev, IN_BACK); break; case DBG_EVT_BREAK: ev.pid = 1; ev.tid = 1; ev.ea = dbg_event->pc; ev.handled = true; ev.eid = BREAKPOINT; ev.bpt.hea = ev.bpt.kea = ev.ea; g_events.enqueue(ev, IN_BACK); break; case DBG_EVT_STEP: ev.pid = 1; ev.tid = 1; ev.ea = dbg_event->pc; ev.handled = true; ev.eid = STEP; g_events.enqueue(ev, IN_BACK); break; case DBG_EVT_STOPPED: ev.eid = PROCESS_EXIT; ev.pid = 1; ev.handled = true; ev.exit_code = 0; g_events.enqueue(ev, IN_BACK); break; default: break; } dbg_event->type = DBG_EVT_NO_EVENT; qsleep(10); } return 0; } static int idaapi start_process(const char *path, const char *args, const char *startdir, int dbg_proc_flags, const char *input_path, uint32 input_file_crc32) { g_events.clear(); dbg_req = open_shared_mem(); if (!dbg_req) { show_wait_box("HIDECANCEL\nWaiting for connection to plugin..."); while (!dbg_req) { dbg_req = open_shared_mem(); } hide_wait_box(); } events_thread = qthread_create(check_debugger_events, NULL); send_dbg_request(dbg_req, REQ_ATTACH); return 1; } static void idaapi rebase_if_required_to(ea_t new_base) { } static int idaapi prepare_to_pause_process(void) { pause_execution(); return 1; } static int idaapi emul_exit_process(void) { stop_debugging(); finish_execution(); return 1; } static gdecode_t idaapi get_debug_event(debug_event_t *event, int timeout_ms) { while (true) { // are there any pending events? if (g_events.retrieve(event)) { return g_events.empty() ? GDE_ONE_EVENT : GDE_MANY_EVENTS; } if (g_events.empty()) break; } return GDE_NO_EVENT; } static int idaapi continue_after_event(const debug_event_t *event) { dbg_notification_t req = get_running_notification(); switch (event->eid) { case STEP: case BREAKPOINT: case PROCESS_SUSPEND: if (req == dbg_null || req == dbg_run_to) continue_execution(); break; } return 1; } static void idaapi stopped_at_debug_event(bool dlls_added) { } static int idaapi thread_suspend(thid_t tid) // Suspend a running thread { return 0; } static int idaapi thread_continue(thid_t tid) // Resume a suspended thread { return 0; } static int idaapi set_step_mode(thid_t tid, resume_mode_t resmod) // Run one instruction in the thread { switch (resmod) { case RESMOD_INTO: ///< step into call (the most typical single stepping) send_dbg_request(dbg_req, REQ_STEP_INTO); break; case RESMOD_OVER: ///< step over call send_dbg_request(dbg_req, REQ_STEP_OVER); break; } return 1; } static int idaapi read_registers(thid_t tid, int clsmask, regval_t *values) { if (!dbg_req) return 0; if (clsmask & RC_GENERAL) { dbg_req->regs_data.type = REG_TYPE_M68K; send_dbg_request(dbg_req, REQ_GET_REGS); // TODO: Set register values for IDA } // TODO: Implement other registers reading return 1; } static void set_reg(register_type_t type, int reg_index, unsigned int value) { dbg_req->regs_data.type = type; dbg_req->regs_data.any_reg.index = reg_index; dbg_req->regs_data.any_reg.val = value; send_dbg_request(dbg_req, REQ_SET_REG); } static int idaapi write_register(thid_t tid, int regidx, const regval_t *value) { // TODO: Implement set registers for emulator return 1; } static int idaapi get_memory_info(meminfo_vec_t &areas) { memory_info_t info; // Don't remove this loop for (int i = 0; i < get_segm_qty(); ++i) { segment_t *segm = getnseg(i); info.start_ea = segm->start_ea; info.end_ea = segm->end_ea; qstring buf; get_segm_name(&buf, segm); info.name = buf; get_segm_class(&buf, segm); info.sclass = buf; info.sbase = 0; info.perm = SEGPERM_READ | SEGPERM_WRITE; info.bitness = 1; areas.push_back(info); } // Don't remove this loop return 1; } static ssize_t idaapi read_memory(ea_t ea, void *buffer, size_t size) { // TODO: Implement memory regions reading return size; } static ssize_t idaapi write_memory(ea_t ea, const void *buffer, size_t size) { return 0; } static int idaapi is_ok_bpt(bpttype_t type, ea_t ea, int len) { switch (type) { //case BPT_SOFT: case BPT_EXEC: case BPT_READ: // there is no such constant in sdk61 case BPT_WRITE: case BPT_RDWR: return BPT_OK; } return BPT_BAD_TYPE; } static int idaapi update_bpts(update_bpt_info_t *bpts, int nadd, int ndel) { for (int i = 0; i < nadd; ++i) { ea_t start = bpts[i].ea; ea_t end = bpts[i].ea + bpts[i].size - 1; bpt_data_t *bpt_data = &dbg_req->bpt_data; switch (bpts[i].type) { case BPT_EXEC: bpt_data->type = BPT_M68K_E; break; case BPT_READ: bpt_data->type = BPT_M68K_R; break; case BPT_WRITE: bpt_data->type = BPT_M68K_W; break; case BPT_RDWR: bpt_data->type = BPT_M68K_RW; break; } bpt_data->address = start; bpt_data->width = bpts[i].size; send_dbg_request(dbg_req, REQ_ADD_BREAK); bpts[i].code = BPT_OK; } for (int i = 0; i < ndel; ++i) { ea_t start = bpts[nadd + i].ea; ea_t end = bpts[nadd + i].ea + bpts[nadd + i].size - 1; bpt_data_t *bpt_data = &dbg_req->bpt_data; switch (bpts[nadd + i].type) { case BPT_EXEC: bpt_data->type = BPT_M68K_E; break; case BPT_READ: bpt_data->type = BPT_M68K_R; break; case BPT_WRITE: bpt_data->type = BPT_M68K_W; break; case BPT_RDWR: bpt_data->type = BPT_M68K_RW; break; } bpt_data->address = start; send_dbg_request(dbg_req, REQ_DEL_BREAK); bpts[nadd + i].code = BPT_OK; } return (ndel + nadd); } //-------------------------------------------------------------------------- // // DEBUGGER DESCRIPTION BLOCK // //-------------------------------------------------------------------------- debugger_t debugger = { IDD_INTERFACE_VERSION, "DBGNAME", 0x8000 + 1, "m68k", DBG_FLAG_NOHOST | DBG_FLAG_CAN_CONT_BPT | DBG_FLAG_FAKE_ATTACH | DBG_FLAG_SAFE | DBG_FLAG_NOPASSWORD | DBG_FLAG_NOSTARTDIR | DBG_FLAG_CONNSTRING | DBG_FLAG_ANYSIZE_HWBPT | DBG_FLAG_DEBTHREAD, register_classes, RC_GENERAL, registers, qnumber(registers), 0x1000, NULL, NULL, 0, DBG_RESMOD_STEP_INTO | DBG_RESMOD_STEP_OVER, init_debugger, term_debugger, process_get_info, start_process, NULL, NULL, rebase_if_required_to, prepare_to_pause_process, emul_exit_process, get_debug_event, continue_after_event, NULL, stopped_at_debug_event, thread_suspend, thread_continue, set_step_mode, read_registers, write_register, NULL, get_memory_info, read_memory, write_memory, is_ok_bpt, update_bpts, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, };
Luego, cree otro archivo, ida_plugin.cpp
y pegue el siguiente código:
Descargar el paquete fuente ida_plugin.cpp #include <ida.hpp> #include <dbg.hpp> #include <idd.hpp> #include <loader.hpp> #include <idp.hpp> #include <offset.hpp> #include <kernwin.hpp> #include "ida_plugin.h" #include "ida_debmod.h" extern debugger_t debugger; static bool plugin_inited; static bool my_dbg; static int idaapi idp_to_dbg_reg(int idp_reg) { int reg_idx = idp_reg; if (idp_reg >= 0 && idp_reg <= 7) reg_idx = 0 + idp_reg; else if (idp_reg >= 8 && idp_reg <= 39) reg_idx = 8 + (idp_reg % 8); else if (idp_reg == 91) reg_idx = 16; else if (idp_reg == 92 || idp_reg == 93) reg_idx = 17; else if (idp_reg == 94) reg_idx = 15; else { char buf[MAXSTR]; ::qsnprintf(buf, MAXSTR, "reg: %d\n", idp_reg); warning("SEND THIS MESSAGE TO you@mail.com:\n%s\n", buf); return 0; } return reg_idx; } #ifdef _DEBUG static const char* const optype_names[] = { "o_void", "o_reg", "o_mem", "o_phrase", "o_displ", "o_imm", "o_far", "o_near", "o_idpspec0", "o_idpspec1", "o_idpspec2", "o_idpspec3", "o_idpspec4", "o_idpspec5", }; static const char* const dtyp_names[] = { "dt_byte", "dt_word", "dt_dword", "dt_float", "dt_double", "dt_tbyte", "dt_packreal", "dt_qword", "dt_byte16", "dt_code", "dt_void", "dt_fword", "dt_bitfild", "dt_string", "dt_unicode", "dt_3byte", "dt_ldbl", "dt_byte32", "dt_byte64", }; static void print_insn(insn_t *insn) { if (my_dbg) { msg("cs=%x, ", insn->cs); msg("ip=%x, ", insn->ip); msg("ea=%x, ", insn->ea); msg("itype=%x, ", insn->itype); msg("size=%x, ", insn->size); msg("auxpref=%x, ", insn->auxpref); msg("segpref=%x, ", insn->segpref); msg("insnpref=%x, ", insn->insnpref); msg("insnpref=%x, ", insn->insnpref); msg("flags["); if (insn->flags & INSN_MACRO) msg("INSN_MACRO|"); if (insn->flags & INSN_MODMAC) msg("OF_OUTER_DISP"); msg("]\n"); } } static void print_op(ea_t ea, op_t *op) { if (my_dbg) { msg("type[%s], ", optype_names[op->type]); msg("flags["); if (op->flags & OF_NO_BASE_DISP) msg("OF_NO_BASE_DISP|"); if (op->flags & OF_OUTER_DISP) msg("OF_OUTER_DISP|"); if (op->flags & PACK_FORM_DEF) msg("PACK_FORM_DEF|"); if (op->flags & OF_NUMBER) msg("OF_NUMBER|"); if (op->flags & OF_SHOW) msg("OF_SHOW"); msg("], "); msg("dtyp[%s], ", dtyp_names[op->dtype]); if (op->type == o_reg) msg("reg=%x, ", op->reg); else if (op->type == o_displ || op->type == o_phrase) msg("phrase=%x, ", op->phrase); else msg("reg_phrase=%x, ", op->phrase); msg("addr=%x, ", op->addr); msg("value=%x, ", op->value); msg("specval=%x, ", op->specval); msg("specflag1=%x, ", op->specflag1); msg("specflag2=%x, ", op->specflag2); msg("specflag3=%x, ", op->specflag3); msg("specflag4=%x, ", op->specflag4); msg("refinfo["); opinfo_t buf; if (get_opinfo(&buf, ea, op->n, op->flags)) { msg("target=%x, ", buf.ri.target); msg("base=%x, ", buf.ri.base); msg("tdelta=%x, ", buf.ri.tdelta); msg("flags["); if (buf.ri.flags & REFINFO_TYPE) msg("REFINFO_TYPE|"); if (buf.ri.flags & REFINFO_RVAOFF) msg("REFINFO_RVAOFF|"); if (buf.ri.flags & REFINFO_PASTEND) msg("REFINFO_PASTEND|"); if (buf.ri.flags & REFINFO_CUSTOM) msg("REFINFO_CUSTOM|"); if (buf.ri.flags & REFINFO_NOBASE) msg("REFINFO_NOBASE|"); if (buf.ri.flags & REFINFO_SUBTRACT) msg("REFINFO_SUBTRACT|"); if (buf.ri.flags & REFINFO_SIGNEDOP) msg("REFINFO_SIGNEDOP"); msg("]"); } msg("]\n"); } } #endif typedef const regval_t &(idaapi *getreg_func_t)(const char *name, const regval_t *regvalues); static ssize_t idaapi hook_idp(void *user_data, int notification_code, va_list va) { switch (notification_code) { case processor_t::ev_get_idd_opinfo: { idd_opinfo_t * opinf = va_arg(va, idd_opinfo_t *); ea_t ea = va_arg(va, ea_t); int n = va_arg(va, int); int thread_id = va_arg(va, int); getreg_func_t getreg = va_arg(va, getreg_func_t); const regval_t *regvalues = va_arg(va, const regval_t *); opinf->ea = BADADDR; opinf->debregidx = 0; opinf->modified = false; opinf->value.ival = 0; opinf->value_size = 4; insn_t out; if (decode_insn(&out, ea)) { op_t op = out.ops[n]; #ifdef _DEBUG print_insn(&out); #endif int size = 0; switch (op.dtype) { case dt_byte: size = 1; break; case dt_word: size = 2; break; default: size = 4; break; } opinf->value_size = size; switch (op.type) { case o_mem: case o_near: case o_imm: { flags_t flags; switch (n) { case 0: flags = get_optype_flags0(get_flags(ea)); break; case 1: flags = get_optype_flags1(get_flags(ea)); break; default: flags = 0; break; } switch (op.type) { case o_mem: case o_near: opinf->ea = op.addr; break; case o_imm: opinf->ea = op.value; break; } opinfo_t info; if (get_opinfo(&info, ea, n, flags) != NULL) { opinf->ea += info.ri.base; } } break; case o_phrase: case o_reg: { int reg_idx = idp_to_dbg_reg(op.reg); regval_t reg = getreg(dbg->registers(reg_idx).name, regvalues); if (op.phrase >= 0x10 && op.phrase <= 0x1F || // (A0)..(A7), (A0)+..(A7)+ op.phrase >= 0x20 && op.phrase <= 0x27) // -(A0)..-(A7) { if (op.phrase >= 0x20 && op.phrase <= 0x27) reg.ival -= size; opinf->ea = (ea_t)reg.ival; switch (size) { case 1: { uint8_t b = 0; dbg->read_memory((ea_t)reg.ival, &b, 1); opinf->value.ival = b; } break; case 2: { uint16_t w = 0; dbg->read_memory((ea_t)reg.ival, &w, 2); w = swap16(w); opinf->value.ival = w; } break; default: { uint32_t l = 0; dbg->read_memory((ea_t)reg.ival, &l, 4); l = swap32(l); opinf->value.ival = l; } break; } } else opinf->value = reg; opinf->debregidx = reg_idx; } break; case o_displ: { regval_t main_reg, add_reg; int main_reg_idx = idp_to_dbg_reg(op.reg); int add_reg_idx = idp_to_dbg_reg(op.specflag1 & 0xF); main_reg.ival = 0; add_reg.ival = 0; if (op.specflag2 & 0x10) { add_reg = getreg(dbg->registers(add_reg_idx).name, regvalues); if (op.specflag1 & 0x10) { add_reg.ival &= 0xFFFF; add_reg.ival = (uint64)((int16_t)add_reg.ival); } } if (main_reg_idx != 16) main_reg = getreg(dbg->registers(main_reg_idx).name, regvalues); ea_t addr = (ea_t)main_reg.ival + op.addr + (ea_t)add_reg.ival; opinf->ea = addr; switch (size) { case 1: { uint8_t b = 0; dbg->read_memory(addr, &b, 1); opinf->value.ival = b; } break; case 2: { uint16_t w = 0; dbg->read_memory(addr, &w, 2); w = swap16(w); opinf->value.ival = w; } break; default: { uint32_t l = 0; dbg->read_memory(addr, &l, 4); l = swap32(l); opinf->value.ival = l; } break; } } break; } opinf->ea &= 0xFFFFFF; return 1; } } break; default: { #ifdef _DEBUG if (my_dbg) { msg("msg = %d\n", notification_code); } #endif } break; } return 0; } //-------------------------------------------------------------------------- static void print_version() { static const char format[] = NAME " debugger plugin v%s;\nAuthor: Dr. MefistO."; info(format, VERSION); msg(format, VERSION); } //-------------------------------------------------------------------------- // Initialize debugger plugin static int idaapi init(void) { if (ph.id == PLFM_68K) { dbg = &debugger; plugin_inited = true; my_dbg = false; hook_to_notification_point(HT_IDP, hook_idp, NULL); print_version(); return PLUGIN_KEEP; } return PLUGIN_SKIP; } //-------------------------------------------------------------------------- // Terminate debugger plugin static void idaapi term(void) { if (plugin_inited) { unhook_from_notification_point(HT_IDP, hook_idp); plugin_inited = false; } } //-------------------------------------------------------------------------- // The plugin method - usually is not used for debugger plugins static bool idaapi run(size_t arg) { return false; } //-------------------------------------------------------------------------- char comment[] = NAME " debugger plugin by Dr. MefistO."; char help[] = NAME " debugger plugin by Dr. MefistO.\n" "\n" "This module lets you debug Genesis roms in IDA.\n"; //-------------------------------------------------------------------------- // // PLUGIN DESCRIPTION BLOCK // //-------------------------------------------------------------------------- plugin_t PLUGIN = { IDP_INTERFACE_VERSION, PLUGIN_PROC | PLUGIN_DBG | PLUGIN_MOD, // plugin flags init, // initialize term, // terminate. this pointer may be NULL. run, // invoke plugin comment, // long comment about the plugin // it could appear in the status line // or as a hint help, // multiline help about the plugin NAME " debugger plugin", // the preferred short name of the plugin "" // the preferred hotkey to run the plugin };
Ahora comprendamos y, al mismo tiempo, escriba el código.
Implementación del depurador
La variable dbg_req
almacenará un puntero a la memoria compartida con el núcleo del depurador. Es allí donde enviaremos solicitudes y recibiremos respuestas.
Las funciones pause_execution()
, continue_execution()
y stop_debugging()
son necesarias para controlar el proceso de depuración.
eventlist_t g_events
es una lista de eventos del depurador que la IDA
esperará en respuesta a algunas de nuestras acciones (por ejemplo, iniciar / detener la emulación, interrupción activada).
Bueno, events_thread
repondrá esta lista, que supervisará la presencia de eventos del depurador en la memoria compartida y los convertirá en los eventos IDA
correspondientes.
Escribiremos la función finish_execution()
, que simplemente terminará el hilo de espera para los eventos de depuración:
static void finish_execution() { if (events_thread != NULL) { qthread_join(events_thread); qthread_free(events_thread); qthread_kill(events_thread); events_thread = NULL; } }
Entonces, lo descubrimos. Ahora vamos a registrar las descripciones.
La información sobre el registro es una estructura de la siguiente forma:
struct register_info_t { const char *name; uint32 flags; register_class_t register_class; op_dtype_t dtype; const char *const *bit_strings; uval_t default_bit_strings_mask; };
El campo de name
es el nombre de texto del registro. Además, en diferentes grupos de registros no puede ser el mismo nombre. Por ejemplo, si desea mostrar el registro de la PC
desde dos procesadores diferentes (y en la Sega Mega Drive
hay dos de ellos: Motorola 68000
y Z80
), entonces debe cambiarle el nombre.
El campo de flags
puede contener una o más de las siguientes banderas:
#define REGISTER_READONLY 0x0001
Está claro que no puede combinar REGISTER_IP
y REGISTER SP
, pero puede especificar que el campo contenga la dirección utilizando el indicador REGISTER_ADDRESS
.
register_class
es el número de máscara del grupo de registros que ha implementado. Por ejemplo, he agregado los siguientes tres:
#define RC_GENERAL (1 << 0) #define RC_VDP (1 << 1) #define RC_Z80 (1 << 2)
dtype
es una indicación del tamaño del registro. Las opciones son las siguientes:
#define dt_byte 0
En realidad, solo necesito dt_word
, dt_dword
.
El campo bit_strings
necesario si, por ejemplo, desea generar algún registro en forma de bits individuales. En particular, esto se puede usar para marcas de registro: Negative
, Overflow
, Zero
, Carry
, etc. Un ejemplo:
static const char *const SRReg[] = { "C", "V", "Z", "N", "X", NULL, NULL, NULL, "I", "I", "I", NULL, NULL, "S", NULL, "T" };
Los bits comienzan de arriba a abajo (de menor a mayor). Si no necesita mostrar el valor de bit, especifique NULL
lugar del nombre. Si en el registro varios bits pertenecen a una bandera, indicamos el mismo nombre la cantidad necesaria de veces.
Bueno, el último campo default_bit_strings_mask
es una máscara de bits que se aplicará antes de recibir los valores de bits de registro.
Aquí hay un ejemplo de mi implementación de la lista de registros para Sega Mega Drive
(activé los registros M68K, Z80 y VDP, así como algunos personalizados):
Descripción de registros para el depurador register_info_t registers[] = { { "D0", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D1", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D2", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D3", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D4", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D5", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D6", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "D7", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A0", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A1", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A2", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A3", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A4", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A5", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A6", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "A7", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "PC", REGISTER_ADDRESS | REGISTER_IP, RC_GENERAL, dt_dword, NULL, 0 }, { "SR", NULL, RC_GENERAL, dt_word, SRReg, 0xFFFF }, { "SP", REGISTER_ADDRESS | REGISTER_SP, RC_GENERAL, dt_dword, NULL, 0 }, { "USP", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "ISP", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 }, { "PPC", REGISTER_ADDRESS | REGISTER_READONLY, RC_GENERAL, dt_dword, NULL, 0 }, { "IR", NULL, RC_GENERAL, dt_dword, NULL, 0 },
Luego viene la lista register_classes[]
, en la que debemos especificar los nombres de texto de los grupos de registros. Se pueden abrir en ventanas separadas durante la depuración.

Aquí está mi implementación (el último elemento debe ser NULL
):
static const char *register_classes[] = { "General Registers", "VDP Registers", "Z80 Registers", NULL };
Callbacks requeridos por IDA
init_debugger ()
static bool idaapi init_debugger(const char *hostname, int portnum, const char *password) { set_processor_type(ph.psnames[0], SETPROC_LOADER);
Como la IDA
implementa varias versiones del procesador Motorola, lo reinicié por la fuerza a la primera de la lista.
term_debugger ()
static bool idaapi term_debugger(void) { dbg_req->is_ida = 0; close_shared_mem(&dbg_req); return true; }
Dato init_debugger()
: la función init_debugger()
se llama una vez al inicio de la emulación por sesión, y la función term_debugger()
se term_debugger()
cada vez que se completa el proceso de depuración. Por lo tanto, cierro la memoria compartida abierta aquí.
Ambas funciones deben devolver true
si tiene éxito.
process_get_info ()
static int idaapi process_get_info(procinfo_vec_t *procs) { return 0; }
Si durante la depuración está trabajando con varios procesos, debe implementar esta devolución de llamada, que informará la información IDA
para cada uno de ellos, es decir, el PID
y el nombre.
No necesito esta función, por lo que devuelvo 0
.
check_debugger_events (): no devolución de llamada, pero muy importante
En realidad, es un hilo que espera eventos de depuración. Aquí es necesario contar con más detalle.
Cuando comienza la depuración, el primer evento que espera recibir una IDA
debe ser PROCESS_START
. Si llega el primero, por ejemplo, un mensaje sobre la pausa de la emulación, la IDA
simplemente caerá.
Después de eso, ya puede recibir otros mensajes. Los principales utilizados son:
PROCESS_SUSPEND
— , , .BREAKPOINT
— IDA
, — , , . ? PROCESS_SUSPEND
, , IDA
STEP
— , IDA
, Step Into
Step Over
, , , PROCESS_SUSPEND
PROCESS_EXIT
— IDA
, . Stop
, IDA
, , , .
:
struct debug_event_t { event_id_t eid;
eid
— ,
pid
, tid
— , Process ID Thread ID ,
ea
— ,
handled
— , , IDA SDK
, , ( ?). true
, .
PROCESS_START
( ), ImageBase
, , , ImageBase
, , IDB
. , , BADADDR
:
case DBG_EVT_STARTED: ev.eid = PROCESS_START; ev.pid = 1; ev.tid = 1; ev.ea = BADADDR; ev.handled = true; ev.modinfo.name[0] = 'G'; ev.modinfo.name[1] = 'P'; ev.modinfo.name[2] = 'G'; ev.modinfo.name[3] = 'X'; ev.modinfo.name[4] = '\0'; ev.modinfo.base = 0; ev.modinfo.size = 0; ev.modinfo.rebase_to = BADADDR; g_events.enqueue(ev, IN_FRONT); break;
BREAKPOINT
bpt
hardware
— kernel
- . , IDA
, .
case DBG_EVT_BREAK: ev.pid = 1; ev.tid = 1; ev.ea = dbg_event->pc; ev.handled = true; ev.eid = BREAKPOINT; ev.bpt.hea = ev.bpt.kea = ev.ea; g_events.enqueue(ev, IN_BACK); break;
PROCESS_EXIT
exit_code
.
start_process()
, , , .
, ( ), , .
1
.
rebase_if_required_to()
, .. , , :
rebase_if_required_to static void idaapi rebase_if_required_to(ea_t new_base) { ea_t currentbase = new_base; ea_t imagebase = inf.startIP; if (imagebase != currentbase) { adiff_t delta = currentbase - imagebase; int code = rebase_program(delta, MSF_FIXONCE); if (code != MOVE_SEGM_OK) { msg("Failed to rebase program, error code %d\n", code); warning("IDA failed to rebase the program.\n" "Most likely it happened because of the debugger\n" "segments created to reflect the real memory state.\n\n" "Please stop the debugger and rebase the program manually.\n" "For that, please select the whole program and\n" "use Edit, Segments, Rebase program with delta 0x%08a", delta); } } }
prepare_to_pause_process()
Pause
IDA
, .
1
.
get_debug_event()
, IDA
, . (?) . , debug_event_t
*event
, :
GDE_ONE_EVENT
, ,GDE_MANY_EVENTS
, ,GDE_NO_EVENT
,
static gdecode_t idaapi get_debug_event(debug_event_t *event, int timeout_ms) { while (true) {
continue_after_event()
, - IDA
. .
, , , - . :
STEP
IDA
, ..- , ,
Step In
continue_after_event()
, . — STEP
IDA
STEP
, , , , !
, , 3
Step Into
, Step Over
, F9
, .
STEP
, BREAKPOINT
PROCESS_SUSPEND
, F9
, continue_execution()
. F9
Run to
:
dbg_notification_t req = get_running_notification(); if (req == dbg_null || req == dbg_run_to) continue_execution();
stopped_at_debug_event()
, . .
thread_suspend(), thread_continue()
, / , . , , 0
. , .
set_step_mode()
, Step Into
, Step Over
, Step Out
. ( debugger_t
, ).
1
.
static int idaapi set_step_mode(thid_t tid, resume_mode_t resmod) { switch (resmod) { case RESMOD_INTO: send_dbg_request(dbg_req, REQ_STEP_INTO); break; case RESMOD_OVER: send_dbg_request(dbg_req, REQ_STEP_OVER); break; } return 1; }
read_registers()
IDA
STEP
, BREAKPOINT
PROCESS_SUSPEND
(.. , ), , .
:
clsmask
— ? .values
— , . registers[]
.
:
read_registers() static int idaapi read_registers(thid_t tid, int clsmask, regval_t *values) { if (!dbg_req) return 0; if (clsmask & RC_GENERAL) { dbg_req->regs_data.type = REG_TYPE_M68K; send_dbg_request(dbg_req, REQ_GET_REGS); regs_68k_data_t *reg_vals = &dbg_req->regs_data.regs_68k; values[REG_68K_D0].ival = reg_vals->d0; values[REG_68K_D1].ival = reg_vals->d1; values[REG_68K_D2].ival = reg_vals->d2; values[REG_68K_D3].ival = reg_vals->d3; values[REG_68K_D4].ival = reg_vals->d4; values[REG_68K_D5].ival = reg_vals->d5; values[REG_68K_D6].ival = reg_vals->d6; values[REG_68K_D7].ival = reg_vals->d7; values[REG_68K_A0].ival = reg_vals->a0; values[REG_68K_A1].ival = reg_vals->a1; values[REG_68K_A2].ival = reg_vals->a2; values[REG_68K_A3].ival = reg_vals->a3; values[REG_68K_A4].ival = reg_vals->a4; values[REG_68K_A5].ival = reg_vals->a5; values[REG_68K_A6].ival = reg_vals->a6; values[REG_68K_A7].ival = reg_vals->a7; values[REG_68K_PC].ival = reg_vals->pc & 0xFFFFFF; values[REG_68K_SR].ival = reg_vals->sr; values[REG_68K_SP].ival = reg_vals->sp & 0xFFFFFF; values[REG_68K_PPC].ival = reg_vals->ppc & 0xFFFFFF; values[REG_68K_IR].ival = reg_vals->ir; } if (clsmask & RC_VDP) { dbg_req->regs_data.type = REG_TYPE_VDP; send_dbg_request(dbg_req, REQ_GET_REGS); vdp_regs_t *vdp_regs = &dbg_req->regs_data.vdp_regs; for (int i = 0; i < sizeof(vdp_regs->regs_vdp) / sizeof(vdp_regs->regs_vdp[0]); ++i) { values[REG_VDP_00 + i].ival = vdp_regs->regs_vdp[i]; } values[REG_VDP_DMA_LEN].ival = vdp_regs->dma_len; values[REG_VDP_DMA_SRC].ival = vdp_regs->dma_src; values[REG_VDP_DMA_DST].ival = vdp_regs->dma_dst; } if (clsmask & RC_Z80) { dbg_req->regs_data.type = REG_TYPE_Z80; send_dbg_request(dbg_req, REQ_GET_REGS); regs_z80_data_t *z80_regs = &dbg_req->regs_data.regs_z80; for (int i = 0; i < (REG_Z80_I - REG_Z80_PC + 1); ++i) { if (i >= 0 && i <= 12)
1
.
write_register()
— . .
:
write_register() static int idaapi write_register(thid_t tid, int regidx, const regval_t *value) { if (regidx >= REG_68K_D0 && regidx <= REG_68K_D7) { set_reg(REG_TYPE_M68K, regidx - REG_68K_D0, (uint32)value->ival); } else if (regidx >= REG_68K_A0 && regidx <= REG_68K_A7) { set_reg(REG_TYPE_M68K, regidx - REG_68K_A0, (uint32)value->ival); } else if (regidx == REG_68K_PC) { set_reg(REG_TYPE_M68K, REG_68K_PC, (uint32)value->ival & 0xFFFFFF); } else if (regidx == REG_68K_SR) { set_reg(REG_TYPE_M68K, REG_68K_SR, (uint16)value->ival); } else if (regidx == REG_68K_SP) { set_reg(REG_TYPE_M68K, REG_68K_SP, (uint32)value->ival & 0xFFFFFF); } else if (regidx == REG_68K_USP) { set_reg(REG_TYPE_M68K, REG_68K_USP, (uint32)value->ival & 0xFFFFFF); } else if (regidx == REG_68K_ISP) { set_reg(REG_TYPE_M68K, REG_68K_ISP, (uint32)value->ival & 0xFFFFFF); } else if (regidx >= REG_VDP_00 && regidx <= REG_VDP_1F) { set_reg(REG_TYPE_VDP, regidx - REG_VDP_00, value->ival & 0xFF); } else if (regidx >= REG_Z80_PC && regidx <= REG_Z80_I) { set_reg(REG_TYPE_Z80, regidx - REG_Z80_PC, value->ival); } return 1; }
1
.
get_memory_info()
, . : IDB
- , . .
- , , . :
info.name = "DBG_VDP_VRAM"; info.start_ea = 0xD00000; info.end_ea = info.start_ea + 0x10000; info.bitness = 1; areas.push_back(info); info.name = "DBG_VDP_CRAM"; info.start_ea = info.end_ea; info.end_ea = info.start_ea + 0x10000; info.bitness = 1; areas.push_back(info); info.name = "DBG_VDP_VSRAM"; info.start_ea = info.end_ea; info.end_ea = info.start_ea + 0x10000; info.bitness = 1; areas.push_back(info);
1
.
read_memory()
. , , XTRN
, IDA
.
ea
, , size
, , buffer
, .
read_memory() static ssize_t idaapi read_memory(ea_t ea, void *buffer, size_t size) { if ((ea >= 0xA00000 && ea < 0xA0FFFF)) { dbg_req->mem_data.address = ea; dbg_req->mem_data.size = size; send_dbg_request(dbg_req, REQ_READ_Z80); memcpy(buffer, &dbg_req->mem_data.z80_ram[ea & 0x1FFF], size);
write_memory()
, - : , RAM, , .
, 0
. 1
.
, , , buffer
— , .
is_ok_bpt()
len
type
ea
.
, BPT_OK
, — BPT_BAD_TYPE
.
update_bpts()
, IDA
. , , . F9
( Continue ).
:
bpts
, , : , , , nadd
ndel
, .- , ,
bpts[i].code
BPT_OK
.
+ . nadd + ndel
.
update_bpts() static int idaapi update_bpts(update_bpt_info_t *bpts, int nadd, int ndel) { for (int i = 0; i < nadd; ++i) { ea_t start = bpts[i].ea; ea_t end = bpts[i].ea + bpts[i].size - 1; bpt_data_t *bpt_data = &dbg_req->bpt_data; switch (bpts[i].type) { case BPT_EXEC: bpt_data->type = BPT_M68K_E; break; case BPT_READ: bpt_data->type = BPT_M68K_R; break; case BPT_WRITE: bpt_data->type = BPT_M68K_W; break; case BPT_RDWR: bpt_data->type = BPT_M68K_RW; break; } bpt_data->address = start; bpt_data->width = bpts[i].size; send_dbg_request(dbg_req, REQ_ADD_BREAK); bpts[i].code = BPT_OK; } for (int i = 0; i < ndel; ++i) { ea_t start = bpts[nadd + i].ea; ea_t end = bpts[nadd + i].ea + bpts[nadd + i].size - 1; bpt_data_t *bpt_data = &dbg_req->bpt_data; switch (bpts[nadd + i].type) { case BPT_EXEC: bpt_data->type = BPT_M68K_E; break; case BPT_READ: bpt_data->type = BPT_M68K_R; break; case BPT_WRITE: bpt_data->type = BPT_M68K_W; break; case BPT_RDWR: bpt_data->type = BPT_M68K_RW; break; } bpt_data->address = start; send_dbg_request(dbg_req, REQ_DEL_BREAK); bpts[nadd + i].code = BPT_OK; } return (ndel + nadd); }
debugger_t
, (, , IDA
, , , , ).
:
debugger_t struct debugger_t { int version;
, , , .
Si si. ida_plugin.cpp
. , .
idp_to_dbg_reg()
( ) .
static int idaapi idp_to_dbg_reg(int idp_reg) { int reg_idx = idp_reg; if (idp_reg >= 0 && idp_reg <= 7) reg_idx = 0 + idp_reg; else if (idp_reg >= 8 && idp_reg <= 39) reg_idx = 8 + (idp_reg % 8); else if (idp_reg == 91) reg_idx = 16; else if (idp_reg == 92 || idp_reg == 93) reg_idx = 17; else if (idp_reg == 94) reg_idx = 15; else { char buf[MAXSTR]; ::qsnprintf(buf, MAXSTR, "reg: %d\n", idp_reg); warning("SEND THIS MESSAGE TO you@mail.com:\n%s\n", buf); return 0; } return reg_idx; }
hook_idp()
hook_to_notification_point()
:
hook_to_notification_point(HT_IDP, hook_idp, NULL);
unhook_from_notification_point()
:
unhook_from_notification_point(HT_IDP, hook_idp);
: debugger hints . "":

, , - , . , , IDA
, . .
notification_code
ev_get_idd_opinfo
. , , , , , .
case processor_t::ev_get_idd_opinfo: { idd_opinfo_t * opinf = va_arg(va, idd_opinfo_t *); ea_t ea = va_arg(va, ea_t); int n = va_arg(va, int); int thread_id = va_arg(va, int); getreg_func_t getreg = va_arg(va, getreg_func_t); const regval_t *regvalues = va_arg(va, const regval_t *); opinf->ea = BADADDR; opinf->debregidx = 0; opinf->modified = false; opinf->value.ival = 0; opinf->value_size = 4; insn_t out; if (decode_insn(&out, ea)) { op_t op = out.ops[n]; #ifdef _DEBUG print_insn(&out); #endif int size = 0; switch (op.dtype) { case dt_byte: size = 1; break; case dt_word: size = 2; break; default: size = 4; break; } opinf->value_size = size; switch (op.type) { case o_mem: case o_near: case o_imm: { flags_t flags; switch (n) { case 0: flags = get_optype_flags0(get_flags(ea)); break; case 1: flags = get_optype_flags1(get_flags(ea)); break; default: flags = 0; break; } switch (op.type) { case o_mem: case o_near: opinf->ea = op.addr; break; case o_imm: opinf->ea = op.value; break; } opinfo_t info; if (get_opinfo(&info, ea, n, flags) != NULL) { opinf->ea += info.ri.base; } } break; case o_phrase: case o_reg: { int reg_idx = idp_to_dbg_reg(op.reg); regval_t reg = getreg(dbg->registers(reg_idx).name, regvalues); if (op.phrase >= 0x10 && op.phrase <= 0x1F || // (A0)..(A7), (A0)+..(A7)+ op.phrase >= 0x20 && op.phrase <= 0x27) // -(A0)..-(A7) { if (op.phrase >= 0x20 && op.phrase <= 0x27) reg.ival -= size; opinf->ea = (ea_t)reg.ival; switch (size) { case 1: { uint8_t b = 0; dbg->read_memory((ea_t)reg.ival, &b, 1); opinf->value.ival = b; } break; case 2: { uint16_t w = 0; dbg->read_memory((ea_t)reg.ival, &w, 2); w = swap16(w); opinf->value.ival = w; } break; default: { uint32_t l = 0; dbg->read_memory((ea_t)reg.ival, &l, 4); l = swap32(l); opinf->value.ival = l; } break; } } else opinf->value = reg; opinf->debregidx = reg_idx; } break; case o_displ: { regval_t main_reg, add_reg; int main_reg_idx = idp_to_dbg_reg(op.reg); int add_reg_idx = idp_to_dbg_reg(op.specflag1 & 0xF); main_reg.ival = 0; add_reg.ival = 0; if (op.specflag2 & 0x10) { add_reg = getreg(dbg->registers(add_reg_idx).name, regvalues); if (op.specflag1 & 0x10) { add_reg.ival &= 0xFFFF; add_reg.ival = (uint64)((int16_t)add_reg.ival); } } if (main_reg_idx != 16) main_reg = getreg(dbg->registers(main_reg_idx).name, regvalues); ea_t addr = (ea_t)main_reg.ival + op.addr + (ea_t)add_reg.ival; opinf->ea = addr; switch (size) { case 1: { uint8_t b = 0; dbg->read_memory(addr, &b, 1); opinf->value.ival = b; } break; case 2: { uint16_t w = 0; dbg->read_memory(addr, &w, 2); w = swap16(w); opinf->value.ival = w; } break; default: { uint32_t l = 0; dbg->read_memory(addr, &l, 4); l = swap32(l); opinf->value.ival = l; } break; } } break; } opinf->ea &= 0xFFFFFF; return 1; } } break;
, .
init()
, .
, id
ph
.
PLFM_68K
. dbg
debugger_t
.
HT_IDP
, PLUGIN_KEEP
. , , PLUGIN_SKIP
.
static int idaapi init(void) { if (ph.id == PLFM_68K) { dbg = &debugger; plugin_inited = true; my_dbg = false; hook_to_notification_point(HT_IDP, hook_idp, NULL); print_version(); return PLUGIN_KEEP; } return PLUGIN_SKIP; }
term()
IDB
-. , init()
.
static void idaapi term(void) { if (plugin_inited) { unhook_from_notification_point(HT_IDP, hook_idp); plugin_inited = false; } }
run()
- . false
.
plugin_t
: , , , , :
plugin_t class plugin_t { public: int version;
Eso es probablemente todo. , , . , IDA
, .
, :
GPGX Debugger
Smd IDA Tools