Last Updated: 2023-06-30 06:31:15 Friday
-- TOC --
Linux平台下二进制文件都是ELF格式,EFL格式的文件有4个部分:ELF Header,Program Headers,Sections,Section Headers。relocatable的.o文件没有Program Headers。
ELF格式标准历史
20世纪90年代,,一些厂商联合起来成立了一个委员会,起草并发布了一个ELF文件格式标准供公开使用,并希望所有人都能遵守这个标准并从中获益。1993年,委员会发布了ELF格式标准。当时参与该委员会的厂商主要是编译器厂商,如Watcom和Borland,CPU厂商,如IBM和Intel,操作系统厂商Windows。1995年,委员会发布了ELF1.2版本标准,自此委员会使命完成,不就就解散了。ELF标准定格在1.2,Windows参与了标准指定,但它却没有在其操作系统中使用ELF,而是用它自己的PE格式。
object file,executables, .so和.ko文件等,都是ELF格式!
玩转ELF格式,先玩转readelf命令!
ELF文件头给出了此二进制文件的类型,32bit或者64bit,以及用于解析或加载此二进制文件的其它必要数据。用readelf命令的-h
参数,查看elf文件头信息。
$ readelf -h test
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401070
Start of program headers: 64 (bytes into file)
Start of section headers: 23160 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
ELF file header structure defined in /usr/include/elf.h
:
/* The ELF file header. This appears at the start of every ELF file. */
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
仔细阅读elf.h文件,能够发现,_Half都是16bit,_Word都是32bit,_Addr和_Off与CPU位数有关,32或64。64位环境下ELF头大小为64bytes,32位环境下ELF头大小为52bytes,比32位环境下ELF头多12bytes。magic number的第5个字节不同。32位环境下的magic number为:7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
。45 4c 46
就是ELF
。7f
是ASCII的最后一个符号。64位环境第5个字节为02
。
e_version: Object file version
,固定为1.
e_entry:Entry point virtual address
,ELF程序的入口虚拟内存地址,操作系统加载程序后,从这个地址开始执行程序指令。Relocatable file不能直接运行,这个地址为0.
e_shoff: Section header table file offset
,section header在文件中的偏移地址。section header并不是紧跟在elf header后面,如下图:
elf文件就是由一个header + sections组成,各个section由section of section header定义!多个section合并成一个segment。
e_shnum: Section header table entry count
,elf文件section的数量。
e_shstrndx: Section header string table index
,这是一个index,指向一个section,此section内存放了一些字符串,这些字符串是section headers需要使用的。用readelf命令的-x
或-p
参数,可以查看某个具体的section内容,如下:
$ readelf -x13 test.o
Hex dump of section '.shstrtab':
0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
0x00000010 002e7368 73747274 6162002e 72656c61 ..shstrtab..rela
0x00000020 2e746578 74002e64 61746100 2e627373 .text..data..bss
0x00000030 002e726f 64617461 002e636f 6d6d656e ..rodata..commen
0x00000040 74002e6e 6f74652e 474e552d 73746163 t..note.GNU-stac
0x00000050 6b002e6e 6f74652e 676e752e 70726f70 k..note.gnu.prop
0x00000060 65727479 002e7265 6c612e65 685f6672 erty..rela.eh_fr
0x00000070 616d6500 ame.
$ readelf -p13 test.o
String dump of section '.shstrtab':
[ 1] .symtab
[ 9] .strtab
[ 11] .shstrtab
[ 1b] .rela.text
[ 26] .data
[ 2c] .bss
[ 31] .rodata
[ 39] .comment
[ 42] .note.GNU-stack
[ 52] .note.gnu.property
[ 65] .rela.eh_frame
注意:没有.text字符串!
字符串一般长度不固定不统一,因此如果用固定的长度来表示字符串有困难。用上面这种方式保存字符串,就可以仅仅使用偏移地址,来定位到所有字符串。(字符串的结束符为
0x00
,这是C语言的选择).rodata放的是代码中写死的字符串或常量,比如printf调用中的格式字符串等。.comment内有编译器和编译平台信息。
program headers
,请参考下面segment部分内容。
如下是section header结构体的定义:
/* 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;
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
_Xword是64位。elf头显示64位环境下,section header大小为64bytes,结合上面的结构体定义,算一下正好64bytes。
$ readelf -S test.o
There are 14 section headers, starting at offset 0x310:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000001b 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000250
0000000000000030 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 0000005b
000000000000000b 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000066
000000000000002b 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000091
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000098
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 000000b8
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000280
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 000000f0
0000000000000138 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000228
0000000000000028 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000298
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
用readelf命令的-S
参数,查看elf文件section headers,以及其offset!与elf header信息能够对应起来。这部分内容,与定义section header的结构体也能够对应起来。
Type | 含义 |
---|---|
PROGBITS | 程序指令或数据 |
SYMTAB | 符号表 |
STRTAB | 字符串表 |
RELA | 重定位表,包含了重定位信息 |
NOTE | note info |
NOBITS | .bss段特有,没有内容 |
sh_name: Section name (string tbl index)
,e_shstrndx指向的section内的偏移,能够直接把section name取出来。
sh_entsize: Entry size if section holds table
,section的内容是table,这个字段定义table的大小,正如elf header中定义了section header的大小一样,有这个size信息,C代码可以很方便的通过指针+1
,来实现遍历。
类型为PROGBITS的段,有需要在链接的时候做重定位的,在object文件中,都有一个对应的.rela
段。如上面的示例,有两个.rela段。此时,section header结构体中的link字段就不再是0,而是指向.symtab
符号表section的编号,info字段指向此rela段作用于哪个段,比如上例,section2->section1,section10->section9。
从elf头获得section headers number和offset,还有固定的header size,以及section header需要的string table,配合section header structure结构,有了这些信息,就可以解析出每一个section!
下面是符号表的结构体定义:
/* 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;
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
32位和64位的结构体,只是成员顺序不一样,仔细看看,内容都是一样的!st_name字段是指向.strtab段的指针,与section headers结构体中指向.shstrtab原理一样。
$ readelf -p12 test.o
String dump of section '.strtab':
[ 1] test.c
[ 8] cc.2319
[ 10] bb.2318
[ 18] g_var1
[ 1f] g_var2
[ 26] main
[ 2b] _GLOBAL_OFFSET_TABLE_
[ 41] puts
[ 46] printf
查看文件符号表的命令有:readelf -s <filename>
或nm <filename>
,当前,这两个命令也都有很多更复杂更具体的参数可以使用。(nm命令的man page内有很多有用的信息,它的输出更简洁)
$ readelf -s test.o
Symbol table '.symtab' contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 cc.2319
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 bb.2318
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 6
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g_var1
13: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM g_var2
14: 0000000000000000 52 FUNC GLOBAL DEFAULT 1 main
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
$ nm test.o
0000000000000004 d bb.2318
0000000000000000 b cc.2319
U _GLOBAL_OFFSET_TABLE_
0000000000000000 D g_var1
0000000000000004 C g_var2
0000000000000000 T main
U printf
U puts
符号表的重要性不言而喻,链接过程就靠它。符号分为LOCAL
,GLOBAL
和WEAK
!GLOBAL
是外部可见的全局符号,可被其它目标文件引用。符号类型有file,section,object,func,或notype。OBJECT
类型是指各种类型的全局变量,FUNC
是指函数,即代码。
st_shndx: Section index
,符号所在section的index,对应Ndx这一列。ABS
表示一个绝对值,值为0xFFF1。COM
,common块,未初始化的全局符号,值为0xFFF2。UND
就是未定义,未在此目标文件中定义(外部符号),值为0。cc是个未定义的静态变量,被放在了section 4,bss段。bb是已初始化的静态变量,g_var1是已初始化的全局变量,都在section 3,data段。
st_value: Symbol value
,在目标文件中,如果是非COM的GLOBAL符号,这个值表示符号所在段的offset。如果是COM的符号,这个值表示此符号的对齐属性,应该是此符号的size。在可执行文件中,这个value表示符号的虚拟地址!因此可以理解为,此value基本上就是符号的地址,要么是所在section的offset,要么是virtual address。
第1个符号,永远是一个未定义的符号!(我怀疑是为了让真正的符号的编号从1开始,故意在前面把0的位置占住)
bb
和cc
是两个静态变量,但在符号表中,这两个符号后面有suffix,这是符号修饰(Name Decoration)
,目的是为了支持在不同函数中可以有相同名称的静态变量。链接过程不能出现相同名称的符号!C++的函数名重载,namespace等特性,在编译器层面,都要通过修饰的方式来使其具有不同的符号名称!(C++的符号修饰和extern "C")
函数签名,Function Signature,包含函数名,参数列表,它所在的类或namespace,以及其它用于唯一辨识此函数的信息。函数签名用于识别不同的函数,就像签名用于识别不同的人一样,函数的名称还是签名的一部分。
符号的定义分为强符号和弱符号!符号的引用分强引用和弱引用。
GCC使用__attribute__((weak))
使某个定义成为弱符号,使用__attribute__((weafref))
使某个引用成为弱引用。默认的函数和全局变量都是强符号(gcc现在默认使用-fno-common
),默认的引用都是强引用。符号和引用的强弱,体现在链接规则上:
每一个.text段,如果里面含有需要重定位的符号,就会有一个对应的.rela(或者.rel)段,包含了所有需要在链接的时候,重定位的符号信息。
/* Relocation table entry without addend (in section of type SHT_REL). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
/* I have seen two different definitions of the Elf64_Rel and
Elf64_Rela structures, so we'll leave them out until Novell (or
whoever) gets their act together. */
/* The following, at least, is used on Sparc v9, MIPS, and Alpha. */
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;
/* Relocation table entry with addend (in section of type SHT_RELA). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
Rel和Rela是两种不同的结构,后者多一个Addend。如果gcc用
-m32
编译,得到的符号表使用rel,如果是64位环境,得到的符号表使用rela。
r_offset
是符号相对于它的.text段的偏移地址,r_info
包含了符号类型和在symbol table中的index。目标文件在未链接时,引用的外部地址都是不确定的,一般编译器会用0占位,在链接过程中分配虚拟地址,此时各个符号的地址就都确定了!(动态链接除外)
$ readelf -r test.o
Relocation section '.rela.text' at offset 0x2e0 contains 10 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000006 000300000002 R_X86_64_PC32 0000000000000000 .data + 0
00000000000c 000400000002 R_X86_64_PC32 0000000000000000 .bss + 4
000000000013 00060000000a R_X86_64_32 0000000000000000 .rodata + 0
00000000001d 000c00000004 R_X86_64_PLT32 0000000000000000 printf - 4
00000000002a 000a00000002 R_X86_64_PC32 0000000000000000 c - 4
000000000030 000400000002 R_X86_64_PC32 0000000000000000 .bss + 0
000000000036 000900000002 R_X86_64_PC32 0000000000000000 a - 4
00000000003d 00060000000a R_X86_64_32 0000000000000000 .rodata + 7
000000000047 000c00000004 R_X86_64_PLT32 0000000000000000 printf - 4
000000000051 000b00000004 R_X86_64_PLT32 0000000000000000 f - 4
Info高位部分是符号的index(在符号表中的index),最后边的Sym. Name
与index对应。低位部分表示Type,同时存在Type列,将数字和具体类型名称对应。Offset列是符号所在section的偏移。下面是对应的符号表:
$ readelf -s test.o
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 .data
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 .bss
5: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 b
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 e.1
8: 0000000000000008 4 OBJECT LOCAL DEFAULT 4 d.0
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 a
10: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 c
11: 0000000000000000 36 FUNC GLOBAL DEFAULT 1 f
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
13: 0000000000000024 56 FUNC GLOBAL DEFAULT 1 main
以上两段打印,对应的源码是:
#include <stdio.h>
int a;
static int b;
int c = 3;
void f(){
static int d;
static int e = 5;
printf("%d %d\n", d, e);
}
int main(){
printf("%d %d %d\n", a,b,c);
f();
return 0;
}
在x64环境下,这是很常见的重定位方式,即基于PC指针(RIP)的重定位,32可能是表示需要修正位置的长度。
Relocation section '.rela.text' at offset 0x2e0 contains 10 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000006 000300000002 R_X86_64_PC32 0000000000000000 .data + 0
...
在链接时,在.text section的0006偏移的位置,填写.data+0-rip+4
。链接器会确定这个.o文件的.text地址和.data地址,上面显示的是偏移(不是最后合并后的.text或.data地址,链接时相同section合并),但rip地址是最后分配后的地址。
绝对地址,重定位时,-rip+4
就没有了。
gcc编译时,加上-g
参数,可以生成带有debug段的elf文件,debug段的类型为PROGBITS
。
ELF文件采用一种叫DWARF(Debug With Arbitrary Record Format)的标准的调试信息格式,2006年DWARF标准委员会发布了DWARF3版本。Microsoft使用自己的格式,叫做CodeView。
值得一提的是,debug段的size比正常代码段和数据段大好几倍。
前面的所有内容,都在说section,这是linking view。而segment,是execution view!
linking时,具有相同权限的section会被编排在一起,地址连续。此时,这些编排在一起的section,就形成了一个segment!链接时,链接器关心的是section,而当可执行程序在被OS加载到内存时,OS关心的是segment,OS按segment分配page。如果OS按section分配page,那么每个需要load进内存的section,必然会至少占用一个page,而如果按segment分配page,可以有效节省内存。
ELF文件头中的program headers,指向的就是segment。因此,.o文件的elf头看不到grogram header,只有可执行的elf文件才有。
$ readelf -l test
Elf file type is EXEC (Executable file)
Entry point 0x401040
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000400318 0x0000000000400318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000510 0x0000000000000510 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000191 0x0000000000000191 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x0000000000000104 0x0000000000000104 R 0x1000
LOAD 0x0000000000002e10 0x0000000000403e10 0x0000000000403e10
0x000000000000021c 0x0000000000000230 RW 0x1000
DYNAMIC 0x0000000000002e20 0x0000000000403e20 0x0000000000403e20
0x00000000000001d0 0x00000000000001d0 RW 0x8
NOTE 0x0000000000000338 0x0000000000400338 0x0000000000400338
0x0000000000000040 0x0000000000000040 R 0x8
NOTE 0x0000000000000378 0x0000000000400378 0x0000000000400378
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000400338 0x0000000000400338
0x0000000000000040 0x0000000000000040 R 0x8
GNU_EH_FRAME 0x0000000000002024 0x0000000000402024 0x0000000000402024
0x0000000000000034 0x0000000000000034 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002e10 0x0000000000403e10 0x0000000000403e10
0x00000000000001f0 0x00000000000001f0 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .got.plt .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .dynamic .got
只有type为LOAD的segment,才会被OS加载。注意看上面展示的打印,Section to Segment mapping
这部分,这里清晰的说明了哪些section被合并到了那个segment。
基本上现在编译的可执行程序,都是动态链接,又能有些程序私有的库是静态链接,但谁还会静态链接libc呢!EFL中定义了一个.dynamic section
,用来存放动态链接相关信息。配套的还有.dynstr
,.dynsym
和.rela.dyn
section。
/* Dynamic section entry. */
typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;
d_tag表示type,然后要么是d_val,要么是d_ptr。
如下是type list:
/* Legal values for d_tag (dynamic entry type). */
#define DT_NULL 0 /* Marks end of dynamic section */
#define DT_NEEDED 1 /* Name of needed library */
#define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */
#define DT_PLTGOT 3 /* Processor defined value */
#define DT_HASH 4 /* Address of symbol hash table */
#define DT_STRTAB 5 /* Address of string table */
#define DT_SYMTAB 6 /* Address of symbol table */
#define DT_RELA 7 /* Address of Rela relocs */
#define DT_RELASZ 8 /* Total size of Rela relocs */
#define DT_RELAENT 9 /* Size of one Rela reloc */
#define DT_STRSZ 10 /* Size of string table */
#define DT_SYMENT 11 /* Size of one symbol table entry */
#define DT_INIT 12 /* Address of init function */
#define DT_FINI 13 /* Address of termination function */
#define DT_SONAME 14 /* Name of shared object */
#define DT_RPATH 15 /* Library search path (deprecated) */
#define DT_SYMBOLIC 16 /* Start symbol search here */
#define DT_REL 17 /* Address of Rel relocs */
#define DT_RELSZ 18 /* Total size of Rel relocs */
#define DT_RELENT 19 /* Size of one Rel reloc */
#define DT_PLTREL 20 /* Type of reloc in PLT */
#define DT_DEBUG 21 /* For debugging; unspecified */
#define DT_TEXTREL 22 /* Reloc might modify .text */
#define DT_JMPREL 23 /* Address of PLT relocs */
#define DT_BIND_NOW 24 /* Process relocations of object */
#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */
#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */
#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */
#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */
#define DT_RUNPATH 29 /* Library search path */
#define DT_FLAGS 30 /* Flags for the object being loaded */
#define DT_ENCODING 32 /* Start of encoded range */
#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/
#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */
#define DT_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */
#define DT_NUM 35 /* Number used */
#define DT_LOOS 0x6000000d /* Start of OS-specific */
#define DT_HIOS 0x6ffff000 /* End of OS-specific */
#define DT_LOPROC 0x70000000 /* Start of processor-specific */
#define DT_HIPROC 0x7fffffff /* End of processor-specific */
#define DT_PROCNUM DT_MIPS_NUM /* Most used by any processor */
DT_NEEDED类型存放的值,是.dynstr section的偏移。
有的时候我们编译出来的代码,copy到别的机器上运行,会提示找不到某个动态链接库,可能就是rpath在作怪。gcc编译的时候,可以给ld传入rpath参数,但一般不会使用。(记忆中,在x64环境下,模拟运行risc-v程序的时候,需要指定rpath,因为需要使用完全不同的一套lib库)
#define DT_RPATH 15 /* Library search path (deprecated) */
elf.h文件中,已经在注释位置说明,deprecated,但实际上还有有可能会遇到。
我个人的经验是,如果elf中存在rpath,环境变量LD_LIBRARY_PATH就失效了,动态链接器只在rpath指定的路径中搜索。有个小工具,可以修改elf文件的rpath,chrpath
!
$ readelf -d libHSRCommon.so.1.0.0
Dynamic section at offset 0x47af0 contains 36 entries:
标记 类型 名称/值
...
0x000000000000000e (SONAME) Library soname: [libHSRCommon.so.1]
0x000000000000001d (RUNPATH) Library runpath: [/home/astute/work/HSR-Windows/Source/Config/../../Target/x86_64/release:/usr/local/qt5.15.2-x64/lib]
...
本文链接:https://cs.pynote.net/sf/c/cdm/202111232/
-- EOF --
-- MORE --