Pull to refresh

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

Reading time 46 min
Views 7.4K


Привет всем,


В предыдущей статье мы успешно модифицировали ядро эмулятора игр на Sega Mega Drive / Genesis, добавив в него возможность отладки. Теперь пришёл черёд написания собственно плагина-отладчика для IDA Pro, версия 7.0. Приступим.


Часть вторая: плагин-отладчик


Для начала создадим новый пустой DLL-проект: File->New->Project->Windows Desktop Wizard->Dynamic link library (.dll), поставив также галку Empty Project, и сняв все остальные:



Распакуем IDA SDK, и пропишем его в макросах Visual Studio (я буду использовать 2017 Community), чтобы в будущем можно было легко ссылаться на него. Заодно мы добавим макрос для пути к IDA Pro.


Заходим в View->Other Windows->Property Manager:



Т.к. мы работаем с версией SDK 7.0, компиляция будет происходить x64-компилятором. Поэтому выбираем Debug | x64->Microsoft.Cpp.x64.user->Properties:



Жмём кнопку Add Macro в разделе User Macros, и прописываем там макрос IDA_SDK с указанием пути, по которому вы распаковали SDK:



Так же поступаем с IDA_DIR (путь к Вашей IDA Pro):



Замечу, что IDA ставится по умолчанию в %Program Files%, что требует прав администратора.


Давайте также удалим Win32 конфигурацию (в данной статье я не буду затрагивать компиляцию по x86 системы), оставив только x64-вариант.


Теперь возьмём шаблон класса очереди событий отладчика:


Исходный код ida_debmod.h
#pragma once

#include <deque>
#include <ida.hpp>
#include <idd.hpp>

//--------------------------------------------------------------------------
// Very simple class to store pending events
enum queue_pos_t
{
    IN_FRONT,
    IN_BACK
};

struct eventlist_t : public std::deque<debug_event_t>
{
private:
    bool synced;
public:
    // save a pending event
    void enqueue(const debug_event_t &ev, queue_pos_t pos)
    {
        if (pos != IN_BACK)
            push_front(ev);
        else
            push_back(ev);
    }

    // retrieve a pending event
    bool retrieve(debug_event_t *event)
    {
        if (empty())
            return false;
        // get the first event and return it
        *event = front();
        pop_front();
        return true;
    }
};

Теперь у студийного проекта появится возможность ставить дефайны для компилятора, поэтому добавляем следующие:


__NT__
__IDP__
__X64__

Добавляем новый пустой файл ida_debug.cpp и вставляем в него следующий шаблон:


Исходный код ida_debug.cpp
#include <ida.hpp>
#include <idd.hpp>
#include <auto.hpp>
#include <funcs.hpp>
#include <idp.hpp>
#include <dbg.hpp>

#include "ida_debmod.h"

#include "debug_wrap.h"

static dbg_request_t *dbg_req = NULL;

static void pause_execution()
{
    send_dbg_request(dbg_req, REQ_PAUSE);
}

static void continue_execution()
{
    send_dbg_request(dbg_req, REQ_RESUME);
}

static void stop_debugging()
{
    send_dbg_request(dbg_req, REQ_STOP);
}

eventlist_t g_events;
static qthread_t events_thread = NULL;

// TODO: Implement status register bits mask
static const char *const SRReg[] =
{

};

#define RC_GENERAL (1 << 0)
// TODO: define different register types

register_info_t registers[] =
{
    // TODO: Implement registers description
};

static const char *register_classes[] =
{
    "General Registers",
    // TODO: Add other register group names
    NULL
};

static void finish_execution()
{
    if (events_thread != NULL)
    {
        qthread_join(events_thread);
        qthread_free(events_thread);
        qthread_kill(events_thread);
        events_thread = NULL;
    }
}

static bool idaapi init_debugger(const char *hostname, int portnum, const char *password)
{
    set_processor_type(ph.psnames[0], SETPROC_LOADER); // reset proc to "M68000"
    return true;
}

static bool idaapi term_debugger(void)
{
    dbg_req->is_ida = 0;
    close_shared_mem(&dbg_req);
    return true;
}

static int idaapi process_get_info(procinfo_vec_t *procs)
{
    return 0;
}

static int idaapi check_debugger_events(void *ud)
{
    while (dbg_req->dbg_active || dbg_req->dbg_events_count)
    {
        dbg_req->is_ida = 1;

        int event_index = recv_dbg_event(dbg_req, 0);
        if (event_index == -1)
        {
            qsleep(10);
            continue;
        }

        debugger_event_t *dbg_event = &dbg_req->dbg_events[event_index];

        debug_event_t ev;
        switch (dbg_event->type)
        {
        case DBG_EVT_STARTED:
            ev.eid = PROCESS_START;
            ev.pid = 1;
            ev.tid = 1;
            ev.ea = BADADDR;
            ev.handled = true;

            ev.modinfo.name[0] = 'E';
            ev.modinfo.name[1] = 'M';
            ev.modinfo.name[2] = 'U';
            ev.modinfo.name[3] = 'L';
            ev.modinfo.name[4] = '\0';
            ev.modinfo.base = 0;
            ev.modinfo.size = 0;
            ev.modinfo.rebase_to = BADADDR;

            g_events.enqueue(ev, IN_FRONT);
            break;
        case DBG_EVT_PAUSED:
            ev.pid = 1;
            ev.tid = 1;
            ev.ea = dbg_event->pc;
            ev.handled = true;
            ev.eid = PROCESS_SUSPEND;
            g_events.enqueue(ev, IN_BACK);
            break;
        case DBG_EVT_BREAK:
            ev.pid = 1;
            ev.tid = 1;
            ev.ea = dbg_event->pc;
            ev.handled = true;
            ev.eid = BREAKPOINT;
            ev.bpt.hea = ev.bpt.kea = ev.ea;
            g_events.enqueue(ev, IN_BACK);
            break;
        case DBG_EVT_STEP:
            ev.pid = 1;
            ev.tid = 1;
            ev.ea = dbg_event->pc;
            ev.handled = true;
            ev.eid = STEP;
            g_events.enqueue(ev, IN_BACK);
            break;
        case DBG_EVT_STOPPED:
            ev.eid = PROCESS_EXIT;
            ev.pid = 1;
            ev.handled = true;
            ev.exit_code = 0;

            g_events.enqueue(ev, IN_BACK);
            break;
        default:
            break;
        }

        dbg_event->type = DBG_EVT_NO_EVENT;
        qsleep(10);
    }

    return 0;
}

static int idaapi start_process(const char *path,
    const char *args,
    const char *startdir,
    int dbg_proc_flags,
    const char *input_path,
    uint32 input_file_crc32)
{
    g_events.clear();

    dbg_req = open_shared_mem();

    if (!dbg_req)
    {
        show_wait_box("HIDECANCEL\nWaiting for connection to plugin...");

        while (!dbg_req)
        {
            dbg_req = open_shared_mem();
        }

        hide_wait_box();
    }

    events_thread = qthread_create(check_debugger_events, NULL);

    send_dbg_request(dbg_req, REQ_ATTACH);

    return 1;
}

static void idaapi rebase_if_required_to(ea_t new_base)
{
}

static int idaapi prepare_to_pause_process(void)
{
    pause_execution();
    return 1;
}

static int idaapi emul_exit_process(void)
{
    stop_debugging();
    finish_execution();

    return 1;
}

static gdecode_t idaapi get_debug_event(debug_event_t *event, int timeout_ms)
{
    while (true)
    {
        // are there any pending events?
        if (g_events.retrieve(event))
        {
            return g_events.empty() ? GDE_ONE_EVENT : GDE_MANY_EVENTS;
        }
        if (g_events.empty())
            break;
    }
    return GDE_NO_EVENT;
}

static int idaapi continue_after_event(const debug_event_t *event)
{
    dbg_notification_t req = get_running_notification();
    switch (event->eid)
    {
    case STEP:
    case BREAKPOINT:
    case PROCESS_SUSPEND:
        if (req == dbg_null || req == dbg_run_to)
            continue_execution();
    break;
    }

    return 1;
}

static void idaapi stopped_at_debug_event(bool dlls_added)
{
}

static int idaapi thread_suspend(thid_t tid) // Suspend a running thread
{
    return 0;
}

static int idaapi thread_continue(thid_t tid) // Resume a suspended thread
{
    return 0;
}

static int idaapi set_step_mode(thid_t tid, resume_mode_t resmod) // Run one instruction in the thread
{
    switch (resmod)
    {
    case RESMOD_INTO:    ///< step into call (the most typical single stepping)
        send_dbg_request(dbg_req, REQ_STEP_INTO);
        break;
    case RESMOD_OVER:    ///< step over call
        send_dbg_request(dbg_req, REQ_STEP_OVER);
        break;
    }

    return 1;
}

static int idaapi read_registers(thid_t tid, int clsmask, regval_t *values)
{
    if (!dbg_req)
        return 0;

    if (clsmask & RC_GENERAL)
    {
        dbg_req->regs_data.type = REG_TYPE_M68K;
        send_dbg_request(dbg_req, REQ_GET_REGS);

        // TODO: Set register values for IDA
    }

    // TODO: Implement other registers reading

    return 1;
}

static void set_reg(register_type_t type, int reg_index, unsigned int value)
{
    dbg_req->regs_data.type = type;
    dbg_req->regs_data.any_reg.index = reg_index;
    dbg_req->regs_data.any_reg.val = value;
    send_dbg_request(dbg_req, REQ_SET_REG);
}

static int idaapi write_register(thid_t tid, int regidx, const regval_t *value)
{
    // TODO: Implement set registers for emulator

    return 1;
}

static int idaapi get_memory_info(meminfo_vec_t &areas)
{
    memory_info_t info;

    // Don't remove this loop
    for (int i = 0; i < get_segm_qty(); ++i)
    {
        segment_t *segm = getnseg(i);

        info.start_ea = segm->start_ea;
        info.end_ea = segm->end_ea;

        qstring buf;
        get_segm_name(&buf, segm);
        info.name = buf;

        get_segm_class(&buf, segm);
        info.sclass = buf;

        info.sbase = 0;
        info.perm = SEGPERM_READ | SEGPERM_WRITE;
        info.bitness = 1;
        areas.push_back(info);
    }
    // Don't remove this loop

    return 1;
}

static ssize_t idaapi read_memory(ea_t ea, void *buffer, size_t size)
{
    // TODO: Implement memory regions reading

    return size;
}

static ssize_t idaapi write_memory(ea_t ea, const void *buffer, size_t size)
{
    return 0;
}

static int idaapi is_ok_bpt(bpttype_t type, ea_t ea, int len)
{
    switch (type)
    {
        //case BPT_SOFT:
    case BPT_EXEC:
    case BPT_READ: // there is no such constant in sdk61
    case BPT_WRITE:
    case BPT_RDWR:
        return BPT_OK;
    }

    return BPT_BAD_TYPE;
}

static int idaapi update_bpts(update_bpt_info_t *bpts, int nadd, int ndel)
{
    for (int i = 0; i < nadd; ++i)
    {
        ea_t start = bpts[i].ea;
        ea_t end = bpts[i].ea + bpts[i].size - 1;

        bpt_data_t *bpt_data = &dbg_req->bpt_data;

        switch (bpts[i].type)
        {
        case BPT_EXEC:
            bpt_data->type = BPT_M68K_E;
            break;
        case BPT_READ:
            bpt_data->type = BPT_M68K_R;
            break;
        case BPT_WRITE:
            bpt_data->type = BPT_M68K_W;
            break;
        case BPT_RDWR:
            bpt_data->type = BPT_M68K_RW;
            break;
        }

        bpt_data->address = start;
        bpt_data->width = bpts[i].size;
        send_dbg_request(dbg_req, REQ_ADD_BREAK);

        bpts[i].code = BPT_OK;
    }

    for (int i = 0; i < ndel; ++i)
    {
        ea_t start = bpts[nadd + i].ea;
        ea_t end = bpts[nadd + i].ea + bpts[nadd + i].size - 1;

        bpt_data_t *bpt_data = &dbg_req->bpt_data;

        switch (bpts[nadd + i].type)
        {
        case BPT_EXEC:
            bpt_data->type = BPT_M68K_E;
            break;
        case BPT_READ:
            bpt_data->type = BPT_M68K_R;
            break;
        case BPT_WRITE:
            bpt_data->type = BPT_M68K_W;
            break;
        case BPT_RDWR:
            bpt_data->type = BPT_M68K_RW;
            break;
        }

        bpt_data->address = start;
        send_dbg_request(dbg_req, REQ_DEL_BREAK);

        bpts[nadd + i].code = BPT_OK;
    }

    return (ndel + nadd);
}

//--------------------------------------------------------------------------
//
//    DEBUGGER DESCRIPTION BLOCK
//
//--------------------------------------------------------------------------

debugger_t debugger =
{
    IDD_INTERFACE_VERSION,
    "DBGNAME",
    0x8000 + 1,
    "m68k",
    DBG_FLAG_NOHOST | DBG_FLAG_CAN_CONT_BPT | DBG_FLAG_FAKE_ATTACH | DBG_FLAG_SAFE | DBG_FLAG_NOPASSWORD | DBG_FLAG_NOSTARTDIR | DBG_FLAG_CONNSTRING | DBG_FLAG_ANYSIZE_HWBPT | DBG_FLAG_DEBTHREAD,

    register_classes,
    RC_GENERAL,
    registers,
    qnumber(registers),

    0x1000,

    NULL,
    NULL,
    0,

    DBG_RESMOD_STEP_INTO | DBG_RESMOD_STEP_OVER,

    init_debugger,
    term_debugger,

    process_get_info,

    start_process,
    NULL,
    NULL,
    rebase_if_required_to,
    prepare_to_pause_process,
    emul_exit_process,

    get_debug_event,
    continue_after_event,

    NULL,
    stopped_at_debug_event,

    thread_suspend,
    thread_continue,
    set_step_mode,

    read_registers,
    write_register,

    NULL,

    get_memory_info,
    read_memory,
    write_memory,

    is_ok_bpt,
    update_bpts,
    NULL,

    NULL,
    NULL,
    NULL,

    NULL,

    NULL,
    NULL,
    NULL,

    NULL,
    NULL,

    NULL,

    NULL,

    NULL,
};

Далее создаём ещё один файл, называем его ida_plugin.cpp и вставляем в него следующий код:


Исходный код ida_plugin.cpp
#include <ida.hpp>
#include <dbg.hpp>
#include <idd.hpp>
#include <loader.hpp>
#include <idp.hpp>
#include <offset.hpp>
#include <kernwin.hpp>

#include "ida_plugin.h"

#include "ida_debmod.h"

extern debugger_t debugger;

static bool plugin_inited;
static bool my_dbg;

static int idaapi idp_to_dbg_reg(int idp_reg)
{
    int reg_idx = idp_reg;
    if (idp_reg >= 0 && idp_reg <= 7)
        reg_idx = 0 + idp_reg;
    else if (idp_reg >= 8 && idp_reg <= 39)
        reg_idx = 8 + (idp_reg % 8);
    else if (idp_reg == 91)
        reg_idx = 16;
    else if (idp_reg == 92 || idp_reg == 93)
        reg_idx = 17;
    else if (idp_reg == 94)
        reg_idx = 15;
    else
    {
        char buf[MAXSTR];
        ::qsnprintf(buf, MAXSTR, "reg: %d\n", idp_reg);
        warning("SEND THIS MESSAGE TO you@mail.com:\n%s\n", buf);
        return 0;
    }
    return reg_idx;
}

#ifdef _DEBUG
static const char* const optype_names[] =
{
    "o_void",
    "o_reg",
    "o_mem",
    "o_phrase",
    "o_displ",
    "o_imm",
    "o_far",
    "o_near",
    "o_idpspec0",
    "o_idpspec1",
    "o_idpspec2",
    "o_idpspec3",
    "o_idpspec4",
    "o_idpspec5",
};

static const char* const dtyp_names[] =
{
    "dt_byte",
    "dt_word",
    "dt_dword",
    "dt_float",
    "dt_double",
    "dt_tbyte",
    "dt_packreal",
    "dt_qword",
    "dt_byte16",
    "dt_code",
    "dt_void",
    "dt_fword",
    "dt_bitfild",
    "dt_string",
    "dt_unicode",
    "dt_3byte",
    "dt_ldbl",
    "dt_byte32",
    "dt_byte64",
};

static void print_insn(insn_t *insn)
{
    if (my_dbg)
    {
        msg("cs=%x, ", insn->cs);
        msg("ip=%x, ", insn->ip);
        msg("ea=%x, ", insn->ea);
        msg("itype=%x, ", insn->itype);
        msg("size=%x, ", insn->size);
        msg("auxpref=%x, ", insn->auxpref);
        msg("segpref=%x, ", insn->segpref);
        msg("insnpref=%x, ", insn->insnpref);
        msg("insnpref=%x, ", insn->insnpref);

        msg("flags[");
        if (insn->flags & INSN_MACRO)
            msg("INSN_MACRO|");
        if (insn->flags & INSN_MODMAC)
            msg("OF_OUTER_DISP");
        msg("]\n");
    }
}

static void print_op(ea_t ea, op_t *op)
{
    if (my_dbg)
    {
        msg("type[%s], ", optype_names[op->type]);

        msg("flags[");
        if (op->flags & OF_NO_BASE_DISP)
            msg("OF_NO_BASE_DISP|");
        if (op->flags & OF_OUTER_DISP)
            msg("OF_OUTER_DISP|");
        if (op->flags & PACK_FORM_DEF)
            msg("PACK_FORM_DEF|");
        if (op->flags & OF_NUMBER)
            msg("OF_NUMBER|");
        if (op->flags & OF_SHOW)
            msg("OF_SHOW");
        msg("], ");

        msg("dtyp[%s], ", dtyp_names[op->dtype]);

        if (op->type == o_reg)
            msg("reg=%x, ", op->reg);
        else if (op->type == o_displ || op->type == o_phrase)
            msg("phrase=%x, ", op->phrase);
        else
            msg("reg_phrase=%x, ", op->phrase);

        msg("addr=%x, ", op->addr);

        msg("value=%x, ", op->value);

        msg("specval=%x, ", op->specval);

        msg("specflag1=%x, ", op->specflag1);
        msg("specflag2=%x, ", op->specflag2);
        msg("specflag3=%x, ", op->specflag3);
        msg("specflag4=%x, ", op->specflag4);

        msg("refinfo[");

        opinfo_t buf;

        if (get_opinfo(&buf, ea, op->n, op->flags))
        {
            msg("target=%x, ", buf.ri.target);
            msg("base=%x, ", buf.ri.base);
            msg("tdelta=%x, ", buf.ri.tdelta);

            msg("flags[");
            if (buf.ri.flags & REFINFO_TYPE)
                msg("REFINFO_TYPE|");
            if (buf.ri.flags & REFINFO_RVAOFF)
                msg("REFINFO_RVAOFF|");
            if (buf.ri.flags & REFINFO_PASTEND)
                msg("REFINFO_PASTEND|");
            if (buf.ri.flags & REFINFO_CUSTOM)
                msg("REFINFO_CUSTOM|");
            if (buf.ri.flags & REFINFO_NOBASE)
                msg("REFINFO_NOBASE|");
            if (buf.ri.flags & REFINFO_SUBTRACT)
                msg("REFINFO_SUBTRACT|");
            if (buf.ri.flags & REFINFO_SIGNEDOP)
                msg("REFINFO_SIGNEDOP");
            msg("]");
        }
        msg("]\n");
    }
}
#endif

typedef const regval_t &(idaapi *getreg_func_t)(const char *name, const regval_t *regvalues);

static ssize_t idaapi hook_idp(void *user_data, int notification_code, va_list va)
{
    switch (notification_code)
    {
    case processor_t::ev_get_idd_opinfo:
    {
        idd_opinfo_t * opinf = va_arg(va, idd_opinfo_t *);
        ea_t ea = va_arg(va, ea_t);
        int n = va_arg(va, int);
        int thread_id = va_arg(va, int);
        getreg_func_t getreg = va_arg(va, getreg_func_t);
        const regval_t *regvalues = va_arg(va, const regval_t *);

        opinf->ea = BADADDR;
        opinf->debregidx = 0;
        opinf->modified = false;
        opinf->value.ival = 0;
        opinf->value_size = 4;

        insn_t out;
        if (decode_insn(&out, ea))
        {
            op_t op = out.ops[n];

#ifdef _DEBUG
            print_insn(&out);
#endif

            int size = 0;
            switch (op.dtype)
            {
            case dt_byte:
                size = 1;
                break;
            case dt_word:
                size = 2;
                break;
            default:
                size = 4;
                break;
            }

            opinf->value_size = size;

            switch (op.type)
            {
            case o_mem:
            case o_near:
            case o_imm:
            {
                flags_t flags;

                switch (n)
                {
                case 0: flags = get_optype_flags0(get_flags(ea)); break;
                case 1: flags = get_optype_flags1(get_flags(ea)); break;
                default: flags = 0; break;
                }

                switch (op.type)
                {
                case o_mem:
                case o_near: opinf->ea = op.addr; break;
                case o_imm: opinf->ea = op.value; break;
                }

                opinfo_t info;
                if (get_opinfo(&info, ea, n, flags) != NULL)
                {
                    opinf->ea += info.ri.base;
                }
            } break;
            case o_phrase:
            case o_reg:
            {
                int reg_idx = idp_to_dbg_reg(op.reg);
                regval_t reg = getreg(dbg->registers(reg_idx).name, regvalues);

                if (op.phrase >= 0x10 && op.phrase <= 0x1F || // (A0)..(A7), (A0)+..(A7)+
                    op.phrase >= 0x20 && op.phrase <= 0x27) // -(A0)..-(A7)
                {
                    if (op.phrase >= 0x20 && op.phrase <= 0x27)
                        reg.ival -= size;

                    opinf->ea = (ea_t)reg.ival;

                    switch (size)
                    {
                    case 1:
                    {
                        uint8_t b = 0;
                        dbg->read_memory((ea_t)reg.ival, &b, 1);
                        opinf->value.ival = b;
                    } break;
                    case 2:
                    {
                        uint16_t w = 0;
                        dbg->read_memory((ea_t)reg.ival, &w, 2);
                        w = swap16(w);
                        opinf->value.ival = w;
                    } break;
                    default:
                    {
                        uint32_t l = 0;
                        dbg->read_memory((ea_t)reg.ival, &l, 4);
                        l = swap32(l);
                        opinf->value.ival = l;
                    } break;
                    }
                }
                else
                    opinf->value = reg;

                opinf->debregidx = reg_idx;
            } break;
            case o_displ:
            {
                regval_t main_reg, add_reg;
                int main_reg_idx = idp_to_dbg_reg(op.reg);
                int add_reg_idx = idp_to_dbg_reg(op.specflag1 & 0xF);

                main_reg.ival = 0;
                add_reg.ival = 0;
                if (op.specflag2 & 0x10)
                {
                    add_reg = getreg(dbg->registers(add_reg_idx).name, regvalues);
                    if (op.specflag1 & 0x10)
                    {
                        add_reg.ival &= 0xFFFF;
                        add_reg.ival = (uint64)((int16_t)add_reg.ival);
                    }
                }

                if (main_reg_idx != 16)
                    main_reg = getreg(dbg->registers(main_reg_idx).name, regvalues);

                ea_t addr = (ea_t)main_reg.ival + op.addr + (ea_t)add_reg.ival;
                opinf->ea = addr;

                switch (size)
                {
                case 1:
                {
                    uint8_t b = 0;
                    dbg->read_memory(addr, &b, 1);
                    opinf->value.ival = b;
                } break;
                case 2:
                {
                    uint16_t w = 0;
                    dbg->read_memory(addr, &w, 2);
                    w = swap16(w);
                    opinf->value.ival = w;
                } break;
                default:
                {
                    uint32_t l = 0;
                    dbg->read_memory(addr, &l, 4);
                    l = swap32(l);
                    opinf->value.ival = l;
                } break;
                }
            } break;
            }

            opinf->ea &= 0xFFFFFF;

            return 1;
        }
    } break;
    default:
    {
#ifdef _DEBUG
        if (my_dbg)
        {
            msg("msg = %d\n", notification_code);
        }
#endif
    } break;
    }
    return 0;
}

//--------------------------------------------------------------------------
static void print_version()
{
    static const char format[] = NAME " debugger plugin v%s;\nAuthor: Dr. MefistO.";
    info(format, VERSION);
    msg(format, VERSION);
}

//--------------------------------------------------------------------------
// Initialize debugger plugin
static int idaapi init(void)
{
    if (ph.id == PLFM_68K)
    {
        dbg = &debugger;
        plugin_inited = true;
        my_dbg = false;

        hook_to_notification_point(HT_IDP, hook_idp, NULL);

        print_version();
        return PLUGIN_KEEP;
    }
    return PLUGIN_SKIP;
}

//--------------------------------------------------------------------------
// Terminate debugger plugin
static void idaapi term(void)
{
    if (plugin_inited)
    {
        unhook_from_notification_point(HT_IDP, hook_idp);

        plugin_inited = false;
    }
}

//--------------------------------------------------------------------------
// The plugin method - usually is not used for debugger plugins
static bool idaapi run(size_t arg)
{
    return false;
}

//--------------------------------------------------------------------------
char comment[] = NAME " debugger plugin by Dr. MefistO.";

char help[] =
NAME " debugger plugin by Dr. MefistO.\n"
"\n"
"This module lets you debug Genesis roms in IDA.\n";

//--------------------------------------------------------------------------
//
//      PLUGIN DESCRIPTION BLOCK
//
//--------------------------------------------------------------------------
plugin_t PLUGIN =
{
    IDP_INTERFACE_VERSION,
    PLUGIN_PROC | PLUGIN_DBG | PLUGIN_MOD, // plugin flags
    init, // initialize

    term, // terminate. this pointer may be NULL.

    run, // invoke plugin

    comment, // long comment about the plugin
             // it could appear in the status line
             // or as a hint

    help, // multiline help about the plugin

    NAME " debugger plugin", // the preferred short name of the plugin

    "" // the preferred hotkey to run the plugin
};

Теперь давайте разбираться, а заодно и писать код.


Реализация отладчика


Переменная dbg_req у нас будет хранить указатель на расшареную с ядром отладчика память. Именно в неё мы будем отправлять запросы, и принимать из неё ответы.


Функции pause_execution(), continue_execution() и stop_debugging() нужны для управления процессом отладки.


eventlist_t g_events представляет из себя список событий отладчика, которые будет ожидать IDA в ответ на какие-то наши действия (например, старт/остановка эмуляции, сработавший бряк).


Ну а пополнять этот список будет events_thread, который будет следить за наличием событий отладчика в расшареной памяти, и преобразовывать их в соответствующие события IDA.


Напишем функцию finish_execution(), которая будет просто завершать поток ожидания отладочных событий:


static void finish_execution()
{
    if (events_thread != NULL)
    {
        qthread_join(events_thread);
        qthread_free(events_thread);
        qthread_kill(events_thread);
        events_thread = NULL;
    }
}

Так, с этим разобрались. Теперь займёмся описанием регистров.
Информация о регистре представляет из себя структуру следующего вида:


struct register_info_t
{
  const char *name;
  uint32 flags;
  register_class_t register_class;
  op_dtype_t dtype;
  const char *const *bit_strings;
  uval_t default_bit_strings_mask;
};

Поле name — это текстовое имя регистра. При том в разных группах регистров не может быть одинаковых имён. Например, если требуется отобразить регистр PC от двух разных процессоров (а в приставке Sega Mega Drive их два: Motorola 68000 и Z80), то придётся переименовывать.


Поле flags может содержать один или несколько следующих флагов:


#define REGISTER_READONLY 0x0001      ///< the user can't modify the current value of this register
#define REGISTER_IP       0x0002      ///< instruction pointer
#define REGISTER_SP       0x0004      ///< stack pointer
#define REGISTER_FP       0x0008      ///< frame pointer
#define REGISTER_ADDRESS  0x0010      ///< may contain an address
#define REGISTER_CS       0x0020      ///< code segment
#define REGISTER_SS       0x0040      ///< stack segment
#define REGISTER_NOLF     0x0080      ///< displays this register without returning to the next line
                                      ///< allowing the next register to be displayed to its right (on the same line)
#define REGISTER_CUSTFMT  0x0100      ///< register should be displayed using a custom data format.
                                      ///< the format name is in bit_strings[0]
                                      ///< the corresponding ::regval_t will use ::bytevec_t

Понятно, что объединять REGISTER_IP и REGISTER SP нельзя, но можно указать, что поле содержит адрес с помощью флага REGISTER_ADDRESS.


register_class — это число-маска группы реализованных у вас регистров. Например, у меня были добавлены следующие три:


#define RC_GENERAL (1 << 0)
#define RC_VDP (1 << 1)
#define RC_Z80 (1 << 2)

dtype представляет из себя указание на размер регистра. Варианты следующие:


#define dt_byte         0       ///< 8 bit
#define dt_word         1       ///< 16 bit
#define dt_dword        2       ///< 32 bit
#define dt_float        3       ///< 4 byte
#define dt_double       4       ///< 8 byte
#define dt_tbyte        5       ///< variable size (\ph{tbyte_size})
#define dt_packreal     6       ///< packed real format for mc68040
#define dt_qword        7       ///< 64 bit
#define dt_byte16       8       ///< 128 bit
#define dt_code         9       ///< ptr to code (not used?)
#define dt_void         10      ///< none
#define dt_fword        11      ///< 48 bit
#define dt_bitfild      12      ///< bit field (mc680x0)
#define dt_string       13      ///< pointer to asciiz string
#define dt_unicode      14      ///< pointer to unicode string
#define dt_ldbl         15      ///< long double (which may be different from tbyte)
#define dt_byte32       16      ///< 256 bit
#define dt_byte64       17      ///< 512 bit

Собственно, мне понадобятся только dt_word, dt_dword.


Поле bit_strings нужно, если, например, вы хотите вывести какой-то регистр в виде его отдельных битов. В частности, это может быть использовано для регистра флагов: Negative, Overflow, Zero, Carry и т.д. Пример:


static const char *const SRReg[] =
{
    "C",
    "V",
    "Z",
    "N",
    "X",
    NULL,
    NULL,
    NULL,
    "I",
    "I",
    "I",
    NULL,
    NULL,
    "S",
    NULL,
    "T"
};

Биты начинаются сверху вниз (от младшего к старшему). Если значение бита выводить не нужно, вместо имени указываем NULL. Если в регистре несколько битов принадлежат одному флагу, указываем одно и то же имя нужное количество раз.


Ну и последнее поле default_bit_strings_mask — битовая маска, которая будет применяться перед получением значений битов регистра.


Вот пример моей реализации списка регистров для Sega Mega Drive (я включил регистры M68K, Z80 и VDP, а также парочку кастомных):


Описание регистров для отладчика
register_info_t registers[] =
{
    { "D0", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D1", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D2", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D3", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D4", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D5", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D6", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "D7", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },

    { "A0", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A1", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A2", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A3", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A4", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A5", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A6", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "A7", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },

    { "PC", REGISTER_ADDRESS | REGISTER_IP, RC_GENERAL, dt_dword, NULL, 0 },
    { "SR", NULL, RC_GENERAL, dt_word, SRReg, 0xFFFF },

    { "SP", REGISTER_ADDRESS | REGISTER_SP, RC_GENERAL, dt_dword, NULL, 0 },
    { "USP", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },
    { "ISP", REGISTER_ADDRESS, RC_GENERAL, dt_dword, NULL, 0 },

    { "PPC", REGISTER_ADDRESS | REGISTER_READONLY, RC_GENERAL, dt_dword, NULL, 0 },
    { "IR", NULL, RC_GENERAL, dt_dword, NULL, 0 },

    // VDP Registers
    { "v00", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v01", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v02", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v03", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v04", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v05", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v06", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v07", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v08", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v09", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v0A", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v0B", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v0C", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v0D", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v0E", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v0F", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v10", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v11", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v12", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v13", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v14", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v15", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v16", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v17", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v18", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v19", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v1A", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v1B", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v1C", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v1D", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v1E", NULL, RC_VDP, dt_byte, NULL, 0 },
    { "v1F", NULL, RC_VDP, dt_byte, NULL, 0 },

    { "DMA_LEN", REGISTER_READONLY, RC_VDP, dt_word, NULL, 0 },
    { "DMA_SRC", REGISTER_ADDRESS | REGISTER_READONLY, RC_VDP, dt_dword, NULL, 0 },
    { "VDP_DST", REGISTER_ADDRESS | REGISTER_READONLY, RC_VDP, dt_dword, NULL, 0 },

    // Z80 regs
    { "zPC", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zSP", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zAF", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zBC", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zDE", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zHL", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zIX", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zIY", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zWZ", NULL, RC_Z80, dt_dword, NULL, 0 },

    { "zAF2", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zBC2", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zDE2", NULL, RC_Z80, dt_dword, NULL, 0 },
    { "zHL2", NULL, RC_Z80, dt_dword, NULL, 0 },

    { "zR", NULL, RC_Z80, dt_byte, NULL, 0 },
    { "zR2", NULL, RC_Z80, dt_byte, NULL, 0 },
    { "zIFFI1", NULL, RC_Z80, dt_byte, NULL, 0 },
    { "zIFFI2", NULL, RC_Z80, dt_byte, NULL, 0 },
    { "zHALT", NULL, RC_Z80, dt_byte, NULL, 0 },
    { "zIM", NULL, RC_Z80, dt_byte, NULL, 0 },
    { "zI", NULL, RC_Z80, dt_byte, NULL, 0 },
};

Далее идёт список register_classes[], в котором мы должны указать текстовые имена групп регистров. Их можно будет открыть в отдельных окнах при отладке.



Вот моя реализация (последним элементом должен быть NULL):


static const char *register_classes[] =
{
    "General Registers",
    "VDP Registers",
    "Z80 Registers",
    NULL
};

Колбэки, необходимые IDA


init_debugger()


static bool idaapi init_debugger(const char *hostname, int portnum, const char *password)
{
    set_processor_type(ph.psnames[0], SETPROC_LOADER); // reset proc to "M68000"
    return true;
}

Так как в IDA реализовано несколько версий мотороловского процессора, я принудительно сбрасываю на самый первый в списке.


term_debugger()


static bool idaapi term_debugger(void)
{
    dbg_req->is_ida = 0;
    close_shared_mem(&dbg_req);
    return true;
}

Забавный факт: функция init_debugger() вызывается один раз при первом старте эмуляции за сессию, а функция term_debugger() — каждый раз при завершении процесса отладки. Поэтому открытую расшареную память я закрываю именно здесь.


Обе функции должны возвращать true в случае успеха.


process_get_info()


static int idaapi process_get_info(procinfo_vec_t *procs)
{
    return 0;
}

Если во время отладки у вас происходит работа с несколькими процессами, необходимо реализовать данный колбэк, который будет сообщать IDA информацию по каждому из них, а именно PID и имя.


Мне данная функция не нужна, поэтому я возвращаю 0.


check_debugger_events() — не колбэк, но очень важен


Собственно, представляет из себя поток, ожидающий отладочных событий. Здесь необходимо рассказать более детально.


При запуске отладки, первым событием, которое ожидает получить IDA, должно быть PROCESS_START. Если первым придёт, например, сообщение о паузе эмуляции, IDA просто упадёт.


После этого уже можно принимать другие сообщения. Основные используемые это:


  • PROCESS_SUSPEND — эмуляция приостановлена, и теперь пользователь отладчика может смотреть или изменять значения регистров, читать или модифицировать память.
  • BREAKPOINT — это сообщение IDA может принять, когда вы хотите сказать ей — сработал бряк, как хардварный, так и софтварный. Почему именно может? Потому что для остановки отладки достаточно принять PROCESS_SUSPEND, а всё остальное лишь детали остановки, которые можно сообщить IDA
  • STEP — можно получить это сообщение, чтобы информировать IDA о том, что цель Step Into или Step Over достигнута, но, опять же, можно отделаться и сообщением PROCESS_SUSPEND
  • PROCESS_EXIT — должно быть принято IDA после остановки отлаживаемого процесса, либо процесса отладки. Если вы нажали кнопку Stop в интерфейсе отладчика, IDA будет ожидать данное сообщение до тех пор, пока оно не придёт, либо наступит конец света, либо вы не убъёте её процесс вручную.

Сама структура объекта события выглядит следующим образом:


struct debug_event_t
{
  event_id_t eid;          ///< Event code (used to decipher 'info' union)
  pid_t pid;               ///< Process where the event occurred
  thid_t tid;              ///< Thread where the event occurred
  ea_t ea;                 ///< Address where the event occurred
  bool handled;            ///< Is event handled by the debugger?.
                           ///< (from the system's point of view)
                           ///< Meaningful for ::EXCEPTION events
  union
  {
    module_info_t modinfo; ///< ::PROCESS_START, ::PROCESS_ATTACH, ::LIBRARY_LOAD
    int exit_code;         ///< ::PROCESS_EXIT, ::THREAD_EXIT
    char info[MAXSTR];     ///< ::LIBRARY_UNLOAD (unloaded library name)
                           ///< ::INFORMATION (will be displayed in the
                           ///<              messages window if not empty)
    e_breakpoint_t bpt;    ///< ::BREAKPOINT
    e_exception_t exc;     ///< ::EXCEPTION
  };
};

eid — это те самые типы событий, описанные мной выше
pid, tid — собственно, Process ID и Thread ID, в котором произошло событие
ea — адрес, где произошло событие
handled — реальное назначение этого параметра мне неизвестно, но, судя по тексту из IDA SDK, используется для указания того, было ли исключение обработано системой (а зачем?). Я устанавливаю в true


Далее идут поля, которые необходимо заполнять в зависимости от типа события.


Для PROCESS_START я указываю имя процесса эмулятора (можно придумать), ImageBase, по которому грузится ром, размер, и новый ImageBase, если он отличается от того, что был указан при создании IDB. Если при старте процесса ничего из этого неизвестно, просто указываем ноли, либо BADADDR:


case DBG_EVT_STARTED:
    ev.eid = PROCESS_START;
    ev.pid = 1;
    ev.tid = 1;
    ev.ea = BADADDR;
    ev.handled = true;

    ev.modinfo.name[0] = 'G';
    ev.modinfo.name[1] = 'P';
    ev.modinfo.name[2] = 'G';
    ev.modinfo.name[3] = 'X';
    ev.modinfo.name[4] = '\0';
    ev.modinfo.base = 0;
    ev.modinfo.size = 0;
    ev.modinfo.rebase_to = BADADDR;

    g_events.enqueue(ev, IN_FRONT);
    break;

Для BREAKPOINT в поле bpt указываем hardware — и kernel-адрес сработавшего бряка. Если пришло такое событие с указанием адреса точки останова, о которой IDA не знает, в окно лога будет выдано сообщение о неизвестном брейкпоинте.


case DBG_EVT_BREAK:
    ev.pid = 1;
    ev.tid = 1;
    ev.ea = dbg_event->pc;
    ev.handled = true;
    ev.eid = BREAKPOINT;
    ev.bpt.hea = ev.bpt.kea = ev.ea;
    g_events.enqueue(ev, IN_BACK);
    break;

Для события PROCESS_EXIT достаточно указать exit_code.


start_process()


Здесь происходит старт эмуляции, инициализация и запуск процессов, которые вы собираетесь отлаживать, либо ожидание подключения к серверу отладки.


В моей реализации я очищаю список событий, выдаю диалоговое окошко с ожиданием подключения к отладчику (а точнее создания ядром расшареной памяти), создаю поток ожидания отладочных событий, и посылаю запрос на подключение к процессу отладки.


Возвращаем 1 в случае успеха.


rebase_if_required_to()


У меня не реализовано, т.к. база у рома всегда одна, но, типичная реализация выглядит вот так:


Реализация rebase_if_required_to
static void idaapi rebase_if_required_to(ea_t new_base)
{
    ea_t currentbase = new_base;
    ea_t imagebase = inf.startIP;

    if (imagebase != currentbase)
    {
        adiff_t delta = currentbase - imagebase;

        int code = rebase_program(delta, MSF_FIXONCE);
        if (code != MOVE_SEGM_OK)
        {
            msg("Failed to rebase program, error code %d\n", code);
            warning("IDA failed to rebase the program.\n"
                "Most likely it happened because of the debugger\n"
                "segments created to reflect the real memory state.\n\n"
                "Please stop the debugger and rebase the program manually.\n"
                "For that, please select the whole program and\n"
                "use Edit, Segments, Rebase program with delta 0x%08a",
                delta);
        }
    }
}

prepare_to_pause_process()


Когда вы нажимаете кнопку Pause в IDA, происходит вызов данной функции.
Возвращаем 1 в случае успешной приостановки процесса отладки.


get_debug_event()


Собственно, сердце отладчика IDA, которое ожидает поступающих отладочных событий. Вызывается с определённой (какой?) периодичностью. Если событие поступило, заполняем структуру debug_event_t входного аргумента *event, и возвращаем:


  • GDE_ONE_EVENT, если было получено одно событие, и больше событий пока нет
  • GDE_MANY_EVENTS, если в очереди ещё есть события, ожидающие обработки
  • GDE_NO_EVENT, если событий в очереди нет

static gdecode_t idaapi get_debug_event(debug_event_t *event, int timeout_ms)
{
    while (true)
    {
        // are there any pending events?
        if (g_events.retrieve(event))
        {
            return g_events.empty() ? GDE_ONE_EVENT : GDE_MANY_EVENTS;
        }
        if (g_events.empty())
            break;
    }
    return GDE_NO_EVENT;
}

continue_after_event()


Если честно, это самый неудачный и плохо спроектированный узел архитектуры плагина-отладчика в IDA. Сейчас вы узнаете почему.


В общем, этот колбэк вызывается, после того, когда требуется что-то делать после пришедшего ранее отладочного сообщения. Вот вам реальный пример:


  1. Пришло событие STEP
  2. IDA становится на паузу и позволяет нам творить всё что угодно с регистрами, памятью и т.п.
  3. Далее мы нажимаем, например, снова Step In
  4. Происходит вызов continue_after_event(), в который передаётся информация о последнем обработанном нами отладочном событии. В данном случае — STEP
  5. IDA получает событие STEP

Вроде бы всё хорошо, но, при реальном использовании отладчика информация о том, какое же там у нас было последнее событие, абсолютно не нужна!
Куда важнее знать, о том, что пользователь в пункте 3 нажал именно Step Into, Step Over, или F9, чтобы продолжить эмуляцию.


Для случаев со STEP, BREAKPOINT или PROCESS_SUSPEND, если пользователь захотел продолжить эмуляцию по F9, логично будет снять отладку с паузы вызовом continue_execution(). Определить нажатие F9 или Run to можно следующим костыльным способом:


    dbg_notification_t req = get_running_notification();
    if (req == dbg_null || req == dbg_run_to)
        continue_execution();

stopped_at_debug_event()


Никогда его не использовал, но реализация требуется. Достаточно пустой функции.


thread_suspend(), thread_continue()


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


set_step_mode()


Важный колбэк, отвечающий за отправку ядру отладчика команд Step Into, Step Over, Step Out. У меня последний вариант шагания не реализован (это задаётся флагами в структуре debugger_t, о которой я расскажу позже).
Возвращаем 1 в случае успеха.


static int idaapi set_step_mode(thid_t tid, resume_mode_t resmod)
{
    switch (resmod)
    {
    case RESMOD_INTO:
        send_dbg_request(dbg_req, REQ_STEP_INTO);
        break;
    case RESMOD_OVER:
        send_dbg_request(dbg_req, REQ_STEP_OVER);
        break;
    }

    return 1;
}

read_registers()


Когда IDA получает сообщение STEP, BREAKPOINT или PROCESS_SUSPEND (т.е. узнаёт, что отладка приостановлена), происходит вызов колбэка для того, чтобы узнать значения регистров для отображения.


Значимые входные аргументы это:


  • clsmask — помните в начале мы задавали маски групп регистров? Вот это они. Может содержать несколько групп за раз для получения
  • values — массив значений регистров, который мы должны заполнить. Индексы регистров соответствуют позициям регистров в массиве registers[].

Пример заполнения массива полученными значениями регистров:


Реализация read_registers()
static int idaapi read_registers(thid_t tid, int clsmask, regval_t *values)
{
    if (!dbg_req)
        return 0;

    if (clsmask & RC_GENERAL)
    {
        dbg_req->regs_data.type = REG_TYPE_M68K;
        send_dbg_request(dbg_req, REQ_GET_REGS);

        regs_68k_data_t *reg_vals = &dbg_req->regs_data.regs_68k;

        values[REG_68K_D0].ival = reg_vals->d0;
        values[REG_68K_D1].ival = reg_vals->d1;
        values[REG_68K_D2].ival = reg_vals->d2;
        values[REG_68K_D3].ival = reg_vals->d3;
        values[REG_68K_D4].ival = reg_vals->d4;
        values[REG_68K_D5].ival = reg_vals->d5;
        values[REG_68K_D6].ival = reg_vals->d6;
        values[REG_68K_D7].ival = reg_vals->d7;

        values[REG_68K_A0].ival = reg_vals->a0;
        values[REG_68K_A1].ival = reg_vals->a1;
        values[REG_68K_A2].ival = reg_vals->a2;
        values[REG_68K_A3].ival = reg_vals->a3;
        values[REG_68K_A4].ival = reg_vals->a4;
        values[REG_68K_A5].ival = reg_vals->a5;
        values[REG_68K_A6].ival = reg_vals->a6;
        values[REG_68K_A7].ival = reg_vals->a7;

        values[REG_68K_PC].ival = reg_vals->pc & 0xFFFFFF;
        values[REG_68K_SR].ival = reg_vals->sr;
        values[REG_68K_SP].ival = reg_vals->sp & 0xFFFFFF;
        values[REG_68K_PPC].ival = reg_vals->ppc & 0xFFFFFF;
        values[REG_68K_IR].ival = reg_vals->ir;
    }

    if (clsmask & RC_VDP)
    {
        dbg_req->regs_data.type = REG_TYPE_VDP;
        send_dbg_request(dbg_req, REQ_GET_REGS);

        vdp_regs_t *vdp_regs = &dbg_req->regs_data.vdp_regs;

        for (int i = 0; i < sizeof(vdp_regs->regs_vdp) / sizeof(vdp_regs->regs_vdp[0]); ++i)
        {
            values[REG_VDP_00 + i].ival = vdp_regs->regs_vdp[i];
        }

        values[REG_VDP_DMA_LEN].ival = vdp_regs->dma_len;
        values[REG_VDP_DMA_SRC].ival = vdp_regs->dma_src;
        values[REG_VDP_DMA_DST].ival = vdp_regs->dma_dst;
    }

    if (clsmask & RC_Z80)
    {
        dbg_req->regs_data.type = REG_TYPE_Z80;
        send_dbg_request(dbg_req, REQ_GET_REGS);

        regs_z80_data_t *z80_regs = &dbg_req->regs_data.regs_z80;

        for (int i = 0; i < (REG_Z80_I - REG_Z80_PC + 1); ++i)
        {
            if (i >= 0 && i <= 12) // PC <-> HL2
            {
                values[REG_Z80_PC + i].ival = ((unsigned int *)&z80_regs->pc)[i];
            }
            else if (i >= 13 && i <= 19) // R <-> I
            {
                values[REG_Z80_PC + i].ival = ((unsigned char *)&z80_regs->r)[i - 13];
            }
        }
    }

    return 1;
}

Возвращаем 1 в случае успеха.


write_register()


Меняем значение во время отладки — происходит вызов этого колбэка. На вход нам подаётся номер регистра в массиве регистров и его значение.


Пример реализации:


Реализация write_register()
static int idaapi write_register(thid_t tid, int regidx, const regval_t *value)
{
    if (regidx >= REG_68K_D0 && regidx <= REG_68K_D7)
    {
        set_reg(REG_TYPE_M68K, regidx - REG_68K_D0, (uint32)value->ival);
    }
    else if (regidx >= REG_68K_A0 && regidx <= REG_68K_A7)
    {
        set_reg(REG_TYPE_M68K, regidx - REG_68K_A0, (uint32)value->ival);
    }
    else if (regidx == REG_68K_PC)
    {
        set_reg(REG_TYPE_M68K, REG_68K_PC, (uint32)value->ival & 0xFFFFFF);
    }
    else if (regidx == REG_68K_SR)
    {
        set_reg(REG_TYPE_M68K, REG_68K_SR, (uint16)value->ival);
    }
    else if (regidx == REG_68K_SP)
    {
        set_reg(REG_TYPE_M68K, REG_68K_SP, (uint32)value->ival & 0xFFFFFF);
    }
    else if (regidx == REG_68K_USP)
    {
        set_reg(REG_TYPE_M68K, REG_68K_USP, (uint32)value->ival & 0xFFFFFF);
    }
    else if (regidx == REG_68K_ISP)
    {
        set_reg(REG_TYPE_M68K, REG_68K_ISP, (uint32)value->ival & 0xFFFFFF);
    }
    else if (regidx >= REG_VDP_00 && regidx <= REG_VDP_1F)
    {
        set_reg(REG_TYPE_VDP, regidx - REG_VDP_00, value->ival & 0xFF);
    }
    else if (regidx >= REG_Z80_PC && regidx <= REG_Z80_I)
    {
        set_reg(REG_TYPE_Z80, regidx - REG_Z80_PC, value->ival);
    }

    return 1;
}

Возвращаем 1 в случае успеха.


get_memory_info()


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


Если на время отладки вы хотите создать какие-то сегменты, которые по её окончании не нужны, именно здесь это и нужно делать. Пример добавления отладочных сегментов:


    info.name = "DBG_VDP_VRAM";
    info.start_ea = 0xD00000;
    info.end_ea = info.start_ea + 0x10000;
    info.bitness = 1;
    areas.push_back(info);

    info.name = "DBG_VDP_CRAM";
    info.start_ea = info.end_ea;
    info.end_ea = info.start_ea + 0x10000;
    info.bitness = 1;
    areas.push_back(info);

    info.name = "DBG_VDP_VSRAM";
    info.start_ea = info.end_ea;
    info.end_ea = info.start_ea + 0x10000;
    info.bitness = 1;
    areas.push_back(info);

Возвращаем 1 в случае успеха.


read_memory()


Ещё один важный колбэк. Любой адрес среди тех, что доступны в сегментах, которые не отмечены как XTRN, IDA может попросить прочитать.
На вход колбэка подаётся адрес ea, для которого требуется чтение, размер памяти size, который нужно прочитать, и указатель на память buffer, в которую нужно будет записать содержимое памяти по запрашиваемому адресу.


Пример реализации read_memory()
static ssize_t idaapi read_memory(ea_t ea, void *buffer, size_t size)
{
    if ((ea >= 0xA00000 && ea < 0xA0FFFF))
    {
        dbg_req->mem_data.address = ea;
        dbg_req->mem_data.size = size;
        send_dbg_request(dbg_req, REQ_READ_Z80);

        memcpy(buffer, &dbg_req->mem_data.z80_ram[ea & 0x1FFF], size);
        // Z80
    }
    else if (ea < MAXROMSIZE)
    {
        dbg_req->mem_data.address = ea;
        dbg_req->mem_data.size = size;
        send_dbg_request(dbg_req, REQ_READ_68K_ROM);

        memcpy(buffer, &dbg_req->mem_data.m68k_rom[ea], size);
    }
    else if ((ea >= 0xFF0000 && ea < 0x1000000))
    {
        dbg_req->mem_data.address = ea;
        dbg_req->mem_data.size = size;
        send_dbg_request(dbg_req, REQ_READ_68K_RAM);

        memcpy(buffer, &dbg_req->mem_data.m68k_ram[ea & 0xFFFF], size);
        // RAM
    }

    return size;
}

write_memory()


Этот колбэк вызывается, когда вы что-то модицифируете в сегментах во время отладки: патчите ром, меняете содержимое RAM, или сегментов, которые были созданы на время отладки.


В моём отладчике данный функционал не нужен, поэтому я возвращаю 0. Иначе возвращаем 1.


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


is_ok_bpt()


Данная функция вызывается каждый раз при установке бряка с целью проверить допустим ли он с длиной len и типом type по адресу ea.


Если бряк разрешён, возвращаем BPT_OK, иначе — BPT_BAD_TYPE.


update_bpts()


Функция, которая предназначена для синхронизации списка бряков IDA и ядра отладчика. Логично предположить, что колбэк вызывается сразу же после установки вами бряка, но нет. Он происходит только после нажатия F9 (Continue).


Принцип обработки бряков в этой функции следующий:


  1. На вход подаётся массив bpts, содержащий информацию о брейкпоинтах, а именно: адресе, типе, размере, а также о количестве брейкпоинтов для добавления nadd и удаления ndel, которые идут друг за другом в массиве.
  2. После того, как бряк был добавлен или удалён, помечаем bpts[i].code как BPT_OK.

Колбэк должен вернуть количество добавленых + удалённых бряков. Обычно это nadd + ndel.


Реализация update_bpts()
static int idaapi update_bpts(update_bpt_info_t *bpts, int nadd, int ndel)
{
    for (int i = 0; i < nadd; ++i)
    {
        ea_t start = bpts[i].ea;
        ea_t end = bpts[i].ea + bpts[i].size - 1;

        bpt_data_t *bpt_data = &dbg_req->bpt_data;

        switch (bpts[i].type)
        {
        case BPT_EXEC:
            bpt_data->type = BPT_M68K_E;
            break;
        case BPT_READ:
            bpt_data->type = BPT_M68K_R;
            break;
        case BPT_WRITE:
            bpt_data->type = BPT_M68K_W;
            break;
        case BPT_RDWR:
            bpt_data->type = BPT_M68K_RW;
            break;
        }

        bpt_data->address = start;
        bpt_data->width = bpts[i].size;
        send_dbg_request(dbg_req, REQ_ADD_BREAK);

        bpts[i].code = BPT_OK;
    }

    for (int i = 0; i < ndel; ++i)
    {
        ea_t start = bpts[nadd + i].ea;
        ea_t end = bpts[nadd + i].ea + bpts[nadd + i].size - 1;

        bpt_data_t *bpt_data = &dbg_req->bpt_data;

        switch (bpts[nadd + i].type)
        {
        case BPT_EXEC:
            bpt_data->type = BPT_M68K_E;
            break;
        case BPT_READ:
            bpt_data->type = BPT_M68K_R;
            break;
        case BPT_WRITE:
            bpt_data->type = BPT_M68K_W;
            break;
        case BPT_RDWR:
            bpt_data->type = BPT_M68K_RW;
            break;
        }

        bpt_data->address = start;
        send_dbg_request(dbg_req, REQ_DEL_BREAK);

        bpts[nadd + i].code = BPT_OK;
    }

    return (ndel + nadd);
}

Структура debugger_t


Здесь мы указываем колбэки функций, которые у нас реализованы (кстати, некоторые обязательны, иначе IDA просто упадёт без объяснений, а о некоторых, если их нет, скажет, что требуется реализация).


Структура выглядит следующим образом:


Структура debugger_t
struct debugger_t
{
  int version;                        ///< Expected kernel version,
                                      ///<   should be #IDD_INTERFACE_VERSION
  const char *name;                   ///< Short debugger name like win32 or linux
  int id;                             ///< one of \ref DEBUGGER_ID_
/// \defgroup DEBUGGER_ID_ Debugger API module id
/// Used by debugger_t::id
//@{
#define DEBUGGER_ID_X86_IA32_WIN32_USER              0 ///< Userland win32 processes (win32 debugging APIs)
#define DEBUGGER_ID_X86_IA32_LINUX_USER              1 ///< Userland linux processes (ptrace())
#define DEBUGGER_ID_ARM_WINCE_ASYNC                  2 ///< Windows CE ARM (ActiveSync transport)
#define DEBUGGER_ID_X86_IA32_MACOSX_USER             3 ///< Userland MAC OS X processes
#define DEBUGGER_ID_ARM_EPOC_USER                    4 ///< Symbian OS
#define DEBUGGER_ID_ARM_IPHONE_USER                  5 ///< iPhone 1.x
#define DEBUGGER_ID_X86_IA32_BOCHS                   6 ///< BochsDbg.exe 32
#define DEBUGGER_ID_6811_EMULATOR                    7 ///< MC6812 emulator (beta)
#define DEBUGGER_ID_GDB_USER                         8 ///< GDB remote
#define DEBUGGER_ID_WINDBG                           9 ///< WinDBG using Microsoft Debug engine
#define DEBUGGER_ID_X86_DOSBOX_EMULATOR             10 ///< Dosbox MS-DOS emulator
#define DEBUGGER_ID_ARM_LINUX_USER                  11 ///< Userland arm linux
#define DEBUGGER_ID_TRACE_REPLAYER                  12 ///< Fake debugger to replay recorded traces
#define DEBUGGER_ID_ARM_WINCE_TCPIP                 13 ///< Windows CE ARM (TPC/IP transport)
#define DEBUGGER_ID_X86_PIN_TRACER                  14 ///< PIN Tracer module
#define DEBUGGER_ID_DALVIK_USER                     15 ///< Dalvik
//@}

  const char *processor;              ///< Required processor name.
                                      ///< Used for instant debugging to load the correct
                                      ///< processor module

  uint32 flags;                             ///< \ref DBG_FLAG_
/// \defgroup DBG_FLAG_ Debugger module features
/// Used by debugger_t::flags
//@{
#define DBG_FLAG_REMOTE       0x00000001    ///< Remote debugger (requires remote host name unless #DBG_FLAG_NOHOST)
#define DBG_FLAG_NOHOST       0x00000002    ///< Remote debugger with does not require network params (host/port/pass).
                                            ///< (a unique device connected to the machine)
#define DBG_FLAG_FAKE_ATTACH  0x00000004    ///< ::PROCESS_ATTACH is a fake event
                                            ///< and does not suspend the execution
#define DBG_FLAG_HWDATBPT_ONE 0x00000008    ///< Hardware data breakpoints are
                                            ///< one byte size by default
#define DBG_FLAG_CAN_CONT_BPT 0x00000010    ///< Debugger knows to continue from a bpt.
                                            ///< This flag also means that the debugger module
                                            ///< hides breakpoints from ida upon read_memory
#define DBG_FLAG_NEEDPORT     0x00000020    ///< Remote debugger requires port number (to be used with DBG_FLAG_NOHOST)
#define DBG_FLAG_DONT_DISTURB 0x00000040    ///< Debugger can handle only
                                            ///<   get_debug_event(),
                                            ///<   prepare_to_pause_process(),
                                            ///<   exit_process().
                                            ///< when the debugged process is running.
                                            ///< The kernel may also call service functions
                                            ///< (file I/O, map_address, etc)
#define DBG_FLAG_SAFE         0x00000080    ///< The debugger is safe (probably because it just emulates the application
                                            ///< without really running it)
#define DBG_FLAG_CLEAN_EXIT   0x00000100    ///< IDA must suspend the application and remove
                                            ///< all breakpoints before terminating the application.
                                            ///< Usually this is not required because the application memory
                                            ///< disappears upon termination.
#define DBG_FLAG_USE_SREGS    0x00000200    ///< Take segment register values into account (non flat memory)
#define DBG_FLAG_NOSTARTDIR   0x00000400    ///< Debugger module doesn't use startup directory
#define DBG_FLAG_NOPARAMETERS 0x00000800    ///< Debugger module doesn't use commandline parameters
#define DBG_FLAG_NOPASSWORD   0x00001000    ///< Remote debugger doesn't use password
#define DBG_FLAG_CONNSTRING   0x00002000    ///< Display "Connection string" instead of "Hostname" and hide the "Port" field
#define DBG_FLAG_SMALLBLKS    0x00004000    ///< If set, IDA uses 256-byte blocks for caching memory contents.
                                            ///< Otherwise, 1024-byte blocks are used
#define DBG_FLAG_MANMEMINFO   0x00008000    ///< If set, manual memory region manipulation commands
                                            ///< will be available. Use this bit for debugger modules
                                            ///< that can not return memory layout information
#define DBG_FLAG_EXITSHOTOK   0x00010000    ///< IDA may take a memory snapshot at ::PROCESS_EXIT event
#define DBG_FLAG_VIRTHREADS   0x00020000    ///< Thread IDs may be shuffled after each debug event.
                                            ///< (to be used for virtual threads that represent cpus for windbg kmode)
#define DBG_FLAG_LOWCNDS      0x00040000    ///< Low level breakpoint conditions are supported.
#define DBG_FLAG_DEBTHREAD    0x00080000    ///< Supports creation of a separate thread in ida
                                            ///< for the debugger (the debthread).
                                            ///< Most debugger functions will be called from debthread (exceptions are marked below)
                                            ///< The debugger module may directly call only #THREAD_SAFE functions.
                                            ///< To call other functions please use execute_sync().
                                            ///< The debthread significantly increases debugging
                                            ///< speed, especially if debug events occur frequently (to be tested)
#define DBG_FLAG_DEBUG_DLL    0x00100000    ///< Can debug standalone DLLs.
                                            ///< For example, Bochs debugger can debug any snippet of code
#define DBG_FLAG_FAKE_MEMORY  0x00200000    ///< get_memory_info()/read_memory()/write_memory() work with the idb.
                                            ///< (there is no real process to read from, as for the replayer module)
                                            ///< the kernel will not call these functions if this flag is set.
                                            ///< however, third party plugins may call them, they must be implemented.
#define DBG_FLAG_ANYSIZE_HWBPT 0x00400000   ///< The debugger supports arbitrary size hardware breakpoints.
#define DBG_FLAG_TRACER_MODULE 0x00800000   ///< The module is a tracer, not a full featured debugger module
#define DBG_FLAG_PREFER_SWBPTS 0x01000000   ///< Prefer to use software breakpoints
//@}

  const char **register_classes;             ///< Array of register class names
  int register_classes_default;              ///< Mask of default printed register classes
  register_info_t *_registers;               ///< Array of registers. Use registers() to access it
  int registers_size;                        ///< Number of registers

  int memory_page_size;                      ///< Size of a memory page

  const uchar *bpt_bytes;                    ///< Array of bytes for a breakpoint instruction
  uchar bpt_size;                            ///< Size of this array
  uchar filetype;                            ///< for miniidbs: use this value
                                             ///< for the file type after attaching
                                             ///< to a new process
  ushort resume_modes;                       ///< \ref DBG_RESMOD_
/// \defgroup DBG_RESMOD_ Resume modes
/// Used by debugger_t::resume_modes
//@{
#define DBG_RESMOD_STEP_INTO      0x0001     ///< ::RESMOD_INTO is available
#define DBG_RESMOD_STEP_OVER      0x0002     ///< ::RESMOD_OVER is available
#define DBG_RESMOD_STEP_OUT       0x0004     ///< ::RESMOD_OUT is available
#define DBG_RESMOD_STEP_SRCINTO   0x0008     ///< ::RESMOD_SRCINTO is available
#define DBG_RESMOD_STEP_SRCOVER   0x0010     ///< ::RESMOD_SRCOVER is available
#define DBG_RESMOD_STEP_SRCOUT    0x0020     ///< ::RESMOD_SRCOUT is available
#define DBG_RESMOD_STEP_USER      0x0040     ///< ::RESMOD_USER is available
#define DBG_RESMOD_STEP_HANDLE    0x0080     ///< ::RESMOD_HANDLE is available
//@}

#if !defined(_MSC_VER)  // this compiler complains :(
  static const int default_port_number = 23946;
#define DEBUGGER_PORT_NUMBER debugger_t::default_port_number
#else
#define DEBUGGER_PORT_NUMBER 23946
#endif

  /// Initialize debugger.
  /// This function is called from the main thread.
  /// \return success
  bool (idaapi *init_debugger)(const char *hostname, int portnum, const char *password);

  /// Terminate debugger.
  /// This function is called from the main thread.
  /// \return success
  bool (idaapi *term_debugger)(void);

  /// Return information about the running processes.
  /// This function is called from the main thread.
  /// \retval 1  ok
  /// \retval 0  failed
  /// \retval -1 network error
  int (idaapi *get_processes)(procinfo_vec_t *procs);

  /// Start an executable to debug.
  /// This function is called from debthread.
  /// \param path              path to executable
  /// \param args              arguments to pass to executable
  /// \param startdir          current directory of new process
  /// \param dbg_proc_flags    \ref DBG_PROC_
  /// \param input_path        path to database input file.
  ///                          (not always the same as 'path' - e.g. if you're analyzing
  ///                          a dll and want to launch an executable that loads it)
  /// \param input_file_crc32  CRC value for 'input_path'
  /// \retval  1                    ok
  /// \retval  0                    failed
  /// \retval -2                    file not found (ask for process options)
  /// \retval  1 | #CRC32_MISMATCH  ok, but the input file crc does not match
  /// \retval -1                    network error
  int (idaapi *start_process)(const char *path,
                              const char *args,
                              const char *startdir,
                              int dbg_proc_flags,
                              const char *input_path,
                              uint32 input_file_crc32);
/// \defgroup DBG_PROC_ Debug process flags
/// Passed as 'dbg_proc_flags' parameter to debugger_t::start_process
//@{
#define DBG_PROC_IS_DLL 0x01            ///< database contains a dll (not exe)
#define DBG_PROC_IS_GUI 0x02            ///< using gui version of ida
#define DBG_PROC_32BIT  0x04            ///< application is 32-bit
#define DBG_PROC_64BIT  0x08            ///< application is 64-bit
#define DBG_NO_TRACE    0x10            ///< do not trace the application (mac/linux)
#define DBG_HIDE_WINDOW 0x20            ///< application should be hidden on startup (windows)
//@}
#define CRC32_MISMATCH  0x40000000      ///< crc32 mismatch bit (see return values for debugger_t::start_process)

  /// Attach to an existing running process.
  /// event_id should be equal to -1 if not attaching to a crashed process.
  /// This function is called from debthread.
  /// \param pid               process id to attach
  /// \param event_id          event to trigger upon attaching
  /// \param dbg_proc_flags    \ref DBG_PROC_
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *attach_process)(pid_t pid, int event_id, int dbg_proc_flags);

  /// Detach from the debugged process.
  /// May be called while the process is running or suspended.
  /// Must detach from the process in any case.
  /// The kernel will repeatedly call get_debug_event() and until ::PROCESS_DETACH.
  /// In this mode, all other events will be automatically handled and process will be resumed.
  /// This function is called from debthread.
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *detach_process)(void);

  /// Rebase database if the debugged program has been rebased by the system.
  /// This function is called from the main thread.
  void (idaapi *rebase_if_required_to)(ea_t new_base);

  /// Prepare to pause the process.
  /// Normally the next get_debug_event() will pause the process
  /// If the process is sleeping then the pause will not occur
  /// until the process wakes up. The interface should take care of
  /// this situation.
  /// If this function is absent, then it won't be possible to pause the program.
  /// This function is called from debthread.
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *prepare_to_pause_process)(void);

  /// Stop the process.
  /// May be called while the process is running or suspended.
  /// Must terminate the process in any case.
  /// The kernel will repeatedly call get_debug_event() and until ::PROCESS_EXIT.
  /// In this mode, all other events will be automatically handled and process will be resumed.
  /// This function is called from debthread.
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *exit_process)(void);

  /// Get a pending debug event and suspend the process.
  /// This function will be called regularly by IDA.
  /// This function is called from debthread.
  /// IMPORTANT: commdbg does not expect immediately after a BPT-related event
  /// any other event with the same thread/IP - this can cause erroneous
  /// restoring of a breakpoint before resume
  /// (the bug was encountered 24.02.2015 in pc_linux_upx.elf)
  gdecode_t (idaapi *get_debug_event)(debug_event_t *event, int timeout_ms);

  /// Continue after handling the event.
  /// This function is called from debthread.
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *continue_after_event)(const debug_event_t *event);

  /// Set exception handling.
  /// This function is called from debthread or the main thread.
  void (idaapi *set_exception_info)(const exception_info_t *info, int qty);

  /// This function will be called by the kernel each time
  /// it has stopped the debugger process and refreshed the database.
  /// The debugger module may add information to the database if it wants.
  ///
  /// The reason for introducing this function is that when an event line
  /// LOAD_DLL happens, the database does not reflect the memory state yet
  /// and therefore we can't add information about the dll into the database
  /// in the get_debug_event() function.
  /// Only when the kernel has adjusted the database we can do it.
  /// Example: for imported PE DLLs we will add the exported function
  /// names to the database.
  ///
  /// This function pointer may be absent, i.e. NULL.
  /// This function is called from the main thread.
  void (idaapi *stopped_at_debug_event)(bool dlls_added);

  /// \name Threads
  /// The following functions manipulate threads.
  /// These functions are called from debthread.
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  //@{
  int (idaapi *thread_suspend) (thid_t tid); ///< Suspend a running thread
  int (idaapi *thread_continue)(thid_t tid); ///< Resume a suspended thread
  int (idaapi *set_resume_mode)(thid_t tid, resume_mode_t resmod); ///< Specify resume action
  //@}

  /// Read thread registers.
  /// This function is called from debthread.
  /// \param tid      thread id
  /// \param clsmask  bitmask of register classes to read
  /// \param values   pointer to vector of regvals for all registers.
  ///                 regval is assumed to have debugger_t::registers_size elements
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *read_registers)(thid_t tid, int clsmask, regval_t *values);

  /// Write one thread register.
  /// This function is called from debthread.
  /// \param tid     thread id
  /// \param regidx  register index
  /// \param value   new value of the register
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *write_register)(thid_t tid, int regidx, const regval_t *value);

  /// Get information about the base of a segment register.
  /// Currently used by the IBM PC module to resolve references like fs:0.
  /// This function is called from debthread.
  /// \param answer      pointer to the answer. can't be NULL.
  /// \param tid         thread id
  /// \param sreg_value  value of the segment register (returned by get_reg_val())
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *thread_get_sreg_base)(ea_t *answer, thid_t tid, int sreg_value);

  /// \name Memory manipulation
  /// The following functions manipulate bytes in the memory.
  //@{

  /// Get information on the memory ranges.
  /// The debugger module fills 'ranges'. The returned vector MUST be sorted.
  /// This function is called from debthread.
  /// \retval  -3  use idb segmentation
  /// \retval  -2  no changes
  /// \retval  -1  the process does not exist anymore
  /// \retval   0  failed
  /// \retval   1  new memory layout is returned
  int (idaapi *get_memory_info)(meminfo_vec_t &ranges);

  /// Read process memory.
  /// Returns number of read bytes.
  /// This function is called from debthread.
  /// \retval 0  read error
  /// \retval -1 process does not exist anymore
  ssize_t (idaapi *read_memory)(ea_t ea, void *buffer, size_t size);

  /// Write process memory.
  /// This function is called from debthread.
  /// \return number of written bytes, -1 if fatal error
  ssize_t (idaapi *write_memory)(ea_t ea, const void *buffer, size_t size);

  //@}

  /// Is it possible to set breakpoint?.
  /// This function is called from debthread or from the main thread if debthread
  /// is not running yet.
  /// It is called to verify hardware breakpoints.
  /// \return ref BPT_
  int (idaapi *is_ok_bpt)(bpttype_t type, ea_t ea, int len);
/// \defgroup BPT_ Breakpoint verification codes
/// Return values for debugger_t::is_ok_bpt
//@{
#define BPT_OK           0 ///< breakpoint can be set
#define BPT_INTERNAL_ERR 1 ///< interr occurred when verifying breakpoint
#define BPT_BAD_TYPE     2 ///< bpt type is not supported
#define BPT_BAD_ALIGN    3 ///< alignment is invalid
#define BPT_BAD_ADDR     4 ///< ea is invalid
#define BPT_BAD_LEN      5 ///< bpt len is invalid
#define BPT_TOO_MANY     6 ///< reached max number of supported breakpoints
#define BPT_READ_ERROR   7 ///< failed to read memory at bpt ea
#define BPT_WRITE_ERROR  8 ///< failed to write memory at bpt ea
#define BPT_SKIP         9 ///< update_bpts(): do not process bpt
#define BPT_PAGE_OK     10 ///< update_bpts(): ok, added a page bpt
//@}

  /// Add/del breakpoints.
  /// bpts array contains nadd bpts to add, followed by ndel bpts to del.
  /// This function is called from debthread.
  /// \return number of successfully modified bpts, -1 if network error
  int (idaapi *update_bpts)(update_bpt_info_t *bpts, int nadd, int ndel);

  /// Update low-level (server side) breakpoint conditions.
  /// This function is called from debthread.
  /// \return nlowcnds. -1-network error
  int (idaapi *update_lowcnds)(const lowcnd_t *lowcnds, int nlowcnds);

  /// \name Remote file
  /// Open/close/read a remote file.
  /// These functions are called from the main thread
  //@{
  int  (idaapi *open_file)(const char *file, uint64 *fsize, bool readonly); // -1-error
  void (idaapi *close_file)(int fn);
  ssize_t (idaapi *read_file)(int fn, qoff64_t off, void *buf, size_t size);
  //@}

  /// Map process address.
  /// This function may be absent.
  /// This function is called from debthread.
  /// \param off      offset to map
  /// \param regs     current register values. if regs == NULL, then perform
  ///                 global mapping, which is independent on used registers
  ///                 usually such a mapping is a trivial identity mapping
  /// \param regnum   required mapping. maybe specified as a segment register number
  ///                 or a regular register number if the required mapping can be deduced
  ///                 from it. for example, esp implies that ss should be used.
  /// \return mapped address or #BADADDR
  ea_t (idaapi *map_address)(ea_t off, const regval_t *regs, int regnum);

  /// Set debugger options (parameters that are specific to the debugger module).
  /// See the definition of ::set_options_t for arguments.
  /// See the convenience function in dbg.hpp if you need to call it.
  /// The kernel will call this function after reading the debugger specific
  /// config file (arguments are: keyword="", type=#IDPOPT_STR, value="")
  /// This function is optional.
  /// This function is called from the main thread
  const char *(idaapi *set_dbg_options)(
        const char *keyword,
        int pri,
        int value_type,
        const void *value);

  /// Get pointer to debugger specific functions.
  /// This function returns a pointer to a structure that holds pointers to
  /// debugger module specific functions. For information on the structure
  /// layout, please check the corresponding debugger module. Most debugger
  /// modules return NULL because they do not have any extensions. Available
  /// extensions may be called from plugins.
  /// This function is called from the main thread.
  const void *(idaapi *get_debmod_extensions)(void);

  /// Calculate the call stack trace.
  /// This function is called when the process is suspended and should fill
  /// the 'trace' object with the information about the current call stack.
  /// If this function is missing or returns false, IDA will use the standard
  /// mechanism (based on the frame pointer chain) to calculate the stack trace
  /// This function is called from the main thread.
  /// \return success
  bool (idaapi *update_call_stack)(thid_t tid, call_stack_t *trace);

  /// Call application function.
  /// This function calls a function from the debugged application.
  /// This function is called from debthread
  /// \param func_ea      address to call
  /// \param tid          thread to use
  /// \param fti          type information for the called function
  /// \param nargs        number of actual arguments
  /// \param regargs      information about register arguments
  /// \param stkargs      memory blob to pass as stack arguments (usually contains pointed data)
  ///                     it must be relocated by the callback but not changed otherwise
  /// \param retregs      function return registers.
  /// \param[out] errbuf  the error message. if empty on failure, see 'event'.
  ///                     should not be filled if an appcall exception
  ///                     happened but #APPCALL_DEBEV is set
  /// \param[out] event   the last debug event that occurred during appcall execution
  ///                     filled only if the appcall execution fails and #APPCALL_DEBEV is set
  /// \param options      appcall options, usually taken from \inf{appcall_options}.
  ///                     possible values: combination of \ref APPCALL_  or 0
  /// \return ea of stkargs blob, #BADADDR if failed and errbuf is filled
  ea_t (idaapi *appcall)(
        ea_t func_ea,
        thid_t tid,
        const struct func_type_data_t *fti,
        int nargs,
        const struct regobjs_t *regargs,
        struct relobj_t *stkargs,
        struct regobjs_t *retregs,
        qstring *errbuf,
        debug_event_t *event,
        int options);

/// \defgroup APPCALL_ Appcall options
/// Passed as 'options' parameter to debugger_t::appcall
//@{
#define APPCALL_MANUAL  0x0001  ///< Only set up the appcall, do not run.
                                ///< debugger_t::cleanup_appcall will not be called by ida!
#define APPCALL_DEBEV   0x0002  ///< Return debug event information
#define APPCALL_TIMEOUT 0x0004  ///< Appcall with timeout.
                                ///< If timed out, errbuf will contain "timeout".
                                ///< See #SET_APPCALL_TIMEOUT and #GET_APPCALL_TIMEOUT
//@}

  /// Cleanup after appcall().
  /// The debugger module must keep the stack blob in the memory until this function
  /// is called. It will be called by the kernel for each successful appcall().
  /// There is an exception: if #APPCALL_MANUAL, IDA may not call cleanup_appcall.
  /// If the user selects to terminate a manual appcall, then cleanup_appcall will be called.
  /// Otherwise, the debugger module should terminate the appcall when the called
  /// function returns.
  /// This function is called from debthread.
  /// \retval  2  ok, there are pending events
  /// \retval  1  ok
  /// \retval  0  failed
  /// \retval -1  network error
  int (idaapi *cleanup_appcall)(thid_t tid);

  /// Evaluate a low level breakpoint condition at 'ea'.
  /// Other evaluation errors are displayed in a dialog box.
  /// This call is rarely used by IDA when the process has already been suspended
  /// for some reason and it has to decide whether the process should be resumed
  /// or definitely suspended because of a breakpoint with a low level condition.
  /// This function is called from debthread.
  /// \retval  1  condition is satisfied
  /// \retval  0  not satisfied
  /// \retval -1  network error
  int (idaapi *eval_lowcnd)(thid_t tid, ea_t ea);

  /// This function is called from main thread
  ssize_t (idaapi *write_file)(int fn, qoff64_t off, const void *buf, size_t size);

  /// Perform a debugger-specific function.
  /// This function is called from debthread
  int (idaapi *send_ioctl)(int fn, const void *buf, size_t size, void **poutbuf, ssize_t *poutsize);

  /// Enable/Disable tracing.
  /// "trace_flags" can be a set of STEP_TRACE, INSN_TRACE, BBLK_TRACE or FUNC_TRACE.
  /// See thread_t::trace_mode in debugger.h.
  /// This function is called from the main thread.
  bool (idaapi *dbg_enable_trace)(thid_t tid, bool enable, int trace_flags);

  /// Is tracing enabled? ONLY used for tracers.
  /// "trace_bit" can be one of the following: STEP_TRACE, INSN_TRACE, BBLK_TRACE or FUNC_TRACE
  bool (idaapi *is_tracing_enabled)(thid_t tid, int tracebit);

  /// Execute a command on the remote computer.
  /// \return exit code
  int (idaapi *rexec)(const char *cmdline);

  /// Get (store to out_pattrs) process/debugger-specific runtime attributes.
  /// This function is called from main thread.
  void (idaapi *get_debapp_attrs)(debapp_attrs_t *out_pattrs);

  /// Get the path to a file containing source debug info for the given module.
  /// This allows srcinfo providers to call into the debugger when looking for debug info.
  /// It is useful in certain cases like the iOS debugger, which is a remote debugger but
  /// the remote debugserver does not provide dwarf info. So, we allow the debugger client
  /// to decide where to look for debug info locally.
  /// \param path  output path (file might not exist)
  /// \param base  base address of a module in the target process
  /// \return success, result stored in 'path'
  bool (idaapi *get_srcinfo_path)(qstring *path, ea_t base);
};

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


А отладчик ведь тоже плагин


Да, да. Именно поэтому нам потребуется файлик ida_plugin.cpp. Давайте разберём, что же в нём написано.


Функция idp_to_dbg_reg()


Нужна во время отладки для преобразования индексов регистров процессорного модуля (были получены экспериментальным путём) в регистры отладчика.


static int idaapi idp_to_dbg_reg(int idp_reg)
{
    int reg_idx = idp_reg;
    if (idp_reg >= 0 && idp_reg <= 7)
        reg_idx = 0 + idp_reg;
    else if (idp_reg >= 8 && idp_reg <= 39)
        reg_idx = 8 + (idp_reg % 8);
    else if (idp_reg == 91)
        reg_idx = 16;
    else if (idp_reg == 92 || idp_reg == 93)
        reg_idx = 17;
    else if (idp_reg == 94)
        reg_idx = 15;
    else
    {
        char buf[MAXSTR];
        ::qsnprintf(buf, MAXSTR, "reg: %d\n", idp_reg);
        warning("SEND THIS MESSAGE TO you@mail.com:\n%s\n", buf);
        return 0;
    }
    return reg_idx;
}

Функция hook_idp()


Данный хук устанавливается вызовом hook_to_notification_point():


hook_to_notification_point(HT_IDP, hook_idp, NULL);

Снимается вызовом unhook_from_notification_point():


unhook_from_notification_point(HT_IDP, hook_idp);

И цель у этого хука всего одна: debugger hints. Вот вам пример такого "хинта":



Да, это то самое окошко, которое появляется при наведении на какой-то адрес или регистр во время отладки, и отображающее содержимое памяти. Когда я только разбирался с написанием отладчика, я думал, что это должна делать сама IDA, но нет. Пришлось реализовывать самостоятельно.


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


Большой и страшный код
case processor_t::ev_get_idd_opinfo:
{
    idd_opinfo_t * opinf = va_arg(va, idd_opinfo_t *);
    ea_t ea = va_arg(va, ea_t);
    int n = va_arg(va, int);
    int thread_id = va_arg(va, int);
    getreg_func_t getreg = va_arg(va, getreg_func_t);
    const regval_t *regvalues = va_arg(va, const regval_t *);

    opinf->ea = BADADDR;
    opinf->debregidx = 0;
    opinf->modified = false;
    opinf->value.ival = 0;
    opinf->value_size = 4;

    insn_t out;
    if (decode_insn(&out, ea))
    {
        op_t op = out.ops[n];

#ifdef _DEBUG
        print_insn(&out);
#endif

        int size = 0;
        switch (op.dtype)
        {
        case dt_byte:
            size = 1;
            break;
        case dt_word:
            size = 2;
            break;
        default:
            size = 4;
            break;
        }

        opinf->value_size = size;

        switch (op.type)
        {
        case o_mem:
        case o_near:
        case o_imm:
        {
            flags_t flags;

            switch (n)
            {
            case 0: flags = get_optype_flags0(get_flags(ea)); break;
            case 1: flags = get_optype_flags1(get_flags(ea)); break;
            default: flags = 0; break;
            }

            switch (op.type)
            {
            case o_mem:
            case o_near: opinf->ea = op.addr; break;
            case o_imm: opinf->ea = op.value; break;
            }

            opinfo_t info;
            if (get_opinfo(&info, ea, n, flags) != NULL)
            {
                opinf->ea += info.ri.base;
            }
        } break;
        case o_phrase:
        case o_reg:
        {
            int reg_idx = idp_to_dbg_reg(op.reg);
            regval_t reg = getreg(dbg->registers(reg_idx).name, regvalues);

            if (op.phrase >= 0x10 && op.phrase <= 0x1F || // (A0)..(A7), (A0)+..(A7)+
                op.phrase >= 0x20 && op.phrase <= 0x27) // -(A0)..-(A7)
            {
                if (op.phrase >= 0x20 && op.phrase <= 0x27)
                    reg.ival -= size;

                opinf->ea = (ea_t)reg.ival;

                switch (size)
                {
                case 1:
                {
                    uint8_t b = 0;
                    dbg->read_memory((ea_t)reg.ival, &b, 1);
                    opinf->value.ival = b;
                } break;
                case 2:
                {
                    uint16_t w = 0;
                    dbg->read_memory((ea_t)reg.ival, &w, 2);
                    w = swap16(w);
                    opinf->value.ival = w;
                } break;
                default:
                {
                    uint32_t l = 0;
                    dbg->read_memory((ea_t)reg.ival, &l, 4);
                    l = swap32(l);
                    opinf->value.ival = l;
                } break;
                }
            }
            else
                opinf->value = reg;

            opinf->debregidx = reg_idx;
        } break;
        case o_displ:
        {
            regval_t main_reg, add_reg;
            int main_reg_idx = idp_to_dbg_reg(op.reg);
            int add_reg_idx = idp_to_dbg_reg(op.specflag1 & 0xF);

            main_reg.ival = 0;
            add_reg.ival = 0;
            if (op.specflag2 & 0x10)
            {
                add_reg = getreg(dbg->registers(add_reg_idx).name, regvalues);
                if (op.specflag1 & 0x10)
                {
                    add_reg.ival &= 0xFFFF;
                    add_reg.ival = (uint64)((int16_t)add_reg.ival);
                }
            }

            if (main_reg_idx != 16)
                main_reg = getreg(dbg->registers(main_reg_idx).name, regvalues);

            ea_t addr = (ea_t)main_reg.ival + op.addr + (ea_t)add_reg.ival;
            opinf->ea = addr;

            switch (size)
            {
            case 1:
            {
                uint8_t b = 0;
                dbg->read_memory(addr, &b, 1);
                opinf->value.ival = b;
            } break;
            case 2:
            {
                uint16_t w = 0;
                dbg->read_memory(addr, &w, 2);
                w = swap16(w);
                opinf->value.ival = w;
            } break;
            default:
            {
                uint32_t l = 0;
                dbg->read_memory(addr, &l, 4);
                l = swap32(l);
                opinf->value.ival = l;
            } break;
            }
        } break;
        }

        opinf->ea &= 0xFFFFFF;

        return 1;
    }
} break;

Надеюсь, для вашего процессора такое не придётся реализовывать.


init()


Тут мы разрешаем или запрещаем загрузку плагина, если выбранный процессор не наш.
Какой именно из процессорных модулей выбран, можно узнать в поле id глобальной структуры ph.


В моём случае процессорный модуль должен быть PLFM_68K. В этом случае в глобальную структуру dbg кладём указатель на нашу реализацию структуры debugger_t.


Также устанавливает HT_IDP хук, и возвращаем PLUGIN_KEEP. Иначе, если наш отладчик не для выбранного процессорного модуля, возвращаем PLUGIN_SKIP.


static int idaapi init(void)
{
    if (ph.id == PLFM_68K)
    {
        dbg = &debugger;
        plugin_inited = true;
        my_dbg = false;

        hook_to_notification_point(HT_IDP, hook_idp, NULL);

        print_version();
        return PLUGIN_KEEP;
    }
    return PLUGIN_SKIP;
}

term()


Вызывается при закрытии IDB-базы. Тут нам нужно снять все хуки, которые мы устанавливали в init().


static void idaapi term(void)
{
    if (plugin_inited)
    {
        unhook_from_notification_point(HT_IDP, hook_idp);

        plugin_inited = false;
    }
}

run()


Для плагинов-отладчиков данная функция не нужна. Возвращаем false.


Структура plugin_t


Тут всё просто: версия плагина, флаги, функция инициализации, завершения плагина, и подсказка:


Структура plugin_t
class plugin_t
{
public:
  int version;                  ///< Should be equal to #IDP_INTERFACE_VERSION
  int flags;                    ///< \ref PLUGIN_
/// \defgroup PLUGIN_ Plugin features
/// Used by plugin_t::flags
//@{
#define PLUGIN_MOD  0x0001      ///< Plugin changes the database.
                                ///< IDA won't call the plugin if
                                ///< the processor module prohibited any changes.
#define PLUGIN_DRAW 0x0002      ///< IDA should redraw everything after calling the plugin.
#define PLUGIN_SEG  0x0004      ///< Plugin may be applied only if the current address belongs to a segment
#define PLUGIN_UNL  0x0008      ///< Unload the plugin immediately after calling 'run'.
                                ///< This flag may be set anytime.
                                ///< The kernel checks it after each call to 'run'
                                ///< The main purpose of this flag is to ease
                                ///< the debugging of new plugins.
#define PLUGIN_HIDE 0x0010      ///< Plugin should not appear in the Edit, Plugins menu.
                                ///< This flag is checked at the start.
#define PLUGIN_DBG  0x0020      ///< A debugger plugin. init() should put
                                ///< the address of ::debugger_t to dbg.
#define PLUGIN_PROC 0x0040      ///< Load plugin when a processor module is loaded. (and keep it
                                ///< until the processor module is unloaded)
#define PLUGIN_FIX  0x0080      ///< Load plugin when IDA starts and keep it in the memory until IDA stops
#define PLUGIN_SCRIPTED 0x8000  ///< Scripted plugin. Should not be used by plugins,
                                ///< the kernel sets it automatically.
//@}

  int (idaapi *init)(void);     ///< Initialize plugin - returns \ref PLUGIN_INIT
/// \defgroup PLUGIN_INIT Plugin initialization codes
/// Return values for plugin_t::init()
//@{
#define PLUGIN_SKIP  0  ///< Plugin doesn't want to be loaded
#define PLUGIN_OK    1  ///< Plugin agrees to work with the current database.
                        ///< It will be loaded as soon as the user presses the hotkey
#define PLUGIN_KEEP  2  ///< Plugin agrees to work with the current database and wants to stay in the memory
//@}

  void (idaapi *term)(void);      ///< Terminate plugin. This function will be called
                                  ///< when the plugin is unloaded. May be NULL.
  bool (idaapi *run)(size_t arg); ///< Invoke plugin
  const char *comment;            ///< Long comment about the plugin.
                                  ///< it could appear in the status line
                                  ///< or as a hint
  const char *help;               ///< Multiline help about the plugin
  const char *wanted_name;        ///< The preferred short name of the plugin
  const char *wanted_hotkey;      ///< The preferred hotkey to run the plugin
};

На этом, пожалуй, всё. Надеюсь, получилось интересно, хотя кода в самом деле очень много для одной статьи. Как видите, процесс написания чего угодно для IDA превращается в сплошные мучения, хотя и довольно таки интересный.


Как и обещал, ссылки на исходники:
GPGX Debugger
Smd IDA Tools

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+30
Comments 0
Comments Leave a comment

Articles