C++;联系实际工作? 如何在实践中进行C++链接?我想要的是关于链接是如何发生的详细解释,而不是什么命令进行链接

C++;联系实际工作? 如何在实践中进行C++链接?我想要的是关于链接是如何发生的详细解释,而不是什么命令进行链接,c++,linker,C++,Linker,关于编译,已经有一个类似的问题,没有太多的细节:实际上,可以说链接相对简单 在最简单的意义上,它只是将对象文件1捆绑在一起,因为这些文件已经包含每个函数/globals/data的已发出程序集。。。包含在各自的来源中。链接器在这里可能非常愚蠢,只将所有内容视为符号(名称)及其定义(或内容) 显然,链接器需要生成一个符合特定格式(通常在Unix上是ELF格式)的文件,并将不同类别的代码/数据分离到文件的不同部分,但这只是分派 我所知道的两个复杂情况是: 消除重复符号的需要:一些符号存在于多个目标

关于编译,已经有一个类似的问题,没有太多的细节:

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

在最简单的意义上,它只是将对象文件1捆绑在一起,因为这些文件已经包含每个函数/globals/data的已发出程序集。。。包含在各自的来源中。链接器在这里可能非常愚蠢,只将所有内容视为符号(名称)及其定义(或内容)

显然,链接器需要生成一个符合特定格式(通常在Unix上是ELF格式)的文件,并将不同类别的代码/数据分离到文件的不同部分,但这只是分派

我所知道的两个复杂情况是:

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

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



1:编译不同翻译单元的结果(大致为预处理的源文件)

除了前面提到的“”,如果您想知道真实和现代的链接器是如何工作的,您可以开始。

编辑:我已将此答案移到副本:

这个答案主要关注地址重新定位,这是链接的关键功能之一

将使用一个最小的示例来澄清该概念

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
汇编和汇编:

nasm -felf64 hello_world.asm            # creates hello_world.o
ld -o hello_world.out hello_world.o     # static ELF executable with no libraries
与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
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
它应该将hello world字符串的地址移动到
rsi
寄存器中,该寄存器被传递给write系统调用

但是等等!当程序加载时,编译器怎么可能知道
“Hello world!”
将在内存中的何处结束

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

只有链接器才能这样做,因为只有他才能拥有所有这些对象文件

所以编译器只是:

  • 将占位符值
    0x0
    放在编译的输出上
  • 向链接器提供一些额外信息,说明如何使用正确的地址修改编译后的代码
此“额外信息”包含在对象文件的
.rela.text
部分中

2) .rela.text
.rela.text
代表“重新定位.text部分”

之所以使用relocation这个词,是因为链接器必须将地址从对象重新定位到可执行文件中

我们可以使用以下方法分解
.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
本节的格式固定记录在:

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

稍微简化一下,对于这一特定线路,我们有以下信息:

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

    如果我们回顾反编译的文本,它正好位于关键的
    movabs$0x0,%rsi
    ,知道x86-64指令编码的人会注意到,这对指令的64位地址部分进行了编码

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

  • Type=R_X86_64_64
    ,它指定了转换地址所需的精确计算

    该字段实际上取决于处理器,因此记录在第4.4节“重新定位”中

    该文件说,
    R_X86_64_64
    做:

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

    • Calculation=S+A

      • S
        是重新定位地址的值,因此
        00
      • A
        是此处为
        0
        的加数。这是重新定位条目的一个字段
      因此,
      S+A==0
      我们将重新定位到
      .data
      部分的第一个地址

3) .text of.out 现在让我们看看为我们生成的可执行文件
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
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