Linux
binutils
Binary
BFD
LinuxDay 6

BFDライブラリを使ってオブジェクトファイルを操作してみる

この記事はLinux Advent Calendar 2018 6日目の記事です。

自分の投稿日の前日(というか当日)になり、何を書こうかと考えていたのですが、BFDライブラリをテーマとする記事がQiitaになさそうであったので、今回取り上げてみることにします。

BFDライブラリとは

BFDは、オブジェクトファイルを操作するためにアプリケーションから使用できる共通ルーチン集です1。BFDは2つの部分から構成されています。

  • フロントエンドは、アプリケーションから見えるインターフェースであり、メモリーやBFDのデータ構造を管理します。また使用するバックエンドを指定します。
  • バックエンドは、それぞれオブジェクトファイルの形式に対応しており、バックエンドを切り替えることで異なる形式のオブジェクトファイルを同じ方法で扱うことができます。

以下のコマンドでドキュメントを表示できます。

$ info bfd

BFDライブラリのインストール

手元のArchLinux環境では、binutilsパッケージの中に入っています。

$ pacman -Ql binutils | grep bfd
binutils /usr/bin/ld.bfd
binutils /usr/include/bfd.h
binutils /usr/include/bfdlink.h
binutils /usr/lib/libbfd-2.31.1.so
binutils /usr/lib/libbfd.a
binutils /usr/lib/libbfd.so
binutils /usr/share/info/bfd.info.gz
()

binutilsパッケージに含まれる多くのコマンド(readelfを除く)がBFDを用いている繋がりで、BFDはbinutilsと一緒に配布されているようです。
BFDのソースコードはここで確認できます。

対応しているアーキテクチャ, ターゲットの確認

まず、以下のファイルを作成します2

arch_target.c
#define PACKAGE "bfd"
#include <stdio.h>
#include <bfd.h>

int main(void) {
    const char **list = bfd_arch_list();
    printf("ARCH:\n");
    while (*list != NULL) {
        printf("%s\n", *list);
        list++;
    }
    list = bfd_target_list();
    printf("\nTARGET:\n");
    while (*list != NULL) {
        printf("%s\n", *list);
        list++;
    }
}

これを以下のように実行すると、対応しているアーキテクチャ, ターゲットを一覧できます。

$ gcc arch_target.c -lbfd
$ ./a.out
ARCH:
i386
i386:x86-64
i386:x64-32
i8086
i386:intel
i386:x86-64:intel
i386:x64-32:intel
i386:nacl
i386:x86-64:nacl
i386:x64-32:nacl
iamcu
iamcu:intel
l1om
l1om:intel
k1om
k1om:intel
plugin

TARGET:
elf64-x86-64
elf32-i386
elf32-iamcu
elf32-x86-64
pei-i386
pei-x86-64
elf64-l1om
elf64-k1om
elf64-little
elf64-big
elf32-little
elf32-big
pe-x86-64
pe-bigobj-x86-64
pe-i386
plugin
srec
symbolsrec
verilog
tekhex
binary
ihex

早速使ってみる

まずBFDを用いて正しくオブジェクトファイルを開けるか確認します。

bfd.c
#define PACKAGE "bfd"
#include <stdio.h>
#include <bfd.h>

int main(void) {
    const char *fname = "/proc/self/exe";

    bfd_init();
    bfd *abfd = bfd_openr(fname, "elf64-x86-64");
    if (abfd == NULL) {
        fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
        return 1;
    }
    if (!bfd_check_format(abfd, bfd_object)) {
        bfd_perror("bfd_check_format");
        return 1;
    }

    bfd_close(abfd);
}

このファイルを以下のようにコンパイルし実行します。

$ gcc bfd.c -lbfd
$ ./a.out
$

なにも表示されなければ成功です。これで自分自身を開いて閉じるだけのプログラムが完成しました。

シンボルを表示してみる

先程のファイルに追記します。

bfd.c
#define PACKAGE "bfd"
#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>

int main(void) {

    const char *fname = "/proc/self/exe";

    bfd_init();
    bfd *abfd = bfd_openr(fname, "elf64-x86-64");
    if (abfd == NULL) {
        fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
        return 1;
    }
    if (!bfd_check_format(abfd, bfd_object)) {
        bfd_perror("bfd_check_format");
        return 1;
    }

    // ここから追記
    long storage_needed = bfd_get_symtab_upper_bound(abfd);
    if (storage_needed < 0) {
        fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
        return 1;
    } else if (storage_needed > 0) {
        asymbol **symbol_table = malloc(storage_needed);
        long number_of_symbols = bfd_canonicalize_symtab(abfd, symbol_table);
        if (number_of_symbols < 0) {
            fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
            return 1;
        }
        int i;
        for (i = 0; i < number_of_symbols; i++) {
            asymbol *asym = symbol_table[i];
            char symclass = (char) bfd_decode_symclass(asym);
            symbol_info syminfo;
            bfd_symbol_info(asym, &syminfo);
            printf("%s\n    values_and_flags: ", asym->name);
            fflush(stdout);
            bfd_print_symbol_vandf(abfd, stdout, asym);
            printf("\n    class: %c\n", symclass);

        }
    }

    bfd_close(abfd);
}

これを実行すると以下のようにシンボルが表示されます。

$ gcc bfd.c -lbfd
$ ./a.out
.interp
    values_and_flags: 00000000000002a8 l    d
    class: r
.note.ABI-tag
    values_and_flags: 00000000000002c4 l    d
    class: r
.note.gnu.build-id
    values_and_flags: 00000000000002e4 l    d
    class: r
.gnu.hash
(...)

セクション情報を表示する

次はセクションを表示してみましょう。

bfd2.c
#define PACKAGE "bfd"
#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>

int main(void) {

    const char *fname = "/proc/self/exe";

    bfd_init();
    bfd *abfd = bfd_openr(fname, "elf64-x86-64");
    if (abfd == NULL) {
        fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
        return 1;
    }
    if (!bfd_check_format(abfd, bfd_object)) {
        bfd_perror("bfd_check_format");
        return 1;
    }

    struct bfd_section *s = abfd->sections;
    do {
        printf("[%d] %s\t%x\t%x\t%x\n", s->id, s->name, s->vma, s->lma, s->size);
    } while ((s = s->next) != NULL);

    bfd_close(abfd);
}

これを実行すると以下のようになります。

$ gcc bfd2.c -lbfd
$ ./a.out
[16] .interp    2a8     2a8     1c
[17] .note.ABI-tag      2c4     2c4     20
[18] .note.gnu.build-id 2e4     2e4     24
[19] .gnu.hash  308     308     dc
[20] .dynsym    3e8     3e8     a50
[21] .dynstr    e38     e38     403
[22] .gnu.version       123c    123c    dc
[23] .gnu.version_r     1318    1318    a0
[24] .rela.dyn  13b8    13b8    31920
[25] .rela.plt  32cd8   32cd8   168
[26] .init      33000   33000   1b
[27] .plt       33020   33020   100
[28] .plt.got   33120   33120   c8
[29] .text      331f0   331f0   af475
[30] .fini      e2668   e2668   d
[31] .rodata    e3000   e3000   1d6ff
[32] .eh_frame_hdr      100700  100700  323c
[33] .eh_frame  103940  103940  13b48
[34] .init_array        118c90  118c90  8
[35] .fini_array        118c98  118c98  8
[36] .data.rel.ro       118ca0  118ca0  12e50
[37] .dynamic   12baf0  12baf0  200
[38] .got       12bcf0  12bcf0  308
[39] .got.plt   12c000  12c000  90
[40] .data      12c0a0  12c0a0  52a0
[41] .bss       131340  131340  4780
[42] .comment   0       0       2b
[43] .debug_aranges     0       0       30
[44] .debug_info        0       0       6468
[45] .debug_abbrev      0       0       23b
[46] .debug_line        0       0       40b
[47] .debug_str 0       0       1e162
[48] .debug_macro       0       0       2d47

特定のセクションの中身をダンプする

折角セクションを表示できたので、今度はセクションの中身をダンプしてみましょう。

bfd3.c
#define PACKAGE "bfd"
#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>

int main(void) {

    const char *fname = "/proc/self/exe";

    bfd_init();
    bfd *abfd = bfd_openr(fname, "elf64-x86-64");
    if (abfd == NULL) {
        fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
        return 1;
    }
    if (!bfd_check_format(abfd, bfd_object)) {
        bfd_perror("bfd_check_format");
        return 1;
    }

    asection *s = bfd_get_section_by_name(abfd, ".text");
    unsigned char *buf;
    if (!bfd_malloc_and_get_section(abfd, s, &buf)) {
        fprintf(stderr, "%s\n", bfd_errmsg(bfd_get_error()));
        return 1;
    }
    int i;
    for (i = 0; i < s->size; i++) printf("%02x%s", buf[i], i % 16 == 15 ? "\n" : " ");

    bfd_close(abfd);
}

以下のようになります。

$ gcc bfd3.c -lbfd
$ ./a.out
be b6 01 00 00 48 8d 3d a4 04 0b 00 e8 cf 06 00
00 0f b6 04 25 00 00 00 00 0f 0b be b6 01 00 00
48 8d 3d 89 04 0b 00 e8 b4 06 00 00 0f b6 04 25
00 00 00 00 0f 0b 48 8b 04 25 e0 01 00 00 0f 0b
48 8b 04 25 d0 07 00 00 0f 0b be 0a 0f 00 00 48
8d 3d 82 43 0b 00 e8 85 06 00 00 48 8b 04 25 e0
00 00 00 0f 0b be d4 2b 00 00 48 8d 3d 67 50 0b
00 e8 6a 06 00 00 48 8b 04 25 10 00 00 00 0f 0b
48 8b 04 25 00 00 00 00 0f 0b e8 b1 fd ff ff 48
(...)

さいごに

今回はBFDライブラリを用いてオブジェクトファイルの解析を行いましたが、BFDライブラリを用いればプログラムからオブジェクトファイルを生成するなど、より高度なこともできます。何らかのオブジェクトファイルを自力で生成したいという状況下では役立つのではないでしょうか。


  1. info bfdより。 

  2. bfd.hを使用するアプリケーションではautomakeの使用が前提となっており、このチェックに引っかかってエラーが出ることを防ぐためにbfd.cの1行目で#define PACKAGEしています。