linker and loader 读书笔记 一

linker and loader 读书笔记 一 用Helloworld 程序示例链接过程


一: 几个概念

   重定位: 编译器和汇编器为每个源文件创建的目标文件的地址往往从0开始,但是没有计算机会从地址0对子程序进行加载。如果一个程序由很多子程序组成,那么所有的子程序都会被加载到位置不重叠的地址上。重定位的功能就是为程序不同的部分分配加载地址。重定位在链接及加载的过程中不仅仅发生一次。一种普遍的情形是由多个程序构建成一个大程序,并生成一个链接好的起始地址为0的输出程序,各个子程通过重定位找到自己的位置。但程序被加载时,系统会选择一个加载地址,这样链接好的程序会被整体重定位到新的加载地址。 在链接和加载过程中,不止一次的发生过重定位的动作。

符号解析:当通过多个子程序来构建一个程序时,子程序的相互引用是通过符号进行的;主程序可能会调用sqrt这个函数,(在数学库中),链接器遇到这个符号会用在数学库中sqrt这个地址来解析,然后修改目标代码使得call指令引用该地址。


程序加载:将主程序从辅助存储器拷贝到主存上准备运行。在某些情况下,加载仅仅意味着将数据从磁盘拷贝到内存中;在其他情况下,还包括分配存储空间,设置保护位或通过虚拟内存将虚拟地址映射到磁盘空间中。

    重定位和符号解析的划分界限是模糊的。由于链接器已经可以用来做解析符号的引用,一种处理重定位的方法是为程序的每一个部分分配一个指向基址的符号 然后重定位地址认为是对该基址的引用。其实概念是概念,实现方法是实现方法,只不过在有些时候两者可以统一。

  链接器和加载器共有的一个重要特性就是他们都会修改目标代码,他们也许是唯一比调试程序在这方面应用更为广泛的程序。这是一个独特而强大的特性,而且细节非常依赖于
机器的规格,如果做错的话就会引发令人困惑的bug。


二:Hello World 示例


例子的源代码:
      ---------------------------------------------------------------------------------------------
图1-3 源程序
源程序m.c
extern void a(char *);
int main(int ac, char **av)
{
static char string[] = "Hello, world!\n";
a(string);
}
源程序a.c
#include <unistd.h>
#include <string.h>
void a(char *s)
{
write(1, s, strlen(s));
}
---------------------------------------------------------------------------------------------
该程序是由两个文件组成,a.c 和 main.c 。在main.c调用了a 函数。

用GCC在Cygwin的环境上查看a.o ,main.o 的目标文件内容,及反编译的内容。


$ objdump -h a.o
a.o:     file format pe-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000002c  00000000  00000000  0000008c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000000  2**2
                  ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000000  2**2
                  ALLOC



$ objdump -D a.o
a.o:     file format pe-i386

Disassembly of section .text:
00000000 <_a>:   每个文件都会编译成以0为首地址的目标文件。
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 18                sub    $0x18,%esp
   6:   8b 45 08                mov    0x8(%ebp),%eax
   9:   89 04 24                mov    %eax,(%esp)
   c:   e8 00 00 00 00          call   11 <_a+0x11> 这个11做为strelen函数的临时地址会在以后被重定位的方式替换,
  11:   89 44 24 08             mov    %eax,0x8(%esp)
  15:   8b 45 08                mov    0x8(%ebp),%eax
  18:   89 44 24 04             mov    %eax,0x4(%esp)
  1c:   c7 04 24 01 00 00 00    movl   $0x1,(%esp)
  23:   e8 00 00 00 00          call   28 <_a+0x28>   这个28 做为write函数的临时地址会在以后被重定位的方式替换
  28:   c9                      leave                                         至于为什么这里用具体的数字而不是0来代替,不是很清楚!
  29:   c3                      ret
  2a:   90                      nop
  2b:   90                      nop

{
 红色为可执行文件的反汇编代码:

0040118c <_a>:

  40118c: 55                                    push   %ebp 
  40118d: 89 e5                              mov    %esp,%ebp
  40118f: 83 ec 18                         sub    $0x18,%esp
  401192: 8b 45 08                        mov    0x8(%ebp),%eax
  401195: 89 04 24                        mov    %eax,(%esp)
  401198: e8 9b 00 00 00            call   401238 <_strlen>
  40119d: 89 44 24 08                  mov    %eax,0x8(%esp)
  4011a1: 8b 45 08                       mov    0x8(%ebp),%eax
  4011a4: 89 44 24 04                 mov    %eax,0x4(%esp)
  4011a8: c7 04 24 01 00 00 00 movl   $0x1,(%esp)
  4011af: e8 8c 00 00 00           call   401240 <_write>
  4011b4: c9                                 leave  
  4011b5: c3                                 ret    
  4011b6: 90                                 nop
  4011b7: 90                                 nop
  4011b8: 66 90                           xchg   %ax,%ax
  4011ba: 66 90                           xchg   %ax,%ax
  4011bc: 66 90                          xchg   %ax,%ax
  4011be: 66 90                         xchg   %ax,%ax

}






$ objdump -h main.o


main.o:     file format pe-i386


Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000001c  00000000  00000000  0000008c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000010  00000000  00000000  000000a8  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000000  2**2
                  ALLOC

$ objdump -D main.o

main.o:     file format pe-i386

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 10                sub    $0x10,%esp
   9:   e8 00 00 00 00          call   e <_main+0xe>     e会被重定位
   e:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  15:   e8 00 00 00 00          call   1a <_main+0x1a>   1a 会被重定位
  1a:   c9                      leave
  1b:   c3                      ret

{
红色为main反汇编代码在可执行文件之中。

00401170 <_main>:
  401170: 55                               push   %ebp
  401171: 89 e5                          mov    %esp,%ebp
  401173: 83 e4 f0                      and    $0xfffffff0,%esp
  401176: 83 ec 10                  sub    $0x10,%esp
  401179: e8 b2 00 00 00      call   401230 <___main>
  40117e: c7 04 24 00 20 40 00 movl   $0x402000,(%esp)
  401185: e8 02 00 00 00      call   40118c <_a>
  40118a: c9                            eave  
  40118b: c3                            ret 

至于符号<___main>

00401230 <___main>:
  401230: ff 25 7c 50 40 00       jmp    *0x40507c    这里会不会是因为动态链接库的原因???。
  401236: 90                                  nop
  401237: 90                                  nop


}


Disassembly of section .data:
00000000 <_string.1219>:
   0:   48                      dec    %eax
   1:   65                      gs
   2:   6c                      insb   (%dx),%es:(%edi)
   3:   6c                      insb   (%dx),%es:(%edi)
   4:   6f                      outsl  %ds:(%esi),(%dx)
   5:   20 77 6f                and    %dh,0x6f(%edi)
   8:   72 6c                   jb     76 <_string.1219+0x76>
   a:   64 21 0a                and    %ecx,%fs:(%edx)
   d:   00 00                   add    %al,(%eax)
        ...

链接的输入:
链接器将一系列的的目标文件,库及可能的命令文件作为它的输入,然后就将目标文件作为输出结果,此外也可能有诸如加载映射信息或调试符号的文件的副产品。

下图很形象的说明了输入输出。




两遍链接过程:

当链接器运行时,会首先对输入文件进行扫描,得到各个段的大小,并收集对所有符号的定义和引用。它会创建一个列出输入文件中定义的所有段的段表,和包含所有导出、导
入符号的符号表。利用第一遍扫描得到的数据,链接器可以为符号分配数字地址,决定各个段在输出地址空间中的大小和位置,并确定每一部分在输出文件中的布局。
第二遍扫描会利用第一遍扫描中收集的信息来控制实际的链接过程。它会读取并重定位目标代码,为符号引用替换数字地址,调整代码和数据的内存地址以反映重定位的段地址,并将重定位后的代码写入到输出文件中。通常还会再向输出文件中写入文件头部信息,重定位的段和符号表信息。如果程序使用了动态链接,那么符号表中还要包含运行时链接器解析动态符号时所需的信息。在很多情况下,链接器自己将会在输出文件中生成少量代码或数据,例如用来调用覆盖中或动态链接库中的例程的“胶水代码”,或在程序启动时需要被调用的指向各初始化例程的函数指针数组。


不论程序是否使用了动态链接,文件中都会保存一个程序本身不会使用的重链接或调试用符号表,但是这些信息可以被处理输出文件的其它程序所使用。有些目标代码的格式是可以重链接的,也就是一次链接器运行的输出文件可以作为下次链接器运行的输入。这要求输出文件要包含一个像输入文件中那样的符号表,以及其它会出现在输入文件中的辅助信息。几乎所有的目标代码格式都预备有调试符号,这样当程序在调试器控制下运行时,调试器可以使用这些符号让程序员通过源代码中的行号或名字来控制程序。根据目标代码格式细节的不同,调试符号可能会与链接器需要的符号混合在一个符号表中,也可能独立于链接器需要的符号表为链接器建立单独建立一个有些许冗余的符号表。
有很少的一些链接器可以在一次扫描中完成工作。他们是通过在链接过程中将输入文件的部分或全部缓冲在内存或磁盘中,并稍后读取被缓冲的信息的方法来实现的。由于这是
一个并不影响链接过程两边扫描实质的实现技巧,因此这里我们不再赘述。


以下是可执行文件信息:

$ objdump -h test


test:     file format pei-i386


Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000778  00401000  00401000  00000400  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE, DATA
  1 .data         000000a4  00402000  00402000  00000c00  2**5
                  CONTENTS, ALLOC, LOAD, DATA
  2 .eh_frame     00000004  00403000  00403000  00000e00  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .bss          00000110  00404000  00404000  00000000  2**5
                  ALLOC
  4 .idata        000001dc  00405000  00405000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  5 .debug_aranges 00000160  00406000  00406000  00001200  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_pubnames 00000245  00407000  00407000  00001400  2**0
                  CONTENTS, READONLY, DEBUGGING
  7 .debug_pubtypes 0000055f  00408000  00408000  00001800  2**0
                  CONTENTS, READONLY, DEBUGGING
  8 .debug_info   00004c76  00409000  00409000  00001e00  2**0
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_abbrev 000008d3  0040e000  0040e000  00006c00  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_line   00000b54  0040f000  0040f000  00007600  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_frame  0000029c  00410000  00410000  00008200  2**2
                  CONTENTS, READONLY, DEBUGGING
 12 .debug_str    00000038  00411000  00411000  00008600  2**0
                  CONTENTS, READONLY, DEBUGGING





链接器将每个输入文件中相应的段合并在一起,故只存在一个合并后的文本段,一个合并后的数据段和一个bss段(两个输入文件不会使用的,被初始化为0的数据段)。由于
每个段都会被填充为 4K对齐以满足x86的页尺寸(这里是在64位机win7的cygwin中编译,不过从VMA 上可以看出401000->402000->403000可以看出每个段的大小为0x1000(4K)),因此文本段为4K(减去文件中20字节长度的a.out头部,逻辑上它并不属于该段),数据段和bss段每个同样也是4K字节。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值