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

简介: 【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还负责链接启动代码,这是程序开始执行时首先运行的代码片段。

总的来说,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)或其他格式的文件头,获取段信息、符号表等。
  1. 符号解析:
  • ld构建一个全局符号表,其中包含所有输入文件中的符号。
  • 对于未解析的符号(例如,一个目标文件中引用但在其他地方定义的函数),ld会在所有输入的库文件中搜索这些符号的定义。
  • 如果找不到某个符号的定义,ld会报错。
  1. 地址分配:
  • ld将所有输入文件的段(如.text、.data)合并为一个大的段,并为其分配地址。
  • 它还处理重定位条目,这些条目指定了在链接时需要修改的代码或数据的位置。
  1. 重定位:
  • 由于目标文件是独立编译的,它们包含的地址引用可能不是最终的正确地址。ld使用重定位条目来更新这些引用,使它们指向正确的地址。
  1. 输出生成:
  • 一旦所有符号都被解析并且所有段都被分配了地址,ld就会生成输出文件。
  • 输出文件通常是ELF格式,但也可以是其他格式,取决于目标平台。
  1. 优化:
  • 在某些情况下,ld还可以进行链接时优化,例如删除未使用的代码或数据。

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

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


示例

示例一

链接两个目标文件 file1.o 和 file2.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

用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编程专家。我期待看到你在这个旅途中取得更大进步!

目录
相关文章
|
算法 Linux Shell
Linux实用技能:打包压缩、热键、Shell与权限管理
本文详解Linux打包压缩技巧、常用命令与原理,涵盖.zip与.tgz格式操作、跨系统传文件方法、Shell运行机制及权限管理,助你高效使用Linux系统。
Linux实用技能:打包压缩、热键、Shell与权限管理
|
存储 安全 Unix
七、Linux Shell 与脚本基础
别再一遍遍地敲重复的命令了,把它们写进Shell脚本,就能一键搞定。脚本本质上就是个存着一堆命令的文本文件,但要让它“活”起来,有几个关键点:文件开头最好用#!/usr/bin/env bash来指定解释器,并用chmod +x给它执行权限。执行时也有讲究:./script.sh是在一个新“房间”(子Shell)里跑,不影响你;而source script.sh是在当前“房间”里跑,适合用来加载环境变量和配置文件。
1021 9
|
存储 Shell Linux
八、Linux Shell 脚本:变量与字符串
Shell脚本里的变量就像一个个贴着标签的“箱子”。装东西(赋值)时,=两边千万不能有空格。用单引号''装进去的东西会原封不动,用双引号""则会让里面的$变量先“变身”再装箱。默认箱子只能在当前“房间”(Shell进程)用,想让隔壁房间(子进程)也能看到,就得给箱子盖个export的“出口”戳。此外,Shell还自带了$?(上条命令的成绩单)和$1(别人递进来的第一个包裹)等许多特殊箱子,非常有用。
1090 2
|
存储 数据管理 Linux
区分Linux中.tar文件与.tar.gz文件的不同。
总之,".tar"文件提供了一种方便的文件整理方式,其归档但不压缩的特点适用于快速打包和解压,而".tar.gz"文件通过额外的压缩步骤,尽管处理时间更长,但可以减小文件尺寸,更适合于需要节约存储空间或进行文件传输的场景。用户在选择时应根据具体需求,考虑两种格式各自的优劣。
2232 13
|
Web App开发 缓存 安全
Linux一键清理系统垃圾:释放30GB空间的Shell脚本实战​
这篇博客介绍了一个实用的Linux系统盘清理脚本,主要功能包括: 安全权限检查和旧内核清理,保留当前使用内核 7天以上日志文件清理和系统日志压缩 浏览器缓存(Chrome/Firefox)、APT缓存、临时文件清理 智能清理Snap旧版本和Docker无用数据 提供磁盘空间使用前后对比和大文件查找功能 脚本采用交互式设计确保安全性,适合定期维护开发环境、服务器和个人电脑。文章详细解析了脚本的关键功能代码,并给出了使用建议。完整脚本已开源,用户可根据需求自定义调整清理策略。
1465 1
|
安全 Linux
Linux赋予文件000权限的恢复技巧
以上这些步骤就像是打开一扇锁住的门,步骤看似简单,但是背后却有着严格的逻辑和规则。切记,在任何时候,变更文件权限都要考虑安全性,不要无谓地放宽权限,那样可能
513 16
|
存储 Linux 数据处理
深入剖析Linux中一切即文件的哲学和重定向的机制
在计算机的奇妙世界中,Linux的这套哲学和机制减少了不同类型资源的处理方式,简化了抽象的概念,并蕴藏着强大的灵活性。就像变戏法一样,轻轻松松地在文件、程序与设备之间转换数据流,标准输入、输出、错误流就在指尖舞动,程序的交互和数据处理因此变得既高效又富有乐趣。
279 4
linux文件重命名命令
本指南介绍Linux文件重命名方法,包括单文件操作的`mv`命令和批量处理的`rename`命令。`mv`可简单更改文件名并保留扩展名,如`mv old_file.txt new_name.txt`;`rename`支持正则表达式,适用于复杂批量操作,如`rename &#39;s/2023/2024/&#39; *.log`。提供实用技巧如大小写转换、数字序列处理等,并提醒覆盖风险与版本差异,建议使用`-n`参数预览效果。
|
关系型数据库 MySQL Shell
MySQL 备份 Shell 脚本:支持远程同步与阿里云 OSS 备份
一款自动化 MySQL 备份 Shell 脚本,支持本地存储、远程服务器同步(SSH+rsync)、阿里云 OSS 备份,并自动清理过期备份。适用于数据库管理员和开发者,帮助确保数据安全。
|
Shell
Shell脚本循环控制:shift、continue、break、exit指令
使用这些命令可以让你的Shell脚本像有生命一样动起来。正确使用它们,你的脚本就能像一场精心编排的舞蹈剧目,既有旋律的起伏,也有节奏的跳跃,最终以一场惊艳的表演结束。每一个动作、每一个转折点,都准确、优雅地完成所需要表达的逻辑。如此,你的脚本不只是冰冷的代码,它透过终端的界面,跳着有节奏的舞蹈,走进观众——使用者的心中。
464 60