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 Proversion 7.0. Let's get started


Part Two: Plugin Debugger


To begin with, we will create a new empty DLLproject: File-> New-> Project-> Windows Desktop Wizard-> Dynamic link library (.dll), by also putting a daw Empty Project, and removing all the others:



We IDA SDKwill 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 Macroin the section User Macros, and write a macro there IDA_SDKwith the path you have unpacked SDK:



We do the same with IDA_DIR(the path to your IDA Pro):



I note that IDAthe default is set to %Program Files%, which requires administrative privileges.


Let's also remove the Win32configuration (in this article I will not affect the compilation on the x86system), leaving only the x64option.


Now take the debugger event queue class template:


Source code ida_debmod.h
#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.cppand paste the following template into it:


Source code 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"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.cppand paste the following code into it:


Source code 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"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_reqwe 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_eventsis a list of debugger events that will be expected IDAin response to some of our actions (for example, start / stop emulation, triggered bryak).


Well, the list will events_threadbe 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 nameis 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 PCfrom two different processors (and Sega Mega Drivethere are two in the device : Motorola 68000i Z80), you will have to rename it.


A field flagscan 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_IPand REGISTER SPcan 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)

dtypeis 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_stringsneeded 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, Carryetc. 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_maskis 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 Description for the Debugger
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 IDAseveral 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 trueif 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 IDAprovide information on each of them, namely the PIDname.


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 IDAshould be PROCESS_START. If the first message comes, for example, a message about the pause of emulation, IDAit 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 message IDAcan 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 accept PROCESS_SUSPEND, and everything else is only details of a stop that can be reportedIDA
  • STEP- you can get this message to inform IDAthat the goal Step Intoor Step Overachieved, but, again, you can get rid of the messagePROCESS_SUSPEND
  • PROCESS_EXIT- should be taken IDAafter stopping the process being debugged, or the debugging process. If you press a button Stopin the debugger interface, it IDAwill wait for this message until it arrives,either come the end of the world or 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_STARTI specify the name of the emulator process (you can think of), ImageBaseaccording 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 BREAKPOINTin the field we bptspecify hardware- and the kerneladdress of the triggered bryak. If such an event has arrived, indicating the address of the breakpoint that it IDAdoes 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_EXITenough 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 1on 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:


Implementation rebase_if_required_to
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 Pausein IDA, there is a call of the function.
We return 1in 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_tinput argument *event, and return:


  • GDE_ONE_EVENTif one event was received, and no more events yet
  • GDE_MANY_EVENTSif there are still pending events in the queue
  • GDE_NO_EVENTif 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:


  1. Event came STEP
  2. IDA pauses and allows us to create anything with registers, memory, etc.
  3. Next we click, for example, again Step In
  4. 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
  5. IDA receives event STEP

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 3clicked it Step Into, Step Overor F9to continue emulation.


For cases with STEP, BREAKPOINTor PROCESS_SUSPENDif the user wanted to continue emulation by F9, it would be logical to debug a pause from a call continue_execution(). Determine the pressure F9or Run tocan 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 1on 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 IDAreceives a message STEP, BREAKPOINTor 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 obtain
  • values- an array of register values ​​that we need to fill. Register indices correspond to register positions in an array registers[].

An example of filling the array with the received register values:


Implementing read_registers ()
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 1on 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:


Implement write_register ()
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 1on 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 IDBsome 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 1on success.


read_memory ()


Another important callback. Any address among those that are available in segments that are not marked XTRN, IDAmay ask to read.
The input to the callback is the address eafor which reading is required, the size of the memory sizeto be read, and a pointer to the memory bufferto which the contents of the memory will be written to the requested address.


Implementation example read_memory ()
static ssize_t idaapi read_memory(ea_t ea, void *buffer, size_t size){
    if ((ea >= 0xA00000 && ea < 0xA0FFFF))
    {
        dbg_req->mem_data.address = ea;
        dbg_req->mem_data.size = size;
        send_dbg_request(dbg_req, REQ_READ_Z80);
        memcpy(buffer, &dbg_req->mem_data.z80_ram[ea & 0x1FFF], size);
        // 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 buffernow 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 lenand type typeat 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 IDAand 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:


  1. The input is an array bptscontaining information about breakpoints, namely: address, type, size, as well as the number of breakpoints to add naddand delete ndel, which follow each other in the array.
  2. After bryak was added or deleted, mark bpts[i].codeas BPT_OK.

The callback should return the number of added + remote breakpoints. Usually it is nadd + ndel.


Implementation update_bpts ()
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, IDAit will simply fall without explanation, and some, if not, will say that implementation is required).


The structure is as follows:


Debugger_t structure
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: