admin管理员组

文章数量:1532656

2024年3月11日发(作者:)

一、map、全局符号及静态符号

一般的大型工程都会在生成可执行文件的同时让链接器生成一个map文件,从而大致查看一下

可执行文件中符号的内存布局以及从哪里引入可执行文件。这个通常对于小型工程是作用不大,

因为代码就那么多,随便grep一下就知道符号定义位置了。但是对于一些大型工程或者涉及了

比较多的第三方库、或者涉及了比较多的功能模块的时候,就需要知道这些符号是在哪里定义,

或者说如果一个符号引用了但是没有知道函数定义,此时也需要找到这个符号是哪个模块引入

的,为什么需要,所以需要一些通用的(形式化)的方法来搜索这些符号,而map文件就是一个

比较好的切入点。但是map符号并不是万能的,它只能列出参与链接的全局变量的位置以及在

哪个模块,对于一些静态变量,map文件中并不能体现它们,而在没有特殊声明的情况下,可

执行文件中将会包含静态符号在符号表中,所以有时候我们只能依赖可执行文件本身里面的符号

表来猜测一个符号的定义位置。说起静态符号,还有就是它是如何保证它只在一个编译模块中可

见和被引用,而对其它模块不可见?

二、map文件相关

对于ld程序来说,生成map文件可以使用-Map=mapfile来指示链接器来生成一个可执行文件

使用的map文件。在内核的构建过程中,也会生成一个文件来表示内核中各个符

号在内核中位置,但是这个文件并不是通过ld的-Map选项生成,而是使用了nm和grep工具

来手动生成的,具体的文件文件及相关说明在linux-2.6.37.1scriptsmksysmap文件中。我们这

里只是结合ld的源代码来看一下这个Map文件是如何生成的。

1、map文件生成代码

对于map文件的生成,在ld的源代码中,名字也比较直观,就是通过lang_map函数来完成的,

它的主要相关流程为

fprintf (_file, _("nLinker script and memory mapnn"));

if (! link__memory_overheads)

{

obstack_begin (&map_obstack, 1000);

for (p = link__bfds; p != (bfd *) NULL; p = p->link_next)

bfd_map_over_sections (p, init_map_userdata, 0);

bfd_link_hash_traverse (link_, sort_def_symbol, 0);

}

lang_statement_iteration ++;

print_statements ();

其中的主要准备工作由bfd_link_hash_traverse (link_, sort_def_symbol, 0)语句完成,

它遍历整个链接过程中所有的符号表,然后对其中的每个符号执行sort_def_symbol函数,这个

函数的功能主要是将这个符号追加到符号定义节的userdata链表的最后,供之后执行的

print_statements函数可以在遍历各个输入节的时候打印输入节的map信息。这里对于

bfd_link_hash_traverse (link_, sort_def_symbol, 0);语句实现要注意两个细节:

①、输出符号性质

在sort_def_symbol函数的定义中,它只会追加类型为bfd_link_hash_defined和

bfd_link_hash_defweak属性的符号(代码不再粘贴,代码比较直观,贴出来影响阅读),其它的

一概忽略,这也就意味着所有的局部变量符号没有机会在map文件中体现。

②、符号遍历规则

符号遍历是通过bfd_link_hash_traverse函数遍历,这个遍历的符号没有任何逻辑规律,它们只

是依赖底层hash算法的选择而被放在不同的bucket中,这会导致对于每个输入节来说,它即

将输出的定义符号列表并不一定是按照它们在内存中的逻辑地址位置排列的。

2、验证代码

[tsecer@Harry maporder]$ cat maporder.c

static int foo;

int main()

{

extern int bar(void);

return foo + bar();

}

int bar(void)

{

return 0x11111111;

}

[tsecer@Harry maporder]$ gcc maporder.c -Wl,-Map=

[tsecer@Harry maporder]$ grep -e 'main' -e 'bar'

0x82c4 __libc_start_main@@GLIBC_2.0

0x83ab bar

0x8394 main

[tsecer@Harry maporder]$ ld -V

GNU ld version 12 20090722

Supported emulations:

elf_i386

i386linux

elf_x86_64

[tsecer@Harry maporder]$

可以看到,其中bar的逻辑地址要比main的逻辑地址高,但是它在map文件中出现的顺序要

比main早,所以说同一个节内符号在map文件中的出现顺序和它们的逻辑地址无关,大家不

要依赖这个顺序。

3、ld2.20版本对map文件的优化

之前验证的代码可以看到,一个节内部符号出现顺序和逻辑地址无关,这个属性在链接器的2.20

版本中进行了修改(优化),优化的结果就是map文件中同一个节中定义符号在map文件中出现

顺序按照逻辑地址排序,这个代码合入时间比较晚,大致是在2009年9月的2.20版本总加入,

我在网上也搜索到了这个补丁的讨论邮件,为了防止链接地址失效,这里还是把邮件内容拷贝一

份过来,原始地址为(/ml/binutils/2009-07/):

Tristan.

2009-07-08TristanGingold<*******************>

* ld.h (fat_user_section_struct): Add map_symbol_def_count field. * ldlang.c

(hash_entry_addr_cmp): New function. (print_all_symbols): Sort the symbols by address

before printing them.

RCS file: /cvs/src/src/ld/ld.h,v

本文标签: 符号文件定义生成代码