重学计算机(三、elf文件布局和符号表)

上一篇写了.o目标文件分析,.o文件只是一个.c文件通过汇编生成的一个可重定位文件,并没有真正进行链接,现在我们就分析一个链接完成后的可执行文件hello_world,经过两个文件的对比,让我们更好的掌握ELF文件。

3.1 hello_world.o补充

上一篇我们只是使用了objdump -h 查看各个段,其实-h只是把关键中的段显示了出来,这次我们用readelf -S来看全部的段。

root@ubuntu:~/c_test/03# readelf -S hello_world.o 
There are 13 section headers, starting at offset 0x490:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       000000000000007f  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000338
       00000000000000c0  0000000000000018   I      11     1     8
  [ 3] .data             PROGBITS         0000000000000000  000000c0
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  000000c8
       0000000000000008  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  000000c8
       000000000000001e  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000e6
       0000000000000036  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  0000011c
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  00000120
       0000000000000058  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  000003f8
       0000000000000030  0000000000000018   I      11     8     8
  [10] .shstrtab         STRTAB           0000000000000000  00000428
       0000000000000061  0000000000000000           0     0     1
  [11] .symtab           SYMTAB           0000000000000000  00000178
       0000000000000180  0000000000000018          12    11     8
  [12] .strtab           STRTAB           0000000000000000  000002f8
       000000000000003b  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
root@ubuntu:~/c_test/03# 

上一节我们介绍了7个section段,这次再来补一补:

3.1.1 .rela.text

重定位的表,这个表记录中程序中需要重定位的函数或变量,就是代码段和数据段那些对绝对地址的引用。

这个.rela.text是对.text段的重定位表,应该就是printf函数的,没有引用全局的数据,所以没有.rela.data段。

root@ubuntu:~/c_test/03# readelf --relocs hello_world.o 

Relocation section '.rela.text' at offset 0x338 contains 8 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000011  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000001b  000e00000002 R_X86_64_PC32     0000000000000000 printf - 4
00000000003e  000400000002 R_X86_64_PC32     0000000000000000 .bss + 0
000000000044  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
000000000057  000d00000002 R_X86_64_PC32     0000000000000000 func1 - 4
00000000005d  000b00000002 R_X86_64_PC32     0000000000000000 g_a - 4
00000000006a  00050000000a R_X86_64_32       0000000000000000 .rodata + 8
000000000074  000e00000002 R_X86_64_PC32     0000000000000000 printf - 4

Relocation section '.rela.eh_frame' at offset 0x3f8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
000000000040  000200000002 R_X86_64_PC32     0000000000000000 .text + 26

这个等下一节分析链接的时候,再好好分析。

3.1.2 字符串表

这个字符串表不是代码中的字符串,代码中的字符串是存储在rodata区,这里说的字符串表是.strtab和.shstrtab这两个section段的字符串。

下面我们继续用readelf工具来看看这两个段有啥

root@ubuntu:~/c_test/03# readelf --string-dump=10 hello_world.o 

String dump of section '.shstrtab':
  [     1]  .symtab
  [     9]  .strtab
  [    11]  .shstrtab
  [    1b]  .rela.text
  [    26]  .data
  [    2c]  .bss
  [    31]  .rodata
  [    39]  .comment
  [    42]  .note.GNU-stack
  [    52]  .rela.eh_frame

root@ubuntu:~/c_test/03# readelf --string-dump=12 hello_world.o 

String dump of section '.strtab':
  [     1]  hello_world.c
  [     f]  s_a.2292
  [    18]  s_b.2293
  [    21]  g_a
  [    25]  g_b
  [    29]  func1
  [    2f]  printf
  [    36]  main

root@ubuntu:~/c_test/03# 

.shstrtab这个段主要是保存section段名的,.strtab这个段主要是保存代码中的函数名和变量名。

下面是存储的格式,所以如果需要取字符串的话,需要取上图的左边的偏移就能取到字符串。

重学计算机(三、elf文件布局和符号表)_第1张图片

3.1.3 .symtab

符号表,很重要,后面再分析

3.1.4 elf header

上一篇也看了头信息,不过没有详细说,差点就忽略了重要信息,

root@ubuntu:~/c_test/02# readelf -h hello_world.o 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          1168 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 10
root@ubuntu:~/c_test/02# 

解释重要字段:

  1. Type:一个是EXEC (Executable file),一个是REL (Relocatable file)
  2. Entry point address:入口地址,规定ELF程序的入口虚拟地址,操作系统会在加载完程序后,从这个地址开始执行程序。可重定位文件一般是没链接成功的,所以这个值为0,可执行文件是链接成功了,所以有了一个地址0x400430。
  3. Start of program headers:编程头信息,对各种Segment的描述,在链接部分会描述
  4. Size of program headers:既然有了编程头信息,那当然有大小了,要不然怎么知道结束位置,这个大小是每个Segment的大小
  5. Number of program headers:顺带送了一个编程头信息有几个segment段
  6. Start of section headers:section段的头,就是上一节讲了那么多段,他们的段信息是存在这里的
  7. Size of section headers:这个也是描述section段的大小,每个section段的大小
  8. Number of section headers:另外还附带了section段的个数
  9. Size of this header:这个是我们现在查看的头信息的大小
  10. Section header string table index:在’.shstrtab’段中的字符串.shstrtab的下标。(但是我们在字符串中看到的下标是0x11,在头信息中看到是10,不是很明白哪里错了,以后理解了再补

3.1.5 hello_world.0分布图

重学计算机(三、elf文件布局和符号表)_第2张图片

0x7d0其实就是文件的大小,不信可以用ls 命令查看文件大小就清楚了。

3.2 可执行文件

上面主要是描述了可重定位文件,下面可以简单看看可执行文件,就是链接后的,可以直接执行的,不过这里不会详细讲,下一节讲链接的时候,就会比较详细的描述。

3.2.1 头信息

root@ubuntu:~/c_test/03# readelf -h hello_world
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400430
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6784 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 28
readelf: Error: Reading 0x19740000 bytes extends past end of file for string table
readelf: Error: no .dynamic section in the dynamic segment

可以看可执行文件跟可重定位文件的区别就是type,还有Entry point address,链接完成了,是有main函数可以执行了。并且多了Start of program headers这个段。

3.2.2 program headers

也可以通过命令来查看这个段有什么:

root@ubuntu:~/c_test/03# readelf -l hello_world

Elf file type is EXEC (Executable file)
Entry point 0x400430
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000007a4 0x00000000000007a4  R E    200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x0000000000000230 0x0000000000000240  RW     200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000000654 0x0000000000400654 0x0000000000400654
                 0x000000000000003c 0x000000000000003c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x00000000000001f0 0x00000000000001f0  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 
root@ubuntu:~/c_test/03# 

这个segment段可以理解为链接器把section段的比较相似的段会统一整理形成一个新的segment段,这样会更利于节省内存。

3.2.3 section段查看

可执行文件中也是有section段的,下面我们来看看:

root@ubuntu:~/c_test/03# readelf -S hello_world
There are 31 section headers, starting at offset 0x1a80:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400318  00000318
       000000000000003f  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400358  00000358
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400360  00000360
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400380  00000380
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400398  00000398
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         00000000004003c8  000003c8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003f0  000003f0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000400420  00000420
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         0000000000400430  00000430
       00000000000001f2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000400624  00000624
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000400630  00000630
       0000000000000022  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000400654  00000654
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400690  00000690
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601028  00001028
       0000000000000018  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601040  00001040
       0000000000000010  0000000000000000  WA       0     0     4
  [27] .comment          PROGBITS         0000000000000000  00001040
       0000000000000035  0000000000000001  MS       0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  00001974
       000000000000010c  0000000000000000           0     0     1
  [29] .symtab           SYMTAB           0000000000000000  00001078
       00000000000006c0  0000000000000018          30    49     8
  [30] .strtab           STRTAB           0000000000000000  00001738
       000000000000023c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
root@ubuntu:~/c_test/03# 

这个段是真多啊,现在是粗略浏览一下,我画了一个hello_world的布局图,可以简单看看。

重学计算机(三、elf文件布局和符号表)_第3张图片

简单粗略的看看就可以了,后面还会继续分析。

3.3 符号表

3.3.1 符号是什么

所说的符号其实就是我们代码中的函数和变量,在链接的过程中,链接器会把这些所有符号都统一管理起来,然后给他们赋值,叫符号值。函数和变量的符号值就是自己的地址。

所以写c语言的时候,函数不能重名,重名的时候会在链接的时候会报错,就是这个道理,当然可以用static来限制作用域。

所以每一个目标文件(hello_world.o)都有一份自己的符号表,存储在.symtab段中,链接的时候,链接器只要去找这个段中的符号,然后各种操作就可以了。

下面具体来看看符号有哪些:

  1. 定义在本目标文件的全局符号,可以被其他目标文件引用。(函数和全局变量)
  2. 在本目标文件中引用的全局符号,却没有定义在本目标文件。这叫外部符号。(printf,这个经常忘记)
  3. 段名,这种符号是编译器产生的。
  4. 局部符号,就是局部静态变量,链接器往往会忽略他们
  5. 行号信息,目标文件指令与源代码中代码行的对应关系。(这个还真没注意)

3.3.2 分析一个符号表

我们就拿hello_world.o分析一下符号表

root@ubuntu:~/c_test/03# readelf -s hello_world.o 

Symbol table '.symtab' contains 16 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello_world.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 s_a.2292
     7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 s_b.2293
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_a
    12: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 g_b
    13: 0000000000000000    38 FUNC    GLOBAL DEFAULT    1 func1
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    15: 0000000000000026    89 FUNC    GLOBAL DEFAULT    1 main

num:个数

value:在可执行文件中,这个值就是符号值

Size:大小

Type:符号类型

Bind:符号绑定信息

Ndx:UND:表示未找到,printf函数在其他地方,其他的表示section段的

图示:

重学计算机(三、elf文件布局和符号表)_第4张图片

重学计算机(三、elf文件布局和符号表)_第5张图片

3.3.3 其他

  1. 符号修饰

    c++语言具有重载、命名空间等特性,那链接器是怎么处理这些符号的,其实链接器处理的时候也比较简单,就在原来的符号上,再做修饰,比如下图:

重学计算机(三、elf文件布局和符号表)_第6张图片

是不是就懂了,重载的话,会把参数带在符号上,命名空间等都是这样处理的。

  1. extren c

    我们在写在c++中引用c是不是需要添加extren c,这个做法其实也是链接器不会吧c的函数进行修饰,这样c++就可以调用了。

  2. 强符号与弱符号

​ gcc编译器是支持定义这种弱符号的,弱符号的意思就是可以定义一个强符号(前面不加修饰就是强符号)来覆盖这个弱符号,调用的时候,如果有强符号就会调用强符号,如果没有强符号就调用弱符号,这样也不会报重复定义的错误。

编译器对弱符号的函数没有定义是没有报错的,以后可以验证一下。

__attribute__((weak)) weak2 = 2;

参考链接:

计算机原理系列之六 ——– 可执行文件详解

计算机原理系列之四 ——– 可重定位文件详解

《程序员的自我修养——链接、装载与库》

你可能感兴趣的:(重学计算机,elf文件布局,符号表,elf文件,elf文件分析)