linker and loader 读书笔记

Posted threepigs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linker and loader 读书笔记相关的知识,希望对你有一定的参考价值。

linker and loader这本书是目前介绍连接器跟加载器最全面的书了。毕业之前在学校的时候就看过一遍,不过由于这本书过于理论、晦涩,当时并没有理解透,如今结合实际来读,来彻底的完成对这本书的学习。
这篇笔记并不是想全面介绍什么是连接器和加载器,只是记下自己读这本书的理解过程,结合一些实际来加深理解。

对广大程序员来说,连接器跟加载器也并不是什么特别陌生的东西,虽然并不是很多人都会非常了解编译连接技术,不过没见过猪跑,难道还没吃过猪肉吗,写程序写多了总会对连接器有点了解。下面从一个简单的“hello world”程序开始,一步一步的了解整个连接过程。
ex01.cpp
#include "iostream"
int times = 9;
int main()

    const char* str = "hello world";
    for(int i = 0; i < times; i++)
   
        std::cout<<str<<std::endl;
   

g++ ex01.cpp -c 生成可重定位的ex01.o ELF目标文件。先用objdump -D -x ex01.o来查看目标文件的内容。
区信息:
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000af  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000004  00000000  00000000  000000e4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000001  00000000  00000000  000000e8  2**2
                  ALLOC
  3 .rodata       0000000c  00000000  00000000  000000e8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .ctors        00000004  00000000  00000000  000000f4  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, DATAi
  5 .comment      00000024  00000000  00000000  000000f8  2**0
                  CONTENTS, READONLY
  6 .note.GNU-stack 00000000  00000000  00000000  0000011c  2**0
                  CONTENTS, READONLY
  7 .eh_frame     00000070  00000000  00000000  0000011c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
该可重定位的目标文件有8个区。.text代码区,大小0xaf,VMA,LMA表示虚拟内存地址,线性内存地址,由于这是可重定义文件,所以这两个域一般都为0,File off文件偏移量是Ox34,前面还有52个字节的ELF头,用readelf -a ex01.o就能很详细的看见。Algn=4,.text区是4字节对齐的。.data只有4个字节,就一个times。.bss是未初始化数据区,有1个size,main里面并没有用到,是libc的静态初始化函数里用的,以后再介绍。由于是全0,所以并不占文件的空间,所以下面的.rodata只读数据区的文件偏移量跟.bss相同,大小0x0c,就是存放“hello world”的段。


接下来是符号表
SYMBOL TABLE:
00000000 l    df *ABS*    00000000 ex01.cpp
00000000 l    d  .text    00000000 .text
00000000 l    d  .data    00000000 .data
00000000 l    d  .bss    00000000 .bss
00000000 l     O .bss    00000001 _ZStL8__ioinit
00000000 l    d  .rodata    00000000 .rodata
00000053 l     F .text    00000040 _Z41__static_initialization_and_destruction_0ii
00000093 l     F .text    0000001c _GLOBAL__I_times
00000000 l    d  .ctors    00000000 .ctors
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame    00000000 .eh_frame
00000000 l    d  .comment    00000000 .comment
00000000 g     O .data    00000004 times
00000000 g     F .text    00000053 main
00000000         *UND*    00000000 __gxx_personality_v0
00000000         *UND*    00000000 _ZSt4cout
00000000         *UND*    00000000 _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
00000000         *UND*    00000000 _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
00000000         *UND*    00000000 _ZNSolsEPFRSoS_E
00000000         *UND*    00000000 _ZNSt8ios_base4InitC1Ev
00000000         *UND*    00000000 _ZNSt8ios_base4InitD1Ev
00000000         *UND*    00000000 __dso_handle
00000000         *UND*    00000000 __cxa_atexit
第一列是表示符号的值,可以看到name为_GLOBAL__I_times的value等于93,根据Objdump的结果可以看到,这是.text区偏移为93的位置的一个函数类型符号。第二列表示符号的bind方式l=local,g=global,w=weak,第三列是类型,第四列是所属的区,第五列是size,最后就是符号名。

Disassembly of section .text:
00000000 <main>:
   0:    55                       push   %ebp
   1:    89 e5                    mov    %esp,%ebp
   3:    83 e4 f0                 and    $0xfffffff0,%esp
   6:    83 ec 20                 sub    $0x20,%esp
   9:    c7 44 24 1c 00 00 00     movl   $0x0,0x1c(%esp)
  10:    00
            d: R_386_32    .rodata
  11:    eb 29                    jmp    3c <main+0x3c>
  13:    8b 44 24 1c              mov    0x1c(%esp),%eax
  17:    89 44 24 04              mov    %eax,0x4(%esp)
  1b:    c7 04 24 00 00 00 00     movl   $0x0,(%esp)
            1e: R_386_32    _ZSt4cout
  22:    e8 fc ff ff ff           call   23 <main+0x23>
            23: R_386_PC32    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
  27:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)
  2e:    00
            2b: R_386_32    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
  2f:    89 04 24                 mov    %eax,(%esp)
  32:    e8 fc ff ff ff           call   33 <main+0x33>
            33: R_386_PC32    _ZNSolsEPFRSoS_E
  37:    83 44 24 18 01           addl   $0x1,0x18(%esp)
  3c:    a1 00 00 00 00           mov    0x0,%eax
            3d: R_386_32    times
  41:    39 44 24 18              cmp    %eax,0x18(%esp)
  45:    0f 9c c0                 setl   %al
  48:    84 c0                    test   %al,%al
  4a:    75 c7                    jne    13 <main+0x13>
  4c:    b8 00 00 00 00           mov    $0x0,%eax
  51:    c9                       leave 
  52:    c3                       ret   
加红部分就是需要重定位的符号,这是用-c生成的可重载目标文件,这些符号都没有被连接过。这就是代码区的反汇编内容。其中offset 0x09开始的指令包含了一个可重定位符号,就是从offset 0x0d的str,它在只读数据区里,在这里还没有被重定位,所以是全0来表示。这个符号在重定位段.rel.text里有一个对应的重定位项,一般之后的连接过程可以对这些未重定位的符号进行重定位。这个可以用readelf -r列出所有的重定位段。
 Offset     Info    Type            Sym.Value  Sym. Name
0000000d  00000601 R_386_32          00000000   .rodata


offset就是改符号出现的位置的偏移,就是我们之前.text看到的str的偏移。Info由两部分组成,低8位是重定位类型,01就是 R_386_32,
是指S+A,S是该符号在符号表的value项,A表示Addend,对于REL型的重定位,Addend嵌入在代码的符号位置中,而 RELA型则是重定位项有个Addend字段。R_386_PC32的重定位类型是PC相对重定位,是S+A-P,P就是符号所在的位置偏移,如果是同区跳转的话S就等于P,_ZNSolsEPFRSoS_E就保持不变(x86是REL类型的A就是fc ff ff ff = -4)。我们的全局变量times就是一直.data区的符号,重定位类型R_386_32,为符号表的value值跟Addend(这里等于 0)的和。

这就是一个没有连接过的可重载文件的格式解析,连接过程并没有开始,因此基本上也就是目标文件的格式介绍,那么我们下面要看 g++ ex01.cpp的可执行目标文件了。老规矩readelf -a a.out查看目标文件格式,objdump -D -x a.out反汇编。看ELF header的Type字段,已经变成可执行文件EXEC了,增加了程序段(把各区整合成一个很大的段)。
Program Header:
    PHDR off    0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
         filesz 0x00000120 memsz 0x00000120 flags r-x
  INTERP off    0x00000154 vaddr 0x08048154 paddr 0x08048154 align 2**0
         filesz 0x00000013 memsz 0x00000013 flags r--
    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x000008f4 memsz 0x000008f4 flags r-x
    LOAD off    0x00000ef0 vaddr 0x08049ef0 paddr 0x08049ef0 align 2**12
         filesz 0x00000140 memsz 0x000001e8 flags rw-
 DYNAMIC off    0x00000f08 vaddr 0x08049f08 paddr 0x08049f08 align 2**2
         filesz 0x000000e8 memsz 0x000000e8 flags rw-
    NOTE off    0x00000168 vaddr 0x08048168 paddr 0x08048168 align 2**2
         filesz 0x00000044 memsz 0x00000044 flags r--
EH_FRAME off    0x0000085c vaddr 0x0804885c paddr 0x0804885c align 2**2
         filesz 0x00000024 memsz 0x00000024 flags r--
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rw-
   RELRO off    0x00000ef0 vaddr 0x08049ef0 paddr 0x08049ef0 align 2**0
         filesz 0x00000110 memsz 0x00000110 flags r--
一共有9个program header,每个大小是32字节。位于在仅次于ELF文件头的位置,而section header则在ELF文件的尾部。这9个header就表示需要载入内存的段,第一个是从偏移量0x34(52)开始的程序头
本身,加载到 vaddr 0x08048034的虚拟地址,4字节对齐,一共120字节。第二个是INTERP解释器部分,只读。下面两个LOAD段,前面的那个表示只读的可执行代码段,后面的则是可读写的数据段,包含了.data,.bss等。下面就是所有的section到这9个segment的的映射。
 Section to Segment mapping:
  Segment Sections...
   00   
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dyns
tr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata
.eh_frame_hdr .eh_frame
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07   
   08     .ctors .dtors .jcr .dynamic .got

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .hash             HASH            080481ac 0001ac 000048 04   A  6   0  4
  [ 5] .gnu.hash         GNU_HASH        080481f4 0001f4 000034 04   A  6   0  4
  [ 6] .dynsym           DYNSYM          08048228 000228 0000d0 10   A  7   1  4
  [ 7] .dynstr           STRTAB          080482f8 0002f8 000183 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          0804847c 00047c 00001a 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         08048498 000498 000060 00   A  7   2  4
  [10] .rel.dyn          REL             080484f8 0004f8 000010 08   A  6   0  4
  [11] .rel.plt          REL             08048508 000508 000048 08   A  6  13  4
  [12] .init             PROGBITS        08048550 000550 000030 00  AX  0   0  4
  [13] .plt              PROGBITS        08048580 000580 0000a0 04  AX  0   0  4
  [14] .text             PROGBITS        08048620 000620 00020c 00  AX  0   0 16
  [15] .fini             PROGBITS        0804882c 00082c 00001c 00  AX  0   0  4
  [16] .rodata           PROGBITS        08048848 000848 000014 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        0804885c 00085c 000024 00   A  0   0  4
  [18] .eh_frame         PROGBITS        08048880 000880 000074 00   A  0   0  4
  [19] .ctors            PROGBITS        08049ef0 000ef0 00000c 00  WA  0   0  4
  [20] .dtors            PROGBITS        08049efc 000efc 000008 00  WA  0   0  4
  [21] .jcr              PROGBITS        08049f04 000f04 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         08049f08 000f08 0000e8 08  WA  7   0  4
  [23] .got              PROGBITS        08049ff0 000ff0 000004 04  WA  0   0  4
  [24] .got.plt          PROGBITS        08049ff4 000ff4 000030 04  WA  0   0  4
  [25] .data             PROGBITS        0804a024 001024 00000c 00  WA  0   0  4
  [26] .bss              NOBITS          0804a040 001030 000098 00  WA  0   0 32
  [27] .comment          PROGBITS        00000000 001030 000023 01  MS  0   0  1
  [28] .shstrtab         STRTAB          00000000 001053 0000fc 00      0   0  1
  [29] .symtab           SYMTAB          00000000 001628 0004d0 10     30  49  4
  [30] .strtab           STRTAB          00000000 001af8 000389 00      0   0  1


这就是所有区的列表,从每个区的Addr这列就可以看出哪些sections被映射到哪个 segment了,segment就是区的集合而已,只是把他们归类整在一起便于加载,最后还是回到对section的理解。

.interp 有0x13个字节,内容是2f  6c  69 62 2f 6c 64 2d 6c 69 6e 75 78 2e 73 6f 2e 32 00,就是连接器的路径“/lib/ld-linux.so.2”。.hash用来查找符号信息。.dynsym是动态符号区,包含了文件的所有导入导出符号,从08048228开始,一共是0xd0字节,ES这列的0x10表示每个符号表项占16字节,它跟.symtab的结构是一样的,不过放的是动态符号,这个等详细介绍连接过程的时候再展开。.dynstr是动态字符串表区,大小为0。.rel.dyn是.dynsym的重定向区,.rel.plt 则是.plt的重定向区。

Relocation section '.rel.dyn' at offset 0x4f8 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ff0  00000206 R_386_GLOB_DAT    00000000   __gmon_start__
0804a040  00000b05 R_386_COPY        0804a040   _ZSt4cout


__gmon_start__就是在.dynsym符号表02位置的重定位符号。rel.plt也有一个__gmon_start__符号。


Relocation section '.rel.plt' at offset 0x508 contains 9 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a000  00000107 R_386_JUMP_SLOT   00000000   __cxa_atexit
0804a004  00000207 R_386_JUMP_SLOT   00000000   __gmon_start__


.rel.plt里面的__gmon_start__是指向一个plt项(0x084a004的位置),.rel.dyn里面的是指向0x08049ff0,从偏移来看是.got项。
.rel.dyn中__gmon_start__的类型是R_386_GLOB_DAT,偏移量是0x08049ff0,定义如下:


This relocation type is used to set a global offset table entry to the address of the specified symbol. The special relocation type allows one to determine the correspondence between symbols and global offset table entries.


也就是说它将会被重定位到一个.got区的一个GOT项。
.rel.plt中的__gmon_start__的类型是R_386_JUMP_SLOT,偏移量为0x084a004(GOT+16),定义如下:


The link editor creates this relocation type for dynamic linking. Its offset member gives the location of a procedure linkage table entry. The dynamic linker modifies the procedure linkage table entry to transfer control to the designated symbol’s address.
在.plt区中
Disassembly of section .plt:

08048580 <__cxa_atexit@plt-0x10>:
 8048580:    ff 35 f8 9f 04 08        pushl  0x8049ff8
 8048586:    ff 25 fc 9f 04 08        jmp    *0x8049ffc
 804858c:    00 00                    add    %al,(%eax)
    ...

08048590 <__cxa_atexit@plt>:
 8048590:    ff 25 00 a0 04 08        jmp    *0x804a000
 8048596:    68 00 00 00 00           push   $0x0
 804859b:    e9 e0 ff ff ff           jmp    8048580 <_init+0x30>

080485a0 <__gmon_start__@plt>:
 80485a0:    ff 25 04 a0 04 08        jmp    *0x804a004
 80485a6:    68 08 00 00 00           push   $0x8
 80485ab:    e9 d0 ff ff ff           jmp    8048580 <_init+0x30>

080485a0 开始的这项就是__gmon_start__的跳转表,调用该例程就是直接jmp到这里,然后jmp *0x804a004,在0x084a004位置(GOT+16)就是__gmon_start__实际GOT项。plt的目的只是迟加载,把加载的过程放到了第一次使用的时候,这时候Got+16的这项是指回到80485a6的push $0x8,然后跳到8048580 <_init+0x30>,来初始化Got+16项,这实际就是.plt的第一项,08048580 <__cxa_atexit@plt-0x10>,首先pushl 0x8049ff8(GOT+4),里面放了特定的库标识来用于库的符号解析,接着jmp *0x8049ffc跳转到GOT+8的地方,这里放这动态连接器的解析例程的地址,来重定位
0804a004(GOT+16)项,下次使用__gmon_start__的时候就能直接jmp到实际地址。

以上主要是分析了ELF目标格式的文件的各个项在连接过程中所起的作用,这对于理解整个连接过程是很关键的,初次接触linker and loader的时候就很容易会被这些区啊,符号呀什么的弄晕。只要弄清了这些区,表在连接过程所起的作用,下一步理解整个连接的详细细节就容易了。

以上是关于linker and loader 读书笔记的主要内容,如果未能解决你的问题,请参考以下文章

大教堂与集市(The Cathedral and the Bazaar)读书笔记

《DISTRIBUTED SYSTEMS Concepts and Design》读书笔记 一

读书笔记之《head first Servlet and Jsp》 第五章 属性和监听者

《Distributed Programming With Ruby》读书笔记一Drb:Hellowold and Pass by Reference

In-memory Computing with SAP HANA读书笔记 - 第七章:Business continuity and resiliency for SAP HANA

Andriod深度探索—HAL与驱动开发 第七章 读书笔记and一点心得