赞
踩
一个elf文件是由很多个sections构成的(包括符号表,也看作一个section,哪怕它是汇编器生成的)
最常见的四个段(sections)
.rodata
.data
.bss
:bss在文件中只是申明一个大小,并不占空间。(分配的堆内存就在这里).text
使用objdump -h
可以把elf文件的各个段的信息打出来,实际上就是从section header table
读出的信息。
一个目标文件可以有多个同名的段。
常见的系统保留的段:
-g
选项会生成).symtab,.strtab,总是紧挨着的,.symtab结束后就是.strtab
我们可以把shstrtab
节的内容打出来(段的全部信息见下文中段头表)
这些实际上都是section名字的字符串。细心的话,可以发现上面只有18个字符串,没有.text
和.data
段的字符串。这是因为.init.text
已经包含字符串.text
了。
.strtab全是符号的名字字符串
.strtab和.shstrtab并不会有交集,但是在.symtab段记录了段的符号,这些段的Elf32_Sym
的st_name
实际上等于0,也就是空字符串。这两个段都是链接时候用的,不一定会加载到内存里面。
elf header
简称ehdr
Elf header可以使用readelf -h
查看
相关具体信息定义在/usr/include/elf.h
typedef struct{ unsigned char e_ident[16]; //16个字节,就是那个magic一直到ABI版本 Elf32_Half e_type;//2字节,文件类型:可重定位,可执行,动态可链接(.so文件就是这种) Elf32_Half e_machine;//2字节,CPU平台,比如x86(常量名为EM_386) Elf32_Word e_version;//4字节,Elf版本,一般是常数1 Elf32_Addr e_entry;//4字节,入口地址,对于可重定位的文件等于0 Elf32_Off e_phoff;//4字节,程序头起点 Elf32_Off e_shoff;//4字节,section header起始字节 Elf32_Word e_flags;//4字节,一般都等于0 Elf32_Half e_ehsize;//2字节,elf头文件大小,52字节,正好是本结构体的字节数 Elf32_Half e_phentsize;//2字节,程序头大小 Elf32_Half e_phnum;//2字节,程序头个数 Elf32_Half e_shentsize;//2字节,section header table里每个条目的大小,一般等于40字节 Elf32_Half e_shnum;//2字节,section header table里面的条数,即这个文件拥有的段的数量。上面的例子是21个 Elf32_Half e_shstrndx;//2字节,section header string table index,段表字符串表的段(.shstrtab)所在段表中的下标(section header table的第几个条目,实际上就是第几个段) }ELF32_Ehdr;
有时候直接叫做section header,不加table
readelf
查看section header时实际上就是从elf header拿到信息e_shoff
,e_shnum
,e_shentsize
,再从文件里面去读取section header
简称sh
,shdr
在一个section header table实际上就是一个ELF32_Shdr
数组,ELF32_Shdr
是一个占40字节的结构体
typedef struct{
Elf32_Word sh_name;//段字符串在段字符串表(.sh)中的下标
Elf32_Word sh_type;//段的类型
ELF32_Word sh_flags;//段的标志,可以取SHF_WRITE,SHT_ALLOC,SHF_EXECINSTR
ELF32_Word sh_addr;//如果该段可以被加载,这个值就是加载之后的虚拟地址否则等于0
ELF32_Word sh_offset;//如果该段存在文件中,表示这个段在文件中的位置,否则没意义,如.bss段
ELF32_Word sh_size;//这个段的长度,单位是字节
ELF32_Word sh_link;//
ELF32_Word sh_info;//
ELF32_Word sh_addralign;//段地址对齐
ELF32_Word sh_entsize;//项的长度
}ELF32_Shdr
sh_type
可以是
SHT_NULL
:无效段
SHT_PROGBITS
:程序段。代码段,数据段都是这种类型的
SHT_SYMTAB
:符号表
SHT_STRTAB
:字符串表
SHT_RELA
:重定位表(.rel.text .rel.data)
SHT_HASH
:符号表的HASH表
SHT_DYNAMIC
:动态链接信息
SHT_NOTE
:提示信息
SHT_NOBITS
:表示该段在文件中没有内容,如.bss段
SHT_REL
:该段包含了重定位信息
SHT_SHLIB
:保留
SHT_DNYSYM
:动态链接符号表
一个问题:SHT_RELA
和SHT_REL
区别是啥,有关动态链接的东西还是不太清楚
SHF_WRITE
:进程空间可写(0x1)SHT_ALLOC
:表示该段在进程空间需要分配空间,比如.bss,.text,.data(0x2)SHF_EXECINSTR
:该段在进程空间可执行(0x4)常见的段的类型以及其标志位
名字 | 类型 | 标志 |
---|---|---|
.bss | STH_NOBITS | SHF_ALLOC+SHF_WRITE |
.comment | SHT_PROGBITS | none |
data | SHT_PROGBITS | SHF_ALLOC+SHF_WRITE |
.debug | SHT_PROGBITS | none |
.dynamic | SHT_DYNAMIC | SHF_ALLOC+SHF_WRITE |
.hash | SHT_HASH | SHT_ALLOC |
.line | SHT_PROGBITS | none |
.note | SHT_NOTE | none |
.rodata | SHT_PROGBITS | SHT_ALLOC |
.shstrtab | SHT_STRTAB | none |
.strtab | SHT_STRTAB | 如果有可装载的段要用那么就是SHT_ALLOC |
.symtab | SHT_SYMTAB | 同上 |
.text | SHT_PROGBITS | SHT_ALLOC+SHF_EXECINSTR |
可加载的sections都是alloc的
sh_type | sh_link | sh_info |
---|---|---|
SHT_DYNAMIC | 该段所使用的字符串表在段表中的下标 | 0 |
SHT_HASH | 该段使用的符号表早段表中的下标 | 0 |
SHT_REL | 该段使用的相应符号表在段表中的下标 | 该重定位表所作用的段在段表中的下标 |
SHT_RELA | 同上 | 同上 |
SHT_SYMTAB | 操作系统相关 | 操作系统相关 |
SHT_DYNSYM | 同上 | 同上 |
其他 | SHN_UNDEF | 0 |
符号表特指.symtab
。
符号表中的符号可以分为下面几种
static int s = 1;
符号表的定义
typedef struct{
Elf32_Word st_name;//符号名字符串所在符号表的下标
Elf32_Addr st_value;//符号对应的值,不同类型符号,值的意义不一样
Elf32_Word st_size;//符号大小,该值是该数据类型的大小,比如double类型的符号是8字节,包括数组
unsigned char st_info;//符号类型与绑定信息
unsigned char st_other;//目前没用
Elf32_Half st_shndx;//符号所在的段(在段表中的下标)
}Elf32_Sym;//符号表每个struct大小为16字节
st_info
低4位表示符号类型
SHN_ABS=0xfff1
st_info
高4位表示绑定信息:
符号所在段st_shndx
有三种情况
SHN_ABS=0xfff1
表示该符号包含了一个绝对的值,比如文件名。SHN_COMMON=0xfff2
表示一般类型,COMMON块,比如一个未初始化的全局变量(.bss段里的符号都是这种)。SHN_UNDEF
表示未定义,本目标引用了但是定义在其他目标文件中要注意,.bss段里的符号类型是SHN_COMMON
,它是没有值的。
Elf32_Sym
的st_name
是.strtab
的下标
我们可以简要分析一下上述的.symtab,(用16进制的方式把内容输出来):
Elf32_Sym
结构体,03000100
,最后两个字节分别是00 03
(注意,高字节在高位,这是小端存放的方式)00
是st_other
的值,03
是st_info
的值。0
是高4位,表示绑定信息,即局部符号。3
是低4位表示该符号类型是一个段。
st_value
,它等于00100000
刚好等于init.text
段的addr。st_name
,它等于0,在.strtab
第0项是空字符串!!st_shndx
即最后两个byte,等于0001
,即这个符号所在的段表下标等于1个。从readelf -S
可以看见第二个段就是.init.text
。sh_addr
, 一次是在symtan里面的st_value
。但是st_value
在可重定位的目标文件里不见得是地址。不同类型的符号值的意义不一样
sh_shndx
不等于COMMON
,则这个值表示该符号在对应段的偏移(字节)。st_value
表示该符号的对齐属性。在上述符号表中,1-17是段:
可以发现.symtab
,.strtab
和.shstrtab
没有在.symtab
里面。因为这三个表里面压根没有变量。。。这三个段是给链接器用的。可执行文件里没有重定位表。
readelf -s
是怎么做的呢(readelf是怎么展示全部的符号信息的)。(下面是我的猜测)readelf
首先要找到.symtab
所在的段所在的文件偏移,这个信息存在section table的表项里面。readelf
从section header table里找到SHT_SYMTAB
类型的shdr entry,这个文件偏移就存在这个entry的sh_offset。在此之前,需要确定section header table所在的文件偏移,这个信息存在elf文件头的e_shoff。所以正着看,流程应该是下面:
sh_type==SHT_SYMTAB
,就找到了一个符号表。
__attribute__((weak))
来定义一个强符号为弱符号。针对强弱符号的概念,链接器的处理规则如下:
弱引用和强引用:生成可执行文件的所有符号要经过正确的决议。如果没有找到符号的定义,链接器就会报未定义错误,这种被称为强引用。与之对应的还有弱引用:如果符号有定义,则链接器将改符号的引用解析,如果未定义,则也不报错,认为其值等于0。
可以使用__attribute__((weakref))
来定义一个引用为弱引用
readelf -S
-h 显示elf header
-S 显示sections header
-s 显示符号表
-r 显示重定位表
-p --string-dump=<number|name> 把某个section的值按照string的形式展示出来,通常可以用这个把.strtab和.shstrtab打印出来
-x --hex-dump=<number|name> 把某个section按照16进制打出来
这个工具可以解析c++符号
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。