操作系统:CentOS Linux release 7.7.1908
内核版本:3.10.0-1062.1.1.el7.x86_64
运行平台:x86_64
参考文献:http://refspecs.linuxfoundation.org/
本文根据/usr/include/elf.h文件和程序编译的详细过程文中所述的tanglinux来分析可执行文件中与动态链接有关的section的构成。
静态编译(使用-static命令行参数)的程序不包含任何动态链接依赖,所有需要的外部库(静态库)的符号都被静态链接进该程序。与之相反,非静态编译的程序不会将所需的外部库(动态库)的符号都链接进该程序,而只要确认所引用的符号存在于相应的外部库中即可。这些所引用的符号的地址直到程序执行时才能确认,符号所在的动态库也会被加载到该程序进程的内存映射中,这个过程就叫动态链接。动态链接由操作系统中的动态链接器(如/lib/ld-linux.so.2)所执行。
程序会动态链接动态库,动态库也会动态链接其它动态库。动态库中的全局变量符号不会被程序所共享,而是每个引用该符号的程序都会拥有一个该符号的副本。
动态链接器对可执行文件符号表中的符号遵循三个规则,a、若Elf32_Sym.st_shndx的值为非SHN_UNDEF(即0),则直接使用Elf32_Sym.st_value的值作为该符号的地址;b、若Elf32_Sym.st_shndx的值为SHN_UNDEF,类型为STT_FUNC,且Elf32_Sym.st_value的值不为零,则动态链接器将该符号标记为特殊项并将Elf32_Sym.st_value作为它的地址;c、否则动态链接器将该符号作为未定义符号,在后续的操作中再行处置。
与动态链接直接相关的section可能有SHT_DYNAMIC(动态链接信息表)、SHT_REL(或SHT_RELA,重定位表),还包括与动态链接器有关的辅助信息表。静态编译的程序中也有重定位表,但相对来说重定位表与动态链接的关系更紧密。本文还附带说明了SHT_NOTE section的构成。
一、动态链接信息表
动态链接信息表的数据结构非常简单,一个成员表示类型,另一个成员表示该类型的值(整数或地址),如下所示:
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;
其中,d_tag就表示类型,单个类型的动态链接信息要么只有个数值(d_val),要么只有个虚拟地址(d_ptr)。可执行文件tanglinux中的动态链接信息表如下所示:
$ readelf -d tanglinux
Dynamic section at offset 0xf14 contains 24 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x804828c
0x0000000d (FINI) 0x8048464
0x00000019 (INIT_ARRAY) 0x8049f08
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x8049f0c
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x80481ac
0x00000005 (STRTAB) 0x804820c
0x00000006 (SYMTAB) 0x80481cc
0x0000000a (STRSZ) 69 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x804a000
0x00000002 (PLTRELSZ) 8 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048284
0x00000011 (REL) 0x804827c
0x00000012 (RELSZ) 8 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x804825c
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048252
0x00000000 (NULL) 0x0
最后一项(类型为DT_NULL,值为0)用来表示动态链接信息表的结尾。
动态链接信息表的类型比较多,大概可以分为基本类型、地址扩展类型、版本控制信息类型等等,还有些是SUN操作系统所特有的(这里不一一列出)。
(1)、基本类型有34种,根据它们的英文注释完全能看明白,最特别的是DT_NULL用来表示动态链接信息表的结尾,如下所示:
#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_NUM 34 /* 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_FLAGS类型的动态链接信息的可能取值(按比特位设置)如下所示:
/* Values of `d_un.d_val' in the DT_FLAGS entry. */
#define DF_ORIGIN 0x00000001 /* Object may use DF_ORIGIN */
#define DF_SYMBOLIC 0x00000002 /* Symbol resolutions starts here */
#define DF_TEXTREL 0x00000004 /* Object contains text relocations */
#define DF_BIND_NOW 0x00000008 /* No lazy binding for this object */
#define DF_STATIC_TLS 0x00000010 /* Module uses the static TLS model */
(2)、地址扩展类型,它们的值都是地址,如下所示:
/* DT_* entries which fall between DT_ADDRRNGHI & DT_ADDRRNGLO use the
Dyn.d_un.d_ptr field of the Elf*_Dyn structure.
If any adjustment is made to the ELF object after it has been
built these entries will need to be adjusted. */
#define DT_ADDRRNGLO 0x6ffffe00
#define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */
#define DT_TLSDESC_PLT 0x6ffffef6
#define DT_TLSDESC_GOT 0x6ffffef7
#define DT_GNU_CONFLICT 0x6ffffef8 /* Start of conflict section */
#define DT_GNU_LIBLIST 0x6ffffef9 /* Library list */
#define DT_CONFIG 0x6ffffefa /* Configuration information. */
#define DT_DEPAUDIT 0x6ffffefb /* Dependency auditing. */
#define DT_AUDIT 0x6ffffefc /* Object auditing. */
#define DT_PLTPAD 0x6ffffefd /* PLT padding. */
#define DT_MOVETAB 0x6ffffefe /* Move table. */
#define DT_SYMINFO 0x6ffffeff /* Syminfo table. */
#define DT_ADDRRNGHI 0x6ffffeff
#define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag)) /* Reverse order! */
#define DT_ADDRNUM 11
(3)、版本控制信息类型,它们与版本控制信息section有关,如下所示:
/* The versioning entry types. The next are defined as part of the
GNU extension. */
#define DT_VERSYM 0x6ffffff0
#define DT_RELACOUNT 0x6ffffff9
#define DT_RELCOUNT 0x6ffffffa
/* These were chosen by Sun. */
#define DT_FLAGS_1 0x6ffffffb /* State flags, see DF_1_* below. */
#define DT_VERDEF 0x6ffffffc /* Address of version definition
table */
#define DT_VERDEFNUM 0x6ffffffd /* Number of version definitions */
#define DT_VERNEED 0x6ffffffe /* Address of table with needed
versions */
#define DT_VERNEEDNUM 0x6fffffff /* Number of needed versions */
#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */
#define DT_VERSIONTAGNUM 16
二、重定位表(Relocation)
由于relocation section中每个项的大小相同,所以也叫重定位表。表示表中符号的地址尚未确定,现有的地址是无效的,须要在链接或加载时重定位该符号的实际地址。在ELF文件中,每个须要重定位的section都有一个对应的重定位表,如.text(如果须要重定位)对应的重定位表就是.rel.text section。重定位表应用于目标文件、可执行文件和动态库等类型文件中。
可执行文件tanglinux中有两个重定位表,如下所示:
$ readelf -S tanglinux
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 5] .dynsym DYNSYM 080481cc 0001cc 000040 10 A 6 1 4
[ 9] .rel.dyn REL 0804827c 00027c 000008 08 A 5 0 4
[10] .rel.plt REL 08048284 000284 000008 08 AI 5 24 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000010 04 WA 0 0 4
其中,重定位表的Elf32_Shdr. sh_link指向符号所在的section(如Lk列的5);Elf32_Shdr. sh_info指向重定位所针对的section(如Inf列的24)。通过readelf命令可查看ELF文件中的重定位表,如下所示:
$ readelf -r tanglinux
Relocation section '.rel.dyn' at offset 0x27c contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049ffc 00000106 R_386_GLOB_DAT 00000000 __gmon_start__
Relocation section '.rel.plt' at offset 0x284 contains 1 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000207 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
重定位表使用以下两个数据结构来定义:
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;
r_offset表示使用该重定位操作的元素的位置。对于目标文件,表示该元素相对它所在section的偏移量(字节数);对于可执行文件和动态库,则表示该元素的虚拟地址(动态库为相对地址)。如/lib/crt1.o目标文件的.rel.text section中有个四个使用重定位的元素,它们的r_offset值如下所示:
$ readelf -r /lib/crt1.o
Relocation section '.rel.text' at offset 0x22c contains 4 entries:
Offset Info Type Sym.Value Sym. Name
0000000c 00000a01 R_386_32 00000000 __libc_csu_fini
00000011 00000c01 R_386_32 00000000 __libc_csu_init
00000018 00000d01 R_386_32 00000000 main
0000001d 00001002 R_386_PC32 00000000 __libc_start_main
其中,Offset列就是这四个元素的r_offset的值,表示的是它们在.text section中的位置(相对该section的偏移量),如下图所示(图中数据通过执行$ objdump -d /lib/crt1.o命令获得):
图中红框中的值就是相对.text section的偏移量,箭头所指的就是使用该重定位的符号。
r_addend表示表示一个加数(有符号整数,可加可减)。跟硬件体系架构密切相关。
r_info由两部分组成,使用其中的两个字节,分别表示重定位的类型(byte[0])和符号在符号表中的索引值(byte[1])。可以使用以下三个宏函数来获取类型或索引的值,或者使用类型和索引的值生成r_info成员:
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))
重定位的类型与硬件体系架构密切相关,如i386平台的类型有以下40余种:
#define R_386_NONE 0 /* No reloc */
#define R_386_32 1 /* Direct 32 bit */
#define R_386_PC32 2 /* PC relative 32 bit */
#define R_386_GOT32 3 /* 32 bit GOT entry */
#define R_386_PLT32 4 /* 32 bit PLT address */
#define R_386_COPY 5 /* Copy symbol at runtime */
#define R_386_GLOB_DAT 6 /* Create GOT entry */
#define R_386_JMP_SLOT 7 /* Create PLT entry */
#define R_386_RELATIVE 8 /* Adjust by program base */
#define R_386_GOTOFF 9 /* 32 bit offset to GOT */
#define R_386_GOTPC 10 /* 32 bit PC relative offset to GOT */
#define R_386_32PLT 11
#define R_386_TLS_TPOFF 14 /* Offset in static TLS block */
#define R_386_TLS_IE 15 /* Address of GOT entry for static TLS block offset */
#define R_386_TLS_GOTIE 16 /* GOT entry for static TLS block offset */
#define R_386_TLS_LE 17 /* Offset relative to static TLS block */
#define R_386_TLS_GD 18 /* Direct 32 bit for GNU version of general dynamic thread local data */
#define R_386_TLS_LDM 19 /* Direct 32 bit for GNU version of local dynamic thread local data in LE code */
#define R_386_16 20
#define R_386_PC16 21
#define R_386_8 22
#define R_386_PC8 23
#define R_386_TLS_GD_32 24 /* Direct 32 bit for general dynamic thread local data */
#define R_386_TLS_GD_PUSH 25 /* Tag for pushl in GD TLS code */
#define R_386_TLS_GD_CALL 26 /* Relocation for call to __tls_get_addr() */
#define R_386_TLS_GD_POP 27 /* Tag for popl in GD TLS code */
#define R_386_TLS_LDM_32 28 /* Direct 32 bit for local dynamic thread local data in LE code */
#define R_386_TLS_LDM_PUSH 29 /* Tag for pushl in LDM TLS code */
#define R_386_TLS_LDM_CALL 30 /* Relocation for call to __tls_get_addr() in LDM code */
#define R_386_TLS_LDM_POP 31 /* Tag for popl in LDM TLS code */
#define R_386_TLS_LDO_32 32 /* Offset relative to TLS block */
#define R_386_TLS_IE_32 33 /* GOT entry for negated static TLS block offset */
#define R_386_TLS_LE_32 34 /* Negated offset relative to static TLS block */
#define R_386_TLS_DTPMOD32 35 /* ID of module containing symbol */
#define R_386_TLS_DTPOFF32 36 /* Offset in TLS block */
#define R_386_TLS_TPOFF32 37 /* Negated offset in static TLS block */
/* 38? */
#define R_386_TLS_GOTDESC 39 /* GOT offset for TLS descriptor. */
#define R_386_TLS_DESC_CALL 40 /* Marker of call through TLS descriptor for relaxation. */
#define R_386_TLS_DESC 41 /* TLS descriptor containing pointer to code and to argument, returning the TLS offset for the symbol. */
#define R_386_IRELATIVE 42 /* Adjust indirectly by program base */
#define R_386_NUM 43 /* Keep this the last entry. */
其中,R_386_GLOB_DAT表示创建一个全局偏移量表项并保存该表项的位置;R_386_JMP_SLOT表示创建一个过程链接表项并保存该表项的位置;R_386_RELATIVE保存动态库文件内符号的相对地址,其中符号索引值必须设置为SHN_UNDEF(即0)。
对于现代的ELF链接器来说,提到重定位表,就不能不说全局偏移量表(GOT,Global Offset Table)和过程链接表(PLT,Procedure Linkage Table)。一般来说,在当前的操作系统中,每个动态库和可执行文件都有全局偏移量表和过程链接表。比如在可执行文件tanglinux中就有这两类表,如下所示:
$ readelf -S tanglinux
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 9] .rel.dyn REL 0804827c 00027c 000008 08 A 5 0 4
[10] .rel.plt REL 08048284 000284 000008 08 AI 5 24 4
[12] .plt PROGBITS 080482b0 0002b0 000020 04 AX 0 0 16
[13] .plt.got PROGBITS 080482d0 0002d0 000008 00 AX 0 0 8
[22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000010 04 WA 0 0 4
其中,.plt section为16字节对齐,.plt.got section为8字节对齐。
1、全局偏移量表和过程链接表
全局偏移量表(如.got section)用来支持位置无关代码(PIC,Position Independent Code),存储的是所有引用的符号的地址,以实现对这些符号的间接访问。由于全局偏移量表中保存的都是地址,所以它的表项的大小就与指针的大小相同,32位程序是4个字节,64位程序就是8个字节。
过程链接表(如.got.plt section)用来支持所引用函数的延迟绑定(即只有当该函数被第一次调用时才确定它的实际地址)。过程链接表在全局偏移量表的基础上又增加了一层间接跳转,但这样做是值得的,因为延迟绑定的使用显著提高了程序的启动速度。对于Intel体系架构,可以通过符号_GLOBAL_OFFSET_TABLE_直接访问过程链接表。
使用命令$ hexdump -Cvs 0xffc tanglinux | more查看tanglinux文件中的.got和.got.plt section中的数据(文件偏移量为0xffc,大小为20个字节),如下图所示:
其中,0x00000000表示该符号的地址尚未确定(当程序执行时动态链接器确定它的地址),0x08049f14指向.dynamic section,0x080482c6指向该符号真正调用的代码。
过程链接表中的前三项是保留的,其中第一项保存.dynamic section的地址,第二项保存用于标识特定动态库的代码,第三项保存动态链接器中符号解析函数(即_dl_runtime_resolve)的地址。
2、使用全局偏移量表和过程链接表的代码
共有两个代码section使用全局偏移量表和过程链接表,.plt section使用过程链接表,.plt.got使用全局偏移量表。
使用命令$ hexdump -Cvs 0x2b0 tanglinux | more查看tanglinux文件中的.plt和.plt.got 代码section中的数据(文件偏移量为0x2b0,大小为40个字节),如下图所示:
重定位表、全局偏移量表和过程链接表的关系如下图所示:
三、辅助信息数组
众所周知,当程序执行时,它对应进程的堆栈中必定保存了命令行参数和环境变量,它们都以字符串数组的形式存储。其实除了这两类数据,每个进程还保存了只供动态链接器所使用的辅助信息。不过,辅助信息是以二进制数组的形式存储,所以这里使用hexdump命令查看它自身的辅助信息,如下所示:
$ hexdump -C /proc/self/auxv
00000000 21 00 00 00 00 00 00 00 00 90 cd 93 ff 7f 00 00 |!...............|
00000010 10 00 00 00 00 00 00 00 ff fb 8b 0f 00 00 00 00 |................|
00000020 06 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 |................|
00000030 11 00 00 00 00 00 00 00 64 00 00 00 00 00 00 00 |........d.......|
00000040 03 00 00 00 00 00 00 00 40 00 40 00 00 00 00 00 |........@.@.....|
00000050 04 00 00 00 00 00 00 00 38 00 00 00 00 00 00 00 |........8.......|
00000060 05 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 |................|
00000070 07 00 00 00 00 00 00 00 00 60 7c 06 10 7f 00 00 |.........`|.....|
00000080 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000090 09 00 00 00 00 00 00 00 ff 17 40 00 00 00 00 00 |..........@.....|
000000a0 0b 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 |................|
000000b0 0c 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 |................|
000000c0 0d 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 |................|
000000d0 0e 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 |................|
000000e0 17 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000f0 19 00 00 00 00 00 00 00 e9 84 c8 93 ff 7f 00 00 |................|
00000100 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000110 1f 00 00 00 00 00 00 00 e7 9f c8 93 ff 7f 00 00 |................|
00000120 0f 00 00 00 00 00 00 00 f9 84 c8 93 ff 7f 00 00 |................|
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000140
由于这里的hexdump是64位程序,所以它的辅助信息数组的每个元素的大小为16个字节。每个元素的前8个字节为辅助信息的类型,后8个字节为该类型对应的值。辅助信息使用以下数据结构定义:
typedef struct
{
uint32_t a_type; /* Entry type */
union
{
uint32_t a_val; /* Integer value */
} a_un;
} Elf32_auxv_t;
其中,a_type表示辅助信息的类型,a_val保存该类型辅助信息的值。目前辅助信息的类型有以下30余种:
#define AT_NULL 0 /* End of vector */
#define AT_IGNORE 1 /* Entry should be ignored */
#define AT_EXECFD 2 /* File descriptor of program */
#define AT_PHDR 3 /* Program headers for program */
#define AT_PHENT 4 /* Size of program header entry */
#define AT_PHNUM 5 /* Number of program headers */
#define AT_PAGESZ 6 /* System page size */
#define AT_BASE 7 /* Base address of interpreter */
#define AT_FLAGS 8 /* Flags */
#define AT_ENTRY 9 /* Entry point of program */
#define AT_NOTELF 10 /* Program is not ELF */
#define AT_UID 11 /* Real uid */
#define AT_EUID 12 /* Effective uid */
#define AT_GID 13 /* Real gid */
#define AT_EGID 14 /* Effective gid */
#define AT_CLKTCK 17 /* Frequency of times() */
/* Some more special a_type values describing the hardware. */
#define AT_PLATFORM 15 /* String identifying platform. */
#define AT_HWCAP 16 /* Machine-dependent hints about processor capabilities. */
/* This entry gives some information about the FPU initialization
performed by the kernel. */
#define AT_FPUCW 18 /* Used FPU control word. */
/* Cache block sizes. */
#define AT_DCACHEBSIZE 19 /* Data cache block size. */
#define AT_ICACHEBSIZE 20 /* Instruction cache block size. */
#define AT_UCACHEBSIZE 21 /* Unified cache block size. */
/* A special ignored value for PPC, used by the kernel to control the
interpretation of the AUXV. Must be > 16. */
#define AT_IGNOREPPC 22 /* Entry should be ignored. */
#define AT_SECURE 23 /* Boolean, was exec setuid-like? */
#define AT_BASE_PLATFORM 24 /* String identifying real platforms.*/
#define AT_RANDOM 25 /* Address of 16 random bytes. */
#define AT_HWCAP2 26 /* More machine-dependent hints about processor capabilities. */
#define AT_EXECFN 31 /* Filename of executable. */
/* Pointer to the global system page used for system calls and other
nice things. */
#define AT_SYSINFO 32
#define AT_SYSINFO_EHDR 33
/* Shapes of the caches. Bits 0-3 contains associativity; bits 4-7 contains
log2 of line size; mask those to get cache size. */
#define AT_L1I_CACHESHAPE 34
#define AT_L1D_CACHESHAPE 35
#define AT_L2_CACHESHAPE 36
#define AT_L3_CACHESHAPE 37
其中,AT_NULL表示辅助信息数组的结尾,AT_PHDR表示program header表的地址,AT_PHENT表示program header表项的大小(字节数),AT_PHNUM表示program header表项的数目,AT_PAGESZ表示操作系统中内存页的大小(字节数),AT_BASE表示动态链接器的基地址,AT_ENTRY表示程序入口点地址。
在程序中,可以使用getauxval函数来获取辅助信息,如果某个类型的辅助信息不存在则函数返回0,如下所示:
/* auxv.c */
#include <sys/auxv.h>
#include <stdio.h>
int main()
{
int type;
unsigned long int value;
for (type = AT_NULL; type <= AT_L3_CACHESHAPE; ++type)
{
if ((value = getauxval(type)) == 0)
continue;
printf("auxv[%2d] = %#lx\n", type, value);
}
return 0;
}
编译并执行,程序auxv的辅助信息如下所示:
$ gcc -m32 auxv.c -o auxv
$ ./auxv
auxv[ 3] = 0x8048034
auxv[ 4] = 0x20
auxv[ 5] = 0x9
auxv[ 6] = 0x1000
auxv[ 7] = 0xf77b5000
auxv[ 9] = 0x8048360
auxv[11] = 0x3e8
auxv[12] = 0x3e8
auxv[13] = 0x3e8
auxv[14] = 0x3e8
auxv[15] = 0xff8ae02b
auxv[16] = 0x1f8bfbff
auxv[17] = 0x64
auxv[25] = 0xff8ae01b
auxv[31] = 0xff8aeff1
auxv[32] = 0xf77b4420
auxv[33] = 0xf77b4000
四、note section
note section用来保存一些关于当前ELF文件的特定信息。note section的类型为SHT_NOTE,从执行的角度看,它是PT_NOTE类别的segment。在可执行文件tanglinux中有两个常见的note section,它们分别是.note.ABI-tag和.note.gnu.build-id(note section的名字都以.note开头),如下所示:
$ readelf -SW tanglinux
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 08048188 000188 000024 00 A 0 0 4
也可以通过执行readelf命令来查看ELF文件中的note section中的信息,如下所示:
$ readelf -n tanglinux
Displaying notes found at file offset 0x00000168 with length 0x00000020:
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 2.6.32
Displaying notes found at file offset 0x00000188 with length 0x00000024:
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 239b4116d98acb090821d475328087fcee184780
note section的头部使用以下数据结构来定义:
typedef struct
{
Elf32_Word n_namesz; /* Length of the note's name. */
Elf32_Word n_descsz; /* Length of the note's descriptor. */
Elf32_Word n_type; /* Type of the note. */
} Elf32_Nhdr;
紧随头部之后就是note section的实际内容,依次是厂商的名称(ASCII字符串)、note的内容。
n_namesz表示厂商名称的大小。目前常用的名称只有以下两个:
/* Solaris entries in the note section have this name. */
#define ELF_NOTE_SOLARIS "SUNW Solaris"
/* Note entries for GNU systems have this name. */
#define ELF_NOTE_GNU "GNU"
对于GNU系统,名称为字符串“GNU”,共4个字节。
n_descsz表示内容的大小。
n_type表示内容的类型。对于GNU系统,常见的类型有以下4个:
/* ABI information. The descriptor consists of words:
word 0: OS descriptor
word 1: major version of the ABI
word 2: minor version of the ABI
word 3: subminor version of the ABI
*/
#define NT_GNU_ABI_TAG 1
#define ELF_NOTE_ABI NT_GNU_ABI_TAG /* Old name. */
/* Synthetic hwcap information. The descriptor begins with two words:
word 0: number of entries
word 1: bitmask of enabled entries
Then follow variable-length entries, one byte followed by a
'\0'-terminated hwcap name string. The byte gives the bit
number to test if enabled, (1U << bit) & bitmask. */
#define NT_GNU_HWCAP 2
/* Build ID bits as generated by ld --build-id.
The descriptor consists of any nonzero number of bytes. */
#define NT_GNU_BUILD_ID 3
/* Version note generated by GNU gold containing a version string. */
#define NT_GNU_GOLD_VERSION 4
NT_GNU_ABI_TAG用来说明与当前ELF文件ABI兼容的操作系统内核的最早版本,它的内容共16个字节,依次为操作系统的类型,内核的主版本、次版本、修订版本,每个信息都是4个字节的整数。其中常见的操作系统类型的取值如下所示:
/* Known OSes. These values can appear in word 0 of an
NT_GNU_ABI_TAG note section entry. */
#define ELF_NOTE_OS_LINUX 0
#define ELF_NOTE_OS_GNU 1
#define ELF_NOTE_OS_SOLARIS2 2
#define ELF_NOTE_OS_FREEBSD 3
NT_GNU_BUILD_ID的内容由程序链接时指定--build-id命令行参数时所生成,默认为SHA1的散列值,如239b4116d98acb090821d475328087fcee184780,这个值中的所有数字被依次保存在note section的内容中,每个数字占用4个比特位。值得注意的是这个散列值不是用来验证该ELF文件的一致性的,虽然这个散列值是使用它的内容计算而来。
可以通过执行$ hexdump -C tanglinux | more命令查看tanglinux中的note section,如下图所示:
当我们通过file命令查看可执行文件的相关信息时,其中的部分内容就是来自它的note section,如下所示:
$ file tanglinux
tanglinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=239b4116d98acb090821d475328087fcee184780, not stripped
如其中的“for GNU/Linux 2.6.32, BuildID[sha1]=239b4116d98acb090821d475328087fcee184780”。
关于ELF文件中常见的数据结构,已经通过ELF文件的文件头、section header和program header,ELF文件中与符号(symbol)相关的section的定义和本文等三篇文章详细说明。本文为该主题的结束篇。