この記事は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。
#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を用いて正しくオブジェクトファイルを開けるか確認します。
#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
$
なにも表示されなければ成功です。これで自分自身を開いて閉じるだけのプログラムが完成しました。
シンボルを表示してみる
先程のファイルに追記します。
#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
(...)
セクション情報を表示する
次はセクションを表示してみましょう。
#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
特定のセクションの中身をダンプする
折角セクションを表示できたので、今度はセクションの中身をダンプしてみましょう。
#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ライブラリを用いればプログラムからオブジェクトファイルを生成するなど、より高度なこともできます。何らかのオブジェクトファイルを自力で生成したいという状況下では役立つのではないでしょうか。