Как получить информацию об инструкции из libopcodes? - PullRequest
12 голосов
/ 03 февраля 2012

Я пишу инструмент, который использует libbfd и libopcodes в x86-32 и x86-64 Linux для выполнения разборки. Проблема заключается в том, что, хотя я могу разобрать libopcodes, я не могу получить какую-либо инструкцию. Для демонстрации я сделал минимальный пример, который воспроизводит мою проблему. Программа должна разобрать себя от точки входа до первой RET / RETQ.

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

#include <bfd.h>
#include <dis-asm.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <libiberty.h>

/*
 * Holds state for BFD and libopcodes.
 */
bfd *        abfd  = NULL;
disassemble_info dinfo = {0};

/*
 * Temporary hack to signal when disassembling should stop.
 */
static bool stop_disassembling = FALSE;

/*
 * Gets path to currently running executable.
 */
bool get_target_path(char * target_path, size_t size)
{
    char *   path;
    ssize_t len;

    pid_t pid = getpid();
    sprintf(target_path, "/proc/%d/exe", (int)pid );

    path = strdup(target_path);
    len  = readlink(path, target_path, size);

    target_path[len] = '\0';
    free(path);
    return TRUE;
}

/*
 * libopcodes appends spaces on the end of some instructions so for
 * comparisons, we want to strip those first.
 */
void strip_tail(char * str, unsigned int size)
{
    int i;
    for(i = 0; i < size; i++) {
        if(!isgraph(str[i])) {
            str[i] = '\0';
            break;
        }
    }
}

/*
 * Checks whether the current instruction will cause the control flow to not
 * proceed to the linearly subsequent instruction (e.g. ret, jmp, etc.)
 */
bool breaks_control_flow(char * str)
{
    if(abfd->arch_info->bits_per_address == 64) {
        if(strcmp(str, "retq") == 0) {
            return TRUE;
        }
    } else {
        if(strcmp(str, "ret") == 0) {
            return TRUE;
        }
    }

    return FALSE;
}

/*
 * Used as a callback for libopcodes so we can do something useful with the
 * disassembly. Currently this just outputs to stdout.
 */
int custom_fprintf(void * stream, const char * format, ...)
{
    /* silly amount */
    char    str[128] = {0};
    int rv;
    va_list args;

    va_start(args, format);
    rv = vsnprintf(str, ARRAY_SIZE(str) - 1, format, args);
    va_end(args);

    puts(str);
    strip_tail(str, ARRAY_SIZE(str));

    if(breaks_control_flow(str)) {
        puts("Stopped disassembly");
        stop_disassembling = TRUE;
    }

    if(dinfo.insn_info_valid) {
        switch(dinfo.insn_type) {
            case dis_noninsn:
                printf("not an instruction\n");
                break;
            case dis_nonbranch:
                printf("not a branch\n");
                break;
            case dis_branch:
                printf("is a branch\n");
                break;
            case dis_condbranch:
                printf("is a conditional branch\n");
                break;
            case dis_jsr:
                printf("jump to subroutine\n");
                break;
            case dis_condjsr:
                printf("conditional jump to subroutine\n");
                break;
            case dis_dref:
                printf("data reference in instruction\n");
                break;
            case dis_dref2:
                printf("two data references in instruction\n");
                break;
            default:
                printf("not enumerated\n");
                break;
        }
    } else {
        printf("insn_info not valid\n");
    }

    return rv;
}

/*
 * Initialises libopcodes disassembler and returns an instance of it.
 */
disassembler_ftype init_disasm(bfd * abfd, disassemble_info * dinfo)
{
    /* Override the stream the disassembler outputs to */
    init_disassemble_info(dinfo, NULL, custom_fprintf);
    dinfo->flavour = bfd_get_flavour(abfd);
    dinfo->arch    = bfd_get_arch(abfd);
    dinfo->mach    = bfd_get_mach(abfd);
    dinfo->endian  = abfd->xvec->byteorder;
    disassemble_init_for_target(dinfo);

    return disassembler(abfd);
}

/*
 * Method of locating section from VMA taken from opdis.
 */
typedef struct {
    bfd_vma    vma;
    asection * sec;
} BFD_VMA_SECTION;

/*
 * Loads section and fills in dinfo accordingly. Since this function allocates
 * memory in dinfo->buffer, callers need to call free once they are finished.
 */
bool load_section(bfd * abfd, disassemble_info * dinfo, asection * s)
{
    int     size = bfd_section_size(s->owner, s);
    unsigned char * buf  = xmalloc(size);

    if(!bfd_get_section_contents(s->owner, s, buf, 0, size)) {
        free(buf);
        return FALSE;
    }

    dinfo->section       = s;
    dinfo->buffer        = buf;
    dinfo->buffer_length = size;
    dinfo->buffer_vma    = bfd_section_vma(s->owner, s);

    printf("Allocated %d bytes for %s section\n: 0x%lX", size, s->name,
            dinfo->buffer_vma);
    return TRUE;
}

/*
 * Used to locate section for a vma.
 */
void vma_in_section(bfd * abfd, asection * s, void * data)
{
    BFD_VMA_SECTION * req = data;

    if(req && req->vma >= s->vma &&
    req->vma < (s->vma + bfd_section_size(abfd, s)) ) {
        req->sec = s;
    }
}

/*
 * Locate and load section containing vma.
 */
bool load_section_for_vma(bfd * abfd, disassemble_info * dinfo,
        bfd_vma vma)
{
    BFD_VMA_SECTION req = {vma, NULL};
    bfd_map_over_sections(abfd, vma_in_section, &req);

    if(!req.sec) {
        return FALSE;
    } else {
        return load_section(abfd, dinfo, req.sec);
    }
}

/*
 * Start disassembling from entry point.
 */
bool disassemble_entry(bfd * abfd, disassemble_info * dinfo,
        disassembler_ftype disassembler)
{
    bfd_vma    vma = bfd_get_start_address(abfd);

    /* First locate and load the section containing the vma */
    if(load_section_for_vma(abfd, dinfo, vma)) {
        int size;

        /* Keep disassembling until signalled otherwise or error */
        while(true) {
            dinfo->insn_info_valid = 0;
            size = disassembler(vma, dinfo);
            printf("Disassembled %d bytes at 0x%lX\n", size, vma);

            if(size == 0 || size == -1 || stop_disassembling) {
                break;
            }

            vma += size;
        }

        free(dinfo->buffer);
        return TRUE;
    }

    return FALSE;
}

int main(void)
{
    char  target_path[PATH_MAX] = {0};

    bfd_init();

    /* Get path for the running instance of this program */
    get_target_path(target_path, ARRAY_SIZE(target_path));

    abfd = bfd_openr(target_path, NULL);

    if(abfd != NULL && bfd_check_format(abfd, bfd_object)) {
        disassembler_ftype disassembler = init_disasm(abfd, &dinfo);

        disassemble_entry(abfd, &dinfo, disassembler);

        bfd_close(abfd);
    }

    return EXIT_SUCCESS;
}

Этот источник может быть построен со следующим makefile. Для успешной связи на локальном компьютере должен быть установлен пакет binutils-dev:

all:
    gcc -Wall disasm.c -o disasm -lbfd -lopcodes

clean:
    rm -f disasm

При запуске выдается следующее:

Allocated 2216 bytes for .text section
: 0x400BF0xor    
insn_info not valid
%ebp
insn_info not valid
,
insn_info not valid
%ebp
insn_info not valid
Disassembled 2 bytes at 0x400BF0
mov    
insn_info not valid
%rdx
insn_info not valid
,
insn_info not valid
%r9
insn_info not valid
Disassembled 3 bytes at 0x400BF2
pop    
insn_info not valid
%rsi
insn_info not valid
Disassembled 1 bytes at 0x400BF5
mov    
insn_info not valid
%rsp
insn_info not valid
,
insn_info not valid
%rdx
insn_info not valid
Disassembled 3 bytes at 0x400BF6
and    
insn_info not valid
$0xfffffffffffffff0
insn_info not valid
,
insn_info not valid
%rsp
insn_info not valid
Disassembled 4 bytes at 0x400BF9
push   
insn_info not valid
%rax
insn_info not valid
Disassembled 1 bytes at 0x400BFD
push   
insn_info not valid
%rsp
insn_info not valid
Disassembled 1 bytes at 0x400BFE
mov    
insn_info not valid
$0x401450
insn_info not valid
,
insn_info not valid
%r8
insn_info not valid
Disassembled 7 bytes at 0x400BFF
mov    
insn_info not valid
$0x4013c0
insn_info not valid
,
insn_info not valid
%rcx
insn_info not valid
Disassembled 7 bytes at 0x400C06
mov    
insn_info not valid
$0x4012ce
insn_info not valid
,
insn_info not valid
%rdi
insn_info not valid
Disassembled 7 bytes at 0x400C0D
callq  
insn_info not valid
0x0000000000400ad8
insn_info not valid
Disassembled 5 bytes at 0x400C14
hlt    
insn_info not valid
Disassembled 1 bytes at 0x400C19
nop
insn_info not valid
Disassembled 1 bytes at 0x400C1A
nop
insn_info not valid
Disassembled 1 bytes at 0x400C1B
sub    
insn_info not valid
$0x8
insn_info not valid
,
insn_info not valid
%rsp
insn_info not valid
Disassembled 4 bytes at 0x400C1C
mov    
insn_info not valid
0x2013b9(%rip)
insn_info not valid
,
insn_info not valid
%rax
insn_info not valid
        # 
insn_info not valid
0x0000000000601fe0
insn_info not valid
Disassembled 7 bytes at 0x400C20
test   
insn_info not valid
%rax
insn_info not valid
,
insn_info not valid
%rax
insn_info not valid
Disassembled 3 bytes at 0x400C27
je     
insn_info not valid
0x0000000000400c2e
insn_info not valid
Disassembled 2 bytes at 0x400C2A
callq  
insn_info not valid
*%rax
insn_info not valid
Disassembled 2 bytes at 0x400C2C
add    
insn_info not valid
$0x8
insn_info not valid
,
insn_info not valid
%rsp
insn_info not valid
Disassembled 4 bytes at 0x400C2E
retq   
Stopped disassembly
insn_info not valid
Disassembled 1 bytes at 0x400C32

Я ожидаю, что смогу прочитать информацию об инструкциях для каждой инструкции через dinfo->insn_type, target и т. Д. Поведение проявляется как на x86-32, так и на x86-64. Если я смогу хотя бы получить подтверждение того, что это не реализовано на этих двух архитектурах, тогда я сам смогу заполнить эту информацию.

Ответы [ 3 ]

10 голосов
/ 07 февраля 2012

К сожалению, начиная с libu-кодов binutils 2.22, insn_type не заполняется ни на i386, ни на x86_64. Единственными широко поддерживаемыми архитектурами являются MIPS, Sparc и SPU ячейки. Это все еще верно для текущей CVS HEAD.

Трудно доказать, что чего-то не существует, но, например, в источнике дизассемблера Sparc вы можете видеть несколько устанавливаемых вхождений insn_type, например info->insn_type = dis_branch, тогда как в источник дизассемблера i386 нет ни вхождения insn_type, ни каких-либо из ожидаемых значений (dis_branch, dis_nonbranch и т. д.).

Проверяя все файлы libopcodes, которые поддерживают insn_type, вы получаете:

  • opcodes/mips-dis.c
  • opcodes/spu-dis.c
  • opcodes/microblaze-dis.c
  • opcodes/cris-dis.c
  • opcodes/sparc-dis.c
  • opcodes/mmix-dis.c
3 голосов
/ 07 февраля 2012

Выполнение этого только с этими библиотеками будет чрезвычайно болезненным и трудным процессом. Я думаю вы должны слушать Necrolis и использовать библиотеку, которая уже делает это. В прошлом я использовал Dyninst (а именно, InstructionAPI + ParseAPI ). Они очень хорошо документированы и будут делать точно , что вы пытаетесь сделать. По крайней мере, проведя час с этой библиотекой и собрав их примеры в руководствах, вы получите приложение, которое позволит вам изучить такие вещи, как коды операций каждой инструкции, длину каждой инструкции, количество аргументов для каждой инструкции и т. Д. Это то, что libopcodes не сообщает и не обрабатывает (он декодирует адреса за раз, что не обязательно является инструкцией).

Вот фрагмент от разработчиков Opdis, который я взял из их руководства (которое я бы посоветовал прочитать, если у вас его нет, там много хорошего о libopcodes):

Библиотека libopcodes - очень удобный дизассемблер, но она имеет три недостатка:

  1. он недостаточно документирован, что затрудняет понимание для новых пользователей
  2. его набор функций ограничен разборкой одного адреса
  3. предназначен главным образом для вывода в поток разобранных инструкций

Помимо прочего, я думаю, что вас может ужалить второй предмет в этом списке. А именно, тот факт, что большинство (все?) Кодов операций будут соответствовать одному адресу и будет соответствовать наблюдаемому результату (например, вы получаете mov и pop и некоторые аргументы регистра). Но как насчет хитрых вещей, таких как инструкции переменной длины или инструкции, которые не совпадают точно на 4-байтовых границах? Ты ничего не делаешь, чтобы справиться с этим.

дизассемблирование, генерируемое libopcodes, представляет собой последовательность строк предназначен для записи в поток. Метаданных нет, поэтому строки необходимо изучить, чтобы определить, какие мнемоники, а какие операнды, и какие из них являются инструкциями перехода / перехода / возврата и каковы их цели.

Я предполагаю, что Opdis умнее вашей программы - он знает, как и что искать в потоке. Возможно, иногда он знает, что ему нужно прочитать два адреса вместо одного перед разборкой. Из вашего кода и описания libopcodes ни один из них не делает этого.

Удачи! Не забудьте прочитать это руководство и, возможно, подумайте об использовании libopdis вместо!

0 голосов
/ 01 мая 2012

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

Итак, в результате разборка вашего двоичного файла будет

xor %ebp, %ebp

mov %rdx, %r9

pop %rsi

mov %rsp, %rdx

and $0xfffffffffffffff0, %rsp

push %rax

push %rsp

mov $0x401450,%r8

...
...