BFD也叫做BFD库。BFD为上层工具(如汇编器,链接器)提供了一组统一的接口,称为前端。前端的功能是提供用户统一的界面,管理内存和各种内部数据结构,同时还决定使用哪一套后端以及何时使用该后端的函数。这些为上层提供的接口在移植的时候是不需要改变的。另一方面,BFD的后端接口,也是一组各种各样的函数,他们负责具体文件读写,与硬件相关,将Binutils移植到新的硬件平台需要实现这些接口。后端的主要功能是实现具体机器的目标文件表示和BFD内部表示的互相转换。
BFD库的设计目标是针对不同体系结构、不同格式目标文件提供一种统一的操作界面,使用户能够使用相同的函数对不同格式的目标文件进行操作。这些操作包括对目标文件内部信息的读写处理。BFD库对多种不同格式目标文件的操作能力,使它能够实现不同格式目标文件(比如ELF,a.out,windows PE)之间的信息转换。
BFD对目标文件进行抽象,在内部使用一种统一规范格式来表示目标文件。这个统一规范格式包括:
l 一个头
l 一些包含原始数据的段(sections),如text段,data段等。
l 一些重定位信息记录(relocations)
l 一些符号信息(symbols)
l 源文件行号规范格式中的重定位信息和符号信息等的结构和具体的各种二进制文件格式中的对应结构是不同的,BFD库在读入或写出具体二进制目标文件时都要做相应的转换。
当使用BFD打开一个目标文件时,返回一个指向内部数据结构bfd的指针。所有在这个目标文件上的操作都通过这个bfd结构中的函数来完成。当需要使用目标文件中的数据时,BFD后端提供的函数负责把具体目标文件的数据表示格式转变成BFD的内在规范的格式。而当BFD需要输出目标文件时,BFD后端提供另外的函数则会把BFD的内在规范格式转变成目标文件的表示格式。例如连接器处理符号表。每个BFD后端都提供一个将目标文件的符号表(symbols)表示转变成BFD内部规范格式的函数。当连接器需要目标文件的符号表信息时,BFD通过bfd中一个函数指针,调用相应后端的读目标文件符号表信息并将其格式转变成BFD内部规范格式的函数。BFD然后在内部规范格式进行处理。当连接器写出新建的符号表信息时,又会通过BFD中的另外一个后端函数来完成相反的转换。
1. 前端在功能划分上,BFD前端为用户提供统一的界面,管理内存和各种内部数据结构,同时还决定使用哪一套后端以及何时使用该后端的函数。而在具体实现上,则是通过数据结构_bfd来组织完成的。下面,具体分析其组成:unsigned int id;BFD实例的唯一标识符。
const char*filename;与BFD实例相关的文件名。
conststructbfd_target*xvec;指向目标结构体bfd_target的指针。由于BFD支持不同的机器结构、不同目标文件格式,为了满足这种需求,就要定制不同的后端bfd_target.而指针xvec则指明了当前的BFD实例对应何种后端,起到了联系BFD前端和后端的桥梁作用。
bfd_booleantarget_defaulted;该成员指出,当一个BFD实例被打开时,是否存在默认的target. structbfd_hash_tablesection_htab;用于保存节名称的hash表。
structbfd_section*sections;指向节链表的指针。
structbfd_section**section_tail;指向节链表尾部的指针,以便于增加新的节到链表中。
structbfd_symbol**outsymbols;指向符号链表的指针,被输出BFD使用。
conststructbfd_arch_info*arch_info;指向结构体bfd_arch_info的指针,该结构体包含后端的体系结构的信息。
union { structaout_data_struct*aout_data;structartdata*aout_ar_data;struct_oasys_data*oasys_obj_data;。。。。。。
structelf_obj_tdata*elf_obj_data;。。。。。。
void*any;}tdata;该联合体用于保存后端的私有数据。如ELF格式的后端可用structelf_obj_tdata *elf_obj_data保存相关信息。
void*memory;该指针指向BFD实例所占内存的起始地址。
2. 后端上文提到,BFD之所以能够支持不同的体系结构和不同的目标文件,是因为它采用了前后端分离的设计。每种后端对应一个bfd_target数据结构的具体实现,其中包含了体系结构的信息、目标文件的信息及相关的处理函数。下面,具体分析其组成。
char*name;标识目标的名称,如SunOS4、Ultrix等。
enumbfd_flavourflavour;变量flavour表明该后端对应的目标文件的类型,如其值为bfd_target_coff_flavour,表明是coff格式;其值为bfd_target_elf_flavour,表明是ELF格式。
enumbfd_endianbyteorder;目标文件中数据区的字节顺序,其值可为BFD_ENDIAN_BIG,BFD_ENDIAN_LITTLE和BFD_ENDIAN_UNKNOWN,分别代表大尾端、小尾端和未知类型。
enumbfd_endianheader_byteorder;目标文件头部的字节顺序。
flagwordobject_flags;目标文件的标志变量的掩码。
flagwordsection_flags;目标文件中节的标志变量的掩码。
char symbol_leading_char;符号前导字符,被编译器使用加于符号名前。
char ar_pad_char;存档文件头部中使用的文件名的填充字符。
unsigned short ar_max_namelen;存档文件头部中出现的名称的最大长度。
bfd_uint64_t(*bfd_getx64)(const void*);bfd_int64_t(*bfd_getx_signed_64)(const void*);void(*bfd_putx64)(bfd_uint64_t,void*);bfd_vma(*bfd_getx32)(const void*);bfd_signed_vma(*bfd_getx_signed_32)(const void*);void(*bfd_putx32)(bfd_vma,void*);bfd_vma(*bfd_getx16)(const void*);bfd_signed_vma(*bfd_getx_signed_16)(const void*);void(*bfd_putx16)(bfd_vma,void*);数据节的字节顺序交换函数,分别针对64为、32位和16位。由于运行平台和目标平台的字节顺序可能会不同,这些函数被用来进行转换。
bfd_uint64_t(*bfd_h_getx64)(const void*);bfd_int64_t(*bfd_h_getx_signed_64)(const void*);void(*bfd_h_putx64)(bfd_uint64_t,void*);bfd_vma(*bfd_h_getx32)(const void*);bfd_signed_vma(*bfd_h_getx_signed_32)(const void*);void(*bfd_h_putx32)(bfd_vma,void*);bfd_vma(*bfd_h_getx16)(const void*);bfd_signed_vma(*bfd_h_getx_signed_16)(const void*);void(*bfd_h_putx16)(bfd_vma,void*);与上一组函数类似,只是这组函数用于处理文件头部中的字节顺序交换。
conststructbfd_target*(*_bfd_check_format[bfd_type_end])(bfd*);bfd_boolean(*_bfd_set_format[bfd_type_end])(bfd*);bfd_boolean(*_bfd_write_contents[bfd_type_end])(bfd*);这三个函数为格式依赖函数。第一个函数用于检查被读取的文件的格式,第二个函数用于被写出的文件的格式,第三个函数用于将缓存信息写入到被写出的文件中。
bfd_boolean(*_close_and_cleanup)(bfd*);bfd_boolean(*_bfd_free_cached_info)(bfd*);bfd_boolean(*_new_section_hook)(bfd*,sec_ptr);bfd_boolean(*_bfd_get_section_contents)
(bfd*,sec_ptr,void*,file_ptr,bfd_size_type);bfd_boolean(*_bfd_get_section_contents_in_window)
(bfd*,sec_ptr,bfd_window*,file_ptr,bfd_size_type);通用函数,分别用于完成在关闭BFD时做必要的清除处理、释放缓存信息、创建新的节、读取节内容等功能。
bfd_boolean(*_bfd_copy_private_bfd_data)(bfd*,bfd*);bfd_boolean(*_bfd_merge_private_bfd_data)(bfd*,bfd*);bfd_boolean(*_bfd_copy_private_section_data)(bfd*,sec_ptr,bfd*,sec_ptr);bfd_boolean(*_bfd_copy_private_symbol_data)
(bfd*,asymbol*,bfd*,asymbol*);bfd_boolean(*_bfd_copy_private_header_data)(bfd*,bfd*);bfd_boolean(*_bfd_set_private_flags)(bfd*,flagword);bfd_boolean(*_bfd_print_private_bfd_data)(bfd*,void*);拷贝私有数据函数,在将私有数据从一种文件格式转换成另一种文件格式、连接文件到输出文件等情况时使用。
char*(*_core_file_failing_command)(bfd*);int(*_core_file_failing_signal)(bfd*);bfd_boolean(*_core_file_matches_executable_p)(bfd*,bfd*);core文件支持函数。
bfd_boolean(*_bfd_slurp_armap)(bfd*);bfd_boolean(*_bfd_slurp_extended_name_table)(bfd*);。。。。。。
int(*_bfd_stat_arch_elt)(bfd*,struct stat*);bfd_boolean(*_bfd_update_armap_timestamp)(bfd*);处理存档文件的函数。
long(*_bfd_get_symtab_upper_bound)(bfd*);long(*_bfd_canonicalize_symtab)(bfd*,structbfd_symbol**);。。。。。。
#define bfd_minisymbol_to_symbol(b,d,m,f)\ BFD_SEND(b,_minisymbol_to_symbol,(b,d,m,f))
asymbol*(*_minisymbol_to_symbol)(bfd*,bfd_boolean,const void*,asymbol*);处理符号的函数。
long(*_get_reloc_upper_bound)(bfd*,sec_ptr);long(*_bfd_canonicalize_reloc)
(bfd*,sec_ptr,arelent**,structbfd_symbol**);reloc_howto_type*(*reloc_type_lookup)(bfd*,bfd_reloc_code_real_type);进行重定位处理的函数。
bfd_boolean(*_bfd_set_arch_mach)(bfd*,enumbfd_architecture,unsigned long);bfd_boolean(*_bfd_set_section_contents)
(bfd*,sec_ptr,const void*,file_ptr,bfd_size_type);写目标文件时使用的函数。
int(*_bfd_sizeof_headers)(bfd*,bfd_boolean);。。。。。。
void(*_section_already_linked)(bfd*,structbfd_section*);连接器使用的函数。
long(*_bfd_get_dynamic_symtab_upper_bound)(bfd*);long(*_bfd_canonicalize_dynamic_symtab)(bfd*,structbfd_symbol**);。。。。。。
long(*_bfd_get_dynamic_reloc_upper_bound)(bfd*);long(*_bfd_canonicalize_dynamic_reloc)(bfd*,arelent**,structbfd_symbol**);处理动态连接信息的函数。
BFD的这种前后端结构具有很好的可扩展性,目前BFD己经支持多种二进制目标文件格式。如果想让BFD支持一种新的二进制目标文件格式时,只需为BFD增加一个新的后端。
3. 符号处理用高级语言编写的源程序,在经过编译、汇编变成二进制文件后,其中的函数名、变量名等名称都以符号的形式存在。这其中,包括当前模块中被定义的全局符号、在当前模块中被引用但未被定义的全局符号、调试器或崩溃转储分析用到的局部符号等。这些各种不同类型的符号所表示的信息为重定位处理、调试程序等提供重要帮助。
BFD为二进制工具程序提供支持,它的关于符号处理的接口涉及到符号的读写、生成等功能,是GNU Binutils中多种工具程序处理文件中符号信息的基础。
1) 符号的格式当前,在UNIX/Linux系统上使用的二进制文件有ELF、a.out、COFF等多种格式。不同的文件格式,由于其设计方式的不同,对其中符号格式的定义也有所不同。
如在BFD中,对ELF文件中的符号格式的定义如下:structelf_internal_sym { bfd_vmast_value;bfd_vmast_size;unsigned long st_name;unsigned char st_info;unsigned char st_other;unsignedintst_shndx;};其中,st_value为符号值。st_size若非零,为符号长度的字节数。st_name为符号名称在字串表中的偏移量。st_info为符号的类型和相应的属性。st_other常为空。st_shndx为与该符号相关的section头索引。
虽然,不同格式二进制文件中的符号格式不同,但是为了保持兼容性,使当前二进制工具程序必须能够对多种不同格式二进制的文件进行交叉处理,如对一个a.out格式文件和一个ELF格式文件进行连接处理,生成的输出文件为ELF格式。这样,符号格式的不同就带来了不便。为了解决这个问题,在BFD内部定义了一种统一的符号格式,如下所示:typedefstructbfd_symbol { structbfd*the_bfd;const char*name;symvalue value;#define BSF_NO_FLAGS 0x00 #define BSF_LOCAL 0x01 #define BSF_GLOBAL 0x02。。。。。。
flagword flags;structbfd_section*section;union { void*p;bfd_vma i;} udata;}asymbol;其中,the_bfd为指向该符号所属于的BFD的指针。name为该符号名称的指针。Value为符号的值。在这之后是一系列的宏定义,用于表明符号的属性,如BSF_LOCAL表示符号具有局部作用域,BSF_GLOBAL表示符号具有全局作用域。section为指向与该符号相关的section的指针。udata为一联合体类型的变量,用于存储后端特殊的数据,即一些二进制文件中符号的私有信息。BFD正是通过这种内部统一格式来处理不同格式二进制文件中的符号。
2) 读取符号在GNU Binutils中,一些二进制工具程序会用到输入文件中的符号信息,如连接器GLD等。在它们工作时,都需要BFD提供支持以读取符号,获得相应信息。
BFD从二进制文件中读取符号,主要有两个阶段。第一阶段是为符号分配空间,第二阶段是实际读取过程。由于BFD内部的符号格式是不同于二进制文件中的,因此读取符号过程的关键就是进行符号的从输入文件中格式到内部统一格式的转换。
下面以ELF文件为例,分析其具体过程。
在第一阶段,首先会调用函数bfd_get_symtab_upper_bound,获得符号表所占用空间的字节数。其中,参数为输入文件对应的BFD,通过它可以得知输入文件中符号表的大小。然后,调用函数bfd_alloc完成空间分配工作。
在第二阶段,调用函数bfd_canonicalize_symtab,完成符号的实际读取工作。在它其中,又会调用ELF后端函数elf_slurp_symbol_table在读取过程中,会进行符号格式的转换。为了实现这种转换,函数elf_slurp_symbol_table中首先定义了两种符号格式的变量:Elf_Internal_Sym和elf_symbol_type.其中,Elf_Internal_Sym即为上文中的ELF文件中的符号格式。而elf_symbol_type的定义如下:typedefstruct { asymbol symbol;Elf_Internal_Syminternal_elf_sym;union { unsignedinthppa_arg_reloc;void*mips_extr;void*any;} tc_data;unsigned short version;}elf_symbol_type;其中,symbol为上文中的BFD内部统一的符号格式变量。internal_elf_sym为ELF文件中的符号格式变量。tc_data是一些后端特定的私有信息。
接着,通过函数bfd_elf_get_elf_syms,以internal_elf_sym格式从ELF文件中取出符号信息,存于buffer中。然后,进入如下的for循环:for(isym=isymbuf+1,sym=symbase;isym
{ memcpy(&sym->internal_elf_sym,isym,sizeof(Elf_Internal_Sym));sym->symbol.the_bfd=abfd;sym->symbol.name=bfd_elf_sym_name(abfd,hdr,isym);sym->symbol.value=isym->st_value;。。。。。。
}在该循环中,ELF文件中的每个符号都得到处理,其中的信息被分别填入symbol对应的域中,如符号所属的BFD、符号名称、符号的值、符号的各种属性标识等。这样,就完成了符号信息在两种不同格式间的传递。
3) 生成符号BFD中的符号,有两种来源。一种是从输入文件中读取,如连接器GLD.它对被连接的目标文件及库文件进行分析,从中读取有用的符号信息,如上文所述。
另一种来源是由二进制工具程序创建,如汇编器GAS.它对由汇编语言写成的源文件进行汇编,将其中的函数名称等信息转换成符号信息。具体是通过调用函数symbol_create实现的。该函数的主要工作为:首先,调用函数bfd_make_empty_symbol,创建一个上文中的elf_symbol_type类型的符号变量,并为其分配相应的空间。这里所分配的空间大于BFD内部符号格式变量所需的空间,因为相对于asymbol类型的变量,大多数target中的符号都带有私有的信息。而这个函数可以满足需求,分配大小合适的空间,能够允许将target中符号的私有信息也存储在其中。
然后,相继调用函数S_SET_NAME、S_SET_SEGMENT和S_SET_VALUE,将相关信息分别填入符号变量对应的域中,完成赋值。
4) 输出符号在二进制文件中,符号是以符号表的形式存储的。某些格式的文件会在一个文件中存在多个符号表,如ELF共享库会有一个动态连接所需信息的符号表,和一个单独的更大的用来调试和重连接的符号表。而符号表的生成,必须按着二进制工具程序的具体要求进行的。因为,并不是所有输入文件中的符号都必须被写到输出文件中。
下面以连接器GLD为例,进行分析。
在连接器GLD中,符号被组织在哈希表中。函数_bfd_final_link完成符号的写出。连接器GLD中的bfd_link_info结构中的strip域控制着哪些符号被写出,该域所有可能的值在bfdlink.h中列出。如果它的值是strip_some,bfd_link_info结构中的keep_hash域则是一个需要保留的符号的哈希表。该表中的符号允许被写到输出文件中。如果bfd_link_info结构中的strip域允许局部符号被写出,则discard域用于进一步控制哪些局部符号被写到输出文件中。如果其值是discard_l,那么所有以特定前缀开头的符号被丢弃。
设定好哪些符号被写出后,BFD中完成关闭和清理功能的代码会进行必要的处理,并将它们写到输出文件中。