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

uclnecmakoun2veml3ooxfvaete.png

Привет всем,

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


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

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

59de456d6d52f649312722.png

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

Заходим в ViewOther WindowsProperty Manager:

59de412a0dc50466086740.png

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

59de423c59c08942765331.png

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

59de43354eef5656763373.png

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

59de4393df765196799873.png

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

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

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


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

#include 
#include 
#include 

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

struct eventlist_t : public std::deque
{
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 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 

#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[], в котором мы должны указать текстовые имена групп регистров. Их можно будет открыть в отдельных окнах при отладке.

qfmgh6-849gg62-8cugjnxqtss4.png

Вот моя реализация (последним элементом должен быть 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, в которую нужно будет записать содержимое памяти по запрашиваемому адресу.


Пример реали

© Habrahabr.ru