天天看点

bfd库源码分析

阅读BFD库可以从简单的接口入手,如上章介绍的nm命令使用的几个接口就是很好的分析对象(https://blog.csdn.net/t3swing/article/details/79671461),通过这几个接口,对bfd源码和流程有初步了解,可以为深入分析bfd源码打下基础。可以使用gdb进行辅助分析,使用gdb辅助阅读代码,有利于提升阅读效率。下面记录一下本人阅读bfd源码流程。

BFD库依赖说明

libiberty库介绍

bfd主要依赖libiberty.a库,iberty库是一个通用工具库,除了bfd,很多开源软件会使用该库,是一个比较基础的工具库。

iberty这个名字很有意思,意为自由软件库(library of free software),单词liberty为自由的意思,原本库的名字就为libliberty.a,链接的时候为-lliberty,链接的时候有两个“l”,为看起来舒服,所以简化为-liberty,库的名字就变成了libiberty.a,后面统一称为libiberty库

libiberty库相关介绍可以参考http://gcc.gnu.org/onlinedocs/libiberty/这个链接。库主要分3类:

  • Supplemental Functions:Providing functions which don’t exist on older operating systems.补充功能,由于各个系统函数支持情况不一样,使用libiberty库可以提供兼容支持。如basename这个函数,使用该库,各个平台都可以统一使用该函数。
  • Replacement Functions:These functions are sometimes buggy or unpredictable on some operating systems.替换的函数,虽然平台上有该函数,但是函数处理不一致(如异常处理部分),该库可以屏蔽差异部分,
  • Extensions Functions: which provide useful extensions or safety wrappers around existing code.扩展函数,主要有Obstacks。

libiberty库最常见的就是x系列函数了,如xmalloc、xrealloc等,再就是Obstacks系列函数,libiberty库中也包含regex相关代码,可以扣出来用在嵌入式等没有正则库的环境下,这个正则实现代码不是很多,有一定的限制,但总体来说轻便易用。

Obstacks介绍

Obstacks是object stacks缩写( a pool of memory containing a stack of objects),对象栈内存池。用法可以参考https://gcc.gnu.org/onlinedocs/libiberty/Obstacks.html#Obstacks这个链接。

Obstacks主要用在:

  • 不需要动态申请释放内存,业务过程中,内存申请过后在业务结束前不用释放(栈顶释放内存用法很鸡肋)。
  • 申请的内存大小不需要变化,像需要realloc的情况就没法用Obstacks

网上介绍Obstacks的文章很多,这里举一个实际的例子来协助理解。

  • 嵌入式设备内存限制比较大,内存碎片会影响内存的使用率与效率。减少内存碎片可以用内存池,特定情况可以用obstack来替换。
  • xml解析过程会大量申请内存(每个节点、属性都会申请一小段内存),使用完成又会释放内存,这个过程中会存在内存碎片(多线程情况更突出)。
  • 如果解析xml的目的只是用来读xml数据(如用xml做协议对接的时候,xml节点并不要修改),则可以使用obstack,内存管理就会简单很多,不用使用链表来管理内存,释放的时候释放整个obstack即可。

注意:obstack并不是线程安全的,如果用在多线程情况,申请释放过程需加锁。

Objalloc介绍

Objalloc即object alloc,对象内存分配,这个东西感觉用的不是很多,但bfd中用到了,与obstack有点类似,也是预分配内存,所有对象申请了就不能释放,通过链表管理内存,使用完成后统一释放内存,但实现比obstack简单不少,每个chunk(一个内存块)固定为4k,一次性分配大于512字节的内存申请可能单独使用一个chunk。用法还是比较简单的,先objalloc_create创建,再objalloc_alloc申请对象内存,然后统一调用objalloc_free释放。

BFD库源码分析

bfd库源码分析,源码和分析环境搭建请参考上一章(https://blog.csdn.net/t3swing/article/details/79671461),使用bfd库编写的nm命令来分析bfd源码,分析流程安装函数调用顺序进行。

bfd结构体

bfd结构体是bfd库最重要的结构体之一,该结构体把前端、后端各类型数据结构体关联起来,本源码分析需重点关注xvec结构和tdata这两个结构体。

struct _bfd
{
  const char *filename; /* 需分析目标文件名称 */
  const struct bfd_target *xvec; /* 目标平台信息,是bfd兼容不同的平台最主要的结构体 */
  PTR iostream; /* 打开需分析文件的句柄 */
  boolean cacheable;
  boolean target_defaulted;
  struct _bfd *lru_prev, *lru_next;
    ...
  bfd_format format;    /* 目标文件格式 */
  enum bfd_direction direction; /* 读或写的方式打开目标文件 */

  struct sec *sections;
  struct sec **section_tail;
  unsigned int section_count;

  bfd_vma start_address;
  unsigned int symcount;
  struct symbol_cache_entry  **outsymbols;
  unsigned int dynsymcount;

  const struct bfd_arch_info *arch_info; /* 平台数据存储结构信息 */
    ...
  union tdata; /* 存放后端数据 */
  PTR usrdata;
  PTR memory;
}
           

bfd_openr

先看bfd_init函数,这个没什么好说的,空函数,反正调用的时候建议带上,老代码换新的bfd库时可能有用。

bfd_set_default_target这个函数不是必须的,稍后分析,先看bfd_openr函数,源码如下:

bfd *bfd_openr (const char *filename,const char *target)    
{
  nbfd = _bfd_new_bfd ();
  target_vec = bfd_find_target (target, nbfd);

  nbfd->filename = filename;
  nbfd->direction = read_direction;

  if (bfd_open_file (nbfd) == NULL)

  return nbfd;
}
           

_bfd_new_bfd函数申请一个空的bfd结构,bfd_zmalloc与calloc函数比较类似,而bfd->memory = (PTR) objalloc_create ();则使用objalloc方式创建对象内存。

重点介绍一下bfd_find_target函数,源码如下:

const bfd_target * bfd_find_target (const char *target_name, bfd *abfd)
{
    /* 默认target情况下处理,处理同平台的目标文件 */
    if (targname == NULL || strcmp (targname, "default") == )
    {
        abfd->target_defaulted = true;
        if (bfd_default_vector[] != NULL)
            abfd->xvec = bfd_default_vector[];
        else
            abfd->xvec = bfd_target_vector[];
        return abfd->xvec;
    }

    /* 指定target处理,可以在当前平台下解析其他平台下目标文件 */
    abfd->target_defaulted = false;
    target = find_target (targname);
    abfd->xvec = target;
    return target;
}
           

该函数最重要的是abfd->xvec中的xvec成员,bfd库分Front End(前端)与Back Ends(后端),这个就是面向对象的做法了,前端统一了接口,后端兼容不同的平台与对象,用户使用的时候,只需要知道目标平台(或者用默认的),不用管目标平台差异,就可以操作不同平台的各种对象了,添加某对象文件支持时,只需要修改后端代码,极大的方便了移植与兼容工作。

而所谓的后端就是通过xvec来实现的,vec是vector容器的缩写,x按个人理解是多个里面某个的意思(数学用x表示未知量),先看同平台的情况,xvec最终指向的对象有点绕,看一下编译参数,如下:

gcc -DHAVE_CONFIG_H -I. -I. -I. -D_GNU_SOURCE -DTRAD_CORE -I. -I. -I./../include -I./../intl -I../intl -W -Wall -Wstrict-prototypes -Wmissing-prototypes -g -O2 -c -DDEFAULT_VECTOR=bfd_elf32_i386_vec “-DSELECT_VECS=&bfd_elf32_i386_vec,&i386linux_vec,&bfd_efi_app_ia32_vec,&bfd_elf32_little_generic_vec,&bfd_elf32_big_generic_vec” “-DSELECT_ARCHITECTURES=&bfd_i386_arch” -DHAVE_bfd_elf32_i386_vec -DHAVE_i386linux_vec -DHAVE_bfd_efi_app_ia32_vec -DHAVE_bfd_elf32_little_generic_vec -DHAVE_bfd_elf32_big_generic_vec ./targets.c -o targets.o

需注意由gcc传入的宏,这些宏是bfd兼容不同平台重要组成部分。宏整理如下:

DEFAULT_VECTOR=bfd_elf32_i386_vec

SELECT_VECS=

* &bfd_elf32_i386_vec,

* &i386linux_vec,

* &bfd_efi_app_ia32_vec,

* &bfd_elf32_little_generic_vec,

* &bfd_elf32_big_generic_vec

SELECT_ARCHITECTURES=&bfd_i386_arch

HAVE_bfd_elf32_i386_vec

HAVE_i386linux_vec

HAVE_bfd_efi_app_ia32_vec

HAVE_bfd_elf32_little_generic_vec

HAVE_bfd_elf32_big_generic_vec

const bfd_target *bfd_default_vector[] = {
#ifdef DEFAULT_VECTOR
    &DEFAULT_VECTOR,
#endif
    NULL
};
           

上面这句话可以知道

abfd->xvec = bfd_default_vector[0] = &bfd_elf32_i386_vec

,即本人在x86 32位平台上编译的target为

bfd_elf32_i386_vec

,搜索代码可以看到target.c中有extern const bfd_target bfd_elf32_i386_vec;此全局量的声明,但该全局量在哪里定义的呢 ?可以在elf32-i386.c中看到如下代码:

#ifndef ELF_ARCH
#define TARGET_LITTLE_SYM       bfd_elf32_i386_vec
#define TARGET_LITTLE_NAME      "elf32-i386"
#define ELF_ARCH                bfd_arch_i386
#define ELF_MACHINE_CODE        EM_386
#define ELF_MAXPAGESIZE         0x1000
#endif /* ELF_ARCH */
...
#include "elf32-target.h"
           

头文件elf32-target.h有如下代码:

#ifdef TARGET_LITTLE_SYM
const bfd_target TARGET_LITTLE_SYM =
{
  /* name: identify kind of target */
  TARGET_LITTLE_NAME,
  /* flavour: general indication about file */
  bfd_target_elf_flavour,
  /* byteorder: data is little endian */
  BFD_ENDIAN_LITTLE,
  /* header_byteorder: header is also little endian */
  BFD_ENDIAN_LITTLE,
  ...
}
           

结合上述宏,bfd_elf32_i386_vec定义即为TARGET_LITTLE_SYM,bfd_elf32_i386_vec.name = elf32-i386;大家可以再看下elf32-target.h头文件,这是个公用头文件,与平台无关,且把差异部分通过宏来定义,各平台通过包含该头文件来做兼容。

再看一下非default目标平台情况,函数 find_target在下面结构体数组中按名称匹配target,

static const bfd_target * const _bfd_target_vector[] = 
{
#ifdef SELECT_VECS
    SELECT_VECS,
#else /* not SELECT_VECS */
    ...
#endif
};
           

宏 SELECT_VECS编译器传下来的,展开为:

static const bfd_target * const _bfd_target_vector[] = 
{
   &bfd_elf32_i386_vec,
   &i386linux_vec,
   &bfd_efi_app_ia32_vec,
   &bfd_elf32_little_generic_vec,
   &bfd_elf32_big_generic_vec
};
           

所以在x86平台下编译的bfd库,只支持以上5个目标,其他平台的代码不会编译进来,自然也支持不了,再回头看一下bfd_set_default_target函数,可以看到这个函数不调用的话,bfd_openr 函数中也会做此工作,所以目标要么不设,要么就设对,否则目标平台就会存在找不到的情况。

继续看bfd_openr 函数,语句

nbfd->direction = read_direction;

中的direction 决定了bfd open时的读写属性,bfd有多种打开方式,对应不同的函数,用来控制是读目标文件还是写目标问题,bfd_open_file函数返回目标文件打开时的句柄。其他打开函数类似

bfd_check_format_matches函数

该函数比较重要,不但校验目标文件是否是指定的类型,还会存储部分数据到tdata中,函数源码如下:

boolean bfd_check_format_matches (bfd *abfd, bfd_format format, char ***matching)
{
    ...
    for (target = bfd_target_vector; *target != NULL; target++)
    {
        abfd->xvec = *target;   /* Change BFD's target temporarily */
        bfd_seek (abfd, (file_ptr) , SEEK_SET);

        temp = BFD_SEND_FMT (abfd, _bfd_check_format, (abfd));
        if (temp)
        {       
            right_targ = temp;
            if (temp == bfd_default_vector[])
            {
                match_count = ;
                break;
            }

            if (matching)
                matching_vector[match_count] = temp->name;

            match_count++;
        }
    }

    if (match_count == )
    {
      abfd->xvec = right_targ;
      if (matching)
        free (matching_vector);

      return true;          
    }
    ...
}
           

先看一下以下两个宏,这两个宏多个地方用到,前端与后端交互,大多通过该宏实现。

#define BFD_SEND(bfd, message, arglist) ((*((bfd)->xvec->message)) arglist)

#define BFD_SEND_FMT(bfd, message, arglist) (((bfd)->xvec->message[(int) ((bfd)->format)]) arglist)

该宏主要调用后端结构体xvec的成员函数,message参数指定需调用函数的名称,如_bfd_check_format,先看一下后端目标结构体bfd_target,bfd_target结构体很大,结构体中大部分是函数指针,用来统一不同平台操作。

typedef struct bfd_target
{
  char *name;
  enum bfd_flavour flavour;
  enum bfd_endian byteorder;
  enum bfd_endian header_byteorder;
  flagword object_flags;
  flagword section_flags;
    ...
  const struct bfd_target *(*_bfd_check_format[bfd_type_end]) PARAMS ((bfd *));
  boolean  (*_bfd_set_format[bfd_type_end]) PARAMS ((bfd *));
    ...
  void     (*_bfd_print_symbol) PARAMS ((bfd *, PTR,struct symbol_cache_entry *,bfd_print_symbol_type));
  void     (*_bfd_get_symbol_info) PARAMS ((bfd *,struct symbol_cache_entry *,symbol_info *));
    ...
};
           

上面bfd_target结构体有一点要注意,函数_bfd_check_format[bfd_type_end]实际上是定义了一个函数指针数组,这么写,实际上包含4个成员。即

_bfd_check_format[0]-_bfd_check_format[3]

分别对应以下几种类型,本文只分析bfd_object类型,包含elf、.o文件等。

typedef enum bfd_format
{
  bfd_unknown = ,  /* File format is unknown.  */
  bfd_object,       /* Linker/assember/compiler output.  */
  bfd_archive,      /* Object archive file.  */
  bfd_core,     /* Core dump.  */
  bfd_type_end      /* Marks the end; don't use it!  */
}
           

前面已经分析了xvec指向bfd_elf32_i386_vec即TARGET_LITTLE_SYM,语句

BFD_SEND_FMT (abfd, _bfd_check_format, (abfd))

调用的是bfd_elf32_object_p函数,这里又开始绕了,先看一下elf-bfd.h中的宏:

#define CONCAT4(a,b,c,d) a##b##c##d

#if ARCH_SIZE==32
#define NAME(x,y) CONCAT4 (x,32,_,y)
#endif
           

NAME(x,y)作用就是在标识符中间插上32_,如NAME(bfd_elf,object_p)展开就是bfd_elf32_object_p。而这个#define elf_object_p NAME(bfd_elf,object_p)就在elfcode.h中定义,elfcode.h只在elf32.c中引用,这种兼容方式特点就是声明(后端部分)和函数实现分离(公用部分),通过宏耦合,这样就可以根据不同的平台来做一定变化。具体来讲就是:

elfcode是公用部分,最终实现的函数是

const bfd_target *elf_object_p (bfd *abfd)

函数。

这个宏#define elf_object_p NAME(bfd_elf,object_p)的存在,函数原型实际上做了一定调整的。

如吧NAME宏改成#define NAME(x,y) CONCAT3 (hp300hpux,32,y),实际函数原型就变成了hp300hpubfd_elf_32_object_p;

通过上面分析,实际上bfd_elf32_object_p执行的就是

const bfd_target * elf_object_p (bfd *abfd);

函数,后续看到调用中间带32_的函数,可以把32_去掉进行搜索,平台确定后,NAME宏也是确定的。

再回到上面,

BFD_SEND_FMT (abfd, _bfd_check_format, (abfd)

实际调用的是elf_object_p函数。elf_object_p函数功能是读取并校验elf文件,理解这个函数需先了解elf格式,elf格式大家可以参考文章(https://blog.csdn.net/t3swing/article/details/79667467)。

看一下下面几个局部变量:

struct elf_backend_data *ebd;
  struct bfd_preserve preserve;
  struct elf_obj_tdata *new_tdata = NULL;
           

preserve这个变量是用来存储bfd之前状态的,elf_object_p执行失败后会用该变量进行状态还原,相关代码可以不用看。

new_tdata这个变量比较关键,在代码中,该变量指向

bfd -> tdata.elf_obj_data

,tdata是一个联合体,这里只用到了elf_obj_data结构体指针,而这个结构体指针指向的内存是由bfd_zalloc分配的,最终会调用objalloc_alloc分配内存,而objalloc_alloc用到了bfd中的memory变量。

elf_obj_data结构体非常大,elf_object_p函数很大一部分代码用来填充该结构体,后续的操作都是通过类似下面宏来实现:

#define elf_elfheader(bfd)  (elf_tdata(bfd) -> elf_header)
#define elf_elfsections(bfd)    (elf_tdata(bfd) -> elf_sect_ptr)
#define elf_numsections(bfd)    (elf_tdata(bfd) -> num_elf_sections)
...
           

ebd指针指向abfd->xvec->backend_data,上面已经分析了xvec指向TARGET_LITTLE_SYM全局量,而此全局量backend_data指向全局量elf32_bed,elf_object_p中的ebd就是利用elf32_bed已初始化好的值来校验elf文件,相关的代码也可以不用细看。

关键代码基本是检查elf格式、填充tdata.elf_obj_data这个结构体,函数返回前还有一段填充bfd->sections结构体的代码。填充检查按elf头、section头、programe头顺序来的。这个函数比较长,不清楚的地方可以通过gdb跟一下。

bfd_read_minisymbols

bfd_read_minisymbols是通过宏

#define bfd_read_minisymbols(b, d, m, s) BFD_SEND (b, _read_minisymbols, (b, d, m, s))

实现的,该函数最终指向_bfd_generic_read_minisymbols函数,这个过程也是绕的很,从bfd_target类型结构体TARGET_LITTLE_SYM中找_read_minisymbols成员函数,先得展开BFD_JUMP_TABLE_SYMBOLS宏,展开该宏BFD_JUMP_TABLE_SYMBOLS (bfd_elf32)可以知道对应成员函数为bfd_elf32_read_minisymbols,这又对应了两个宏:

#define bfd_elf32_read_minisymbols  _bfd_elf_read_minisymbols
#define _bfd_elf_read_minisymbols _bfd_generic_read_minisymbols
           

所以最终调用的是_bfd_generic_read_minisymbols函数,该函数源码如下:

long _bfd_generic_read_minisymbols (bfd *abfd, boolean dynamic, PTR *minisymsp, unsigned int *sizep)
{

    if (dynamic)
        storage = bfd_get_dynamic_symtab_upper_bound (abfd);
    else
        storage = bfd_get_symtab_upper_bound (abfd);

    syms = (asymbol **) bfd_malloc ((bfd_size_type) storage);

    if (dynamic)
        symcount = bfd_canonicalize_dynamic_symtab (abfd, syms);
    else
        symcount = bfd_canonicalize_symtab (abfd, syms);

    *minisymsp = (PTR) syms;
    *sizep = sizeof (asymbol *);
    return symcount;
}
           

dynamic这个参数,用来决定是否查看动态符号的,nm命令-D选项时,此时dynamic为true,用来查看.so中的符号表,这里我们只看dynamic为false的情况(为true的情况,代码差别并不大)。 bfd_get_symtab_upper_bound又是一个宏,指向_bfd_elf_get_symtab_upper_bound函数,这个函数内容比较简单,按函数名称的意思是,或者符号表上边界,实际返回的是symcount * 4大小用来存储asymbol类型指针,里面做了 一点小处理,保证最小分配为4字节。

获取到指针的大小,使用bfd_malloc分配实际指针数组的空间,bfd_canonicalize_symtab也是一个宏,最终指向_bfd_elf_get_symtab函数,看下这个函数代码:

long _bfd_elf_get_symtab (bfd *abfd, asymbol **alocation)
{
  struct elf_backend_data *bed = get_elf_backend_data (abfd);
  long symcount = bed->s->slurp_symbol_table (abfd, alocation, false);

  if (symcount >= )
    bfd_get_symcount (abfd) = symcount;
  return symcount;
}
           

bed这个缩写不错,bed实际指向的是elf32_bed,s这个成员指向elf_backend_size_info结构体,又开始绕了:

#define elf_backend_size_info _bfd_elf32_size_info
extern const struct elf_size_info _bfd_elf32_size_info;
const struct elf_size_info NAME(_bfd_elf,size_info) = {
    ...
    elf_slurp_symbol_table,
    ...
};
#define elf_slurp_symbol_table      NAME(bfd_elf,slurp_symbol_table)
           

由上述的代码可知bed->s->slurp_symbol_table最终指向的是elf_slurp_symbol_table函数,这个函数是个宏,实际名称应该是bfd_elf32_slurp_symbol_table,这个名称对看代码没什么用,代码中我们仍然看elf_slurp_symbol_table函数,这个函数在elfcode.h这个头文件中,这个头文件代码还不少。

[email protected]:~$ nm binutils-2.13.2.1/bfd/.libs/libbfd.a |grep bfd_elf32_slurp_symbol_table

00000700 T bfd_elf32_slurp_symbol_table

函数

long elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, boolean dynamic)

代码比较多,可以从两方面来梳理,一是内存的使用,另外一个是获取符号流程。

函数elf_slurp_symbol_table调用了bfd_elf_get_elf_syms,这个函数的作用就是取出符号表,代码如下:

isymbuf = bfd_elf_get_elf_syms (abfd, hdr, symcount, , NULL, NULL, NULL);

Elf_Internal_Sym * bfd_elf_get_elf_syms (bfd *ibfd, Elf_Internal_Shdr *symtab_hdr, 
     size_t symcount, size_t symoffset, Elf_Internal_Sym *intsym_buf, 
     PTR extsym_buf, Elf_External_Sym_Shndx *extshndx_buf)
{

  bed = get_elf_backend_data (ibfd);
  extsym_size = bed->s->sizeof_sym;
  amt = symcount * extsym_size;
  pos = symtab_hdr->sh_offset + symoffset * extsym_size;
  if (extsym_buf == NULL)
    {
      alloc_ext = bfd_malloc (amt);
      extsym_buf = alloc_ext;
    }

      amt = symcount * sizeof (Elf_External_Sym_Shndx);
      pos = shndx_hdr->sh_offset + symoffset * sizeof (Elf_External_Sym_Shndx);
      if (extshndx_buf == NULL)
    {
      alloc_extshndx = (Elf_External_Sym_Shndx *) bfd_malloc (amt);
      extshndx_buf = alloc_extshndx;
    }

  if (intsym_buf == NULL)
    {
      bfd_size_type amt = symcount * sizeof (Elf_Internal_Sym);
      intsym_buf = (Elf_Internal_Sym *) bfd_malloc (amt);
    }

  isymend = intsym_buf + symcount;
  for (esym = extsym_buf, isym = intsym_buf, shndx = extshndx_buf;
       isym < isymend;
       esym += extsym_size, isym++, shndx = shndx != NULL ? shndx +  : NULL)
    (*bed->s->swap_symbol_in) (ibfd, esym, (const PTR) shndx, isym);

 out:
  if (alloc_ext != NULL)
    free (alloc_ext);
  if (alloc_extshndx != NULL)
    free (alloc_extshndx);

  return intsym_buf;
}
           

由上面代码可以看出,bfd_elf_get_elf_syms开始分配了3块内存,amt是amount总量的缩写,每块内存都是amt * sizeof(Elf_xx)大小,有2块内存只是临时使用,使用完后会释放,bed->s->swap_symbol_in这个函数最终调用的是elf_swap_symbol_in函数,会把外部格式(External)转成内部格式(Internal),最终返回的是amt * sizeof(Elf_Internal_Sym)的首地址,这个内存会在elf_slurp_symbol_table函数里释放掉。

注意:梳理内存管理,需注意bfd malloc相关函数的区别,内存分配函数有bfd_malloc、bfd_zmalloc、bfd_zalloc、bfd_alloc等函数,长的很像,但用途用法不一样。前面2函数是使用malloc进行内存管理的,内存使用完后需立即释放,而后面两个函数是调用objalloc_alloc实现的,bfd_close时才会释放,全生命周期有效,一般用来做数据缓存,上述函数这两种类型的内存申请函数都有使用,需注意区分。

函数elf_slurp_symbol_table获取到内部符号表后,会把这些数据转成asymbol型传出来,注意该函数中的两个临时变量,isym与sym,前面对应的是bfd_elf_get_elf_syms返回的内存数据,后面是需返回的数据,这两种结构的转换用了不少代码,需注意的是,传入参数asymbol只是一个指针数组,传入的时候数组里的指针都是空的,该函数返回时,最终指向sym对应的内存数据,即由

symbase = (elf_symbol_type *) bfd_zalloc (abfd, amt);

语句分配的内存。

bfd_make_empty_symbol

该函数也是一个宏,最终调用_bfd_elf_make_empty_symbol函数,作用申请一个asymbol * 类型的结构体,注意用的是bfd_zalloc函数申请的。

bfd_minisymbol_to_symbol

该宏最终调用的是_bfd_generic_minisymbol_to_symbol函数,该函数只做了一个类型强转。

bfd_get_symbol_info

该宏调用的是_bfd_elf_get_symbol_info函数,用来做结构体转换的,只提取了asymbol中的部分信息,代码注释说该函数主要是给nm命令用的。

bfd_close

资源释放,释放文件资源和内存资源。

bfd分析小结

以上的分析只是个人看bfd代码过程的一部分,涉及到的bfd源码也非常少,但使用nm命令作为分析载体的思路应该没错,通过nm用到函数的分析,对bfd基本架构有一定的了解,为后续深入分析bfd源码打下基础。

本人读bfd源码的原因是,这个库在二进制操作工具里用的非常广,如不清楚bfd的实现方式,更高层次的源码没法深入理解,后续应该会根据需要进行有选择性的阅读。文档与源码结合着读,会加深对bfd库的理解,如bfd的前端与后端,文档的描述就比较难理解,而源码就有较直接的体现。

继续阅读