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 或其他参数"吃掉"

相关推荐
何妨呀~2 分钟前
Firewalld防火墙端口配置
linux
切糕师学AI9 分钟前
Vim 深度解析:从经典 vi 到现代编辑器之巅
linux·vim·文本编辑器
计算机安禾33 分钟前
【Linux从入门到精通】第49篇:服务器故障排查终极指南——思路决定出路
linux·运维·服务器
古月-一个C++方向的小白35 分钟前
Linux——初识文件
linux·运维·服务器
北山有鸟1 小时前
编译香橙派内核
linux·运维·服务器
小此方1 小时前
Re:Linux系统篇(八)权限篇 ·三:深度解析从 umask 位运算到粘滞位的“权力锁”
linux·运维·服务器
10Eugene1 小时前
LVGL V9增加实体按键 驱动层和应用层的处理
c·lvgl
晨曦夜月1 小时前
进程的五大状态及特殊进程解析
linux·服务器·算法
生而为虫1 小时前
Claude Code 最新版安装教程(Windows/Mac/Linux 全平台) 面向普通用户的 Claude Code 安装与模型接入指南
linux·windows·macos
Sarvartha2 小时前
三目运算符
linux·服务器·前端