libbfdのめも

libbfdの使い方のめもです。利用してるバージョンはbinutils-devel-2.31.1-13.fc29.x86_64です。

nmもどき

アドレスとセクション名、それにdebug情報があればファイル名と行数を表示。連想配列が使いたかったのでヘッダファイルだけで実装されてるuthashというライブラリを使ってます。

simple_nm.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bfd.h>

#include "uthash.h"

struct section_info {
    char name[64];
    unsigned long vma;
    UT_hash_handle hh;
};

static struct section_info *sections_info = NULL;

static void delete_all_hash_values(void)
{
    struct section_info *pos, *tmp;

    HASH_ITER(hh, sections_info, pos, tmp) {
        HASH_DEL(sections_info, pos);
        free(pos);
    }

    HASH_CLEAR(hh, sections_info);
}

static struct section_info *add_section(bfd *abfd, const char *sname)
{
    struct section_info *info;

    asection *section = bfd_get_section_by_name(abfd, sname);
    if (!section)
        return NULL;

    info = malloc(sizeof(*info));
    if (!info) {
        fprintf(stderr, "malloc failed\n");
        exit(-1);
    }
    memset(info, 0x0, sizeof(*info));

    strcpy(info->name, sname);
    info->vma = section->vma;

    HASH_ADD_STR(sections_info, name, info);

    return info;
}

static struct section_info *get_section_info(bfd *abfd, const char *sname)
{
    struct section_info *info = NULL;

    HASH_FIND_STR(sections_info, sname, info);
    if (info)
        return info;

    return add_section(abfd, sname);
}

static void show_function_info(bfd *abfd, asection *debug_info, asymbol **symbols, int idx)
{
    const char *file, *func;
    unsigned int line;
    const char *sname = symbols[idx]->section->name;
    const char *name = symbols[idx]->name;
    struct section_info *info = get_section_info(abfd, sname);

    if (!info)
        return ;

    unsigned long addr = symbols[idx]->value + info->vma;
    if (bfd_find_nearest_line(abfd, debug_info, symbols, addr, &file, &func, &line))
        printf("0x%lx %s %s %s:%u\n", addr, sname, name, file, line);
    else
        printf("0x%lx %s %s\n", addr, sname, name);
}

static void show(char *prog)
{
    bfd *abfd;
    asection *debug_info;
    size_t nsyms;
    asymbol **symbols;
    int ret;

    bfd_init();

    abfd = bfd_openr(prog, NULL);
    if (!abfd) {
        fprintf(stderr, "failed to open %s\n", prog);
        exit(-1);
    }

    ret = bfd_check_format(abfd, bfd_object);
    if (!ret) {
        fprintf(stderr, "invalid object format\n");
        exit(-1);
    }

    debug_info = bfd_get_section_by_name(abfd, ".debug_info");

    symbols = malloc(bfd_get_symtab_upper_bound(abfd));
    if (!symbols) {
        fprintf(stderr, "malloc failed\n");
        exit(-1);
    }

    nsyms = bfd_canonicalize_symtab(abfd, symbols);

    for (int i = 0; i < nsyms; i++)
        show_function_info(abfd, debug_info, symbols, i);

    delete_all_hash_values();
    free(symbols);
    bfd_close(abfd);
}

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        exit(-1);
    }

    show(argv[1]);
    return 0;
}

Makefile

objs=simple_nm.o

prog=simple_nm

all: $(objs)
  $(CC) -O0 -g $(objs) -o $(prog) -lbfd

.c.o:
  $(CC) -O0 -I. -g -c -Wall $<

test:
  ./$(prog) $(prog)

clean:
  rm -f core* $(objs) $(prog)

実行例

0xffffffff829e0018 .bss panic_param
0xffffffff827347e3 .init.text unknown_bootoption /home/masami/linux-kernel/init/main.c:294
0xffffffff81002010 .text trace_initcall_finish_cb /home/masami/linux-kernel/init/main.c:841
0xffffffff810028e3 .text trace_initcall_start_cb /home/masami/linux-kernel/init/main.c:832
0xffffffff81002911 .text run_init_process /home/masami/linux-kernel/init/main.c:1011
0xffffffff8100294f .text try_to_run_init_process /home/masami/linux-kernel/init/main.c:1020
0xffffffff82734977 .init.text trace_event_define_fields_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff8273499f .init.text trace_event_define_fields_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff827349c4 .init.text trace_event_define_fields_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff81002050 .text perf_trace_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002120 .text perf_trace_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff81002200 .text trace_raw_output_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff81002250 .text trace_raw_output_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002290 .text trace_raw_output_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff810022e0 .text __bpf_trace_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff810022f0 .text __bpf_trace_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002300 .text __bpf_trace_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff82734a1c .init.text loglevel /home/masami/linux-kernel/init/main.c:230
0xffffffff81002310 .text initcall_blacklisted /home/masami/linux-kernel/init/main.c:790
0xffffffff82213020 .data blacklisted_initcalls
0xffffffff82329a50 .data descriptor.60757
0xffffffff82734a77 .init.text set_debug_rodata /home/masami/linux-kernel/ini

ptrace(2)で呼び出す関数を動的に変更

bfdで関数のアドレスを調べてptrace(2)でブレークポイントを設定して、ブレークポイントにきたらripを呼び出したい関数に変えて本来の関数は実行しない形。inline化されてるものには対応してませんけども。。

simple_break_point.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <assert.h>
#include <bfd.h>

static void stub_function(void)
{
    printf("stub function is called\n");
}

static void say_hello(void)
{
    printf("hello, world\n");
}

void *get_function_address(char *prog_name, char *func_name)
{
    bfd *abfd;
    asection *text;
    size_t nsyms;
    asymbol **symbols;
    void *addr = NULL;
    int ret;

    bfd_init();

    abfd = bfd_openr(prog_name, NULL);
    assert(abfd != NULL);

    ret = bfd_check_format(abfd, bfd_object);
    assert(ret != 0);

    text = bfd_get_section_by_name(abfd, ".text");

    symbols = malloc(bfd_get_symtab_upper_bound(abfd));
    assert(symbols != NULL);

    nsyms = bfd_canonicalize_symtab(abfd, symbols);

    for (int i = 0; i < nsyms; i++) {
        if (!strcmp(symbols[i]->name, func_name)) {
            addr = (void *) (symbols[i]->value + text->vma);
            break;
        }
    }

    free(symbols);
    bfd_close(abfd);

    return addr;
}

int main(int argc, char **argv)
{
    pid_t pid;
    long ret;
    void *addr;

    if (argc != 2) {
        printf("usage: %s <function name>\n", argv[0]);
        exit(-1);
    }

    addr = get_function_address(argv[0], argv[1]);
    if (!addr) {
        printf("function %s is not found\n", argv[1]);
        exit(-1);
    }

    pid = fork();
    assert(pid >=  0);

    if (!pid) {
        say_hello();
    } else {
        int status;
        void *break_point;
        long orig_text;

        ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
        assert(ret != -1);
        waitpid(pid, &status, 0);

        orig_text = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
        break_point = (void *) (unsigned long) (orig_text | 0xcc);
        ret = ptrace(PTRACE_POKETEXT, pid, addr, break_point);
        assert(pid != -1);

        printf("[*] set break point at %p\n", addr);

        ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
        assert(pid != -1);

        waitpid(pid, &status, 0);

        if (WIFEXITED(status)) {
            printf("program normally finishd\n");
            exit(0);
        } else if (WIFSTOPPED(status)) {
            struct user_regs_struct regs = { 0 };
            ret = ptrace(PTRACE_GETREGS, pid, 0, &regs);
            assert(ret != -1);
            regs.rip = (unsigned long long) stub_function;

            ret = ptrace(PTRACE_SETREGS, pid, 0, &regs);
            assert(ret != -1);

            ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
            assert(pid != -1);
        } else {
            printf("unkown error\n");
            exit(-1);
        }
    }

    return 0;
}

Makefile

objs=simple_break_point.o

prog=simple_break_point

all: $(objs)
  $(CC) -O0 -g $(objs) -o $(prog) -lbfd

.c.o:
  $(CC) -O0 -g -c -Wall $<

test:
  ./$(prog) say_hello

clean:
  rm -f core* $(objs) $(prog)

実行例

ここでブレークポイントを設定する関数のアドレスはnmで見るとこうなってます。

masami@saga:~/codes/simple_break_point$ nm ./simple_break_point | grep "say_hello\|main"
                 U __libc_start_main@@GLIBC_2.2.5
0000000000402a69 T main
00000000004028c7 t say_hello
ブレークポイントにヒットする場合
masami@saga:~/codes/simple_break_point$ ./simple_break_point say_hello
[*] set break point at 0x4028c7
stub function is called
ブレークポイントにヒットしない場合

すでに実行されたmain()に設定することでブレークポイントにヒットしない感じで。

mi@saga:~/codes/simple_break_point$ ./simple_break_point main
[*] set break point at 0x402a69
hello, world
program normally finishd