Ubuntu 22.04编译Linux内核实战:从"multiple definition"错误到完整解决方案
当你在Ubuntu 22.04上尝试编译Linux内核时,可能会遇到一个令人困惑的错误信息:/usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of 'yylloc'。这个错误看似简单,却可能让你花费数小时甚至数天时间才能找到真正的解决方案。本文将带你深入理解这个问题的本质,并提供一套完整的排查思路和解决方案,而不仅仅是简单的步骤说明。
1. 问题背景与初步分析
Linux内核编译是一个复杂的过程,涉及数百个文件和工具链的协同工作。当你在Ubuntu 22.04这样的较新系统上编译为旧版系统设计的内核时,版本差异往往会带来各种意想不到的问题。
multiple definition of 'yylloc'错误通常出现在编译设备树编译器(DTC)组件时。这个错误表明链接器(ld)在尝试将多个目标文件合并时,发现yylloc变量被重复定义。具体来说:
- 第一次定义出现在
scripts/dtc/dtc-lexer.lex.o - 第二次定义出现在
scripts/dtc/dtc-parser.tab.o
为什么会出现这个问题?
在较新的Ubuntu系统中,工具链和编译器的行为可能与旧版系统有所不同。特别是:
- GCC版本差异:Ubuntu 22.04默认使用GCC 11,而旧版系统可能使用GCC 5或7
- 链接器行为变化:新版ld可能对符号重复定义更加严格
- Bison/Flex版本:语法分析器生成工具的版本差异可能导致生成的代码不同
2. 常见解决方案及其局限性
大多数在线资源会建议以下解决方案:
// 在dtc-parser.tab.c中添加extern声明 extern YYLTYPE yylloc;这个方案看似合理,因为它遵循了C语言中解决重复定义问题的常规方法:将其中一个定义改为声明。然而,在实际操作中,你可能会发现:
- 修改后错误依然存在
- 重新编译时,你的修改似乎被"撤销"了
为什么会这样?
关键在于理解内核编译系统的完整工作流程。DTC(设备树编译器)的源文件在编译过程中会经历以下步骤:
- 由Bison和Flex工具从
.y和.l文件生成.c文件 - 生成的
.c文件被编译为目标文件 - 目标文件被链接为最终的可执行文件
问题在于,每次编译时,系统都会重新生成这些中间文件,覆盖你手动修改的版本。这就是为什么简单的编辑解决方案往往无效。
3. 深入解决方案:修改生成规则
要永久解决这个问题,我们需要修改生成这些中间文件的规则。以下是详细步骤:
3.1 定位相关Makefile
首先,找到控制DTC编译的Makefile:
cd linux-kernel-directory find . -name Makefile | grep dtc通常,相关Makefile位于scripts/dtc/Makefile。
3.2 修改生成规则
在Makefile中,我们需要修改Bison和Flex的生成规则。添加以下内容:
# 在scripts/dtc/Makefile中添加 dtc-parser.tab.o: dtc-parser.tab.c dtc-lexer.lex.o $(CC) $(CFLAGS) -c -o $@ $< -Wno-error=redundant-decls dtc-lexer.lex.o: dtc-lexer.lex.c $(CC) $(CFLAGS) -c -o $@ $< -Wno-error=redundant-decls3.3 修改源文件
同时,我们需要修改.y和.l源文件:
在
scripts/dtc/dtc-parser.y开头添加:%code requires { extern YYLTYPE yylloc; }在
scripts/dtc/dtc-lexer.l开头添加:YYLTYPE yylloc;
3.4 清理并重新编译
make clean make mrproper make oldconfig make -j$(nproc)4. 问题背后的技术原理
理解这个问题的根本原因有助于你在遇到类似问题时更快找到解决方案。
YYLTYPE和yylloc是什么?
YYLTYPE是Bison定义的结构体,用于存储位置信息(行号、列号等)yylloc是Flex和Bison共享的全局变量,用于在词法分析器和语法分析器之间传递位置信息
为什么会出现重复定义?
- Flex生成的词法分析器(
dtc-lexer.lex.c)默认会定义yylloc - Bison生成的语法分析器(
dtc-parser.tab.c)在某些配置下也会定义yylloc - 当这两个目标文件被链接时,链接器发现同一个符号被定义了两次
版本差异的影响
在较新的Ubuntu系统中:
- Bison/Flex版本可能生成略有不同的代码
- GCC默认启用更多警告和错误检查
- 链接器对符号可见性规则更加严格
5. 替代解决方案比较
除了上述方法,还有其他几种解决思路:
| 解决方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 修改生成规则 | 永久解决 | 需要理解Makefile | 长期开发 |
| 添加extern声明 | 简单直接 | 可能被覆盖 | 快速测试 |
| 使用旧版工具链 | 兼容性好 | 需要安装旧版 | 兼容性测试 |
| 修改编译器标志 | 无需改代码 | 可能掩盖其他问题 | 临时解决方案 |
使用旧版工具链的方法
如果你需要与旧系统完全兼容,可以考虑使用Docker容器:
# 创建Ubuntu 16.04容器 docker run -it --name kernel-build -v $(pwd):/src ubuntu:16.04 # 在容器内安装工具链 apt update && apt install build-essential flex bison libssl-dev6. 预防类似问题的通用策略
在编译复杂项目时,以下策略可以帮助你更高效地解决问题:
理解完整的构建流程:
- 不要只关注错误本身,要了解整个构建系统的运作方式
- 使用
make V=1查看详细的构建命令
版本控制是关键:
git init git add . git commit -m "Initial source code"这样你可以随时回退到已知良好的状态
增量调试技巧:
- 先尝试单独编译出问题的模块
- 使用
make scripts/dtc/dtc只编译DTC组件
文档与社区资源:
- 查阅内核文档(
Documentation/kbuild) - 搜索LKML(Linux内核邮件列表)中的相关讨论
- 查阅内核文档(
7. 高级技巧:深入构建系统
对于需要频繁修改和编译内核的开发者,以下高级技巧可能有用:
使用ccache加速编译
sudo apt install ccache export CC="ccache gcc" make -j$(nproc)自定义构建输出
# 将构建输出与源码分离 mkdir build-output make O=build-output调试链接问题
当遇到链接问题时,可以使用以下命令检查符号定义:
nm dtc-parser.tab.o | grep yylloc nm dtc-lexer.lex.o | grep yylloc8. 实际案例:从错误到解决的完整过程
让我们通过一个真实场景来巩固所学内容:
初始错误:
/usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of 'yylloc' scripts/dtc/dtc-lexer.lex.o:(.bss+0x0): first defined here第一次尝试:
- 在
dtc-parser.tab.c中添加extern YYLTYPE yylloc; - 重新编译,问题依旧
- 在
深入分析:
- 使用
make V=1发现文件被重新生成 - 检查
.tmp_dtc目录发现备份文件
- 使用
根本解决:
- 修改
dtc-parser.y和dtc-lexer.l - 更新Makefile规则
- 彻底清理并重新编译
- 修改
验证:
file scripts/dtc/dtc scripts/dtc/dtc: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped
在解决这个问题的过程中,最关键的是理解构建系统的完整工作流程,而不仅仅是关注表面错误。这种系统性的思维方式对于解决复杂的编译问题至关重要。