Привет всем,
В предыдущей статье мы успешно модифицировали ядро эмулятора игр на Sega Mega Drive
/ Genesis
, добавив в него возможность отладки. Теперь пришёл черёд написания собственно плагина-отладчика для IDA Pro
, версия 7.0
. Приступим.
Часть вторая: плагин-отладчик
Для начала создадим новый пустой DLL
-проект: File
->New
->Project
->Windows Desktop Wizard
->Dynamic link library (.dll)
, поставив также галку Empty Project
, и сняв все остальные:
Распакуем IDA SDK
, и пропишем его в макросах Visual Studio
(я буду использовать 2017 Community
), чтобы в будущем можно было легко ссылаться на него. Заодно мы добавим макрос для пути к IDA Pro
.
Заходим в View
->Other Windows
->Property Manager
:
Т.к. мы работаем с версией SDK 7.0
, компиляция будет происходить x64
-компилятором. Поэтому выбираем Debug | x64
->Microsoft.Cpp.x64.user
->Properties
:
Жмём кнопку Add Macro
в разделе User Macros
, и прописываем там макрос IDA_SDK
с указанием пути, по которому вы распаковали SDK
:
Так же поступаем с IDA_DIR
(путь к Вашей IDA Pro
):
Замечу, что IDA
ставится по умолчанию в %Program Files%
, что требует прав администратора.
Давайте также удалим Win32
конфигурацию (в данной статье я не буду затрагивать компиляцию по x86
системы), оставив только x64
-вариант.
Теперь возьмём шаблон класса очереди событий отладчика:
#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;
}
};
Теперь у студийного проекта появится возможность ставить дефайны для компилятора, поэтому добавляем следующие:
__NT__
__IDP__
__X64__
Добавляем новый пустой файл 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,
};
Далее создаём ещё один файл, называем его 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
};
Теперь давайте разбираться, а заодно и писать код.
Реализация отладчика
Переменная dbg_req
у нас будет хранить указатель на расшареную с ядром отладчика память. Именно в неё мы будем отправлять запросы, и принимать из неё ответы.
Функции pause_execution()
, continue_execution()
и stop_debugging()
нужны для управления процессом отладки.
eventlist_t g_events
представляет из себя список событий отладчика, которые будет ожидать IDA
в ответ на какие-то наши действия (например, старт/остановка эмуляции, сработавший бряк).
Ну а пополнять этот список будет events_thread
, который будет следить за наличием событий отладчика в расшареной памяти, и преобразовывать их в соответствующие события IDA
.
Напишем функцию finish_execution()
, которая будет просто завершать поток ожидания отладочных событий:
static void finish_execution()
{
if (events_thread != NULL)
{
qthread_join(events_thread);
qthread_free(events_thread);
qthread_kill(events_thread);
events_thread = NULL;
}
}
Так, с этим разобрались. Теперь займёмся описанием регистров.
Информация о регистре представляет из себя структуру следующего вида:
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;
};
Поле name
— это текстовое имя регистра. При том в разных группах регистров не может быть одинаковых имён. Например, если требуется отобразить регистр PC
от двух разных процессоров (а в приставке Sega Mega Drive
их два: Motorola 68000
и Z80
), то придётся переименовывать.
Поле flags
может содержать один или несколько следующих флагов:
#define REGISTER_READONLY 0x0001 ///< the user can't modify the current value of this register
#define REGISTER_IP 0x0002 ///< instruction pointer
#define REGISTER_SP 0x0004 ///< stack pointer
#define REGISTER_FP 0x0008 ///< frame pointer
#define REGISTER_ADDRESS 0x0010 ///< may contain an address
#define REGISTER_CS 0x0020 ///< code segment
#define REGISTER_SS 0x0040 ///< stack segment
#define REGISTER_NOLF 0x0080 ///< displays this register without returning to the next line
///< allowing the next register to be displayed to its right (on the same line)
#define REGISTER_CUSTFMT 0x0100 ///< register should be displayed using a custom data format.
///< the format name is in bit_strings[0]
///< the corresponding ::regval_t will use ::bytevec_t
Понятно, что объединять REGISTER_IP
и REGISTER SP
нельзя, но можно указать, что поле содержит адрес с помощью флага REGISTER_ADDRESS
.
register_class
— это число-маска группы реализованных у вас регистров. Например, у меня были добавлены следующие три:
#define RC_GENERAL (1 << 0)
#define RC_VDP (1 << 1)
#define RC_Z80 (1 << 2)
dtype
представляет из себя указание на размер регистра. Варианты следующие:
#define dt_byte 0 ///< 8 bit
#define dt_word 1 ///< 16 bit
#define dt_dword 2 ///< 32 bit
#define dt_float 3 ///< 4 byte
#define dt_double 4 ///< 8 byte
#define dt_tbyte 5 ///< variable size (\ph{tbyte_size})
#define dt_packreal 6 ///< packed real format for mc68040
#define dt_qword 7 ///< 64 bit
#define dt_byte16 8 ///< 128 bit
#define dt_code 9 ///< ptr to code (not used?)
#define dt_void 10 ///< none
#define dt_fword 11 ///< 48 bit
#define dt_bitfild 12 ///< bit field (mc680x0)
#define dt_string 13 ///< pointer to asciiz string
#define dt_unicode 14 ///< pointer to unicode string
#define dt_ldbl 15 ///< long double (which may be different from tbyte)
#define dt_byte32 16 ///< 256 bit
#define dt_byte64 17 ///< 512 bit
Собственно, мне понадобятся только dt_word
, dt_dword
.
Поле bit_strings
нужно, если, например, вы хотите вывести какой-то регистр в виде его отдельных битов. В частности, это может быть использовано для регистра флагов: Negative
, Overflow
, Zero
, Carry
и т.д. Пример:
static const char *const SRReg[] =
{
"C",
"V",
"Z",
"N",
"X",
NULL,
NULL,
NULL,
"I",
"I",
"I",
NULL,
NULL,
"S",
NULL,
"T"
};
Биты начинаются сверху вниз (от младшего к старшему). Если значение бита выводить не нужно, вместо имени указываем NULL
. Если в регистре несколько битов принадлежат одному флагу, указываем одно и то же имя нужное количество раз.
Ну и последнее поле default_bit_strings_mask
— битовая маска, которая будет применяться перед получением значений битов регистра.
Вот пример моей реализации списка регистров для Sega Mega Drive
(я включил регистры M68K, Z80 и VDP, а также парочку кастомных):
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 },
// VDP Registers
{ "v00", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v01", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v02", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v03", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v04", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v05", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v06", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v07", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v08", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v09", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v0A", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v0B", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v0C", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v0D", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v0E", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v0F", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v10", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v11", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v12", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v13", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v14", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v15", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v16", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v17", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v18", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v19", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v1A", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v1B", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v1C", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v1D", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v1E", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "v1F", NULL, RC_VDP, dt_byte, NULL, 0 },
{ "DMA_LEN", REGISTER_READONLY, RC_VDP, dt_word, NULL, 0 },
{ "DMA_SRC", REGISTER_ADDRESS | REGISTER_READONLY, RC_VDP, dt_dword, NULL, 0 },
{ "VDP_DST", REGISTER_ADDRESS | REGISTER_READONLY, RC_VDP, dt_dword, NULL, 0 },
// Z80 regs
{ "zPC", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zSP", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zAF", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zBC", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zDE", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zHL", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zIX", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zIY", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zWZ", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zAF2", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zBC2", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zDE2", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zHL2", NULL, RC_Z80, dt_dword, NULL, 0 },
{ "zR", NULL, RC_Z80, dt_byte, NULL, 0 },
{ "zR2", NULL, RC_Z80, dt_byte, NULL, 0 },
{ "zIFFI1", NULL, RC_Z80, dt_byte, NULL, 0 },
{ "zIFFI2", NULL, RC_Z80, dt_byte, NULL, 0 },
{ "zHALT", NULL, RC_Z80, dt_byte, NULL, 0 },
{ "zIM", NULL, RC_Z80, dt_byte, NULL, 0 },
{ "zI", NULL, RC_Z80, dt_byte, NULL, 0 },
};
Далее идёт список register_classes[]
, в котором мы должны указать текстовые имена групп регистров. Их можно будет открыть в отдельных окнах при отладке.
Вот моя реализация (последним элементом должен быть NULL
):
static const char *register_classes[] =
{
"General Registers",
"VDP Registers",
"Z80 Registers",
NULL
};
Колбэки, необходимые IDA
init_debugger()
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;
}
Так как в IDA
реализовано несколько версий мотороловского процессора, я принудительно сбрасываю на самый первый в списке.
term_debugger()
static bool idaapi term_debugger(void)
{
dbg_req->is_ida = 0;
close_shared_mem(&dbg_req);
return true;
}
Забавный факт: функция init_debugger()
вызывается один раз при первом старте эмуляции за сессию, а функция term_debugger()
— каждый раз при завершении процесса отладки. Поэтому открытую расшареную память я закрываю именно здесь.
Обе функции должны возвращать true
в случае успеха.
process_get_info()
static int idaapi process_get_info(procinfo_vec_t *procs)
{
return 0;
}
Если во время отладки у вас происходит работа с несколькими процессами, необходимо реализовать данный колбэк, который будет сообщать IDA
информацию по каждому из них, а именно PID
и имя.
Мне данная функция не нужна, поэтому я возвращаю 0
.
check_debugger_events() — не колбэк, но очень важен
Собственно, представляет из себя поток, ожидающий отладочных событий. Здесь необходимо рассказать более детально.
При запуске отладки, первым событием, которое ожидает получить IDA
, должно быть PROCESS_START
. Если первым придёт, например, сообщение о паузе эмуляции, IDA
просто упадёт.
После этого уже можно принимать другие сообщения. Основные используемые это:
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; ///< Event code (used to decipher 'info' union)
pid_t pid; ///< Process where the event occurred
thid_t tid; ///< Thread where the event occurred
ea_t ea; ///< Address where the event occurred
bool handled; ///< Is event handled by the debugger?.
///< (from the system's point of view)
///< Meaningful for ::EXCEPTION events
union
{
module_info_t modinfo; ///< ::PROCESS_START, ::PROCESS_ATTACH, ::LIBRARY_LOAD
int exit_code; ///< ::PROCESS_EXIT, ::THREAD_EXIT
char info[MAXSTR]; ///< ::LIBRARY_UNLOAD (unloaded library name)
///< ::INFORMATION (will be displayed in the
///< messages window if not empty)
e_breakpoint_t bpt; ///< ::BREAKPOINT
e_exception_t exc; ///< ::EXCEPTION
};
};
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()
У меня не реализовано, т.к. база у рома всегда одна, но, типичная реализация выглядит вот так:
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)
{
// 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;
}
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[]
.
Пример заполнения массива полученными значениями регистров:
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) // PC <-> HL2
{
values[REG_Z80_PC + i].ival = ((unsigned int *)&z80_regs->pc)[i];
}
else if (i >= 13 && i <= 19) // R <-> I
{
values[REG_Z80_PC + i].ival = ((unsigned char *)&z80_regs->r)[i - 13];
}
}
}
return 1;
}
Возвращаем 1
в случае успеха.
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
, в которую нужно будет записать содержимое памяти по запрашиваемому адресу.
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);
// Z80
}
else if (ea < MAXROMSIZE)
{
dbg_req->mem_data.address = ea;
dbg_req->mem_data.size = size;
send_dbg_request(dbg_req, REQ_READ_68K_ROM);
memcpy(buffer, &dbg_req->mem_data.m68k_rom[ea], size);
}
else if ((ea >= 0xFF0000 && ea < 0x1000000))
{
dbg_req->mem_data.address = ea;
dbg_req->mem_data.size = size;
send_dbg_request(dbg_req, REQ_READ_68K_RAM);
memcpy(buffer, &dbg_req->mem_data.m68k_ram[ea & 0xFFFF], size);
// RAM
}
return 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
.
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
просто упадёт без объяснений, а о некоторых, если их нет, скажет, что требуется реализация).
Структура выглядит следующим образом:
struct debugger_t
{
int version; ///< Expected kernel version,
///< should be #IDD_INTERFACE_VERSION
const char *name; ///< Short debugger name like win32 or linux
int id; ///< one of \ref DEBUGGER_ID_
/// \defgroup DEBUGGER_ID_ Debugger API module id
/// Used by debugger_t::id
//@{
#define DEBUGGER_ID_X86_IA32_WIN32_USER 0 ///< Userland win32 processes (win32 debugging APIs)
#define DEBUGGER_ID_X86_IA32_LINUX_USER 1 ///< Userland linux processes (ptrace())
#define DEBUGGER_ID_ARM_WINCE_ASYNC 2 ///< Windows CE ARM (ActiveSync transport)
#define DEBUGGER_ID_X86_IA32_MACOSX_USER 3 ///< Userland MAC OS X processes
#define DEBUGGER_ID_ARM_EPOC_USER 4 ///< Symbian OS
#define DEBUGGER_ID_ARM_IPHONE_USER 5 ///< iPhone 1.x
#define DEBUGGER_ID_X86_IA32_BOCHS 6 ///< BochsDbg.exe 32
#define DEBUGGER_ID_6811_EMULATOR 7 ///< MC6812 emulator (beta)
#define DEBUGGER_ID_GDB_USER 8 ///< GDB remote
#define DEBUGGER_ID_WINDBG 9 ///< WinDBG using Microsoft Debug engine
#define DEBUGGER_ID_X86_DOSBOX_EMULATOR 10 ///< Dosbox MS-DOS emulator
#define DEBUGGER_ID_ARM_LINUX_USER 11 ///< Userland arm linux
#define DEBUGGER_ID_TRACE_REPLAYER 12 ///< Fake debugger to replay recorded traces
#define DEBUGGER_ID_ARM_WINCE_TCPIP 13 ///< Windows CE ARM (TPC/IP transport)
#define DEBUGGER_ID_X86_PIN_TRACER 14 ///< PIN Tracer module
#define DEBUGGER_ID_DALVIK_USER 15 ///< Dalvik
//@}
const char *processor; ///< Required processor name.
///< Used for instant debugging to load the correct
///< processor module
uint32 flags; ///< \ref DBG_FLAG_
/// \defgroup DBG_FLAG_ Debugger module features
/// Used by debugger_t::flags
//@{
#define DBG_FLAG_REMOTE 0x00000001 ///< Remote debugger (requires remote host name unless #DBG_FLAG_NOHOST)
#define DBG_FLAG_NOHOST 0x00000002 ///< Remote debugger with does not require network params (host/port/pass).
///< (a unique device connected to the machine)
#define DBG_FLAG_FAKE_ATTACH 0x00000004 ///< ::PROCESS_ATTACH is a fake event
///< and does not suspend the execution
#define DBG_FLAG_HWDATBPT_ONE 0x00000008 ///< Hardware data breakpoints are
///< one byte size by default
#define DBG_FLAG_CAN_CONT_BPT 0x00000010 ///< Debugger knows to continue from a bpt.
///< This flag also means that the debugger module
///< hides breakpoints from ida upon read_memory
#define DBG_FLAG_NEEDPORT 0x00000020 ///< Remote debugger requires port number (to be used with DBG_FLAG_NOHOST)
#define DBG_FLAG_DONT_DISTURB 0x00000040 ///< Debugger can handle only
///< get_debug_event(),
///< prepare_to_pause_process(),
///< exit_process().
///< when the debugged process is running.
///< The kernel may also call service functions
///< (file I/O, map_address, etc)
#define DBG_FLAG_SAFE 0x00000080 ///< The debugger is safe (probably because it just emulates the application
///< without really running it)
#define DBG_FLAG_CLEAN_EXIT 0x00000100 ///< IDA must suspend the application and remove
///< all breakpoints before terminating the application.
///< Usually this is not required because the application memory
///< disappears upon termination.
#define DBG_FLAG_USE_SREGS 0x00000200 ///< Take segment register values into account (non flat memory)
#define DBG_FLAG_NOSTARTDIR 0x00000400 ///< Debugger module doesn't use startup directory
#define DBG_FLAG_NOPARAMETERS 0x00000800 ///< Debugger module doesn't use commandline parameters
#define DBG_FLAG_NOPASSWORD 0x00001000 ///< Remote debugger doesn't use password
#define DBG_FLAG_CONNSTRING 0x00002000 ///< Display "Connection string" instead of "Hostname" and hide the "Port" field
#define DBG_FLAG_SMALLBLKS 0x00004000 ///< If set, IDA uses 256-byte blocks for caching memory contents.
///< Otherwise, 1024-byte blocks are used
#define DBG_FLAG_MANMEMINFO 0x00008000 ///< If set, manual memory region manipulation commands
///< will be available. Use this bit for debugger modules
///< that can not return memory layout information
#define DBG_FLAG_EXITSHOTOK 0x00010000 ///< IDA may take a memory snapshot at ::PROCESS_EXIT event
#define DBG_FLAG_VIRTHREADS 0x00020000 ///< Thread IDs may be shuffled after each debug event.
///< (to be used for virtual threads that represent cpus for windbg kmode)
#define DBG_FLAG_LOWCNDS 0x00040000 ///< Low level breakpoint conditions are supported.
#define DBG_FLAG_DEBTHREAD 0x00080000 ///< Supports creation of a separate thread in ida
///< for the debugger (the debthread).
///< Most debugger functions will be called from debthread (exceptions are marked below)
///< The debugger module may directly call only #THREAD_SAFE functions.
///< To call other functions please use execute_sync().
///< The debthread significantly increases debugging
///< speed, especially if debug events occur frequently (to be tested)
#define DBG_FLAG_DEBUG_DLL 0x00100000 ///< Can debug standalone DLLs.
///< For example, Bochs debugger can debug any snippet of code
#define DBG_FLAG_FAKE_MEMORY 0x00200000 ///< get_memory_info()/read_memory()/write_memory() work with the idb.
///< (there is no real process to read from, as for the replayer module)
///< the kernel will not call these functions if this flag is set.
///< however, third party plugins may call them, they must be implemented.
#define DBG_FLAG_ANYSIZE_HWBPT 0x00400000 ///< The debugger supports arbitrary size hardware breakpoints.
#define DBG_FLAG_TRACER_MODULE 0x00800000 ///< The module is a tracer, not a full featured debugger module
#define DBG_FLAG_PREFER_SWBPTS 0x01000000 ///< Prefer to use software breakpoints
//@}
const char **register_classes; ///< Array of register class names
int register_classes_default; ///< Mask of default printed register classes
register_info_t *_registers; ///< Array of registers. Use registers() to access it
int registers_size; ///< Number of registers
int memory_page_size; ///< Size of a memory page
const uchar *bpt_bytes; ///< Array of bytes for a breakpoint instruction
uchar bpt_size; ///< Size of this array
uchar filetype; ///< for miniidbs: use this value
///< for the file type after attaching
///< to a new process
ushort resume_modes; ///< \ref DBG_RESMOD_
/// \defgroup DBG_RESMOD_ Resume modes
/// Used by debugger_t::resume_modes
//@{
#define DBG_RESMOD_STEP_INTO 0x0001 ///< ::RESMOD_INTO is available
#define DBG_RESMOD_STEP_OVER 0x0002 ///< ::RESMOD_OVER is available
#define DBG_RESMOD_STEP_OUT 0x0004 ///< ::RESMOD_OUT is available
#define DBG_RESMOD_STEP_SRCINTO 0x0008 ///< ::RESMOD_SRCINTO is available
#define DBG_RESMOD_STEP_SRCOVER 0x0010 ///< ::RESMOD_SRCOVER is available
#define DBG_RESMOD_STEP_SRCOUT 0x0020 ///< ::RESMOD_SRCOUT is available
#define DBG_RESMOD_STEP_USER 0x0040 ///< ::RESMOD_USER is available
#define DBG_RESMOD_STEP_HANDLE 0x0080 ///< ::RESMOD_HANDLE is available
//@}
#if !defined(_MSC_VER) // this compiler complains :(
static const int default_port_number = 23946;
#define DEBUGGER_PORT_NUMBER debugger_t::default_port_number
#else
#define DEBUGGER_PORT_NUMBER 23946
#endif
/// Initialize debugger.
/// This function is called from the main thread.
/// \return success
bool (idaapi *init_debugger)(const char *hostname, int portnum, const char *password);
/// Terminate debugger.
/// This function is called from the main thread.
/// \return success
bool (idaapi *term_debugger)(void);
/// Return information about the running processes.
/// This function is called from the main thread.
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *get_processes)(procinfo_vec_t *procs);
/// Start an executable to debug.
/// This function is called from debthread.
/// \param path path to executable
/// \param args arguments to pass to executable
/// \param startdir current directory of new process
/// \param dbg_proc_flags \ref DBG_PROC_
/// \param input_path path to database input file.
/// (not always the same as 'path' - e.g. if you're analyzing
/// a dll and want to launch an executable that loads it)
/// \param input_file_crc32 CRC value for 'input_path'
/// \retval 1 ok
/// \retval 0 failed
/// \retval -2 file not found (ask for process options)
/// \retval 1 | #CRC32_MISMATCH ok, but the input file crc does not match
/// \retval -1 network error
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);
/// \defgroup DBG_PROC_ Debug process flags
/// Passed as 'dbg_proc_flags' parameter to debugger_t::start_process
//@{
#define DBG_PROC_IS_DLL 0x01 ///< database contains a dll (not exe)
#define DBG_PROC_IS_GUI 0x02 ///< using gui version of ida
#define DBG_PROC_32BIT 0x04 ///< application is 32-bit
#define DBG_PROC_64BIT 0x08 ///< application is 64-bit
#define DBG_NO_TRACE 0x10 ///< do not trace the application (mac/linux)
#define DBG_HIDE_WINDOW 0x20 ///< application should be hidden on startup (windows)
//@}
#define CRC32_MISMATCH 0x40000000 ///< crc32 mismatch bit (see return values for debugger_t::start_process)
/// Attach to an existing running process.
/// event_id should be equal to -1 if not attaching to a crashed process.
/// This function is called from debthread.
/// \param pid process id to attach
/// \param event_id event to trigger upon attaching
/// \param dbg_proc_flags \ref DBG_PROC_
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *attach_process)(pid_t pid, int event_id, int dbg_proc_flags);
/// Detach from the debugged process.
/// May be called while the process is running or suspended.
/// Must detach from the process in any case.
/// The kernel will repeatedly call get_debug_event() and until ::PROCESS_DETACH.
/// In this mode, all other events will be automatically handled and process will be resumed.
/// This function is called from debthread.
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *detach_process)(void);
/// Rebase database if the debugged program has been rebased by the system.
/// This function is called from the main thread.
void (idaapi *rebase_if_required_to)(ea_t new_base);
/// Prepare to pause the process.
/// Normally the next get_debug_event() will pause the process
/// If the process is sleeping then the pause will not occur
/// until the process wakes up. The interface should take care of
/// this situation.
/// If this function is absent, then it won't be possible to pause the program.
/// This function is called from debthread.
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *prepare_to_pause_process)(void);
/// Stop the process.
/// May be called while the process is running or suspended.
/// Must terminate the process in any case.
/// The kernel will repeatedly call get_debug_event() and until ::PROCESS_EXIT.
/// In this mode, all other events will be automatically handled and process will be resumed.
/// This function is called from debthread.
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *exit_process)(void);
/// Get a pending debug event and suspend the process.
/// This function will be called regularly by IDA.
/// This function is called from debthread.
/// IMPORTANT: commdbg does not expect immediately after a BPT-related event
/// any other event with the same thread/IP - this can cause erroneous
/// restoring of a breakpoint before resume
/// (the bug was encountered 24.02.2015 in pc_linux_upx.elf)
gdecode_t (idaapi *get_debug_event)(debug_event_t *event, int timeout_ms);
/// Continue after handling the event.
/// This function is called from debthread.
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *continue_after_event)(const debug_event_t *event);
/// Set exception handling.
/// This function is called from debthread or the main thread.
void (idaapi *set_exception_info)(const exception_info_t *info, int qty);
/// This function will be called by the kernel each time
/// it has stopped the debugger process and refreshed the database.
/// The debugger module may add information to the database if it wants.
///
/// The reason for introducing this function is that when an event line
/// LOAD_DLL happens, the database does not reflect the memory state yet
/// and therefore we can't add information about the dll into the database
/// in the get_debug_event() function.
/// Only when the kernel has adjusted the database we can do it.
/// Example: for imported PE DLLs we will add the exported function
/// names to the database.
///
/// This function pointer may be absent, i.e. NULL.
/// This function is called from the main thread.
void (idaapi *stopped_at_debug_event)(bool dlls_added);
/// \name Threads
/// The following functions manipulate threads.
/// These functions are called from debthread.
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
//@{
int (idaapi *thread_suspend) (thid_t tid); ///< Suspend a running thread
int (idaapi *thread_continue)(thid_t tid); ///< Resume a suspended thread
int (idaapi *set_resume_mode)(thid_t tid, resume_mode_t resmod); ///< Specify resume action
//@}
/// Read thread registers.
/// This function is called from debthread.
/// \param tid thread id
/// \param clsmask bitmask of register classes to read
/// \param values pointer to vector of regvals for all registers.
/// regval is assumed to have debugger_t::registers_size elements
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *read_registers)(thid_t tid, int clsmask, regval_t *values);
/// Write one thread register.
/// This function is called from debthread.
/// \param tid thread id
/// \param regidx register index
/// \param value new value of the register
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *write_register)(thid_t tid, int regidx, const regval_t *value);
/// Get information about the base of a segment register.
/// Currently used by the IBM PC module to resolve references like fs:0.
/// This function is called from debthread.
/// \param answer pointer to the answer. can't be NULL.
/// \param tid thread id
/// \param sreg_value value of the segment register (returned by get_reg_val())
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *thread_get_sreg_base)(ea_t *answer, thid_t tid, int sreg_value);
/// \name Memory manipulation
/// The following functions manipulate bytes in the memory.
//@{
/// Get information on the memory ranges.
/// The debugger module fills 'ranges'. The returned vector MUST be sorted.
/// This function is called from debthread.
/// \retval -3 use idb segmentation
/// \retval -2 no changes
/// \retval -1 the process does not exist anymore
/// \retval 0 failed
/// \retval 1 new memory layout is returned
int (idaapi *get_memory_info)(meminfo_vec_t &ranges);
/// Read process memory.
/// Returns number of read bytes.
/// This function is called from debthread.
/// \retval 0 read error
/// \retval -1 process does not exist anymore
ssize_t (idaapi *read_memory)(ea_t ea, void *buffer, size_t size);
/// Write process memory.
/// This function is called from debthread.
/// \return number of written bytes, -1 if fatal error
ssize_t (idaapi *write_memory)(ea_t ea, const void *buffer, size_t size);
//@}
/// Is it possible to set breakpoint?.
/// This function is called from debthread or from the main thread if debthread
/// is not running yet.
/// It is called to verify hardware breakpoints.
/// \return ref BPT_
int (idaapi *is_ok_bpt)(bpttype_t type, ea_t ea, int len);
/// \defgroup BPT_ Breakpoint verification codes
/// Return values for debugger_t::is_ok_bpt
//@{
#define BPT_OK 0 ///< breakpoint can be set
#define BPT_INTERNAL_ERR 1 ///< interr occurred when verifying breakpoint
#define BPT_BAD_TYPE 2 ///< bpt type is not supported
#define BPT_BAD_ALIGN 3 ///< alignment is invalid
#define BPT_BAD_ADDR 4 ///< ea is invalid
#define BPT_BAD_LEN 5 ///< bpt len is invalid
#define BPT_TOO_MANY 6 ///< reached max number of supported breakpoints
#define BPT_READ_ERROR 7 ///< failed to read memory at bpt ea
#define BPT_WRITE_ERROR 8 ///< failed to write memory at bpt ea
#define BPT_SKIP 9 ///< update_bpts(): do not process bpt
#define BPT_PAGE_OK 10 ///< update_bpts(): ok, added a page bpt
//@}
/// Add/del breakpoints.
/// bpts array contains nadd bpts to add, followed by ndel bpts to del.
/// This function is called from debthread.
/// \return number of successfully modified bpts, -1 if network error
int (idaapi *update_bpts)(update_bpt_info_t *bpts, int nadd, int ndel);
/// Update low-level (server side) breakpoint conditions.
/// This function is called from debthread.
/// \return nlowcnds. -1-network error
int (idaapi *update_lowcnds)(const lowcnd_t *lowcnds, int nlowcnds);
/// \name Remote file
/// Open/close/read a remote file.
/// These functions are called from the main thread
//@{
int (idaapi *open_file)(const char *file, uint64 *fsize, bool readonly); // -1-error
void (idaapi *close_file)(int fn);
ssize_t (idaapi *read_file)(int fn, qoff64_t off, void *buf, size_t size);
//@}
/// Map process address.
/// This function may be absent.
/// This function is called from debthread.
/// \param off offset to map
/// \param regs current register values. if regs == NULL, then perform
/// global mapping, which is independent on used registers
/// usually such a mapping is a trivial identity mapping
/// \param regnum required mapping. maybe specified as a segment register number
/// or a regular register number if the required mapping can be deduced
/// from it. for example, esp implies that ss should be used.
/// \return mapped address or #BADADDR
ea_t (idaapi *map_address)(ea_t off, const regval_t *regs, int regnum);
/// Set debugger options (parameters that are specific to the debugger module).
/// See the definition of ::set_options_t for arguments.
/// See the convenience function in dbg.hpp if you need to call it.
/// The kernel will call this function after reading the debugger specific
/// config file (arguments are: keyword="", type=#IDPOPT_STR, value="")
/// This function is optional.
/// This function is called from the main thread
const char *(idaapi *set_dbg_options)(
const char *keyword,
int pri,
int value_type,
const void *value);
/// Get pointer to debugger specific functions.
/// This function returns a pointer to a structure that holds pointers to
/// debugger module specific functions. For information on the structure
/// layout, please check the corresponding debugger module. Most debugger
/// modules return NULL because they do not have any extensions. Available
/// extensions may be called from plugins.
/// This function is called from the main thread.
const void *(idaapi *get_debmod_extensions)(void);
/// Calculate the call stack trace.
/// This function is called when the process is suspended and should fill
/// the 'trace' object with the information about the current call stack.
/// If this function is missing or returns false, IDA will use the standard
/// mechanism (based on the frame pointer chain) to calculate the stack trace
/// This function is called from the main thread.
/// \return success
bool (idaapi *update_call_stack)(thid_t tid, call_stack_t *trace);
/// Call application function.
/// This function calls a function from the debugged application.
/// This function is called from debthread
/// \param func_ea address to call
/// \param tid thread to use
/// \param fti type information for the called function
/// \param nargs number of actual arguments
/// \param regargs information about register arguments
/// \param stkargs memory blob to pass as stack arguments (usually contains pointed data)
/// it must be relocated by the callback but not changed otherwise
/// \param retregs function return registers.
/// \param[out] errbuf the error message. if empty on failure, see 'event'.
/// should not be filled if an appcall exception
/// happened but #APPCALL_DEBEV is set
/// \param[out] event the last debug event that occurred during appcall execution
/// filled only if the appcall execution fails and #APPCALL_DEBEV is set
/// \param options appcall options, usually taken from \inf{appcall_options}.
/// possible values: combination of \ref APPCALL_ or 0
/// \return ea of stkargs blob, #BADADDR if failed and errbuf is filled
ea_t (idaapi *appcall)(
ea_t func_ea,
thid_t tid,
const struct func_type_data_t *fti,
int nargs,
const struct regobjs_t *regargs,
struct relobj_t *stkargs,
struct regobjs_t *retregs,
qstring *errbuf,
debug_event_t *event,
int options);
/// \defgroup APPCALL_ Appcall options
/// Passed as 'options' parameter to debugger_t::appcall
//@{
#define APPCALL_MANUAL 0x0001 ///< Only set up the appcall, do not run.
///< debugger_t::cleanup_appcall will not be called by ida!
#define APPCALL_DEBEV 0x0002 ///< Return debug event information
#define APPCALL_TIMEOUT 0x0004 ///< Appcall with timeout.
///< If timed out, errbuf will contain "timeout".
///< See #SET_APPCALL_TIMEOUT and #GET_APPCALL_TIMEOUT
//@}
/// Cleanup after appcall().
/// The debugger module must keep the stack blob in the memory until this function
/// is called. It will be called by the kernel for each successful appcall().
/// There is an exception: if #APPCALL_MANUAL, IDA may not call cleanup_appcall.
/// If the user selects to terminate a manual appcall, then cleanup_appcall will be called.
/// Otherwise, the debugger module should terminate the appcall when the called
/// function returns.
/// This function is called from debthread.
/// \retval 2 ok, there are pending events
/// \retval 1 ok
/// \retval 0 failed
/// \retval -1 network error
int (idaapi *cleanup_appcall)(thid_t tid);
/// Evaluate a low level breakpoint condition at 'ea'.
/// Other evaluation errors are displayed in a dialog box.
/// This call is rarely used by IDA when the process has already been suspended
/// for some reason and it has to decide whether the process should be resumed
/// or definitely suspended because of a breakpoint with a low level condition.
/// This function is called from debthread.
/// \retval 1 condition is satisfied
/// \retval 0 not satisfied
/// \retval -1 network error
int (idaapi *eval_lowcnd)(thid_t tid, ea_t ea);
/// This function is called from main thread
ssize_t (idaapi *write_file)(int fn, qoff64_t off, const void *buf, size_t size);
/// Perform a debugger-specific function.
/// This function is called from debthread
int (idaapi *send_ioctl)(int fn, const void *buf, size_t size, void **poutbuf, ssize_t *poutsize);
/// Enable/Disable tracing.
/// "trace_flags" can be a set of STEP_TRACE, INSN_TRACE, BBLK_TRACE or FUNC_TRACE.
/// See thread_t::trace_mode in debugger.h.
/// This function is called from the main thread.
bool (idaapi *dbg_enable_trace)(thid_t tid, bool enable, int trace_flags);
/// Is tracing enabled? ONLY used for tracers.
/// "trace_bit" can be one of the following: STEP_TRACE, INSN_TRACE, BBLK_TRACE or FUNC_TRACE
bool (idaapi *is_tracing_enabled)(thid_t tid, int tracebit);
/// Execute a command on the remote computer.
/// \return exit code
int (idaapi *rexec)(const char *cmdline);
/// Get (store to out_pattrs) process/debugger-specific runtime attributes.
/// This function is called from main thread.
void (idaapi *get_debapp_attrs)(debapp_attrs_t *out_pattrs);
/// Get the path to a file containing source debug info for the given module.
/// This allows srcinfo providers to call into the debugger when looking for debug info.
/// It is useful in certain cases like the iOS debugger, which is a remote debugger but
/// the remote debugserver does not provide dwarf info. So, we allow the debugger client
/// to decide where to look for debug info locally.
/// \param path output path (file might not exist)
/// \param base base address of a module in the target process
/// \return success, result stored in 'path'
bool (idaapi *get_srcinfo_path)(qstring *path, ea_t base);
};
Описывать всё в ней я не буду, из подробных коментариев, думаю, всё должно быть понятно.
А отладчик ведь тоже плагин
Да, да. Именно поэтому нам потребуется файлик 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
Тут всё просто: версия плагина, флаги, функция инициализации, завершения плагина, и подсказка:
class plugin_t
{
public:
int version; ///< Should be equal to #IDP_INTERFACE_VERSION
int flags; ///< \ref PLUGIN_
/// \defgroup PLUGIN_ Plugin features
/// Used by plugin_t::flags
//@{
#define PLUGIN_MOD 0x0001 ///< Plugin changes the database.
///< IDA won't call the plugin if
///< the processor module prohibited any changes.
#define PLUGIN_DRAW 0x0002 ///< IDA should redraw everything after calling the plugin.
#define PLUGIN_SEG 0x0004 ///< Plugin may be applied only if the current address belongs to a segment
#define PLUGIN_UNL 0x0008 ///< Unload the plugin immediately after calling 'run'.
///< This flag may be set anytime.
///< The kernel checks it after each call to 'run'
///< The main purpose of this flag is to ease
///< the debugging of new plugins.
#define PLUGIN_HIDE 0x0010 ///< Plugin should not appear in the Edit, Plugins menu.
///< This flag is checked at the start.
#define PLUGIN_DBG 0x0020 ///< A debugger plugin. init() should put
///< the address of ::debugger_t to dbg.
#define PLUGIN_PROC 0x0040 ///< Load plugin when a processor module is loaded. (and keep it
///< until the processor module is unloaded)
#define PLUGIN_FIX 0x0080 ///< Load plugin when IDA starts and keep it in the memory until IDA stops
#define PLUGIN_SCRIPTED 0x8000 ///< Scripted plugin. Should not be used by plugins,
///< the kernel sets it automatically.
//@}
int (idaapi *init)(void); ///< Initialize plugin - returns \ref PLUGIN_INIT
/// \defgroup PLUGIN_INIT Plugin initialization codes
/// Return values for plugin_t::init()
//@{
#define PLUGIN_SKIP 0 ///< Plugin doesn't want to be loaded
#define PLUGIN_OK 1 ///< Plugin agrees to work with the current database.
///< It will be loaded as soon as the user presses the hotkey
#define PLUGIN_KEEP 2 ///< Plugin agrees to work with the current database and wants to stay in the memory
//@}
void (idaapi *term)(void); ///< Terminate plugin. This function will be called
///< when the plugin is unloaded. May be NULL.
bool (idaapi *run)(size_t arg); ///< Invoke plugin
const char *comment; ///< Long comment about the plugin.
///< it could appear in the status line
///< or as a hint
const char *help; ///< Multiline help about the plugin
const char *wanted_name; ///< The preferred short name of the plugin
const char *wanted_hotkey; ///< The preferred hotkey to run the plugin
};
На этом, пожалуй, всё. Надеюсь, получилось интересно, хотя кода в самом деле очень много для одной статьи. Как видите, процесс написания чего угодно для IDA
превращается в сплошные мучения, хотя и довольно таки интересный.
Как и обещал, ссылки на исходники:
GPGX Debugger
Smd IDA Tools