CSAPP 第七章 Linking part2

CSAPP 第七章 Linking part2

Executable Object Files

在这里插入图片描述
可执行目标文件(executable object file)的格式与可重定位目标文件(relocatable object file)相似。ELF 头(ELF header)描述了文件的总体格式。它还包括程序的入口点,即程序运行时执行的第一条指令的地址。.text、.rodata 和 .data 部分与可重定位目标文件中的相似,不同之处在于这些部分已经被重定位到它们最终的运行时内存地址。.init 部分定义了一个名为 _init 的小函数,该函数将被程序的初始化代码调用。由于可执行文件已经完全链接(重定位),因此不需要 .rel 部分。
在这里插入图片描述
在这里插入图片描述
ELF 可执行文件被设计为易于加载到内存中,可执行文件的连续块映射到连续的内存段。这种映射由程序头表(program header table)描述。图 7.14 显示了示例可执行文件 prog 的程序头表的一部分,如 objdump 所示。从程序头表中,我们看到将用可执行目标文件的内容初始化两个内存段。第 1 和第 2 行告诉我们第一个段(代码段)具有读/执行权限,从内存地址 0x400000 开始,总大小为 0x69c 字节,并使用可执行目标文件的前 0x69c 字节进行初始化,其中包括 ELF 头、程序头表以及 .init、.text 和 .rodata 部分。

第 3 和第 4 行告诉我们第二个段(数据段)具有读/写权限,从内存地址 0x600df8 开始,总内存大小为 0x230 字节,并使用从目标文件中偏移量 0xdf8 处开始的 .data 部分的 0x228 字节进行初始化。段中的剩余 8 字节对应于在运行时将被初始化为零的 .bss 数据。

对于任何段 s,链接器必须选择一个起始地址 vaddr,使得 vaddr mod align = off mod align,其中 off 是段在目标文件中第一个部分的偏移量,align 是程序头中指定的对齐方式(这里是 0x200000)。例如,在图 7.14 中的数据段中:

vaddr mod align = 0x600df8 mod 0x200000 = 0xdf8

off mod align = 0xdf8 mod 0x200000 = 0xdf8
这种对齐要求是一种优化,使得在程序执行时,目标文件中的段可以有效地传输到内存中。这是由虚拟内存以大块连续的2的幂字节组织的方式导致的。

Loading Executable Object Files

运行executable object files:
在这里插入图片描述

在Linux系统中,当用户运行一个程序时,Shell会假定该程序是一个可执行对象文件,并通过调用 execve 函数来执行该程序。加载器(loader)负责将可执行对象文件中的代码和数据从磁盘复制到内存,并通过跳转到其第一条指令或入口点来运行程序。这个过程被称为加载。
在这里插入图片描述
每个运行中的Linux程序都有一个类似于图7.15中所示的运行时内存图像。在Linux x86-64系统上,代码段从地址0x400000开始,其后是数据段。运行时堆紧随数据段后面,通过调用malloc库而向上增长(我们将在第9.9节详细介绍malloc和堆)。然后是一个为共享模块保留的区域。用户栈从最大的合法用户地址(2^ 48 − 1)开始向下增长,朝着较小的内存地址。栈上方的区域,从地址2^48开始,为内核中的代码和数据保留。

为了简化,我们将堆、数据和代码段绘制成相邻的,将栈的顶部放在最大的合法用户地址上。实际上,由于.data段的对齐要求(第7.8节),代码和数据段之间存在间隙。此外,链接器在为栈、共享库和堆分配运行时地址时使用地址空间布局随机化(ASLR,第3.10.4节)。尽管这些区域的位置在每次运行程序时都会改变,但它们的相对位置保持不变。

加载器运行时,它创建了一个类似于图7.15所示的内存图像。在程序头表的指导下,加载器将可执行对象文件的块复制到代码和数据段中。接下来,加载器跳转到程序的入口点,该入口点始终是_start函数的地址。该函数在系统对象文件(system object file)crt1.o中定义,对于所有C程序都是相同的。_start函数调用系统启动函数__libc_start_main,该函数在libc.so中定义。它初始化执行环境,调用用户级main函数,处理其返回值,并在必要时将控制权返回给内核。

Dynamic Linking with Shared Libraries

我们在第7.6.2节中学到的静态库解决了许多与向应用程序提供大量相关功能的问题。然而,静态库仍然存在一些显著的缺点。静态库,像所有软件一样,需要定期维护和更新。如果应用程序员想使用库的最新版本,他们必须以某种方式得知库已更改,然后明确地重新链接他们的程序以使用更新后的库。

更新静态库

重新链接使用修改过的静态库时,你需要确保在编译时使用更新后的库文件。以下是一些建议的步骤:

  1. 重新编译修改后的静态库:
    在修改静态库的源代码后,确保你重新编译了这个库,生成了更新后的静态库文件(.a 文件)。

    gcc -c 修改后的源文件.c -o 修改后的目标文件.o
    ar rcs libyourlibrary.a 修改后的目标文件.o 其他目标文件.o
    
  2. 编译源文件并链接新的库:
    在编译源文件时,使用更新后的库文件路径和名称。确保在编译命令中包含了新的静态库。

    gcc -o your_program your_source.c -L/path/to/updated_library -lyourlibrary
    

    这里的 /path/to/updated_library 是新库文件的路径,yourlibrary 是新库文件的名称。

  3. 运行程序:
    如果编译没有错误,你可以运行生成的可执行文件。

注意事项:

  • 确保新的静态库路径和名称正确。
  • 如果你使用的是相对路径,确保当前工作目录正确设置。
  • 如果库文件位于标准路径中,你可能无需指定路径,只需指定库文件名。

这样,你就能够使用更新后的静态库重新链接你的程序。

另一个问题是几乎每个C程序都使用标准I/O函数,比如printf和scanf。在运行时,这些函数的代码会在每个运行中的进程的文本段中复制。在运行数百个进程的典型系统上,这可能是对稀缺内存系统资源的重大浪费。(内存的一个有趣属性是,无论系统中有多少内存,它总是一种稀缺资源。)

共享库(Shared libraries)是解决静态库缺点的现代创新。共享库是一个目标模块,可以在运行时或加载时加载到任意内存地址,并与内存中的程序链接。这个过程被称为动态链接(dynamic linking),由一个称为动态链接器(dynamic linker)的程序执行。共享库也被称为共享对象(shared objects),在Linux系统上它们以.so后缀表示。Microsoft操作系统大量使用共享库,它们称之为DLL(动态链接库)。

共享库(Shared libraries)在两个不同的方面是“共享”的。首先,在给定的文件系统中,一个特定库只有一个.so文件。该.so文件中的代码和数据被引用该库的所有可执行对象文件共享&#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值