大话 elf 格式! -- 可连接elf 格式

来源:互联网 发布:dota2和lol的区别 知乎 编辑:程序博客网 时间:2024/06/09 04:01

生成一个bin文件,一个连接elf, 一个共享elf文件

elf 格式,网上内容介绍的不少, 能不能结合一种实际的,简单的练习来说明事情.
这里,我需要一个魔法棒,变出一个简单的可连接elf文件 和一个简单的可共享elf文件!
练习:

$ echo 1234 |xxd -r -ps > 1.bin$ objcopy -I binary -O elf64-x86-64 -B i386 1.bin 1.o$ gcc -shared -o 1.so 1.o

简单的3条命令,生成了3个文件
1.bin (二进制文件)
1.o( 可连接elf文件)
1.so( 动态连接库elf文件).
此处应有掌声!

注意, objcopy 不使用-B 指明 arch, 可以生成1.o, 但连接为动态库会出问题.

$ objcopy -I binary -O elf64-x86-64  1.bin 1.o$ gcc -shared -o 1.so 1.o/usr/bin/ld: unknown architecture of input file `1.o' is incompatible with i386:x86-64 outputcollect2: error: ld returned 1 exit status

看一看文件大小.

$ ll 1.*-rw-rw-r-- 1 hjj hjj    8  626 11:35 1.bin-rw-rw-r-- 1 hjj hjj  610  626 12:21 1.o-rwxrwxr-x 1 hjj hjj 7828  626 11:54 1.so*

我简单的8个bytes二进制文件, 竟生成了610字节的可连接文件,
更可怕的是生成了近8K 的可加载文件, 这些文件内容都是什么呢?

连接elf 是可以供gcc 或 ld 连接成执行文件或共享对象的文件.

可连接文件概揽.

objdump -s 1.o1.o:     file format elf64-x86-64Contents of section .data: 0000 12345678 9abcdef0                    .4Vx....        
$ xxd 1.o0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............0000010: 0100 0000 0100 0000 0000 0000 0000 0000  ................0000020: 0000 0000 0000 0000 7000 0000 0000 0000  ........p.......0000030: 0000 0000 4000 0000 0000 4000 0500 0200  ....@.....@.....0000040: 1234 5678 9abc def0 002e 7379 6d74 6162  .4Vx......symtab0000050: 002e 7374 7274 6162 002e 7368 7374 7274  ..strtab..shstrt0000060: 6162 002e 6461 7461 0000 0000 0000 0000  ab..data........0000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................00000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................00000b0: 1b00 0000 0100 0000 0300 0000 0000 0000  ................00000c0: 0000 0000 0000 0000 4000 0000 0000 0000  ........@.......00000d0: 0800 0000 0000 0000 0000 0000 0000 0000  ................00000e0: 0100 0000 0000 0000 0000 0000 0000 0000  ................00000f0: 1100 0000 0300 0000 0000 0000 0000 0000  ................0000100: 0000 0000 0000 0000 4800 0000 0000 0000  ........H.......0000110: 2100 0000 0000 0000 0000 0000 0000 0000  !...............0000120: 0100 0000 0000 0000 0000 0000 0000 0000  ................0000130: 0100 0000 0200 0000 0000 0000 0000 0000  ................0000140: 0000 0000 0000 0000 b001 0000 0000 0000  ................0000150: 7800 0000 0000 0000 0400 0000 0200 0000  x...............0000160: 0800 0000 0000 0000 1800 0000 0000 0000  ................0000170: 0900 0000 0300 0000 0000 0000 0000 0000  ................0000180: 0000 0000 0000 0000 2802 0000 0000 0000  ........(.......0000190: 3a00 0000 0000 0000 0000 0000 0000 0000  :...............00001a0: 0100 0000 0000 0000 0000 0000 0000 0000  ................00001b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................00001c0: 0000 0000 0000 0000 0000 0000 0300 0100  ................00001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................00001e0: 0100 0000 1000 0100 0000 0000 0000 0000  ................00001f0: 0000 0000 0000 0000 1500 0000 1000 0100  ................0000200: 0800 0000 0000 0000 0000 0000 0000 0000  ................0000210: 2700 0000 1000 f1ff 0800 0000 0000 0000  '...............0000220: 0000 0000 0000 0000 005f 6269 6e61 7279  ........._binary0000230: 5f31 5f62 696e 5f73 7461 7274 005f 6269  _1_bin_start._bi0000240: 6e61 7279 5f31 5f62 696e 5f65 6e64 005f  nary_1_bin_end._0000250: 6269 6e61 7279 5f31 5f62 696e 5f73 697a  binary_1_bin_siz0000260: 6500                                     e.

结合 readelf -a 1.o
可以分析出这个1.o 文件,

还好,该文件只添加了elf 头部信息, 并添加了3个符号, 还算一种紧凑的格式.

节区划分,

$ readelf -S 1.oThere are 5 section headers, starting at offset 0x70:Section Headers:  [Nr] Name              Type             Address           Offset       Size              EntSize          Flags  Link  Info  Align  [ 0]                   NULL             0000000000000000  00000000       0000000000000000  0000000000000000           0     0     0  [ 1] .data             PROGBITS         0000000000000000  00000040       0000000000000008  0000000000000000  WA       0     0     1  [ 2] .shstrtab         STRTAB           0000000000000000  00000048       0000000000000021  0000000000000000           0     0     1  [ 3] .symtab           SYMTAB           0000000000000000  000001b0       0000000000000078  0000000000000018           4     2     8  [ 4] .strtab           STRTAB           0000000000000000  00000228       000000000000003a  0000000000000000           0     0     1

第一个空节区可以看做是elf-header占位区,64byte
构造了一个.data 节区,
.data 区8个字节, 顶起了这610个字节的可连接elf文件,
.shstrtab 表是必须有的,
符号表区sh_link表示使用的字符串表的段下标。
符号表区sh_info为2,说明本地符号最大下标是1.

符号的概念

符号是为连接而生的. 创建符号是为了引用符号,也有符号仅为显示信息.
你一定要深刻理解”符号”的概念哦.
符号有名称和值的概念, 符号有类型,
变量和函数地址都可以用符号来表达, 外部变量,外部函数都是用外部符号表示的.
外部符号地址为0, 表示现在还不能确定它们的具体地址.
节名称, 文件名称也可以用符号来表达.

符号表:

$ readelf -s 1.oSymbol table '.symtab' contains 5 entries:   Num:    Value          Size Type    Bind   Vis      Ndx Name     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND      1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1      2: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 _binary_1_bin_start     3: 0000000000000008     0 NOTYPE  GLOBAL DEFAULT    1 _binary_1_bin_end     4: 0000000000000008     0 NOTYPE  GLOBAL DEFAULT  ABS _binary_1_bin_size

它包含了5个符号,
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;
0,1 是无名的, 有的符号无名!, 0 是固定的空, 1是节区名称,因其index为1,故知是”.data”区
三个有名符号_binary_1_bin_start,_binary_1_bin_end,_binary_1_bin_size
意义也很明确,分别表示二进制数据的开始位置,结束位置和大小.
_binary_1_bin_size 开始位置被定义为0, 将来会被重定位, size 是ABS (absolute 符号)
定义符号是为了将来有人使用, 就这样了.!

我忽然想把 .strtab 从.o 文件中提取出来, 好查一下,到底有多少个字符串!
我用如下命令:
objcopyj.strtab1.ostr1.ostr1.o,,,elf,.objcopy.strtab,readelf -p .strtab 1.o 来查看字符串表.

最简单的可连接文件. 再小就不是标准elf了,

hjj@hjj-Inspiron:~/MyTest/test6$ ll str1.o -rw-rw-r-- 1 hjj hjj 208  626 12:09 str1.ohjj@hjj-Inspiron:~/MyTest/test6$ xxd str1.o0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............0000010: 0100 3e00 0100 0000 0000 0000 0000 0000  ..>.............0000020: 0000 0000 0000 0000 5000 0000 0000 0000  ........P.......0000030: 0000 0000 4000 0000 0000 4000 0200 0100  ....@.....@.....0000040: 002e 7368 7374 7274 6162 0000 0000 0000  ..shstrtab......0000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................0000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................0000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................0000090: 0100 0000 0300 0000 0000 0000 0000 0000  ................00000a0: 0000 0000 0000 0000 4000 0000 0000 0000  ........@.......00000b0: 0b00 0000 0000 0000 0000 0000 0000 0000  ................00000c0: 0100 0000 0000 0000 0000 0000 0000 0000  ................
hjj@hjj-Inspiron:~/MyTest/test6$ readelf -S str1.oThere are 2 section headers, starting at offset 0x50:Section Headers:  [Nr] Name              Type             Address           Offset       Size              EntSize          Flags  Link  Info  Align  [ 0]                   NULL             0000000000000000  00000000       0000000000000000  0000000000000000           0     0     0  [ 1] .shstrtab         STRTAB           0000000000000000  00000040       000000000000000b  0000000000000000           0     0     1

进一步阅读:
可连接elf文件重定位信息,
.text,.bss,.rodata节区

readelf -S

[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000020 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000598
0000000000000030 0000000000000018 11 1 8
……
[11] .symtab SYMTAB 0000000000000000 00000478
0000000000000108 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 00000580
0000000000000014 0000000000000000 0 0 1
重定位表的Link,Info
它的”Link”表示符号表的下标,
它的”Info”表示它作用于哪个段。
比如”.rela.text”, Link=11, Info=1, 符号表从11节取,作用于1节

符号表的Link,Info
符号表Link是说字符串表的索引,
符号表Info 的含义,
经查英文资料,为: One greater than the symbol table index of the last local symbol (binding STB_LOCAL).
是说比本地bind 符号大1, 说明这个文件的本地bind符号(节名)最大为8.
readelf -s 或者 objdump -t 可以看到本地bind符号.

重定位信息是连接器把.o文件 或者加载器把.so文件连接在一起的依据.
一个重定位表同时也是ELF的一个段,那么这个段的类型(sh_type)就是”RELA”类型的,

链接文件.o 中相对位置重定位信息

链接文件.o 中程序如何访问外部全局数据.本地全局数据及本地静态变量.
答: 一视同仁,统一按外部符号处理! 需要重定位信息
访问外部数据,偏移确定不下来. 所以先用0填充,并填写符号重定位信息.
访问本模块数据,虽然地址偏移可以确定, 但由于连接时各模块节会合并, 所以将来地址偏移还是不能确定
访问本地静态变量, 同访问本地模块数据,由于连接时各模块节会合并,所以地址还是不能确定,也需要重新定位.
用法举例: (相对寻址) R_X86_64_PC32
8b 05 00 00 00 00 mov 0x0(%rip),%eax # a

共享对象.so 中的重定位信息.

我们只考虑PIC 的so,此是linux 下标准的so
so 文件中, 考察其symbol, 只关心size!=0的symbol, size=0是外部符号或系统内部符号,我们无兴趣.
so 文件中, 不能有代码重定位, 只能有数据重定位.
.rela.dyn 重定位 .got 中数据, 通常是外部数据地址,本地数据地址,本地函数地址.
.rela.plt 重定位 .got.plt 中数据, 通常是外部函数地址.
有点cpu例如arm, 把.got, .got.plt 都合并为一个.got

本地数据地址,本地函数地址(绝对地址)由于模块加载位置不同,是需要rebase 的
外地数据地址,外地函数地址由加载器填充.

每定义一个变量或者函数都会引入一个符号, 这个符号叫本地符号, 本地符号其值就是本地地址,
在可连接文件中,对本地符号的引用也是不确定的,也需要重定位信息, 当把它们连接好后,本地符号就没有
作用了,可以把本地符号消除.