分类:
2010-03-20 15:17:29
-------------------------------------------
前几天一个师弟问了一个动态链接库变量的问题,突然答不上来,才发现原来知道的东西都忘了,于是找到以前看的东西,大概扫了一下,了解了,觉得有必要对elf文件格式记录下来,以加深了解。
本文系本站原创,欢迎转载!
转载请注明出处:http://sjj0412.cublog.cn
------------------------------------------
估计用过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索引的字符串。
字符串表索引可以引用节区中任意字节。
字符串可以出现多次