C++;联系实际工作? 如何在实践中进行C++链接?我想要的是关于链接是如何发生的详细解释,而不是什么命令进行链接
关于编译,已经有一个类似的问题,没有太多的细节:实际上,可以说链接相对简单 在最简单的意义上,它只是将对象文件1捆绑在一起,因为这些文件已经包含每个函数/globals/data的已发出程序集。。。包含在各自的来源中。链接器在这里可能非常愚蠢,只将所有内容视为符号(名称)及其定义(或内容) 显然,链接器需要生成一个符合特定格式(通常在Unix上是ELF格式)的文件,并将不同类别的代码/数据分离到文件的不同部分,但这只是分派 我所知道的两个复杂情况是:C++;联系实际工作? 如何在实践中进行C++链接?我想要的是关于链接是如何发生的详细解释,而不是什么命令进行链接,c++,linker,C++,Linker,关于编译,已经有一个类似的问题,没有太多的细节:实际上,可以说链接相对简单 在最简单的意义上,它只是将对象文件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
,知道x86-64指令编码的人会注意到,这对指令的64位地址部分进行了编码movabs$0x0,%rsi
:地址指向Name=.data
部分.data
,它指定了转换地址所需的精确计算 该字段实际上取决于处理器,因此记录在第4.4节“重新定位”中 该文件说,Type=R_X86_64_64
做:R_X86_64_64
:8个字节,因此地址Field=word64
0xC
Calculation=S+A
是重新定位地址的值,因此S
00
是此处为A
的加数。这是重新定位条目的一个字段0
我们将重新定位到S+A==0
部分的第一个地址.data
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