C++ 链接器做什么?
我一直在想。我知道编译器会把你写的代码转换成二进制文件,但是链接器做什么呢?它们对我来说一直是个谜 我大致了解什么是“链接”。当对库和框架的引用被添加到二进制文件中时。除此之外我什么都不懂。对我来说,它“只是工作”。我也了解动态链接的基本知识,但不太深入C++ 链接器做什么?,c++,linker,dynamic-linking,C++,Linker,Dynamic Linking,我一直在想。我知道编译器会把你写的代码转换成二进制文件,但是链接器做什么呢?它们对我来说一直是个谜 我大致了解什么是“链接”。当对库和框架的引用被添加到二进制文件中时。除此之外我什么都不懂。对我来说,它“只是工作”。我也了解动态链接的基本知识,但不太深入 有人能解释一下这些术语吗?在像“C”这样的语言中,单个代码模块传统上被分别编译成目标代码块,这些目标代码块在各个方面都可以执行,除了模块在自身之外所做的所有引用(即对库或其他模块的引用)尚未解决之外(也就是说,它们是空白的,等待有人出现并建立所
有人能解释一下这些术语吗?在像“C”这样的语言中,单个代码模块传统上被分别编译成目标代码块,这些目标代码块在各个方面都可以执行,除了模块在自身之外所做的所有引用(即对库或其他模块的引用)尚未解决之外(也就是说,它们是空白的,等待有人出现并建立所有连接) 链接器所做的是一起查看所有模块,查看每个模块需要连接到自身外部的内容,并查看它正在导出的所有内容。然后,它修复所有这些内容,并生成最终的可执行文件,然后可以运行
在动态链接也在进行的情况下,链接器的输出仍然无法运行-仍然有一些对外部库的引用尚未解析,并且这些引用在加载应用程序时由操作系统解析(甚至可能在运行期间稍后)其他对象文件中定义的符号满足每个文件的所有外部参照 然后,它将所有这些对象文件组合在一起,并为每个符号分配地址,当一个对象文件对另一个对象文件具有外部引用时,它会在另一个对象使用的任何位置填充每个符号的地址。在典型情况下,它还将构建一个包含所使用的任何绝对地址的表,以便加载程序可以/将“修复”文件加载时的地址(即,它会将基本加载地址添加到这些地址中的每个地址,以便它们都引用正确的内存地址)
相当多的现代链接器还可以执行一些(在少数情况下是很多)其他“东西”,例如优化代码的方式只有在所有模块都可见时才可能(例如,删除包含的函数,因为其他模块可能调用它们,但是一旦所有模块都放在一起,很明显没有任何东西调用它们。)理解链接器,有助于首先理解当转换源文件(例如C或C++文件)时“引擎盖下”发生了什么。进入可执行文件(可执行文件是可以在您的机器或运行相同机器体系结构的其他人的机器上执行的文件) 在后台,当编译程序时,编译器将源文件转换为目标字节码。该字节码(有时称为目标码)是只有计算机体系结构才能理解的助记符指令。传统上,这些文件的扩展名为.OBJ 创建目标文件后,链接器开始发挥作用。通常情况下,执行任何有用操作的实际程序都需要引用其他文件。例如,在C语言中,将您的姓名打印到屏幕上的简单程序包括:
printf("Hello Kristina!\n");
当编译器将您的程序编译成obj文件时,它只是将一个引用放入printf
函数。链接器解析此引用。大多数编程语言都有一个标准的例程库来涵盖该语言所需的基本内容。链接器将您的obj文件与此标准库链接。链接器c还可以将您的OBJ文件与其他OBJ文件链接。您可以创建其他OBJ文件,这些文件具有其他OBJ文件可以调用的功能。链接器的工作原理几乎类似于文字处理器的复制和粘贴。它“复制”输出程序引用的所有必要函数并创建单个可执行文件。有时,复制的其他库依赖于其他OBJ或库文件。有时,链接器必须非常递归才能完成其工作
请注意,并非所有操作系统都创建一个可执行文件。例如,Windows使用DLL将所有这些函数保存在一个文件中。这会减少可执行文件的大小,但使可执行文件依赖于这些特定的DLL。DOS用于使用称为覆盖(.OVL文件)的内容。这有很多目的,但其中一个目的是将常用函数保存在一个文件中(如果您想知道的话,它的另一个目的是能够将大型程序装入内存中。DOS在内存中有一个限制,覆盖可以从内存中“卸载”,其他覆盖可以“加载”在该内存上,因此得名,Linux有共享库,这与DLL的想法基本相同(我认识的Linux硬核朋友会告诉我有很多很大的不同)
希望这有助于您理解!地址重新定位示例
地址迁移是链接的关键功能之一
让我们用一个简单的例子来看看它是如何工作的
0)导言
摘要:重新定位编辑要翻译的对象文件的.text
部分:
- 对象文件地址
- 进入可执行文件的最终地址
- 解析未定义的符号,如已声明的未定义函数
- 不碰撞多个对象文件的多个
和.text
部分.data
- x86-64或IA-32 a
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
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
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
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