Linux 内核 Kbuild 中的 ld 调用机制

链接

在 Linux 内核及其衍生项目(如 Jailhouse、Xen、U-Boot)中,链接(link)阶段并不是通过显式的 ld 命令完成的,而是由 Kbuild 构建系统通过一套高度抽象、模板化的机制来统一管理。

本文将围绕两个核心问题展开:

  1. Kbuild 中的 ld 是在哪里、如何被调用的?
  2. 在 Kbuild 体系下使用链接器脚本(.lds)时,有哪些容易踩坑的地方?

一、Kbuild 中并不存在"直接调用 ld 的函数"

在阅读某些 Makefile 时,我们可能会困惑于如下规则:

make 复制代码
$(obj)/hypervisor.o: hypervisor.lds $(objs)
	$(call if_changed,ld)

问题是:

这里并没有看到 ld -T hypervisor.lds ...,那链接器到底是怎么被调用的?

答案是:

  • Kbuild 中并不存在一个叫"ld"的函数
  • ld 是一个 命令模板(command template)名 ,由 if_changed 间接展开

二、if_changed,ld 的真正入口在哪里?

if_changed 宏的定义

if_changed 系列宏定义在内核源码中,这里不深入:

复制代码
scripts/Kbuild.include

其核心职责是:

  • 判断命令行或依赖是否发生变化
  • 只有在变化时才执行对应命令

ld 对应的命令模板在哪里?

真正定义 如何调用链接器 的地方在:

复制代码
scripts/Makefile.lib

(不同内核版本略有差异,但结构一致)

概念化后的定义大致如下:

make 复制代码
quiet_cmd_ld = LD      $@
cmd_ld = $(LD) $(LDFLAGS_$(@F)) $(LDFLAGS) \
         -o $@ $(real-prereqs)

关键点在这里:

  • cmd_ld 才是真正的 ld 命令
  • if_changed,ld 实际上就是执行 cmd_ld
  • 所有参数都来自 Make 变量拼接,而非写死

三、为什么使用 $(@F) 而不是 $@

这是理解 Kbuild 的关键点之一

GNU make 自动变量回顾

变量 含义
$@ 目标完整路径
$(@F) 目标文件名(不含路径)
$(@D) 目标目录

例如:

make 复制代码
$(obj)/arch/riscv/hypervisor.o
  • $@build/arch/riscv/hypervisor.o
  • $(@F)hypervisor.o

Kbuild 的设计约定

Kbuild 规定:

按目标文件名(不含路径)设置专属 flags

make 复制代码
LDFLAGS_hypervisor.o := --whole-archive -T

因此在模板中只能使用:

make 复制代码
$(LDFLAGS_$(@F))

否则变量根本匹配不到。


四、链接器脚本 .lds 是如何"传给 ld 的"?

这是一个非常容易被误解的地方

常见 Kbuild 写法

make 复制代码
LDFLAGS_hypervisor.o := -T

$(obj)/hypervisor.o: hypervisor.lds $(objs)
	$(call if_changed,ld)

你会注意到:

  • -T hypervisor.lds 并没有显式写在命令中
  • .lds 只是作为 依赖文件

真正发生了什么?

Kbuild 的行为
  • $(LDFLAGS_$(@F)) 原样拼到 ld 命令前
  • $^(所有依赖)原样拼到命令后
  • 不解析 .lds,也不理解 -T 的语义
GNU ld 的行为

GNU ld 的规则是:

text 复制代码
-T script
    Use script as the linker script

即:

-T 后面的"下一个参数"会被当作 linker script

由于 .lds 通常是依赖列表中的第一个真实文件,最终形成的命令类似:

bash 复制代码
ld -T hypervisor.lds -o hypervisor.o a.o b.o

这是 ld 的命令行语义,而不是 Kbuild 的特殊规则


因此,严格来说:

Kbuild 只是按模板拼接参数;
-T.lds 的配对完全依赖 GNU ld 的命令行解析规则。


五、为什么一定要把 .lds 写进依赖?

这是一个工程级的设计决策

正确做法

make 复制代码
hypervisor.o: hypervisor.lds

原因

  1. 修改链接脚本会触发重新链接
  2. 保证布局、段地址、对齐变化不会被 silently 忽略
  3. 支持 make 的增量构建语义

如果你只在命令行里写 -T hypervisor.lds,但不写依赖,会有以下问题:

  • 修改 .lds 可能 不会触发重建,这是非常危险的系统级 bug

六、在 Kbuild 中使用链接器脚本的注意事项

1. 永远把 .lds 放进依赖列表

make 复制代码
target.o: target.lds $(objs)

2. 使用 LDFLAGS_<target>,不要写死命令

make 复制代码
LDFLAGS_target.o := -T --whole-archive

3. 注意 .lds 在依赖列表中的顺序

  • 通常放在最前
  • 避免被 -o 或其他参数"吃掉"

相关推荐
Trouvaille ~1 小时前
【Linux】理解“一切皆文件“与缓冲区机制:Linux文件系统的设计哲学
linux·运维·服务器·操作系统·进程·文件·缓冲区
ITKEY_1 小时前
archlinux 通过wpa_supplicant 连接wifi固定ip设置方法
linux·固定ip
幸存者letp3 小时前
Python 常用方法分类大全
linux·服务器·python
知识分享小能手3 小时前
Ubuntu入门学习教程,从入门到精通,Linux操作系统概述(1)
linux·学习·ubuntu
KnowFlow企业知识库3 小时前
KnowFlow v2.3.0 重磅发布:适配 RAGFlow v0.22.1 和 MinerU v2.6.5、新增支持多模态视频解析,让知识库"看见"更多
linux·github
悟空空心3 小时前
服务器长ping,traceroute
linux·服务器·网络·ssh·ip·ping++
此生只爱蛋4 小时前
【Linux】正/反向代理
linux·运维·服务器
qq_5470261794 小时前
Linux 基础
linux·运维·arm开发
zfj3214 小时前
sshd除了远程shell外还有哪些功能
linux·ssh·sftp·shell