ELF 格式里包含了许多的节区(sections),如:
.text 用来存放执行代码;
.data 用来存放初始化的数据;
.bss 用来存放初始化数据;
以 .rel 开头的则用来存放重定位条目;
.symtab 和 .dynsym 里存放符号信息(这些符号包括文件名,函数名,变量名等等),前者一般是静态符号,后者则是动态链接相关符号;
.strtab 和 .dynstr 用来存放字符串信息,这些字符串会包括各个节区名,函数名等等。
上面所列是常见的节区,但根据不同需要还可以有不同的节区,如初始化类型,调试类型等等。而查看一个 ELF 类型文件的所有节区可用 readelf -S ELFile 命令列出。该命令所列出实际上是在打印节区头部表格,而这个表格是一个结构体数组,该结构体类型定义在 elf.h 文件中,如下所示:
[C++] 纯文本查看 复制代码 /* Section header. */
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
其中节区的名字可由成员 sh_name 索引 .strtab 这个节区而得来,测试代码可以参考:http://www.groad.net/bbs/read.php?tid-4228.html
sh_addr 这个成员,由注释我们知道它表示的是可执行文件的虚拟地址。所以,如果你查看的文件是可重定位的目标文件(*.o),那么该值就会为 0,因为 *.o 仅参与链接过程(由链接工具 link editor 完成)。 如果查看的是可执行文件或者动态链接库文件,那么该值是会有取值的。
sh_offset 成员表示该节区与文件的头部偏移。
sh_size 成员表示该节区的大小。
sh_entsize 成员由注释知道,它表示的是持有某些表格结构的节区的大小,如 .symtab 节区( .symtab 中每个结构单元长度固定为 10 个字节,所以该成员在这种情况下其值就为 10)。
sh_addralign 成员用来对齐节区。
sh_link 成员的值由 sh_type 来决定,它也可以用来索引其它的节区,详细例子可参考:http://www.groad.net/bbs/read.php?tid=4228#2779
sh_flag 成员用来表示节区的相关标志。取不同的值有不同的意义,比如可以表示该节区是不是存放可执行代码,该节区是否包含有在进程执行时可写的数据等。
其中有一个 A 标志,是 Allocable 之意,表示在程序运行时,进程需要使用它们,所以它们会被加载到内存中去,比如 .data 一般就是 allocable 的。 反之,则是 non-Allocable,这类型的节区只是被链接器、调试器或者其他类似工具所使用,不会参与程序运行时的内存中去,如 .symtab 和 .strtab 以及各种 .debug 相关节区就属于这种类型。在可执行文件执行时,allocable 部分会被加载到内存中,而 non-Allocable 部分则仍留在文件内。
比如在编译一个可执行文件时添加了 -g 选项表示可以用 gdb 来调试该程序,此时我们可能会看到可执行文件包含如下几个 .debug 相关的节区:.debug_aranges
.debug_info
.debug_abbrev
.debug_line
.debug_str 通过节区头部的 sh_flag 成员可以看到这些节区都是 non-Allocable 的。下面先来运行这个程序:[beyes@beyes elf]$ ./testefl.debug
31
hello 2012 正确,可以输出内容,是个正常的可执行程序,当然我们还可以调试它。下面使用 strip 工具去掉相关的 .debug 节区:[beyes@beyes elf]$ strip --strip-debug testefl.debug -o testefl.debugstrip 此时我们可以再利用 readelf 命令来观察生成的 testefl.debugstrip 文件,发现之前的那些 .debug 相关节区都没有了,表明已经被去除。下面再运行一下这个输出的文件:[beyes@beyes elf]$ ./testefl.debugstrip
31
hello 2012 可以运行,但是如果尝试使用 gdb 去调试它时,会提示:Reading symbols from /home/beyes/c/elf/testefl.debugstrip...(no debugging symbols found)...done.
(gdb) l
No symbol table is loaded. Use the "file" command. 从上面实验看到去掉 .debug 相关节区并不会影响程序正常运行,从这里也可以证明这种 non-Allocable 类型节区在运行时不会加载到内存中,对程序不会构成破坏性影响。
.data, .rodata 和 .bss :
.data 和 .bss 存放的都是可写的数据(如程序中的变量,但不包括局部变量,局部变量在运行时在堆栈中定义)。
.rodata 存放的是只读数据,比如给全局字符指针变量赋值的字符串,以及在函数中使用的字符串,比如 printf() 函数中第一个格式字符串。比如我在一个程序中定义了一个全局变量指针 char *strp = "hello 2012"; 然后在 main() 中还用 printf() 打印了一个整数 printf ("%d\n", c); 。那么在 .rodata 的长度会是 15,这是因为这个长度有两个部分组成,"hello 2012" (后面有一个 '\0' 共11 个字符) 以及 "%d\n"(后面有一个 '\0' 共 4 个字符)。
.data 包含的是已经初始化过的数据,比如程序中赋过初值的全局变量。
.bss 存放的是未初始化过的数据;没有初始化意味着没有确定的值,也就是在文件中不会为该节区多划分出一部分的存储空间,所以 .bss 节区的大小总是为 0.
当程序被执行时,动态链接器会在内存中开辟一定大小的空间用来存放这些未初始化的数据,里面的内存单元都会被初始化为 0 ,也就是在可执行程序文件中 .bss 的长度就等于动态链接器所开辟的空间大小。
.strtab 和 .shstrtab
.strtab 和 .shstrtab 这两个节区都是字符串数组类型。 .shstrtab 里存放的是各个节区名;.strtab 里存放的是文件名,函数名,以及变量名等名称。
假如在 .symtab 这个节区里,如果有一个条目所描述的符号在 .strtab 节区里存在,那么通过这个条目的 st_name 成员索引到 .strtab ,从而获得该符号的名字。
字符串表在真正链接和生成进程映像过程中是不需要使用的,但是其对我们调试程序来说就特别有帮助,因为我们人看起来最舒服的还是自然形式的字符串,而非像天书一样的数字符号。在使用 objdump 来反汇编 .text section 的时候,之所以能看到定义了函数名 ,那也是因为存在这个字符串表的原因。当然起关键作用的,还是符号表 .symtab section 在其中作为中介,下面我们就来看看符号表。
对于 .strtab , .shstrtab 相关的程序实例可以参考:http://www.groad.net/bbs/read.php?tid-4228.html
.symtab 节区
.symtab 节区可以看做是如下结构体的数组:
[C++] 纯文本查看 复制代码 /* Symbol table entry. */
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
每个这样的结构体构成了 .symbol 节区的一个存储单元。为了更加直观的观察 .symtab ,我么可以用 readelf -s elfile.o 命令来观察之,如:[beyes@beyes elf]$ readelf -s testefl.o
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS testefl.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 5
5: 00000000 0 SECTION LOCAL DEFAULT 6
6: 00000000 0 SECTION LOCAL DEFAULT 8
7: 00000000 0 SECTION LOCAL DEFAULT 9
8: 00000000 0 SECTION LOCAL DEFAULT 7
9: 00000000 4 OBJECT GLOBAL DEFAULT 3 strp
10: 00000004 4 OBJECT GLOBAL DEFAULT COM c
11: 00000000 13 FUNC GLOBAL DEFAULT 1 plusfunc
12: 0000000d 19 FUNC GLOBAL DEFAULT 1 printstring
13: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts
14: 00000020 93 FUNC GLOBAL DEFAULT 1 main
15: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf 上面,Type 列显示出了符号的类型;Bind 列定义了符号的绑定类型。这两者合并到一起由上面结构体中的 st_info 成员来表示。它们的合并方法通过下面宏来表示:
[C++] 纯文本查看 复制代码 /* How to extract and insert information held in the st_info field. */
#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val) ((val) & 0xf)
#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
在 ELF 中,符号类型有以下几种:
[table=978px][tr][td]Name
[/td][td]Value
[/td][/tr][tr][td]STT_NOTYPE
[/td][td]0
[/td][/tr][tr][td]STT_OBJECT
[/td][td]1
[/td][/tr][tr][td]STT_FUNC
[/td][td]2
[/td][/tr][tr][td]STT_SECTION
[/td][td]3
[/td][/tr][tr][td]STT_FILE
[/td][td]4
[/td][/tr][tr][td]STT_LOPROC
[/td][td]13
[/td][/tr][tr][td]STT_HIPROC
[/td][td]15
[/td][/tr][/table]
STT_NOTYPE 表示该类型没有指定。
STT_OBJECT 表示该符号对应的是一个数据对象,比如变量,数组等。
STT_FUNC 表示该符号对应的是函数。
STT_SECTION 该类型与一个节区相关,这种符号主要用于重定位,通常具有 STB_LOCAL 绑定。
STT_LOPROC -- STT_HIPROC 类型保留给处理器专用语意用途。
STT_FILE 如果一个文件符号具有 STB_LOCAL 绑定,那么她的节区索引为 SHN_ABS,并且它会优先于其他的 STB_LOCAL 符号(如果存在的话)
符号的绑定类型
符号的绑定类型表示了这个符号的可见性,是仅本对象可见,还是全局可见,其取值有:
STB_LOCAL :局部符号在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响。
STB_GLOBAL :全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。
STB_WEAK :弱符号与全局符号类似,不过他们的定义优先级比较低。
STB_LOPROC -- STB_HIPROC :处于这个范围的取值保留给处理器专用语义。
可重定位
... ...(未完) |