★1. 前言
BFD是Binary File Descriptor的简称。我们可以使用它来方便的操作应用程序,
可以在你不了解程序文件格式的情况下,读写ELF header,program header
table,section header table还有各个section等等。当然也可以是其他的BFD
支持的object文件(比如说是COFF,a.out等等)。如果想让BFD支持新的文件格式的话,
只需把一个新的BFD的后端加到它的库里。
对每一个文件格式来说,BFD都分两个部分:一个前端(front end)和一个后端(back ends)。
.BFD的前端给用户提供接口。它管理内存和规范数据结构。前端也决定了哪个后端被使用
和什么时候后端的例程被调用。
.BFD的前端维持了它的语言形式。后端也可能是它们自己使用的信息。
★2. 如何使用BFD
为了使用BFD,需要包括'bfd.h'并且连接的时候需要和静态库'libbfd.a'或者
动态库'libbfd.so'一起连接。
下面看一个简单的例子:
[alert7@redhat62 elf]$ cat test1.c
/*test1.c only for alert7 test*/
#include
#include
#include
#include
#include
#define nonfatal(s) {perror(s); return;}
#define fatal(s) {perror(s); exit(-1);}
#define bfd_nonfatal(s) {bfd_perror(s); return;}
#define bfd_fatal(s) {bfd_perror(s); exit(-1);}
main(int argc, char *argv[])
{
bfd *ibfd;
char *filename;
char **matching;
if (argc<2) exit(-1);
filename = argv[1];
bfd_init();
ibfd = bfd_openr(filename, NULL);
if (ibfd == NULL)
{
bfd_nonfatal("openr");
}
if (bfd_check_format_matches(ibfd, bfd_object, &matching))
{
int sections_count=bfd_count_sections(ibfd);
printf("%s section count %d\n",argv[1],sections_count);
} else {
bfd_fatal("format_matches");
}
bfd_close(ibfd);
}
[alert7@redhat62 elf]$ gcc -c test1.c
[alert7@redhat62 elf]$ gcc -o test1 test1.o -lbfd -liberty
[alert7@redhat62 elf]$ ./test1 /lib/ld-linux.so.2
/lib/ld-linux.so.2 section count 19
★3. BFD一些基本的函数
bfd_init(void)
初始化BFD。
bfd *bfd_openr(CONST char *filename, CONST char *target);
以目标target打开filename文件。返回指向bfd的指针。target由
Calls_bfd_find_target函数来解析。
如果错误发生,返回NULL,那些错误可能是bfd_error_no_memory,
bfd_error_invalid_target or system_call error.
boolean bfd_set_default_target (const char *name);
设置默认的target向量,用来查找一个匹配的BFD。
boolean bfd_check_format(bfd *abfd, bfd_format format);
确定是否是BFD支持的文件格式(例如是bfd_object, bfd_archive或者bfd_core之一).
假如在该调用之前已经设置了特别的target,那么仅仅和target相关联的格式被检查。
假如target没有被设置或者被设置为默认的,所有已知的后端将被询问是否跟他们匹配。
函数成功返回true,否则false,以下是一些错误代码:
bfd_error_invalid_operation - 假如格式不是bfd_object, bfd_archive和
bfd_core之一。
bfd_error_system_call - 假如一个错误发生在读期间 - 甚至一些文件错误促发
bfd_error_system_calls.
file_not_recognised - 没有后端可以认识该文件格式。
bfd_error_file_ambiguously_recognized - 超过一个的后端能认识该文件格式。
boolean bfd_check_format_matches(bfd *abfd, bfd_format format,
char ***matching);
类似bfd_check_format, 除了当false返回时,bfd_errno被设置为
bfd_error_file_ambiguously_recognized.在这种情况下,假如matching不等于NULL,
它被填充为以NULL为结尾的一个已经注册了的所有的格式名称的列表。然后用户可以从中
选择一个格式再试一次。
当不需要时候,用户应该free指向列表指针的内存。
bfd *bfd_openw(CONST char *filename, CONST char *target);
使用格式的target,为文件filename创建一个BFD。返回指向BFD的指针。
错误可能是bfd_error_system_call, bfd_error_no_memory, bfd_error_invalid_target.
boolean bfd_close(bfd *abfd);
Close BFD.假如以写打开的BFD,未完成的操作会被完成,然后文件才会被关闭。假如创建文件是
可执行的,将会调用chmod使它一致。所有BFD相关的内存被释放。
所有OK的话返回true,否则false.
bfd_get_symtab_upper_bound
返回所有标号需要的字节大小(也包括一个NULL指针)。假如没有标号,将返回为0。
错误返回-1。
bfd_canonicalize_symtab
从BFD中读标号。返回标号指针的个数(不包括NULL)。
boolean bfd_get_section_contents
(bfd *abfd, asection *section, PTR location,
file_ptr offset, bfd_size_type count);
把BFD abfd section中的数据读入内存location开始地址。数据从section的偏移量offset
开始读,count为要读数据的字节数。
boolean bfd_set_section_contents
(bfd *abfd,
asection *section,
PTR data,
file_ptr offset,
bfd_size_type count);
把内存中data地址开始的数据设置为abfd BFD的节section的内容,数据从section的偏移量
offset开始写,count为要写的数据字节数。
void bfd_map_over_sections(bfd *abfd,
void (*func)(bfd *abfd,
asection *sect,
PTR obj),
PTR obj);
为BFD abfd相关的每个section调用私有的函数func,该函数将会被如下调用:
func(abfd, the_section, obj);
该函数看起来就象执行了如下代码:
section *p;
for (p = abfd->sections; p != NULL; p = p->next)
func(abfd, p, ...)
更多的bfd的库函数说明可以在以下找到:
http://www.gnu.org/manual/bfd-2.9.1/html_node/bfd_toc.html
★4. 通过BFD读符号表中符号
/*test2.c only for alert7 test*/
#include
#include
#include
#include
#include
#define nonfatal(s) {perror(s); return;}
#define fatal(s) {perror(s); exit(-1);}
#define bfd_nonfatal(s) {bfd_perror(s); return;}
#define bfd_fatal(s) {bfd_perror(s); exit(-1);}
main(int argc, char *argv[])
{
bfd *ibfd;
char *filename;
char **matching;
if (argc<2) exit(-1);
filename = argv[1];
bfd_init();
ibfd = bfd_openr(filename, NULL);
if (ibfd == NULL)
{
bfd_nonfatal("openr");
}
if (bfd_check_format_matches(ibfd, bfd_object, &matching))
{
long storage_needed;
asymbol **symbol_table;
long number_of_symbols;
long i;
symbol_info symbolinfo ;
storage_needed = bfd_get_symtab_upper_bound (ibfd);
if (storage_needed < 0)
bfd_nonfatal("bfd_get_symtab_upper_bound");
if (storage_needed == 0) {
return ;
}
symbol_table = (asymbol **) xmalloc (storage_needed);
number_of_symbols =
bfd_canonicalize_symtab (ibfd, symbol_table);
if (number_of_symbols < 0)
bfd_nonfatal("bfd_canonicalize_symtab");
printf("Scanning %i symbols\n", number_of_symbols);
for(i=0;i
if (symbol_table[i]->section==NULL) continue;
printf("Section %s ",symbol_table[i]->section->name);
bfd_symbol_info(symbol_table[i],&symbolinfo);
printf("Symbol \"%s\" value 0x%x\n",
symbolinfo.name, symbolinfo.value);
}
} else {
bfd_fatal("format_matches");
}
bfd_close(ibfd);
}
/* EOF */
以上小程序打印符号的name和value还有相关的section.
[alert7@redhat62 elf]$ gcc -c test2.c
[alert7@redhat62 elf]$ gcc -o test2 test2.o -lbfd -liberty
[alert7@redhat62 elf]$ ./test2 helo
Scanning 75 symbols
Section .interp Symbol "" value 0x80480f4
Section .note.ABI-tag Symbol "" value 0x8048108
Section .hash Symbol "" value 0x8048128
......
Section *ABS* Symbol "initfini.c" value 0x0
Section .text Symbol "gcc2_compiled." value 0x804841c
Section *ABS* Symbol "helo.c" value 0x0
......
Section *ABS* Symbol "_end" value 0x8049540
Section .rodata Symbol "_IO_stdin_used" value 0x804843c
Section .data Symbol "__data_start" value 0x8049448
Section *UND* Symbol "__gmon_start__" value 0x0
再看看objdump输出什么
[alert7@redhat62 elf]$ objdump -t helo
helo: file format elf32-i386
SYMBOL TABLE:
080480f4 l d .interp 00000000
08048108 l d .note.ABI-tag 00000000
08048128 l d .hash 00000000
......
00000000 l df *ABS* 00000000 initfini.c
08048344 l .text 00000000 gcc2_compiled.
00000000 l df *ABS* 00000000 init.c
......
08049540 g O *ABS* 00000000 _end
0804843c g O .rodata 00000004 _IO_stdin_used
08049448 g .data 00000000 __data_start
00000000 w *UND* 00000000 __gmon_start__
(输出太多了,占太多大家宝贵的硬盘空间不好,所以只保留了以上输出)
其实objdump也是使用BFD的
[alert7@redhat62 elf]$ ldd /usr/bin/objdump
libopcodes-2.9.5.0.22.so => /usr/lib/libopcodes-2.9.5.0.22.so (0x4001c000)
libbfd-2.9.5.0.22.so => /usr/lib/libbfd-2.9.5.0.22.so (0x40032000)
libdl.so.2 => /lib/libdl.so.2 (0x40077000)
libc.so.6 => /lib/libc.so.6 (0x4007b000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
★5. 写标号
演示如何进行写操作
/*test3.c for just show wirte operation*/
#include
#include
main()
{
bfd *abfd;
asymbol *ptrs[2];
asymbol *new;
abfd = bfd_openw("foo",NULL);
if (abfd==NULL) exit(0);
bfd_set_format(abfd, bfd_object);
new = bfd_make_empty_symbol(abfd);
new->name = "dummy_symbol";
new->section = bfd_make_section_old_way(abfd, ".text");
new->flags = BSF_GLOBAL;
new->value = 0x12345;
ptrs[0] = new;
ptrs[1] = (asymbol *)0;
bfd_set_symtab(abfd, ptrs, 1);
bfd_close(abfd);
}
[alert7@redhat62 elf]$ gcc -c test3.c
[alert7@redhat62 elf]$ gcc -o test3 test3.o -lbfd -liberty
[alert7@redhat62 elf]$ ./test3
[alert7@redhat62 elf]$ ls -ls foo
4 -rw-rw-r-- 1 alert7 alert7 350 Sep 21 09:56 foo
[alert7@redhat62 elf]$ file foo
foo: ELF 32-bit LSB relocatable, no machine, version 1, not stripped
[alert7@redhat62 elf]$ nm foo
00012345 T dummy_symbol
★ 后记
本文只用来抛砖引玉,如果想用BFD写个实际有用的程序(比如实现绿盟月刊第14期上有篇
wangdb写的《如何修改动态库符号表》上的功能),需要自己好好研究研究那些BFD的库函数。
No pain,No gain...
不过虽然BFD功能很强大,但毕竟是别人封装过的,所以或许在自己琢磨那些库函数的时候,
就已经头晕了,而且不灵活,很不爽。我就是这样啦:(
封装了的库函数会屏蔽底层操作的细节,追求技术的你还是不要用它吧,但这就需要一些
ELF的知识。