程序的本质之四ELF文件中与动态链接有关的section的定义

操作系统: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 headerELF文件中与符号(symbol)相关的section的定义和本文等三篇文章详细说明。本文为该主题的结束篇。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tanglinux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值