bfd库使用-nm源码分析

bfd介绍

想深入了解elf等可执行文件的原理(包括结构、运行等细节),用bfd库作切入点是比较好的选择。

BFD是Binary format descriptor的缩写, 即二进制文件格式描述,是很多可执行文件相关二进制工具(如nm、objdump、ar、as等命令)的基础库。bfd库可以用来分析、创建、修改二进制文件,支持多种平台(如x86、arm等)及多种二进制格式(如elf、core、so等)。

bfd库编译

bfd库是gnu开源软件的一部分,可以在gnu的binutils(即binary utils二进制工具集)目录中找到,地址是http://www.gnu.org/software/binutils/,源码路径为http://ftp.gnu.org/gnu/binutils/,源码分析一般基于较低的版本进行分析,低版本一般来说代码量少一些,特性少一些,但核心的东西都会具备,就像内核代码,0.11版本还是学习内核源码的经典。本来想直接分析binutils-2.7.tar.gz的,但2.7的源码不支持i686-linux-gnu平台(96年的20多年了),后选用了binutils-2.13.2.1.tar.gz版本进行分析,后续都是基于2.13版本分析。

binutils-2.13.2.1.tar.gz这个软件包,里面包含了bfd库、nm等命令,编译通过后,bfd库与nm都编译出来了,编译很简单,编译步骤:

  1. 代码解压到linux目录中

    sw@sw:mypro$ tar xzvf binutils-2.13.2.1.tar.gz

  2. 执行configure命令

    sw@sw:binutils-2.13.2.1$ ./configure

  3. 直接make编译
    我的机器上编译过程会报错,gcc版本是gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3),报错如下:

    ./config/tc-i386.h:530:32: error: array type has incomplete element type
    extern const struct relax_type md_relax_table[];
    ./config/tc-i386.h:531:32: error: ‘md_relax_table’ undeclared (first use in this function)
    #define TC_GENERIC_RELAX_TABLE md_relax_table

    把gas/config/tc-i386.h的530及531行直接删除即可编译通过。

    //extern const struct relax_type md_relax_table[];
    //#define TC_GENERIC_RELAX_TABLE md_relax_table

编译出的bfd库在bfd/.libs即libbfd.a,nm在binutils目录下,名称为nm-new

nm源码分析

学习bfd库,可以通过nm命令入手,gdb也用到了bfd库,使用gdb的源码阅读bfd库也是可行的,但gdb源码较复杂,而nm命令实现很简单,使用了较少的bfd接口,看过nm源码,就对bfd库的使用有大致的了解,为阅读bfd源码作准备,通过自己编写nm命令,很方便阅读与调试(打印调试或gdb调试)bfd库。

先看一下main函数,选项部分先不管,先按nm不带参数进行分析

int main (argc, argv)int argc; char **argv;
{
    bfd_init ();    /* 初始化bfd库 */
    set_default_bfd_target ();/* 设置默认的目标平台 */

    if (!display_file (argv[optind++])) /* */
    ...
}

主要功能函数是display_file函数,核心代码如下:

static boolean display_file (filename) char *filename;
{
    file = bfd_openr (filename, target); /* 打开bfd库 */
    //...
    if (bfd_check_format (file, bfd_archive))
    {
        display_archive (file);
    }
     /* 检查分析的文件是否为 bfd_object类型,elf属这种类型 */
    else if (bfd_check_format_matches (file, bfd_object, &matching))
    {
        char buf[30];

        bfd_sprintf_vma (file, buf, (bfd_vma) -1);
        print_width = strlen (buf);
        (*format->print_object_filename) (filename);
        display_rel_file (file, NULL); /* 显示相关文件符号  */
    }
    //...
    if (bfd_close (file) == false) /* 关闭bfd库 */
    //...
}

nm使用的对象暂先定位elf可执行文件,上面代码用的是 bfd_object这个分支。bfd_sprintf_vma先不用管,看bfd代码再说,(*format->print_object_filename) (filename);这一句注意一下,代码如下:

static struct output_fns *format = &formats[FORMAT_DEFAULT];
static struct output_fns formats[] =
{
    {
        print_object_filename_bsd,
        print_archive_filename_bsd,
        print_archive_member_bsd,
        print_symbol_filename_bsd,
        print_symbol_info_bsd
    },
}

这个结构体数组加函数指针,最终(*format->print_object_filename)指向的函数是print_object_filename_bsd,这个函数没什么内容,把这个函数提出来说一下的原因是,像bfd这类兼容多平台、多文件类型的代码,结构体数组加函数指针的用法非常普遍。再看一下display_rel_file函数代码:

static void display_rel_file (abfd, archive_bfd) bfd *abfd; bfd *archive_bfd;
{
    /* 读取bfd最小符号表 */
    symcount = bfd_read_minisymbols (abfd, dynamic, &minisyms, &size);
    /* 过滤不需显示的符号 */
    symcount = filter_symbols (abfd, dynamic, minisyms, symcount, size);
    //...
    if (! no_sort)
    {
        if (! sort_by_size) /* 对符号按名称排序 */
            qsort (minisyms, symcount, size,sorters[sort_numerically][reverse_sort]);
    }

    if (! sort_by_size) /* 打印符号表 */
        print_symbols (abfd, dynamic, minisyms, symcount, size, archive_bfd);

    free (minisyms); /* */
}

符号的过滤与排序可以先不管,主要关注print_symbols函数

static void print_symbols (abfd, dynamic, minisyms, symcount, size, archive_bfd)
{
    for (; from < fromend; from += size)
    {
        /* 把minisymbol结构转成symbol,方便打印符号 */ 
        sym = bfd_minisymbol_to_symbol (abfd, dynamic, from, store);
        /* 打印符号 */
        print_symbol (abfd, sym, (bfd_vma) 0, archive_bfd);
    }
}

print_symbol函数最主要执行 (*format->print_symbol_info) (&info, abfd);
语句,最终执行的是print_symbol_info_bsd函数。

以上是nm命令简单的流程,基本就是调bfd接口实现。

基于bfd库的简单nm命令实现

nm命令调用的bfd函数较少,为方便理解bfd使用流程,可以把里面的函数抽出来,写一个简单的nm命令,以此为基础,不断增加命令,加深对bfd函数用法的理解。下面是最简单的nm实现源码,大家以此为基础增加功能,进一步理解bfd库。代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <linux/elf.h>
#include "bfd.h"

#define TARGET "i686-pc-linux-gnu"

int main(int argc, char * argv[])
{
    int ret = 0, i = 0, size = 0, dynamic = 0;
    long symcount = 0;
    bfd * ibfd = NULL;
    char * from = NULL;
    PTR minisyms;
    asymbol * sym = NULL, *store = NULL;
    symbol_info syminfo;

    bfd_init();
#if 0    
    bfd_set_default_target(TARGET);
    ibfd = bfd_openr(argv[1], TARGET);
#else
    ibfd = bfd_openr(argv[1], NULL);
#endif
    if (ibfd == NULL)
    {
        return -1;
    }

    ret = bfd_check_format_matches(ibfd, bfd_object, NULL);
    if (ret <= 0)
    {
        return -1;
    }

    symcount = bfd_read_minisymbols(ibfd, dynamic, &minisyms, &size);
    if (symcount <= 0)
    {
        return -1;
    }

    for (i = 0; i < symcount; i++)
    {
        store = bfd_make_empty_symbol(ibfd);
        if (store == NULL)
        {
            continue;
        }
        from = (char *)minisyms + i * size;
        sym = bfd_minisymbol_to_symbol(ibfd, dynamic, from, store);
        if (sym)
        {
            bfd_get_symbol_info(ibfd, sym, &syminfo);
            printf("%#010lx  %c  %-s\n",syminfo.value,syminfo.type,syminfo.name);
        }
    }
    free(minisyms);
    bfd_close(ibfd);

    return 0;
}

编译命令依赖bfd库和liberty库,这两个库编译binutils会生成,注意改路径,如下

gcc test.c -g -o test -lbfd -liberty -I../binutils-2.13.2.1/bfd -L../binutils-2.13.2.1/bfd/.libs -L../binutils-2.13.2.1/libiberty/

这个nm命令不支持选项、没过滤以及排序功能。

bfd接口说明

bfd可以用的接口非常多,用法主要参考官方文档:https://sourceware.org/binutils/docs-2.30/bfd/index.html ,文档包括前段与后端,我们主要关注前端文档,即第二节的内容,不太明白的地方自己再验证,下面介绍一些上面用到的接口。

bfd初始化

void bfd_init (void);

bfd初始化,反正2.13版本这个函数是空的,但官方文档上要求初始化时调用该函数。

bfd target选择

bfd_boolean bfd_set_default_target (const char *name);
const bfd_target *bfd_find_target (const char *target_name, bfd *abfd);

目标平台一般情况下不要设置,bfd库会自动确定。

bfd打开及关闭文件

bfd *bfd_openr (const char *filename, const char *target);
bfd *bfd_fdopenr (const char *filename, const char *target, int fd);
bfd_boolean bfd_close (bfd *abfd);

bfd打开文件函数有很多个,用在不同场合,打开时,target可以填NULL,填NULL后,bfd库会自动查找target,要填的话,一定要填对。打开成功后返回bfd结构体,后续操作都是基于该结构体的。操作完成后都需要调用关闭文件接口以释放资源。

bfd获取minisymbols

long bfd_read_minisymbols(bfd , boolean, PTR ,unsigned int *);
asymbol bfd_minisymbol_to_symbol(bfd , boolean, const PTR,asymbol *);

minisymbols接口以只读方式读取符号表,该类接口使用较少的内存,但这类接口更耗时,可以处理符号表非常大的情况,常用在nm、objdump等工具。

该bfd_read_minisymbols函数将以内部形式将符号读入内存,返回符号的个数,minisyms指针是使用malloc分配内存,用完后需注意释放,bfd_minisymbol_to_symbol函数把指针转为asymbol结构,需先申请一个空的symbol。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值