IDA Pro Upgrade. Debugger for Sega Mega Drive (part 2)
- Tutorial
Hello to all,
In the previous article, we successfully modified the kernel of the games emulator to Sega Mega Drive
/ Genesis
, adding debugging capabilities to it. Now came the turn of writing a plugin debugger for the IDA Pro
version 7.0
. Let's get started
Part Two: Plugin Debugger
To begin with, we will create a new empty DLL
project: File
-> New
-> Project
-> Windows Desktop Wizard
-> Dynamic link library (.dll)
, by also putting a daw Empty Project
, and removing all the others:
We IDA SDK
will unpack it and write it in macros Visual Studio
(I will use it 2017 Community
) so that in future you can easily refer to it. At the same time, we will add a macro for the path to IDA Pro
.
Go to View
-> Other Windows
-> Property Manager
:
Because we work with the version SDK 7.0
, compilation will occur by x64
-compiler. Therefore, choose Debug | x64
-> Microsoft.Cpp.x64.user
-> Properties
:
We press the button Add Macro
in the section User Macros
, and write a macro there IDA_SDK
with the path you have unpacked SDK
:
We do the same with IDA_DIR
(the path to your IDA Pro
):
I note that IDA
the default is set to %Program Files%
, which requires administrative privileges.
Let's also remove the Win32
configuration (in this article I will not affect the compilation on the x86
system), leaving only the x64
option.
Now take the debugger event queue class template:
#pragma once#include<deque>#include<ida.hpp>#include<idd.hpp>//--------------------------------------------------------------------------// Very simple class to store pending eventsenumqueue_pos_t
{
IN_FRONT,
IN_BACK
};
structeventlist_t :publicstd::deque<debug_event_t>
{
private:
bool synced;
public:
// save a pending eventvoidenqueue(constdebug_event_t &ev, queue_pos_t pos){
if (pos != IN_BACK)
push_front(ev);
else
push_back(ev);
}
// retrieve a pending eventboolretrieve(debug_event_t *event){
if (empty())
returnfalse;
// get the first event and return it
*event = front();
pop_front();
returntrue;
}
};
Now the studio project will be able to set defaults for the compiler, therefore we add the following:
__NT__
__IDP__
__X64__
Add a new empty file ida_debug.cpp
and paste the following template into it:
#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"staticdbg_request_t *dbg_req = NULL;
staticvoidpause_execution(){
send_dbg_request(dbg_req, REQ_PAUSE);
}
staticvoidcontinue_execution(){
send_dbg_request(dbg_req, REQ_RESUME);
}
staticvoidstop_debugging(){
send_dbg_request(dbg_req, REQ_STOP);
}
eventlist_t g_events;
staticqthread_t events_thread = NULL;
// TODO: Implement status register bits maskstaticconstchar *const SRReg[] =
{
};
#define RC_GENERAL (1 << 0)// TODO: define different register typesregister_info_t registers[] =
{
// TODO: Implement registers description
};
staticconstchar *register_classes[] =
{
"General Registers",
// TODO: Add other register group namesNULL
};
staticvoidfinish_execution(){
if (events_thread != NULL)
{
qthread_join(events_thread);
qthread_free(events_thread);
qthread_kill(events_thread);
events_thread = NULL;
}
}
staticbool idaapi init_debugger(constchar *hostname, int portnum, constchar *password){
set_processor_type(ph.psnames[0], SETPROC_LOADER); // reset proc to "M68000"returntrue;
}
staticbool idaapi term_debugger(void){
dbg_req->is_ida = 0;
close_shared_mem(&dbg_req);
returntrue;
}
staticint idaapi process_get_info(procinfo_vec_t *procs){
return0;
}
staticint 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);
}
return0;
}
staticint idaapi start_process(constchar *path,
constchar *args,
constchar *startdir,
int dbg_proc_flags,
constchar *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);
return1;
}
staticvoid idaapi rebase_if_required_to(ea_t new_base){
}
staticint idaapi prepare_to_pause_process(void){
pause_execution();
return1;
}
staticint idaapi emul_exit_process(void){
stop_debugging();
finish_execution();
return1;
}
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;
}
staticint idaapi continue_after_event(constdebug_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;
}
return1;
}
staticvoid idaapi stopped_at_debug_event(bool dlls_added){
}
staticint idaapi thread_suspend(thid_t tid)// Suspend a running thread{
return0;
}
staticint idaapi thread_continue(thid_t tid)// Resume a suspended thread{
return0;
}
staticint 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;
}
return1;
}
staticint idaapi read_registers(thid_t tid, int clsmask, regval_t *values){
if (!dbg_req)
return0;
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 readingreturn1;
}
staticvoidset_reg(register_type_t type, int reg_index, unsignedint 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);
}
staticint idaapi write_register(thid_t tid, int regidx, constregval_t *value){
// TODO: Implement set registers for emulatorreturn1;
}
staticint idaapi get_memory_info(meminfo_vec_t &areas){
memory_info_t info;
// Don't remove this loopfor (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 loopreturn1;
}
static ssize_t idaapi read_memory(ea_t ea, void *buffer, size_t size){
// TODO: Implement memory regions readingreturn size;
}
static ssize_t idaapi write_memory(ea_t ea, constvoid *buffer, size_t size){
return0;
}
staticint 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 sdk61case BPT_WRITE:
case BPT_RDWR:
return BPT_OK;
}
return BPT_BAD_TYPE;
}
staticint 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,
};
Next, create another file, call it ida_plugin.cpp
and paste the following code into it:
#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"externdebugger_t debugger;
staticbool plugin_inited;
staticbool my_dbg;
staticint 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;
elseif (idp_reg >= 8 && idp_reg <= 39)
reg_idx = 8 + (idp_reg % 8);
elseif (idp_reg == 91)
reg_idx = 16;
elseif (idp_reg == 92 || idp_reg == 93)
reg_idx = 17;
elseif (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);
return0;
}
return reg_idx;
}
#ifdef _DEBUGstaticconstchar* 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",
};
staticconstchar* 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",
};
staticvoidprint_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");
}
}
staticvoidprint_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);
elseif (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");
}
}
#endiftypedefconstregval_t &(idaapi *getreg_func_t)(constchar *name, constregval_t *regvalues);
static ssize_t idaapi hook_idp(void *user_data, int notification_code, va_list va){
switch (notification_code)
{
caseprocessor_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);
constregval_t *regvalues = va_arg(va, constregval_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);
#endifint 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)
{
case0: flags = get_optype_flags0(get_flags(ea)); break;
case1: 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)
{
case1:
{
uint8_t b = 0;
dbg->read_memory((ea_t)reg.ival, &b, 1);
opinf->value.ival = b;
} break;
case2:
{
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)
{
case1:
{
uint8_t b = 0;
dbg->read_memory(addr, &b, 1);
opinf->value.ival = b;
} break;
case2:
{
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;
return1;
}
} break;
default:
{
#ifdef _DEBUGif (my_dbg)
{
msg("msg = %d\n", notification_code);
}
#endif
} break;
}
return0;
}
//--------------------------------------------------------------------------staticvoidprint_version(){
staticconstchar format[] = NAME " debugger plugin v%s;\nAuthor: Dr. MefistO.";
info(format, VERSION);
msg(format, VERSION);
}
//--------------------------------------------------------------------------// Initialize debugger pluginstaticint 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 pluginstaticvoid 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 pluginsstaticbool idaapi run(size_t arg){
returnfalse;
}
//--------------------------------------------------------------------------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
};
Now let's understand, and at the same time write code.
Debugger implementation
The variable dbg_req
we have will store a pointer to the memory shared with the debugger kernel. It is in it that we will send requests, and receive answers from it.
Functions pause_execution()
, continue_execution()
and stop_debugging()
needed to control the debugging process.
eventlist_t g_events
is a list of debugger events that will be expected IDA
in response to some of our actions (for example, start / stop emulation, triggered bryak).
Well, the list will events_thread
be updated, which will monitor the presence of debugger events in the shared memory, and convert them to the corresponding events IDA
.
Let's write a function finish_execution()
that will simply terminate the thread waiting for debug events:
staticvoidfinish_execution(){
if (events_thread != NULL)
{
qthread_join(events_thread);
qthread_free(events_thread);
qthread_kill(events_thread);
events_thread = NULL;
}
}
So, we figured it out. Now we will be engaged in the description of registers.
Register information is a structure of the following form:
structregister_info_t
{constchar *name;
uint32 flags;
register_class_t register_class;
op_dtype_t dtype;
constchar *const *bit_strings;
uval_t default_bit_strings_mask;
};
The field name
is the text name of the register. While in different groups of registers can not be the same name. For example, if you want to display the register PC
from two different processors (and Sega Mega Drive
there are two in the device : Motorola 68000
i Z80
), you will have to rename it.
A field flags
can contain one or more of the following 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
It is clear that unite REGISTER_IP
and REGISTER SP
can not be, but you can specify that the field contains the address using the flag REGISTER_ADDRESS
.
register_class
- this is the mask number of the group of registers you have implemented. For example, I added the following three:
#define RC_GENERAL (1 << 0)#define RC_VDP (1 << 1)#define RC_Z80 (1 << 2)
dtype
is an indication of the size of the register. The options are as follows:
#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
Actually, I only needed dt_word
, dt_dword
.
The field is bit_strings
needed if, for example, you want to output some register in the form of its individual bits. In particular, it can be used for flags register: Negative
, Overflow
, Zero
, Carry
etc. Example:
staticconstchar *const SRReg[] =
{
"C",
"V",
"Z",
"N",
"X",
NULL,
NULL,
NULL,
"I",
"I",
"I",
NULL,
NULL,
"S",
NULL,
"T"
};
Bits start from top to bottom (from low to high). If the value of the bit does not need to be output, we specify instead of the name NULL
. If several bits in the register belong to the same flag, specify the same name the required number of times.
Well, the last field default_bit_strings_mask
is the bitmask that will be applied before getting the register bit values.
Here is an example of my implementation of the list of registers for Sega Mega Drive
(I included the registers M68K, Z80 and VDP, as well as a couple of custom ones):
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 },
};
Next comes the list register_classes[]
in which we must specify the text names of the groups of registers. They can be opened in separate windows when debugging.
Here is my implementation (the last element should be NULL
):
staticconstchar *register_classes[] =
{
"General Registers",
"VDP Registers",
"Z80 Registers",
NULL
};
Callbacks required by IDA
init_debugger ()
staticbool idaapi init_debugger(constchar *hostname, int portnum, constchar *password){
set_processor_type(ph.psnames[0], SETPROC_LOADER); // reset proc to "M68000"returntrue;
}
Since IDA
several versions of the motorola processor have been implemented, I forcibly drop to the very first in the list.
term_debugger ()
staticbool idaapi term_debugger(void){
dbg_req->is_ida = 0;
close_shared_mem(&dbg_req);
returntrue;
}
Fun fact : the function init_debugger()
is called once at the first start of the emulation during the session, and the function term_debugger()
- every time the debugging process is completed. Therefore, I close the open shared memory here.
Both functions should return true
if successful.
process_get_info ()
staticint idaapi process_get_info(procinfo_vec_t *procs){
return0;
}
If during debugging you work with several processes, you need to implement this callback, which will IDA
provide information on each of them, namely the PID
name.
I do not need this function, so I return 0
.
check_debugger_events () is not a callback, but very important
Actually, it is a thread waiting for debug events. Here it is necessary to tell in more detail.
When you start debugging, the first event you expect to receive IDA
should be PROCESS_START
. If the first message comes, for example, a message about the pause of emulation, IDA
it will simply fall.
After that, you can already receive other messages. The main uses are:
PROCESS_SUSPEND
- emulation is suspended, and now the debugger user can view or change register values, read or modify memory.BREAKPOINT
- this messageIDA
can accept when you want to tell her - the breakpoint, both hardware and software, has worked. Why exactly can ? Because to stop debugging, it is enough to acceptPROCESS_SUSPEND
, and everything else is only details of a stop that can be reportedIDA
STEP
- you can get this message to informIDA
that the goalStep Into
orStep Over
achieved, but, again, you can get rid of the messagePROCESS_SUSPEND
PROCESS_EXIT
- should be takenIDA
after stopping the process being debugged, or the debugging process. If you press a buttonStop
in the debugger interface, itIDA
will wait for this message until it arrives,either come the end of the worldor you do not kill her process manually.
The structure of the event object itself is as follows:
structdebug_event_t
{event_id_t eid; ///< Event code (used to decipher 'info' union)pid_t pid; ///< Process where the event occurredthid_t tid; ///< Thread where the event occurredea_t ea; ///< Address where the event occurredbool handled; ///< Is event handled by the debugger?.///< (from the system's point of view)///< Meaningful for ::EXCEPTION eventsunion
{
module_info_t modinfo; ///< ::PROCESS_START, ::PROCESS_ATTACH, ::LIBRARY_LOADint exit_code; ///< ::PROCESS_EXIT, ::THREAD_EXITchar info[MAXSTR]; ///< ::LIBRARY_UNLOAD (unloaded library name)///< ::INFORMATION (will be displayed in the///< messages window if not empty)e_breakpoint_t bpt; ///< ::BREAKPOINTe_exception_t exc; ///< ::EXCEPTION
};
};
eid
- these are the types of events that I have described above pid
, tid
- in fact, the Process ID and Thread A ID , in which the event occurred ea
- the address where the event occurred handled
- the real purpose of this parameter is unknown to me, but, judging by the text of IDA SDK
, is used to indicate, whether the exception was handled by the system (and why?). I set intrue
Next are the fields that must be filled depending on the type of event.
For PROCESS_START
I specify the name of the emulator process (you can think of), ImageBase
according to which the rum is loaded, the size, and the new one ImageBase
, if it differs from what was specified at the time of creation IDB
. If at the start of the process, none of this is unknown, simply specify zeros, either 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;
For BREAKPOINT
in the field we bpt
specify hardware
- and the kernel
address of the triggered bryak. If such an event has arrived, indicating the address of the breakpoint that it IDA
does not know about, a message about an unknown breakpoint will be displayed in the log window.
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;
For an event it is PROCESS_EXIT
enough to specify exit_code
.
start_process ()
This is where the emulation starts, initializes and starts the processes you are going to debug, or waits for a connection to the debug server.
In my implementation, I clear the list of events, issue a dialog box with a wait for connection to the debugger (or rather, creation of shared memory by the kernel), create a thread waiting for debugging events, and send a connection request to the debugging process.
Return 1
on success.
rebase_if_required_to ()
I have not implemented, because The base of the Roma is always the same, but a typical implementation looks like this:
staticvoid 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 ()
When you press the button Pause
in IDA
, there is a call of the function.
We return 1
in case of successful suspension of the debugging process.
get_debug_event ()
Actually, the heart of the debugger IDA
, which is waiting for incoming debug events. Called with a certain (what?) Frequency. If the event is received, fill in the structure of the debug_event_t
input argument *event
, and return:
GDE_ONE_EVENT
if one event was received, and no more events yetGDE_MANY_EVENTS
if there are still pending events in the queueGDE_NO_EVENT
if there are no events in the queue
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 ()
To be honest, this is the most unsuccessful and poorly designed plugin-debugger architecture node in IDA
. Now you will find out why.
In general, this callback is called after when something needs to be done after the debug message that came earlier. Here is a real example:
- Event came
STEP
IDA
pauses and allows us to create anything with registers, memory, etc.- Next we click, for example, again
Step In
- A call
continue_after_event()
is made to which information about the last debug event that we have processed is transmitted . In this case -STEP
IDA
receives eventSTEP
It seems to be all right, but, with real use of the debugger, information about what kind of last event we had there was absolutely not necessary !
It is more important to know that a user in step 3
clicked it Step Into
, Step Over
or F9
to continue emulation.
For cases with STEP
, BREAKPOINT
or PROCESS_SUSPEND
if the user wanted to continue emulation by F9
, it would be logical to debug a pause from a call continue_execution()
. Determine the pressure F9
or Run to
can be as follows:
dbg_notification_t req = get_running_notification();
if (req == dbg_null || req == dbg_run_to)
continue_execution();
stopped_at_debug_event ()
Never used it, but implementation is required. Enough empty function.
thread_suspend (), thread_continue ()
At first you might think that these functions will be called when you pause / continue debugging, but no. You need to implement them, but if you don’t have work with streams, it’s enough to return 0
. They work only in the window with the list of threads, when you select the corresponding commands by right-clicking.
set_step_mode ()
Important callback responsible for sending commands to the kernel debugger Step Into
, Step Over
, Step Out
. My last variant of stepping is not implemented (this is set by the flags in the structure debugger_t
, which I will discuss later).
Return 1
on success.
staticint 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;
}
return1;
}
read_registers ()
When it IDA
receives a message STEP
, BREAKPOINT
or PROCESS_SUSPEND
(i.e., it learns that debugging is suspended), a callback is called in order to know the values of the registers for display.
Significant input arguments are:
clsmask
- remember at the beginning we asked the masks of groups of registers? Here it is. May contain several groups at a time to obtainvalues
- an array of register values that we need to fill. Register indices correspond to register positions in an arrayregisters[]
.
An example of filling the array with the received register values:
staticint idaapi read_registers(thid_t tid, int clsmask, regval_t *values){
if (!dbg_req)
return0;
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 = ((unsignedint *)&z80_regs->pc)[i];
}
elseif (i >= 13 && i <= 19) // R <-> I
{
values[REG_Z80_PC + i].ival = ((unsignedchar *)&z80_regs->r)[i - 13];
}
}
}
return1;
}
Return 1
on success.
write_register ()
We change the value during debugging - this callback is called. At the entrance we are given the register number in the register array and its value.
Example of implementation:
staticint idaapi write_register(thid_t tid, int regidx, constregval_t *value){
if (regidx >= REG_68K_D0 && regidx <= REG_68K_D7)
{
set_reg(REG_TYPE_M68K, regidx - REG_68K_D0, (uint32)value->ival);
}
elseif (regidx >= REG_68K_A0 && regidx <= REG_68K_A7)
{
set_reg(REG_TYPE_M68K, regidx - REG_68K_A0, (uint32)value->ival);
}
elseif (regidx == REG_68K_PC)
{
set_reg(REG_TYPE_M68K, REG_68K_PC, (uint32)value->ival & 0xFFFFFF);
}
elseif (regidx == REG_68K_SR)
{
set_reg(REG_TYPE_M68K, REG_68K_SR, (uint16)value->ival);
}
elseif (regidx == REG_68K_SP)
{
set_reg(REG_TYPE_M68K, REG_68K_SP, (uint32)value->ival & 0xFFFFFF);
}
elseif (regidx == REG_68K_USP)
{
set_reg(REG_TYPE_M68K, REG_68K_USP, (uint32)value->ival & 0xFFFFFF);
}
elseif (regidx == REG_68K_ISP)
{
set_reg(REG_TYPE_M68K, REG_68K_ISP, (uint32)value->ival & 0xFFFFFF);
}
elseif (regidx >= REG_VDP_00 && regidx <= REG_VDP_1F)
{
set_reg(REG_TYPE_VDP, regidx - REG_VDP_00, value->ival & 0xFF);
}
elseif (regidx >= REG_Z80_PC && regidx <= REG_Z80_I)
{
set_reg(REG_TYPE_Z80, regidx - REG_Z80_PC, value->ival);
}
return1;
}
Return 1
on success.
get_memory_info ()
Here we must inform the debugger about all regions of memory that will be available during the debugging process. There is one thing: even if IDB
some segments have already been created, you will still have to add them. Therefore, in the template, I inserted a cycle for obtaining information about segments from an existing database.
If at the time of debugging you want to create some segments that are not needed after its completion, this is exactly what you need to do. An example of adding debug segments:
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);
Return 1
on success.
read_memory ()
Another important callback. Any address among those that are available in segments that are not marked XTRN
, IDA
may ask to read.
The input to the callback is the address ea
for which reading is required, the size of the memory size
to be read, and a pointer to the memory buffer
to which the contents of the memory will be written to the requested address.
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
}
elseif (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);
}
elseif ((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 ()
This callback is called when you modify something in segments during debugging: patch, change the contents of the RAM, or segments that were created at the time of debugging.
In my debugger, this functionality is not needed, so I return 0
. Otherwise we return 1
.
The input arguments are the same as in the previous function, with the only difference being that buffer
now the modified contents of the memory to be written.
is_ok_bpt ()
This function is called each time a block is installed in order to check whether it is valid with the length len
and type type
at the address ea
.
If bryak is allowed, we return BPT_OK
, otherwise - BPT_BAD_TYPE
.
update_bpts ()
A function that is designed to synchronize the list of breakpoints IDA
and the debugger kernel. It is logical to assume that the callback is invoked immediately after you set the breakpoint, but no. It occurs only after clicking F9
( Continue ).
The principle of processing breakpoints in this function is as follows:
- The input is an array
bpts
containing information about breakpoints, namely: address, type, size, as well as the number of breakpoints to addnadd
and deletendel
, which follow each other in the array. - After bryak was added or deleted, mark
bpts[i].code
asBPT_OK
.
The callback should return the number of added + remote breakpoints. Usually it is nadd + ndel
.
staticint 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 structure
Here we indicate callbacks of the functions that we have implemented (by the way, some are mandatory, otherwise, IDA
it will simply fall without explanation, and some, if not, will say that implementation is required).
The structure is as follows:
structdebugger_t
{int version; ///< Expected kernel version,///< should be #IDD_INTERFACE_VERSIONconstchar *name; ///< Short debugger name like win32 or linuxint 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
Also popular now:
-
About the importance of IT caches
-
Mohawk / Object Model
-
Automation or trying to find the impossible. Reflections on the past and present.
-
Unclesoсky podcast - Issue # 28
-
VKontakte: jokes-jokes
-
Oslo - pipe
-
Microsoft will continue to release Windows XP after the release of Windows 7
-
Advise VDS / VPS in Russian DC
-
Working with text in OS X 10.6 Snow Leopard
-
The "Back" action in Firefox transfers you from a topic on Habré