厦门网站制作哪里好薇,宁波网站建设速成,wordpress 360友链,织梦网站打不开一、引子 上篇文章介绍了目标文件#xff0c;也就是讲到编译过程中的汇编这个阶段。本篇要讲目标文件怎么变成一个可执行文件的#xff0c;介绍编译过程中的链接。 链接主要分为两种#xff0c;静态链接和动态链接。它们本质上的区别#xff0c;是在程序的编译和运行过程中…一、引子 上篇文章介绍了目标文件也就是讲到编译过程中的汇编这个阶段。本篇要讲目标文件怎么变成一个可执行文件的介绍编译过程中的链接。 链接主要分为两种静态链接和动态链接。它们本质上的区别是在程序的编译和运行过程中使用库的方式不同。
特性静态链接动态链接文件大小较大较小运行独立性高无需外部库低需要外部库启动速度较快稍慢内存使用不共享库可能使用更多内存库共享节省内存更新和维护更新库需要重新编译和分发整个程序更新库只需替换库文件兼容性无库版本问题较高可能存在库版本冲突问题
二、静态链接要做什么
在编译时将所有必要的库和模块直接嵌入到生成的可执行文件中这是静态链接的定义。这意味着可执行文件包含了所有运行所需的代码因此无需依赖外部库文件。 下面将用两个Demo来示范1.c和2.c
这是1.c
extern int a;
extern void func1(void);int main()
{int b 0;func1();return 0;
}这是2.c
int a 0;void func1(void)
{int c 0;c;
}objdump -h 1.o1.o 文件格式 elf64-x86-64节
Idx Name Size VMA LMA File off Algn0 .text 0000001f 0000000000000000 0000000000000000 00000040 2**0CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE1 .data 00000000 0000000000000000 0000000000000000 0000005f 2**0CONTENTS, ALLOC, LOAD, DATA2 .bss 00000000 0000000000000000 0000000000000000 0000005f 2**0ALLOC3 .comment 0000002c 0000000000000000 0000000000000000 0000005f 2**0CONTENTS, READONLY4 .note.GNU-stack 00000000 0000000000000000 0000000000000000 0000008b 2**0CONTENTS, READONLY5 .note.gnu.property 00000020 0000000000000000 0000000000000000 00000090 2**3CONTENTS, ALLOC, LOAD, READONLY, DATA6 .eh_frame 00000038 0000000000000000 0000000000000000 000000b0 2**3CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATAobjdump -h 2.o2.o 文件格式 elf64-x86-64节
Idx Name Size VMA LMA File off Algn0 .text 00000016 0000000000000000 0000000000000000 00000040 2**0CONTENTS, ALLOC, LOAD, READONLY, CODE1 .data 00000000 0000000000000000 0000000000000000 00000056 2**0CONTENTS, ALLOC, LOAD, DATA2 .bss 00000004 0000000000000000 0000000000000000 00000058 2**2ALLOC3 .comment 0000002c 0000000000000000 0000000000000000 00000058 2**0CONTENTS, READONLY4 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000084 2**0CONTENTS, READONLY5 .note.gnu.property 00000020 0000000000000000 0000000000000000 00000088 2**3CONTENTS, ALLOC, LOAD, READONLY, DATA6 .eh_frame 00000038 0000000000000000 0000000000000000 000000a8 2**3CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATAld 1.o 2.o -e main -o a.out
objdump -h a.out a.out 文件格式 elf64-x86-64节
Idx Name Size VMA LMA File off Algn0 .note.gnu.property 00000020 00000000004001c8 00000000004001c8 000001c8 2**3CONTENTS, ALLOC, LOAD, READONLY, DATA1 .text 00000035 0000000000401000 0000000000401000 00001000 2**0CONTENTS, ALLOC, LOAD, READONLY, CODE2 .eh_frame 00000058 0000000000402000 0000000000402000 00002000 2**3CONTENTS, ALLOC, LOAD, READONLY, DATA3 .bss 00000008 0000000000404000 0000000000404000 00003000 2**2ALLOC4 .comment 0000002b 0000000000000000 0000000000000000 00002058 2**0CONTENTS, READONLY2.1 地址和空间的分配
我们将上面两个文件变成目标文件后每个目标文件都是有自己的代码段数据段bss段等。但可执行文件是一个文件那也就是说链接时要找到各文件中的各符号地址和各段地址并将它们合并起来并且分配运行时候的空间。 链接器会为可执行文件或共享库分配内存空间还会为每个符号分配一个唯一的内存地址。
2.1.1 各段依次堆叠Sequential Stacking
最简单的一个方案是将输入的目标文件按照次序依次叠加起来生成输出的可执行文件或共享库。 步骤 扫描目标文件: 链接器首先会扫描所有输入的目标文件提取每个目标文件的信息包括代码段、数据段、BSS 段以及符号表等。 地址分配: 链接器会为可执行文件或共享库分配内存空间并为每个符号分配一个唯一的内存地址。 生成输出文件: 链接器会将所有输入的目标文件的内容依次复制到输出文件中并根据符号表修正符号地址。 特点 实现简单: 按序叠加的实现代码比较简单易于理解和开发。 效率低下: 按序叠加会导致输出文件体积较大并且可能会产生大量的内存碎片降低程序的运行效率。对于一些硬件来讲段的装载和空间对齐单位是页也就是4KB大小。即便只有1B大小也会分配出4KB的内存。 不适用于大型项目: 对于大型项目按序叠加会导致链接过程非常耗时并且可能无法有效利用内存空间。
2.1.2 相似段合并Merge Similar Segments
各段依次堆叠会产生各种问题。所以在链接目标文件的过程中可能会执行相似段合并的优化步骤以减少可执行文件的大小并提高执行效率。相似段合并的步骤通常在优化和合并阶段进行其主要目标是识别并合并具有相似代码或数据的段以消除重复并节省空间。 步骤 识别相似段链接器分析目标文件中的代码段和数据段寻找具有相似内容的段。相似内容可能是相同的代码片段、常量数据或其他类型的数据。 合并相似段对于找到的相似段链接器将其合并成一个单一的段。合并过程可能涉及将重复的代码片段或数据项替换为单个引用并确保所有引用指向合并后的段。 更新引用 在合并后的段被创建后链接器需要更新目标文件中的所有引用以便指向合并后的段。这可能涉及修改目标文件中的地址引用或符号表条目。 删除冗余段完成相似段的合并后链接器可能会删除原始目标文件中的冗余段以释放空间并简化可执行文件的结构。
2.1.3 示例分析
a.out的.text段的size就是目标文件的.text段段的size总和。a.out的VMA和LMA是有数值的但目标文件并没有的。VMA是虚拟地址是在链接后根据各段的大小被分配的。LMA是加载地址在确定每个段的大小、对齐要求以及其他系统布局策略并并保证各段在内存中不会相互冲突的情况下链接器为每个段分配一个加载地址。
有些资料会讲Linux中的.text段默认会分配到0x08048094.data段默认会分配到0x08049108说是ELF文件默认从该地址分配。但目前新一点版本的静态链接的可执行文件的默认加载地址也不是固定的它的加载地址同样是由操作系统的动态链接器 ld.so或者其替代品在可执行文件被加载时动态确定的。如果强制指定可执行文件的加载地址可以使用一些链接选项来指定加载地址例如-Wl,-Ttext-segment地址 。该技术是地址空间布局随机化ASLRAddress Space Layout Randomization主要目的是在每次加载可执行文件时随机选择加载地址从而增加攻击者利用程序漏洞的难度。
File off是各个段的起始偏移量。这个偏移量表示了每个段在文件中的位置即相对于文件的起始位置的偏移量。在链接时File off的作用主要是用来确定每个段在最终生成的可执行文件中的位置并且在重定位阶段被使用。在生成最终的可执行文件时File off 信息通常会被包含在可执行文件的头部或段表中以便操作系统在加载可执行文件时正确地映射每个段到内存中的位置。
2.2 符号解析和重定位
符号解析和重定位对于程序的正确运行至关重要。如果没有符号解析程序将无法找到符号定义的变量、函数等对象。如果没有重定位程序将无法在不同的内存地址上运行。 一般出现在该步骤的错误 符号未定义: 程序中使用了未定义的符号。 符号重复定义: 程序中有多个符号定义使用了相同的名称。 符号地址冲突: 两个或多个符号被分配了相同的地址。
2.2.1 符号解析Symbol Resolution
通过符号解析链接器能够确保所有的符号引用都能正确指向其在其他目标文件或库中的定义从而使得最终生成的可执行文件能够正确地执行。
链接器首先解析所有目标文件中的符号函数和变量。每个目标文件包含的符号可以分为以下两类 定义的符号在目标文件中定义的符号例如函数实现或变量定义。 引用的符号目标文件中使用但未定义的符号例如外部函数或变量。 链接器需要确保所有引用的符号都能在某个目标文件或库中找到定义。
主要过程 定义和引用在程序中符号可以是函数、变量或其他命名实体。在目标文件中这些符号可以被定义有实际的实现或引用被使用但未定义。 扫描目标文件链接器首先会扫描所有的目标文件和库文件从中提取出所有的符号。这个过程包括解析目标文件的符号表、重定位表等信息。 符号解析对于每个引用的符号链接器需要在其他目标文件或库中找到对应的定义。这可能涉及搜索多个目标文件和库文件直到找到符号的定义。如果找不到符号的定义链接器将会报错并提示未定义的符号。 符号表管理链接器维护一个符号表记录了每个符号的定义和引用情况。对于每个引用的符号链接器会在符号表中查找其定义以确定其在可执行文件中的实际位置。 生成符号表在符号解析过程完成后链接器会生成一个最终的符号表其中包含了所有解析后的符号信息。这个符号表将被用于后续的符号重定位等操作。
2.2.2 符号重定位Symbol Relocation
目的是将程序中使用的符号引用例如函数地址、变量地址等修正为实际的内存地址。符号重定位是确保程序在不同环境下能够正确运行的关键步骤之一。
在目标文件中符号的地址通常是相对的或未确定的。链接器需要将这些符号地址调整到它们在最终可执行文件中的实际内存地址。这一步包括以下过程 地址计算在符号解析完成后链接器需要计算每个符号在最终可执行文件中的内存地址。这个地址通常是相对于程序的起始地址的偏移量。 地址调整链接器将目标文件中的所有引用符号的地址调整为其在最终可执行文件中的实际内存地址。这包括修改目标文件中的指令、数据、符号表等以便在程序执行时正确地指向符号的位置。 重定位表重定位表记录了需要进行重定位的符号及其偏移量。链接器根据重定位表中的信息将引用的符号地址进行调整以确保程序能够正确执行。 代码和数据重定位对于代码段和数据段中的引用符号链接器会修改指令或数据中的地址使其指向正确的内存位置。这可能涉及修改指令中的跳转地址、函数调用地址或者修改数据中的符号地址引用。 符号表更新在完成符号重定位后链接器可能会更新符号表中的符号信息以反映符号在最终可执行文件中的实际位置。
2.2.3 示例分析
objdump -d a.out
a.out 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000401000 main:401000: f3 0f 1e fa endbr64 401004: 55 push %rbp401005: 48 89 e5 mov %rsp,%rbp401008: 48 83 ec 10 sub $0x10,%rsp40100c: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)401013: e8 07 00 00 00 callq 40101f func1401018: b8 00 00 00 00 mov $0x0,%eax40101d: c9 leaveq 40101e: c3 retq 000000000040101f func1:40101f: f3 0f 1e fa endbr64 401023: 55 push %rbp401024: 48 89 e5 mov %rsp,%rbp401027: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)40102e: 83 45 fc 01 addl $0x1,-0x4(%rbp)401032: 90 nop401033: 5d pop %rbp401034: c3 retq
例子1.o中.text段的大小是1F,2.o中的.text段的大小是16。并且我们将程序的入口函数定位了main函数。所以a.out中.text段的起始位置也就是main函数的位置。那func1函数的位置就是main 1F了。查看a.out的汇编代码发现callq 40101f 这就是调用了func1函数。这样可执行文件就可以正常运行了。
三、静态链接过程注意点
3.1 强符号和弱符号不一致
在链接器的世界中符号可以被分为两种类型强符号Strong Symbol和 弱符号Weak Symbol。这些术语通常用于描述符号的可见性和优先级。 强符号Strong Symbol强符号是具有全局可见性和固定地址的符号。当多个目标文件或库文件中存在相同名称的强符号时链接器会选择其中的一个作为最终的定义。通常情况下全局变量和函数都是强符号。强符号的定义具有最高的优先级将覆盖所有弱符号和普通符号。 弱符号Weak Symbol弱符号是具有全局可见性但没有固定地址的符号。当多个目标文件或库文件中存在相同名称的弱符号时链接器会选择其中的一个作为最终的定义。弱符号的定义具有较低的优先级当没有找到强符号时链接器才会考虑使用弱符号。通常情况下全局变量的声明和函数的声明会被视为弱符号因为它们没有固定的地址。 在程序中强弱符号定义不一致主要分三种 两个及以上强符号类型不一致多个强符号定义会在链接时报重定义因为链接器无法判断该符号的类型。 类型不一致的一个强符号和多个弱符号在程序分配内存时会按强符号的类型分配内存同时报告警size of symbole 符号 change from 。在连接的时候要是弱符号的类型大于强符号将报alignment 1 of symbol 符号 in 目标文件 is smaller than 8 in 目标文件。 两个及以上弱符号类型不一致这个时候编译器不会给出要符号内存只能到链接过程中看有没有强符号在分配内存。这种情况下不会报告警和错误但运行时会有未定义符号或者直接段错误。 综上我们应该可以清楚了为什么目标文件的bss段在磁盘中的大小是0。因为编译器无法判断其他编译单元中是否有比本单元该符号所占的内存更大的符号所以给不出内存空间。
3.2 静态库链接
将静态库Static Library嵌入到可执行文件Executable File中的过程。静态库是一种预编译的代码库包含函数、变量和其他代码对象但并非直接可执行。在静态库链接过程中链接器会将静态库中的所有代码和数据复制到可执行文件中使其成为可执行文件的一部分。 优点 可执行文件独立性: 静态链接的可执行文件无需依赖外部库即使在没有库的情况下也能运行。 提高安全性: 静态链接的可执行文件包含所有代码因此更不容易受到外部库漏洞的影响。 减小可执行文件大小: 在某些情况下静态链接可使可执行文件更小因为无需包含额外的库文件。
缺点: 增加可执行文件大小: 在大多数情况下静态链接会使可执行文件更大因为包含了所有静态库代码。 浪费资源: 如果多个可执行文件使用相同的静态库则每个可执行文件都包含该库的副本这会导致资源浪费。 更新困难: 当静态库更新时需要重新编译所有依赖它的可执行文件。
本篇大致讲述了静态链接的情况下一篇文章将会对动态链接的情况进行阐述。