时间:2010-07-23 22:44:43

标签: c++ linker dynamic-linking

4 个答案:

答案 0 :(得分:129)

答案 1 :(得分:55)

地址重定位最小示例

地址重定位是链接的关键功能之一。

让我们看看它如何与最小的例子一起工作。

0)简介

摘要:重定位编辑要转换的目标文件的.text部分:

  • 目标文件地址
  • 进入可执行文件的最终地址

这必须由链接器完成,因为编译器一次只能看到一个输入文件,但我们必须一次知道所有目标文件以决定如何:

  • 解析未定义的符号,如声明的未定义函数
  • 不会冲突多个目标文件的多个.text.data部分

先决条件:对:

的理解最少

链接与C或C ++无关:编译器只生成目标文件。然后链接器将它们作为输入,而不知道编译它们的语言。它也可能是Fortran。

为了减少外壳,让我们研究一下NASM x86-64 ELF Linux hello world:

section .data
    hello_world db "Hello world!", 10
section .text
    global _start
    _start:

        ; sys_write
        mov rax, 1
        mov rdi, 1
        mov rsi, hello_world
        mov rdx, 13
        syscall

        ; sys_exit
        mov rax, 60
        mov rdi, 0
        syscall

编译和汇编:

nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o

使用NASM 2.10.09。

1)。文本.o

首先我们反编译目标文件的.text部分:

objdump -d hello_world.o

给出:

0000000000000000 <_start>:
   0:   b8 01 00 00 00          mov    $0x1,%eax
   5:   bf 01 00 00 00          mov    $0x1,%edi
   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00
  14:   ba 0d 00 00 00          mov    $0xd,%edx
  19:   0f 05                   syscall
  1b:   b8 3c 00 00 00          mov    $0x3c,%eax
  20:   bf 00 00 00 00          mov    $0x0,%edi
  25:   0f 05                   syscall

关键路线是:

   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00

应将hello world字符串的地址移动到rsi寄存器中,该寄存器将传递给写入系统调用。

但是等等!当程序加载时,编译器如何知道"Hello world!"在内存中的哪个位置?

嗯,它不能,特别是我们将一堆.o个文件与多个.data部分链接在一起。

只有链接器可以这样做,因为只有他将拥有所有这些目标文件。

所以编译器只是:

  • 在已编译的输出
  • 上放置占位符值0x0
  • 向链接器提供了一些额外的信息,告诉他们如何使用好的地址修改编译的代码

此“额外信息”包含在目标文件的.rela.text部分

2).rela.text

.rela.text代表“.text部分的重定位”。

使用了重定位一词,因为链接器必须将对象的地址重定位到可执行文件中。

我们可以使用以下代码反汇编.rela.text部分

readelf -r hello_world.o

包含;

Relocation section '.rela.text' at offset 0x340 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000000c  000200000001 R_X86_64_64       0000000000000000 .data + 0

本节的格式已修复,记录在:http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html

每个条目告诉链接器一个需要重新定位的地址,这里我们只有一个用于字符串。

简化一下,对于这个特定的行,我们有以下信息:

  • Offset = C:此条目更改的.text的第一个字节是什么。

    如果我们回头看反编译的文本,它正好位于关键movabs $0x0,%rsi内,那些知道x86-64指令编码的人会注意到这会编码指令的64位地址部分。

  • Name = .data:地址指向.data部分

  • Type = R_X86_64_64,用于指定翻译地址的具体计算内容。

    此字段实际上取决于处理器,因此记录在AMD64 System V ABI extension第4.4节“重定位”中。

    该文件说R_X86_64_64确实:

    • Field = word64:8个字节,因此00 00 00 00 00 00 00 00位于地址0xC

    • Calculation = S + A

      • S在重新定位的地址 value ,因此00 00 00 00 00 00 00 00
      • A是加法,0在这里。这是重定位条目的一个字段。

      所以S + A == 0我们将重新定位到.data部分的第一个地址。

3).text的文本

现在让我们看一下为我们生成的可执行文件ld的文本区域:

objdump -d hello_world.out

给出:

00000000004000b0 <_start>:
  4000b0:   b8 01 00 00 00          mov    $0x1,%eax
  4000b5:   bf 01 00 00 00          mov    $0x1,%edi
  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00
  4000c4:   ba 0d 00 00 00          mov    $0xd,%edx
  4000c9:   0f 05                   syscall
  4000cb:   b8 3c 00 00 00          mov    $0x3c,%eax
  4000d0:   bf 00 00 00 00          mov    $0x0,%edi
  4000d5:   0f 05                   syscall

所以从目标文件中唯一改变的是关键线:

  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00

现在指向地址0x6000d8(小端的d8 00 60 00 00 00 00 00)而不是0x0

这是hello_world字符串的正确位置吗?

决定我们必须检查程序头,它告诉Linux在哪里加载每个部分。

我们将它们拆解为:

readelf -l hello_world.out

给出:

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000d7 0x00000000000000d7  R E    200000
  LOAD           0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
                 0x000000000000000d 0x000000000000000d  RW     200000

 Section to Segment mapping:
  Segment Sections...
   00     .text
   01     .data

这告诉我们.data部分是第二部分,从VirtAddr = 0x06000d8开始。

数据部分唯一的问题是我们的hello world字符串。

奖金等级

答案 2 :(得分:15)

答案 3 :(得分:10)