C++链接如何在实践中发挥作用?

Kla*_*fir 32 c++ linker

C++链接如何在实践中发挥作用?我正在寻找的是关于链接如何发生的详细解释,而不是链接的命令.

关于编译已经有类似的问题,但没有详细说明:编译/链接过程如何工作?

Cir*_*四事件 50

编辑:我已将此答案移至副本:https://stackoverflow.com/a/33690144/895245

这个答案侧重于地址重定位,这是链接的关键功能之一.

将使用一个最小的例子来阐明这个概念.

0)简介

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

  • 对象文件地址
  • 进入可执行文件的最终地址

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

  • 解决未定义的符号,如声明的未定义函数
  • 并不冲突多.text.data多目标文件节

先决条件:对:

  • x86-64或IA-32程序集
  • ELF文件的全局结构.我为此做了一个教程

链接与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
Run Code Online (Sandbox Code Playgroud)

编译和汇编:

nasm -felf64 hello_world.asm            # creates hello_world.o
ld -o hello_world.out hello_world.o     # static ELF executable with no libraries
Run Code Online (Sandbox Code Playgroud)

与NASM 2.10.09.

1).o的文字.o

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

objdump -d hello_world.o
Run Code Online (Sandbox Code Playgroud)

这使:

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
Run Code Online (Sandbox Code Playgroud)

关键的是:

   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00
Run Code Online (Sandbox Code Playgroud)

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

可是等等!编译器如何可能知道"Hello world!"加载程序时最终会在内存中的哪个位置?

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

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

所以编译器只是:

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

这个"额外信息"包含在.rela.text目标文件的部分中

2).rela.text

.rela.text 代表".text部分的重新定位".

使用单词重定位是因为链接器必须将对象的地址重定位到可执行文件中.

我们可以用以下方式拆解该.rela.text部分:

readelf -r hello_world.o
Run Code Online (Sandbox Code Playgroud)

其中包含;

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
Run Code Online (Sandbox Code Playgroud)

本节的格式已修复,记录在以下网址: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扩展部分4.4"重定位"中.

    该文件说R_X86_64_64:

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

    • Calculation = S + A

      • S因此,是要重新定位的地址的00 00 00 00 00 00 00 00
      • A0这里的加数.这是重定位条目的字段.

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

3).out的文本.out

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

objdump -d hello_world.out
Run Code Online (Sandbox Code Playgroud)

得到:

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
Run Code Online (Sandbox Code Playgroud)

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

  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00
Run Code Online (Sandbox Code Playgroud)

现在指向地址0x6000d8(d8 00 60 00 00 00 00 00以little-endian为单位)而不是0x0.

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

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

我们用以下方法拆卸它们:

readelf -l hello_world.out
Run Code Online (Sandbox Code Playgroud)

这使:

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
Run Code Online (Sandbox Code Playgroud)

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

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

  • "在大端" - 当然你的意思是"在小端"?好的写作,但可能会受益于查看`objdump -dr`并使用两个字符串,两个函数和两个编译单元,因此可以看到不同类型的重定位和不同的偏移(此时它成为值得的博客文章). (2认同)

Mat*_* M. 9

实际上,可以说链接相对简单.

从最简单的意义上讲,它只是将目标文件1捆绑在一起,因为它们已经包含了各自源代码中包含的每个函数/全局/数据的发出程序集.链接器在这里可能非常愚蠢,只需将所有内容视为符号(名称)及其定义(或内容).

显然,链接器需要生成一个尊重某种格式的文件(一般在Unix上的ELF格式),并将各种类别的代码/数据分成文件的不同部分,但这只是调度.

我所知道的两个并发症是:

  • 需要去除重复符号:一些符号存在于几个目标文件中,只有一个符号应该在创建的结果库/可执行文件中出现; 链接器作业只包含其中一个定义

  • 链接时优化:在这种情况下,目标文件不包含发出的程序集,而是包含中间表示,链接器将所有目标文件合并在一起,应用优化传递(例如内联),将其编译为汇编并最终发出结果.


1:编译不同翻译单元的结果(粗略地说,预处理的源文件)


Emp*_*ian 7

除了已经提到的" 链接器和加载器 "之外,如果你想知道真实和现代链接器的工作原理,你可以从这里开始.

  • 我一直认为SO的这个规则是愚蠢的,在一个简短的SO答案中,尝试总结一个关于连接器的领先专家的整个系列帖子是浪费时间.这个问题不适合SO,因为答案不能在几个段落中总结出来,OP得到答案的正确方法是在其他地方阅读详细信息,而不是在这里总结,如果SO关闭怎么办?Ian已经使用airs.com的时间比SO已经存在的时间长,也许他也会比这更长久了;) (3认同)
  • 感谢博客链接,确实非常有趣的系列.通常情况下只包含链接的答案是皱眉头(如果博客突然关闭怎么办?)所以我鼓励你完成这个答案.例如,提供系列的概述(链接+每个部分的摘要)? (2认同)