【Shell 命令集合 链接器(linker)工具】Linux ld命令 将目标文件与库链接为可执行文件或库文件


Shell 命令专栏:Linux Shell 命令全解析


描述


ld是Linux环境下的链接器(linker)工具,它的主要作用是将多个目标文件(.o.obj文件)链接成一个可执行文件或库文件。以下是ld命令的主要功能和作用:

  1. 目标文件链接ld可以将多个目标文件链接成一个单一的输出文件。这些目标文件通常是由编译器生成的,并包含程序的机器代码。

  2. 解析符号:在链接过程中,ld会解析目标文件中的符号,并确保所有的函数和变量引用都被正确地解析。

  3. 生成可执行文件ld不仅可以生成可执行文件,还可以生成共享库或静态库。

  4. 处理重定位:由于目标文件中的代码可能在任何位置执行,ld需要处理这些代码的重定位,确保代码在正确的内存地址上执行。

  5. 合并段ld会合并目标文件中的相似段,例如.text(代码段)或.data(数据段)。

  6. 链接时优化:在某些情况下,ld还可以进行链接时优化,以提高生成的可执行文件的性能。

  7. 处理启动代码ld还负责链接启动代码,这是程序开始执行时首先运行的代码片段。

    扫描二维码关注公众号,回复: 16693788 查看本文章

总的来说,ld命令是Linux编程中不可或缺的工具,它确保程序中的所有部分都被正确地组合在一起,生成一个可以运行的可执行文件。


语法格式

ld [options] object_files [--] [library_files]

参数说明

  • -b <input-format>: 指定目标代码输入文件的格式。
  • -Bstatic: 只使用静态库。
  • -Bdynamic: 只使用动态库。
  • -Bsymbolic: 把引用捆绑到共享库中的全局符号。
  • -c <MRI-commandfile>, --mri-script=<MRI-commandfile>: 为与 MRI 链接器兼容,ld 接受由 MRI 命令语言编写的脚本文件。
  • --cref: 创建跨引用表。
  • -d,-dc,-dp: 即使指定了可重定位的输出文件(使用-r),也会为公共符号分配空间。脚本命令“FORCE_COMMON_ALLOCATION”具有相同的效果。
  • -defsym: 在输出文件中创建指定的全局符号。
  • -demangle: 在错误消息中还原符号名称。
  • -e <entry>: 使用指定的符号作为程序的初始执行点。
  • -E,--export-dynamic: 对于ELF格式文件,创建动态链接的可执行文件时,把所有符号添加到动态符号表。
  • -f <name>, --auxiliary=<name>: 对于 ELF 格式共享对象,设置 DT_AUXILIARY 名称。
  • -F <name>, --filter=<name>: 对于ELF格式共享对象,设置 DT_FILTER 名称。这告诉动态链接器,正在创建的共享对象的符号表应该用作共享对象名称的符号表的筛选器。
  • -g: 被忽略。用于提供和其他工具的兼容性。
  • -h: 对于 ELF 格式共享对象,设置 DT_SONAME 名称。
  • -I<file>, -dynamic-linker <file>, --dynamic-linker=<file>: 指定动态链接器。这仅在生成依赖动态链接库的 ELF 可执行文件时才有意义。默认的动态链接器通常是正确的,除非您知道正在做什么,否则不要使用该选项。
  • -l <namespec>, --library=<namespec>: 把指定的库文件添加到要链接的文件清单。
  • -L <searchdir>, --library-path=searchdir: 把指定的路径添加添加到搜索库的目录清单。
  • -M, --print-map: 显示链接映射,用于诊断目的。
  • -Map=<mapfile>: 将链接映射输出到指定的文件。
  • -m <emulation>: 模拟指定的链接器。
  • -N,--omagic: 指定读取/写入文本和数据段。
  • -n,--nmagic: 关闭节的页面对齐,并禁用对共享库的链接。如果输出格式支持Unix样式的幻数,则将输出标记为"NMAGIC"。
  • -noinhibit-exec: 生成输出文件,即使出现非致命链接错误。通常,如果链接器在链接过程中遇到错误,它将不会生成输出文件。
  • -no-keep-memory: ld 通常在内存中缓存输入文件的符号表来优化内存使用速度。此选项告诉 ld 不要缓存符号表。当链接大型可执行文件时,如果ld耗尽内存空间,则可能需要使用该选项。
  • -O <level>: 对于非零的优化等级,ld将优化输出。此操作会比较耗时,应该在生成最终的结果时使用。
  • -o <output>, --output=<output>: 指定输出文件的名称。
  • -oformat=<output-format>: 指定输出文件的二进制格式。
  • -R <filename>,--just-symbols=<filename>: 从指定的文件读取符号名称和地址。
  • -r,--relocatable: 生成可重定位的输出(称为部分连接)。
  • -rpath=<dir>: 把指定的目录添加到运行时库搜索路径。
  • -rpath-link=<dir>: 指定搜索运行时共享库的目录。
  • -S,--strip-debug: 忽略来自输出文件的调试器符号信息。
  • -s,--strip-all: 忽略来自输出文件的所有符号信息。
  • -shared, -Bshareable: 创建共享库。
  • -split-by-file[=size]: 为每个目标文件在输出文件中创建额外的段大小达到size。size默认为1。
  • -split-by-reloc[=count]: 按照指定的长度在输出文件中创建额外的段。
  • --section-start=<sectionname>=<org>: 在输出文件中指定的地址定位指定的段。
  • -T <scriptfile>, --script=<scriptfile>: 使用 scriptfile 作为链接器脚本。此脚本将替换 ld 的默认链接器脚本(而不是添加到其中),因此脚本必须指定输出文件所需的所有内容。如果当前目录中不存在脚本文件,ld 会在 -L 选项指定的目录中查找。
  • -Ttext=<org>: 使用指定的地址作为文本段的起始点。
  • -Tdata=<org>: 使用指定的地址作为数据段的起始点。
  • -Tbss=<org>: 使用指定的地址作为bss段的起始点。
  • -t,--trace: 在处理输入文件时显示它们的名称。
  • -u <symbol>, --undefined=<symbol>: 强制指定符号在输出文件中作为未定义符号。
  • -v, -V, --version: 显示ld版本号。
  • -warn-common: 当一个通用符号和另一个通用符号结合时发出警告。
  • -warn-constructors: 如果没有使用任何全局构造器,则发出警告。
  • -warn-once: 对于每个未定义的符号只发出一次警告。
  • -warn-section-align: 如果为了对齐而改动了输出段地址,则发出警告。
  • --whole-archive: 对于指定的存档文件,在存档中包含所有文件。
  • -X, --discard-locals: 删除所有本地临时符号。
  • -x, --discard-all: 删除所有本地符号。

错误情况

  • 未解析的符号:如果ld在链接过程中找不到某个符号的定义,它会报错,提示未解析的符号。
  • 多重定义:如果同一个符号在多个目标文件中都有定义,ld会报错。
  • 文件格式不匹配:如果尝试链接不同格式的目标文件,例如ELF和a.out,ld会报错。
  • 库未找到:使用-l选项指定的库如果在库搜索路径中未找到,ld会报错。
  • 输入/输出错误:如果在读取输入文件或写入输出文件时出现错误,ld会报错。

注意事项

当使用Linux中的ld命令时,有一些注意事项需要考虑,以确保链接过程顺利进行并生成正确的输出文件:

  1. 库的顺序:在命令行中,库的顺序很重要。通常,应该先列出目标文件,然后列出库文件。这是因为ld从左到右处理参数,解析目标文件中的符号引用时,它会在后续的库文件中查找这些符号。

  2. 系统库的链接:当链接系统库时,通常使用-l选项,例如-lm用于链接数学库。但要确保库的搜索路径设置正确,可以使用-L选项添加额外的搜索路径。

  3. 静态与动态链接:默认情况下,ld会尝试动态链接库。如果需要静态链接,可以使用-static选项。但要注意,静态链接会使输出文件体积增大。

  4. 符号冲突:确保不同的目标文件中没有重复定义的符号,否则ld会报多重定义错误。

  5. 使用适当的链接脚本ld使用链接脚本来控制输出文件的布局。如果有特殊需求,可以使用-T选项指定自定义的链接脚本。

  6. 去除不必要的符号信息:为了减小输出文件的大小,可以使用-s选项去除符号信息。但这会使得后续的调试变得困难。

  7. 确保兼容性:如果链接的目标文件是在不同的系统或使用不同的编译器选项生成的,可能会出现兼容性问题。确保所有文件都是在相同的环境下编译的。

  8. 检查输出:链接完成后,使用file命令检查输出文件的类型,确保它是期望的格式(例如,ELF可执行文件)。

  9. 避免使用过时的库或选项:随着时间的推移,某些库或选项可能已被弃用。确保使用的都是当前版本的库和推荐的链接选项。

  10. 阅读文档ld的功能和选项可能会随着版本的更新而变化。定期查阅man ld或其他相关文档,以获取最新的信息和建议。

总之,使用ld命令时需要注意多个方面,确保链接过程正确无误,并生成期望的输出文件。


底层实现

ld命令,作为GNU Binutils套件的一部分,是Linux中的标准链接器。它的底层实现涉及多个复杂的步骤和算法,以下是其核心实现的概述:

  1. 输入处理

    • ld首先读取所有输入的目标文件和库文件。
    • 它解析ELF(Executable and Linkable Format)或其他格式的文件头,获取段信息、符号表等。
  2. 符号解析

    • ld构建一个全局符号表,其中包含所有输入文件中的符号。
    • 对于未解析的符号(例如,一个目标文件中引用但在其他地方定义的函数),ld会在所有输入的库文件中搜索这些符号的定义。
    • 如果找不到某个符号的定义,ld会报错。
  3. 地址分配

    • ld将所有输入文件的段(如.text.data)合并为一个大的段,并为其分配地址。
    • 它还处理重定位条目,这些条目指定了在链接时需要修改的代码或数据的位置。
  4. 重定位

    • 由于目标文件是独立编译的,它们包含的地址引用可能不是最终的正确地址。ld使用重定位条目来更新这些引用,使它们指向正确的地址。
  5. 输出生成

    • 一旦所有符号都被解析并且所有段都被分配了地址,ld就会生成输出文件。
    • 输出文件通常是ELF格式,但也可以是其他格式,取决于目标平台。
  6. 优化

    • 在某些情况下,ld还可以进行链接时优化,例如删除未使用的代码或数据。

ld的底层实现主要是用C语言编写的,并且是开源的。 interested in the detailed implementation, you can check out the source code of the GNU Binutils project.

总的来说,ld的底层实现涉及多个复杂的步骤,从输入文件的解析到地址分配、重定位和最终的输出生成。这些步骤确保了生成的可执行文件或库文件在运行时能够正确地访问其代码和数据。


示例

示例一

链接两个目标文件 file1.ofile2.o 生成可执行文件 program:

ld -o program file1.o file2.o

示例二

链接目标文件 file.o 并使用静态库 libstatic.a:

ld -o program file.o -Bstatic -lstatic

示例三

链接目标文件,同时指定动态链接器的路径:

ld -o program file.o -I/usr/local/lib/ld-linux.so.2

示例四

链接目标文件并指定输出文件的二进制格式为 elf64-x86-64:

ld -o program file.o -oformat=elf64-x86-64

示例五

链接目标文件并强制符号 my_symbol 在输出文件中作为未定义符号:

ld -o program file.o -u my_symbol

示例六

链接目标文件并使用链接器脚本 linker_script.ld:

ld -o program file.o -T linker_script.ld

示例七

链接目标文件并显示处理输入文件时的名称:

ld -o program file.o -t

这是七个 ld 使用示例,希望对您有所帮助。如果您有其他问题或需要进一步的解释,请告诉我。


用c语言 模拟ld思想

实现ld命令的功能是一个复杂的任务,因为ld是一个完整的程序链接器,它处理多种文件格式、解析符号、处理重定位等。完整地实现ld的功能需要大量的代码和深入的知识。

但我可以为您提供一个简化版的链接器的概念实现,这将帮助您理解链接器的基本工作原理。这只是一个非常基础的示例,不能用于实际的链接任务。

#include <stdio.h>
#include <stdlib.h>

// 假设我们有两个简单的目标文件格式:
// file1.o: "HELLO "
// file2.o: "WORLD"

int main(int argc, char *argv[]) {
    
    
    if (argc < 4) {
    
    
        printf("Usage: %s output input1 input2\n", argv[0]);
        return 1;
    }

    FILE *input1 = fopen(argv[2], "rb");
    FILE *input2 = fopen(argv[3], "rb");
    FILE *output = fopen(argv[1], "wb");

    if (!input1 || !input2 || !output) {
    
    
        perror("Error opening files");
        return 1;
    }

    // 简单地将两个输入文件的内容复制到输出文件
    char ch;
    while ((ch = fgetc(input1)) != EOF) {
    
    
        fputc(ch, output);
    }
    while ((ch = fgetc(input2)) != EOF) {
    
    
        fputc(ch, output);
    }

    fclose(input1);
    fclose(input2);
    fclose(output);

    printf("Linking completed.\n");
    return 0;
}

这个简化版的链接器只是将两个输入文件的内容复制到一个输出文件。在实际的ld链接器中,它会处理各种目标文件格式、解析符号、处理重定位、合并段等复杂任务。

如果您真的想深入了解链接器的工作原理,我建议您阅读关于链接器和加载器的专业书籍,或查看开源链接器如GNU ld的源代码。



结语

在我们的探索过程中,我们已经深入了解了Shell命令的强大功能和广泛应用。然而,学习这些技术只是开始。真正的力量来自于你如何将它们融入到你的日常工作中,以提高效率和生产力。

心理学告诉我们,学习是一个持续且积极参与的过程。所以,我鼓励你不仅要阅读和理解这些命令,还要动手实践它们。尝试创建自己的命令,逐步掌握Shell编程,使其成为你日常工作的一部分。

同时,请记住分享是学习过程中非常重要的一环。如果你发现本博客对你有帮助,请不吝点赞并留下评论。分享你自己在使用Shell命令时遇到的问题或者有趣的经验,可以帮助更多人从中学习。
此外,我也欢迎你收藏本博客,并随时回来查阅。因为复习和反复实践也是巩固知识、提高技能的关键。

最后,请记住:每个人都可以通过持续学习和实践成为Shell编程专家。我期待看到你在这个旅途中取得更大进步!


阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_21438461/article/details/132913787