文章目录inside ELF段sections.shstrtab示例. strtab示例文件标头ELF header段标头表SECTION HEADER TABLE段类型段的flagssh_link和sh
insideelfelf : executableandlinkablefile .大多数现代*nix操作系统的目标文件都显示在elf的单个elf中,如下所示: BSS实际上不占用文件空间。
一个elf文件由许多sections组成。 即使是在组件中生成的(包括符号表),也会被视为一个section。
有四个最常见的分段: sections分段sections
. rodata.data.bss:bss是文件中cjdnm的一个大小,不占用空间。 (分配的堆内存在这里).text可以使用objdump -h输出有关elf文件中每个段的信息。 实际上是从section header table中读取的信息。
目标文件可以有多个同名的段。
常用系统保留第:段
. rodata1 ).rodata ) :只读段.comment:编译器版本信息. debug :调试信息. dynamic :动态链接信息(位于共享库中).hash :符号对符号表的访问(定义的函数名称,全局变量名称在此).symtab (符号表)变量和字符串),每个符号和符号的地址) shstrtab:段表字符串表
在. shstrtab示例中,可以导出shstrtab部分的内容。 (部分的所有信息,请参考以下中段的头表。 )
这些实际上是section名称的字符串。 注意,上面只有18个字符串,没有. text和. data段的字符串。 这是因为. init.text已经包含字符串. text。
. strtab例. strtab都是符号的名称字符串
. strtab和. shstrtab不相交,但. symtab段记录该段的符号,这些段的Elf32_Sym的st_name实际上为0,即空字符串。 这两个段都是链接时使用的,不一定加载到内存中。
文件头中ELF header可执行文件elf的前52个字节始终固定,前16个字节始终将幻像ELF header缩写为ehdr
Elf header可以在readelf -h中显示
具体信息的定义是/usr/include/elf.h
typedef struct { unsigned chare _ ident [ 16 ]; //16字节,其magic至ABI版本El f32 _ half e _ type//2字节,文件类型:可重定位、可执行、可动态链接(.so文件就是这样的) El f32 _ so文件//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 )中的第几个条目,以及段首表SECTION HEADER TABLE直接称为section header,有时不带table
当readelf查看section header时,它实际上从elf header获取信息e_shoff、e_shnum、e_shentsize,并从文件中将section header缩写为sh、shdr
section header table实际上是ELF32_S
hdr数组,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区别是啥,有关动态链接的东西还是不太清楚
段的flags SHF_WRITE:进程空间可写(0x1)SHT_ALLOC:表示该段在进程空间需要分配空间,比如.bss,.text,.data(0x2)SHF_EXECINSTR:该段在进程空间可执行(0x4)常见的段的类型以及其标志位
名字类型标志.bssSTH_NOBITSSHF_ALLOC+SHF_WRITE.commentSHT_PROGBITSnonedataSHT_PROGBITSSHF_ALLOC+SHF_WRITE.debugSHT_PROGBITSnone.dynamicSHT_DYNAMICSHF_ALLOC+SHF_WRITE.hashSHT_HASHSHT_ALLOC.lineSHT_PROGBITSnone.noteSHT_NOTEnone.rodataSHT_PROGBITSSHT_ALLOC.shstrtabSHT_STRTABnone.strtabSHT_STRTAB如果有可装载的段要用那么就是SHT_ALLOC.symtabSHT_SYMTAB同上.textSHT_PROGBITSSHT_ALLOC+SHF_EXECINSTR可加载的sections都是alloc的
sh_link 和 sh_info 只对段的类型与链接相关的有意义,如重定位表,符号表 sh_typesh_linksh_infoSHT_DYNAMIC该段所使用的字符串表在段表中的下标0SHT_HASH该段使用的符号表早段表中的下标0SHT_REL该段使用的相应符号表在段表中的下标该重定位表所作用的段在段表中的下标SHT_RELA同上同上SHT_SYMTAB操作系统相关操作系统相关SHT_DYNSYM同上同上其他SHN_UNDEF0符号表符号表特指.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位表示符号类型
0:未知类型1:数据对象,如变量,数组2:函数3:表示一个段,高四位一定是04:文件名,高四位一定是0,st_shndx一定是SHN_ABS=0xfff1st_info高4位表示绑定信息:
0:局部符号1:全局符号,对外部可见2:弱引用符号所在段st_shndx有三种情况
SHN_ABS=0xfff1表示该符号包含了一个绝对的值,比如文件名。SHN_COMMON=0xfff2表示一般类型,COMMON块,比如一个未初始化的全局变量(.bss段里的符号都是这种)。SHN_UNDEF表示未定义,本目标引用了但是定义在其他目标文件中要注意,.bss段里的符号类型是SHN_COMMON,它是没有值的。
.symtab例子
Elf32_Sym的st_name是.strtab的下标
我们可以简要分析一下上述的.symtab,(用16进制的方式把内容输出来):
每一行就是一个Elf32_Sym结构体,1-17(从0开始计数)行的symbol都是section,其st_info在最后一个word的倒数第三个字节(从0开始计数),比如第1行对应的最后一个word是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。所以在可执行文件中,段的值出现了两次,一次是在section header table的sh_addr , 一次是在symtan里面的st_value。但是st_value在可重定位的目标文件里不见得是地址。 符号的值不同类型的符号值的意义不一样
在目标文件中,如果是符号定义且符号不是"COMMON",即符号的sh_shndx不等于COMMON,则这个值表示该符号在对应段的偏移(字节)。在目标文件中,如果符号是"COMMON"块的,则st_value表示该符号的对齐属性。在可执行文件中代表符号的虚拟地址 例子在上述符号表中,1-17是段:
可以发现.symtab,.strtab和.shstrtab没有在.symtab里面。因为这三个表里面压根没有变量。。。这三个段是给链接器用的。可执行文件里没有重定位表。
针对强弱符号的概念,链接器的处理规则如下:
不允许强符号被定义多次如果一个符号在某个目标文件是强符号,在其他目标文件是弱符号,那么选择强符号作为链接是的引用解析如果一个符号在所有目标文件中都是弱符号,选择占用空间最大的那个。比如定义了var在A.c定义成int类型,在B.c定义成double类型,则最后的符号是double类型弱引用和强引用:生成可执行文件的所有符号要经过正确的决议。如果没有找到符号的定义,链接器就会报未定义错误,这种被称为强引用。与之对应的还有弱引用:如果符号有定义,则链接器将改符号的引用解析,如果未定义,则也不报错,认为其值等于0。
可以使用__attribute__((weakref))来定义一个引用为弱引用
objdump -s 按照16进制打印所有section的值-t 打印符号表-h 打印所有section的信息,等价于readelf -S-x 打印所有section的信息和符号表,程序的开始地址-d 反汇编所有的指令 readelf-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++filt这个工具可以解析c++符号