分类: LINUX
2011-03-26 11:12:08
估计用过linux的人都知道elf文件格式吧,其实在嵌入式领域也是用到elf文件格式,比如ads软件编译生成的就是axf文件格式的文件,这个其实是elf文件格式的扩展, 用gcc在编译时包含dwarf-2格式的调试信息,就可被ads识别,其实像arm公司的最新软件,realview等可直接支持elf文件的调试。
下面就来讲下elf文件格式:
为了更好的完整的表述elf,我以一个例子来说:
my.c是libmy.so的源文件:
vi my.c
int i=1;
void show() {
printf("share lib test:%d\n",i);
}
gcc -fPIC -shared -o libmy.so my.c
test.c是使用这个共享库的测试文件:
vi test.c
extern int i;
int main(){
i++;
show();
printf("%d",i);
}
gcc -o test test.c ./libmy.so
讲到elf文件格式,不得不提到三个概念。
elf文件头,程序头表,节区头部表,
1.elf文件头。
这个顾明思议就是在文件的开始位置。
文件的最开始几个字节给出如何解释文件的提示信息。
typedef struct{
unsigned char e_ident[EI_NIDENT]; //magic,就是elf文件标识,是elf等
Elf32_Half e_type; //用来标示是可重定位文件(1),可执行文件(2),还是共享文件(3)
Elf32_Half e_machine; //那种cpu的文件,SPARC(2),i386(3)
Elf32_Word e_version; //版本EV_NONE 0 非法版本 V_CURRENT 1 当前版本
Elf32_Addr e_entry; //程序入口
Elf32_Off e_phoff; //程序头表在文件中的偏移量
Elf32_Off e_shoff; //节区头部表在文件中的偏移量
Elf32_Word e_flags; //保存与文件相关的,特定于处理器的标志
Elf32_Half e_ehsize; //ELF 头部的大小(以字节计算)
Elf32_Half e_phentsize;//程序头部表格的表项大小(按字节计算
Elf32_Half e_phnum;//程序头部表格的表项的数量
Elf32_Half e_shentsize;//节区头部表格的表项大小(按字节计算)。
Elf32_Half e_shnum;//节区头部表格的表项数目。可以为 0。
Elf32_Half e_shstrndx;//节区头部表格中与节区名称字符串表相关的表项的索引。
}Elf32_Ehdr;
从上可见elf文件头是灵魂,有它才可以找到真正描述文件的 程序头表, 节区头部表的数据
上面的test的文件头如下:
jivin@jivin-desktop:~/test/dym_so$ readelf test -h
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048440 //入口是__start
Start of program headers: 52 (bytes into file) //elf头部大小为52btye
所以program headers在elf header下
Start of section headers: 6004 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)//elf头部大小为52btye
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 36
Section header string table index: 33
2.节区头部表。
节区头部表是给链接器用的,在编译的时候,和重定位的时候用的。
(1).目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意味着有节区。
(2).每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
(3).文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
(4).目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容未指定。
所谓表就是一个数组,每个元素是一个节区描述结构。
typedef struct{
Elf32_Word sh_name; //节区的名字,比如.text,.data,.symbol,.dynmatic等
Elf32_Word sh_type; //节区类型,SHT_SYMTAB,SHT_PROGBITS,SHT_STRTAB,SHT_RELA
Elf32_Word sh_flags;//一个节区中包含的内容是否可以修改、是否可以执行
Elf32_Addr sh_addr; //如果节区将在进程的内存映像中,此成员给出节区的第一个字节应处的位置这个一般在可执行文件中才不为0,因为可重定位文件(.o)没法确定地址.
Elf32_Off sh_offset; //节区在文件中的偏移
Elf32_Word sh_size; //节区大小
Elf32_Word sh_link;//
Elf32_Word sh_info;//
Elf32_Word sh_addralign;//对齐
Elf32_Word sh_entsize;//节区每个元素数据结构的大小,比如一个.symbol的大小
}Elf32_Shdr;
jivin@jivin-desktop:~/test/dym_so$ readelf test -S
Section Headers:
(这个处于6004偏移处)
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
//要链接如内存映像的节区开始处
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
[ 4] .hash HASH 0804818c 00018c 000048 04 A 6 0 4
[ 5] .gnu.hash GNU_HASH 080481d4 0001d4 000040 04 A 6 0 4
//紫色的这些节区和动态链接库链接相关的节区
[ 6] .dynsym DYNSYM 08048214 000214 0000d0 10 A 7 1 4
[ 7] .dynstr STRTAB 080482e4 0002e4 000094 00 A 0 0 1
[ 8] .gnu.version VERSYM 08048378 000378 00001a 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 08048394 000394 000020 00 A 7 1 4
[10] .rel.dyn REL 080483b4 0003b4 000010 08 A 6 0 4
[11] .rel.plt REL 080483c4 0003c4 000020 08 A 6 13 4
[12] .init PROGBITS 080483e4 0003e4 000030 00 AX 0 0 4
[13] .plt PROGBITS 08048414 000414 000050 04 AX 0 0 4
[14] .text PROGBITS 08048470 000470 00018c 00 AX 0 0 16
[15] .fini PROGBITS 080485fc 0005fc 00001c 00 AX 0 0 4
[16] .rodata PROGBITS 08048618 000618 00000b 00 A 0 0 4
[17] .eh_frame PROGBITS 08048624 000624 000004 00 A 0 0 4
[18] .ctors PROGBITS 08049f04 000f04 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049f0c 000f0c 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049f14 000f14 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f18 000f18 0000d8 08 WA 7 0 4
[22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 08049ff4 000ff4 00001c 04 WA 0 0 4
[24] .data PROGBITS 0804a010 001010 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a018 001018 00000c 00 WA 0 0 4
//要链接如内存映像的节区结束处
[26] .comment PROGBITS 00000000 001018 000046 01 MS 0 0 1
[27] .debug_aranges PROGBITS 00000000 001060 000020 00 0 0 8
[28] .debug_pubnames PROGBITS 00000000 001080 000025 00 0 0 1
[29] .debug_info PROGBITS 00000000 0010a5 0000ef 00 0 0 1
[30] .debug_abbrev PROGBITS 00000000 001194 00005f 00 0 0 1
[31] .debug_line PROGBITS 00000000 0011f3 000082 00 0 0 1
[32] .debug_str PROGBITS 00000000 001275 000092 01 MS 0 0 1
[33] .shstrtab STRTAB 00000000 001307 00013e 00 0 0 1
[34] .symtab SYMTAB 00000000 0019e8 0004a0 10 35 52 4
[35] .strtab STRTAB 00000000 001e88 00020a 00 0 0 1
下面讲下特殊节区:
1..symtab,.dynsym;(符号表节区)
这个节区也是一个数组,每个元素描述一个符号:
其结构如下:
typedef struct {
Elf32_Word st_name; //符号名字,比如变量i,b
Elf32_Addr st_value;//符号的取值。依赖于具体的上下文,它可能是一个绝对值、一个地址等等
Elf32_Word st_size;//这个符号的对应的数据的大小(int,long等),如没有大小此成员为 0
unsigned char st_info;//此成员给出符号的类型和绑定属性。下面给出若干取值和含义的绑定关系。
unsigned char st_other;//预留的,没有使用
Elf32_Half st_shndx;每个符号表项都以和其他节区间的关系的方式给出定义。此成员给出相关的节区 头部表索引。某些索引具有特殊含义。
} Elf32_sym;
st_info这个内容比较丰富:
#define ELF32_ST_BIND(i) ((i)>>4)//高四位用于绑定信息
#define ELF32_ST_TYPE(i) ((i)&0xf) //第四位用于符号类型
#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))
绑定信息主要是局部变量,全局变量等描述:
STB_LOCAL 0
局部符号在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响。
STB_GLOBAL 1
全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。
STB_WEAK 2 弱符号与全局符号类似,不过他们的定义优先级比较低。
STB_LOPROC 13
STB_HIPROC 15 处于13-15这个范围的取值是保留给处理器专用语义的。
符号类型主要是说是变量,还是函数,还是其他的东西:
STT_OBJECT 1
符号与某个数据对象相关,比如一个变量、数组等等
STT_FUNC 2
符号与某个函数或者其他可执行代码相关
STT_SECTION 3
符号与某个节区相关。这种类型的符号表项主要用于重定位,通常具有 STB_LOCAL 绑定。
STT_FILE 4
传统上,符号的名称给出了与目标文件相关的源文件的名称。文件符号具有 STB_LOCAL 绑定,其节区索引是
jivin@jivin-desktop:~/test/dym_so$ readelf test -s
Symbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
5: 00000000 0 FUNC GLOBAL DEFAULT UND show
6: 0804a018 4 OBJECT GLOBAL DEFAULT 25 i
10: 0804a018 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
11: 080483e4 0 FUNC GLOBAL DEFAULT 12 _init
12: 080485fc 0 FUNC GLOBAL DEFAULT 15 _fini
Symbol table '.symtab' contains 74 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
33: 00000000 0 FILE LOCAL DEFAULT ABS init.c
62: 0804a018 4 OBJECT GLOBAL DEFAULT 25 i
63: 0804a014 0 OBJECT GLOBAL HIDDEN 24 __dso_handle
67: 0804a018 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
68: 00000000 0 FUNC GLOBAL DEFAULT UND show
69: 0804a024 0 NOTYPE GLOBAL DEFAULT ABS _end
72: 08048524 50 FUNC GLOBAL DEFAULT 14 main
73: 080483e4 0 FUNC GLOBAL DEFAULT 12 _init
2..strtab节区:
这个节区包含字符串,不在是一个结构的数组
其组成形式如下:
sjj0412\0 rtlab\0
那么如果我们使用sjj0412这个字符串,则是使用了这个节区的0索引的字符串,而rtlab就是1索引的字符串。
字符串表索引可以引用节区中任意字节。
字符串可以出现多次
�� 可以存在对子字符串的引用
同一个字符串可以被引用多次。
字符串表中也可以存在未引用的字符串
3..rel从定位节区:
这个一般在可重定位文件中(.o)或使用了共享文件的可执行文件中。
对于可执行文件,只有使用动态链接库的才有:
jivin@jivin-desktop:~/test/dym_so$ readelf test -r
Relocation section '.rel.dyn' at offset 0x3b4 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
08049ff0 00000106 R_386_GLOB_DAT 00000000 __gmon_start__
0804a018 00000605 R_386_COPY 0804a018 i
//动态库中的变量重定位信息在rel.dyn节区里,是R_386_COPY。
Relocation section '.rel.plt' at offset 0x3c4 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
0804a000 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a004 00000307 R_386_JUMP_SLOT 00000000 __libc_start_main
0804a008 00000407 R_386_JUMP_SLOT 00000000 printf
0804a00c 00000507 R_386_JUMP_SLOT 00000000 show
//动态库中的函数重定位信息在rel.plt节区里,是R_386_JUMP_SLOT。
下面来分析下他们的偏移:
[24] .bss NOBITS 0804a018 001018 00000c 00 WA 0 0 4
0804a018 00000605 R_386_COPY 0804a018 i
//可知动态库中的变量(i)在使用者中分配在了.bbs节区。
[21] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
[22] .got.plt PROGBITS 08049ff4 000ff4 00001c 04 WA 0 0 4
0804a00c 00000507 R_386_JUMP_SLOT 00000000 show
//可知动态库中的函数(show)在使用者中分配在了.got.plt节区。
下面来差下这些变量在共享库中如何分配的:
jivin@jivin-desktop:~/test/dym_so$ readelf libmy.so -r
Relocation section '.rel.dyn' at offset 0x2e4 contains 5 entries:
Offset Info Type Sym.Value Sym. Name
0000200c 00000008 R_386_RELATIVE
00001fe4 00000106 R_386_GLOB_DAT 00000000 __gmon_start__
00001fe8 00000206 R_386_GLOB_DAT 00000000 _Jv_RegisterClasses
00001fec 00000506 R_386_GLOB_DAT 00002010 i
00001ff0 00000406 R_386_GLOB_DAT 00000000 __cxa_finalize
Relocation section '.rel.plt' at offset 0x30c contains 3 entries:
Offset Info Type Sym.Value Sym. Name
00002000 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__
00002004 00000307 R_386_JUMP_SLOT 00000000 printf
00002008 00000407 R_386_JUMP_SLOT 00000000 __cxa_finalize
可知show函数在libmy.so不需要重定位:
i变量要重定位且在.got节区
jivin@jivin-desktop:~/test/dym_so$ readelf libmy.so -S
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[19] .got PROGBITS 00001fe4 000fe4 000010 04 WA 0 0 4
[20] .got.plt PROGBITS 00001ff4 000ff4 000018 04 WA 0 0 4
既然说到got,plt节区,也就谈谈.plt节区:
这个就是插桩代码节区,每个共享库中的函数调用要插一个桩:
[13] .plt PROGBITS 08048414 000414 000050 04 AX 0 0 4
再看下代码:
jivin@jivin-desktop:~/test/dym_so$ objdump -S test
Disassembly of section .plt:
08048414 <>: //GOT[0]是.dynamic节区地址
8048414: ff 35 f8 9f 04 08 pushl 0x8049ff8 //GOT[1]动态标识信息在got.plt节区
804841a: ff 25 fc 9f 04 08 jmp *0x8049ffc //GOT[2]动态链接器入口在.got.plt
8048420: 00 00 add %al,(%eax)
...
08048424 <__gmon_start__@plt>:
8048424: ff 25 00 a0 04 08 jmp *0x804a000
804842a: 68 00 00 00 00 push $0x0
804842f: e9 e0 ff ff ff jmp 8048414 <_init+0x30>
08048434 <__libc_start_main@plt>:
8048434: ff 25 04 a0 04 08 jmp *0x804a004
804843a: 68 08 00 00 00 push $0x8
804843f: e9 d0 ff ff ff jmp 8048414 <_init+0x30>
08048444
8048444: ff 25 08 a0 04 08 jmp *0x804a008
804844a: 68 10 00 00 00 push $0x10
804844f: e9 c0 ff ff ff jmp 8048414 <_init+0x30>
08048454
8048454: ff 25 0c a0 04 08 jmp *0x804a00c 对应got.plt节区的show
804845a: 68 18 00 00 00 push $0x18
804845f: e9 b0 ff ff ff jmp 8048414 <_init+0x30>
从上面可以分析出:
在共享库中:
定义的变量会出现在.rel.dyn里,且占用一个.got节用来重定位到引用这个变量的应用程序的.bbs段中,装载时,动态链接器会修改动态库中的.got数据,从而导致cow,从而新的进程会有个自己的copy,即变量i私有了。。
在使用共享库的程序中:
对于引用的函数,重定位信息会出现在.rel.plt里,每个函数都要生成对应的plt代码(在.plt节区里),并且在.got.plt节区里有一个成员来指向共享库中函数的地址。
对于引用的变量,重定位信息会出现在.rel.dyn里,在.bbs节中。
当我们要调用show时,其实是调用08048454
对可重定位文件:
jivin@jivin-desktop:~/test/dym_so$ readelf my.o -r
Relocation section '.rel.text' at offset 0x35c contains 3 entries:
Offset Info Type Sym.Value Sym. Name
00000007 00000801 R_386_32 00000000 i
00000012 00000501 R_386_32 00000000 .rodata
00000017 00000a02 R_386_PC32 00000000 printf
R_386_32 类型的重定位是:*(offset)=adr(sym);
R_386_PC32 *(offset)=offset-sym.offset+adr(sym);
3.程序头部表:
程序头部表是给装载器用的,在将程序装载到内存中要用的。
jivin@jivin-desktop:~/test/dym_so$ readelf test -l
Elf file type is EXEC (Executable file)
Entry point 0x8048470
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00628 0x00628 R E 0x1000
LOAD 0x000f04 0x08049f04 0x08049f04 0x00114 0x00120 RW 0x1000
DYNAMIC 0x000f18 0x08049f18 0x08049f18 0x000d8 0x000d8 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000f04 0x08049f04 0x08049f04 0x000fc 0x000fc R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06
07 .ctors .dtors .jcr .dynamic .got
从上面我们可以看出:
一个程序段可以包含很多节区。
所以段和节区是从不同角度来表述程序。