链接装载实验一

最近在看《程序员的自我修养-链接、装载与库》一书。以前在一些其他书中看过一些这方面的资料,主要是《深入理解计算机系统》,《编程之道卓越一、二》这三本,对这方面有一个大概的认知,但一直没有一个完整的理解,最近通过看这本书,解决了很多细节上的疑惑。

当然东西还是要自己动手实验一下理解的更深。今天通过一个汇编语言的例子,初步试验了看到的一些知识。书上用的c语言和GUN的objdump工具,我拿Windows下的MASM和dumpbin来实验一下。

汇编源码如下, 是从《Windows汇编语言程序设计》上摘的一段MASM代码, 编译工具用的是MASM6.15, 是一个打印5的factoria值的简单例子:

.386
.model flat, stdcall
option casemap:none

includelib msvcrt.lib

printf    PROTO c :dword, :vararg

.data
    Fact dword ?
    N equ 5
    szFormat byte 'factorial(%d) = %d', 0ah, 0

.code
start:
    mov ecx, N
    mov eax, 1
e10:
    imul eax, ecx
    loop e10
    mov Fact, eax
    invoke printf, offset szFormat, N, Fact
    ret
end start


编译(为了生成obj文件,不采取一步编译成exe的方法)
    ml /c /coff factoria.asm
然后生成exe
    link /subsystem:console factoria.obj
运行:
    factoria
输出
    factoria(5) = 120

先把用到的信息生成到文本文件中。命令如下
    dumpbin /all factoria.obj > obj_all.txt
    dumpbin /disasm factoria.obj > obj_disasm.txt

    dumpbin /all factoria.exe > exe_all.txt
    dumpbin /disasm factoria.exe > exe_disasm.txt

dumpbin有很多选项,可通过/? 查看, 除了上面两个,常用的应该还有/header /relocation

先来看obj_disasm中
    _start:
      00000000: B9 05 00 00 00     mov         ecx,5
      00000005: B8 01 00 00 00     mov         eax,1
      0000000A: 0F AF C1           imul        eax,ecx
      0000000D: E2 FB              loop        0000000A
      0000000F: A3 00 00 00 00     mov         [_start],eax
      00000014: FF 35 00 00 00 00  push        dword ptr [_start]
      0000001A: 6A 05              push        5
      0000001C: 68 00 00 00 00     push        offset _start
      00000021: E8 00 00 00 00     call        00000026
      00000026: 83 C4 0C           add         esp,0Ch
      00000029: C3                 ret
对照原始的汇编语句
    我们发现了中间四个00 00 00 00,分别应该是源码中的Fact(两处), szFormat和prinf的地方 这就是所谓的需要重定位的地方,因为在编译成obj的时候还不知道这些地址将来是什么,所以先写0
    需要注意的是,反汇编的时候,对于这些00 00 00 00 的解释可能会有误,上面的例子就把这些统一认为了_start。这点要注意。
再来看一下exe里这些地方变成什么了,打开exe_disasm:
      00401000: B9 05 00 00 00     mov         ecx,5
      00401005: B8 01 00 00 00     mov         eax,1
      0040100A: 0F AF C1           imul        eax,ecx
      0040100D: E2 FB              loop        0040100A
      0040100F: A3 00 30 40 00     mov         [00403000],eax
      00401014: FF 35 00 30 40 00  push        dword ptr ds:[00403000h]
      0040101A: 6A 05              push        5
      0040101C: 68 04 30 40 00     push        403004h
      00401021: E8 04 00 00 00     call        0040102A
      00401026: 83 C4 0C           add         esp,0Ch
      00401029: C3                 ret
      0040102A: FF 25 00 20 40 00  jmp         dword ptr ds:[00402000h]
我们看到几个00 00 00 00 的地方变成了00 30 40 00,04 30 40 00之类的地址。
   
下面就是分析过程了,exe中的地址是链接器写进去的,要写这些信息,应该要知道如下:
    1. 哪些地方需要重定位?
    2. 这些地方需要换成什么样的地址?


先看第一个问题,哪些地方需要重定位?
这个是在汇编器汇编的时候记录到了obj文件中的重定位表中。看obj_all.txt中
    SECTION HEADER #1
     .text name
这是txt段,有源码中的代码转来,下面有如下信息
    RELOCATIONS #1
                                                Symbol    Symbol
     Offset    Type              Applied To         Index     Name
     --------  ----------------  -----------------  --------  ------
     00000010  DIR32                      00000000         D  Fact
     00000016  DIR32                      00000000         D  Fact
     0000001D  DIR32                      00000000         C  szFormat
     00000022  REL32                      00000000         B  _printf
   
这就是是text段中需要重定位的地方列表,第一列是偏移地址,也就是这些地方需要修改,将来利用这些地址来定位。对应上面的反汇编,可以看到,正好是那四个 00000000 的地址。
知道哪里需要修改了,那怎么知道改成什么样的地址呢, 这需要用到符号表, 先看上面重定位表倒数第二列, Index 是对应的符号表的序号, 分别是BCD三项。再看obj_all.txt最后的符号表
    COFF SYMBOL TABLE
    000 00000000 DEBUG  notype       Filename     | .file
        factoria.asm
    002 002A2263 ABS    notype       Static       | @comp.id
    003 00000000 SECT1  notype       Static       | .text
        Section length   2A, #relocs    4, #linenums    0, checksum        0
    005 00000000 SECT2  notype       Static       | .data
        Section length   18, #relocs    0, #linenums    0, checksum        0
    007 00000000 SECT3  notype       Static       | .debug$S
        Section length   51, #relocs    0, #linenums    0, checksum        0
    009 00000000 SECT4  notype       Static       | .drectve
        Section length   24, #relocs    0, #linenums    0, checksum        0
    00B 00000000 UNDEF  notype ()    External     | _printf
    00C 00000004 SECT2  notype       Static       | szFormat
    00D 00000000 SECT2  notype       Static       | Fact
    00E 00000000 SECT1  notype       External     | _start
我们看到了BCD三个对应的符号表中的项, _printf, szFormat, Fact。这里面除了序号还有几列是下面要用到的,第二列偏移地址和第三列所述段。

下面是链接器的工作了。链接器首先要通过obj文件的段表读取各个段的信息,当然这里面一定有一个属性就是大小。然后定一个初始的虚拟地址, 就可以计算出各个段的起始虚拟地址了。
打开exe_all.txt可以看到
    OPTIONAL HEADER VALUES
         400000 image base
    和
    SECTION HEADER #1
       .text name
          30 virtual size
        1000 virtual address
    和
    SECTION HEADER #3
       .data name
          18 virtual size
        3000 virtual address
定了这些地址,那么符号表中的符号对应的虚拟地址也就定了,分别是段的这个地址加上其记录在obj符号表中的偏移地址。再遍历obj中的重定位表,就可以把这个地址替换了。
例如obj_dasm中第一个00 00 00 00,是Fact的地址,Fact属于数据段,数据段的虚拟地址是image base + data 段的virtual address, 400000 +  3000 = 403000
Fact在符号表中记录的其偏移地址是00000000, 那其最终的虚拟地址就是0x00403000, 在exe_dasm中看到正是这个地址。同样szFormat和Fact一样属于data段,但其偏移地址是00000004,紧跟在Fact之后,在exe_dasm的0040101C行,看到了这个地址。

总结一下这个过程:
    汇编器完成部分:
        1. 找出代码中需要重定位的地方,列在重定位表中,记录其偏移地址, 和符号表索引
        2. 生成符号表, 记录其所属段、偏移地址。
        3. 段表中记录段的属性、大小。
    链接器完成部分:
        4. 从段表的大小等属性加上虚拟基址计算出段的起始须知。
        5. 遍历符号表,其所属段的基址加上其偏移地址(见2.)计算出其虚拟地址。
        6. 遍历重定位表(见1.),通过其符号表索引找到符号表,用其虚拟地址替换掉需要替换的地方, 这个偏移地址是不会变的。

 

中间有一个printf的地址涉及到动态链接库,下次再动手实验。

你可能感兴趣的:(windows,汇编,image,header,exe,语言)