ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

U-Boot编译报错multiple definition of ‘yylloc‘?老项目适配GCC 10+的解决指南

U-Boot编译报错multiple definition of ‘yylloc‘?老项目适配GCC 10+的解决指南 1. 报错现场还原这个编译失败到底长什么样做嵌入式的老哥应该都有这种体验翻出一个几年没人动的 BSP第一件事不是看原理图而是先把编译环境恢复起来。我这次是在 Ubuntu 22.04 上重编 iTOP4412 的 U-Boot刚敲完 make屏幕刷出好几条multiple definition of yylloc然后是multiple definition of yylval链接器直接collect2: error: ld returned 1 exit status编译流程当场死掉。先说结论这不是板子坏了也不是 U-Boot 源码被改坏了而是你电脑上的 GCC 太新老项目还没跟上。iTOP4412 这类开发板厂商提供的 U-Boot 一般基于 2012、2013 年的老分支那时候的编译工具链默认行为和现在差别很大从 GCC 10 开始这种差距直接变成编译错误。这个坑我在很多老板上都见过只要你在新系统上编旧 U-Boot、旧内核大概率会踩到。1.1 完整报错信息与构建环境先看一段我当时记录的报错现场方便你对照自己是不是同一个问题scripts/dtc/dtc-parser.tab.o: In function dtc_parse_begin: /path/to/u-boot/scripts/dtc/dtc-parser.tab.c:431: multiple definition of yylloc; scripts/dtc/dtc-lexer.lex.o:/path/to/u-boot/scripts/dtc/dtc-lexer.lex.c:735: first defined here scripts/dtc/dtc-parser.tab.o: In function dtc_parse_begin: /path/to/u-boot/scripts/dtc/dtc-parser.tab.c:432: multiple definition of yylval; scripts/dtc/dtc-lexer.lex.o:/path/to/u-boot/scripts/dtc/dtc-lexer.lex.c:736: first defined here collect2: error: ld returned 1 exit status构建环境是三件套x86_64 的 Ubuntu 22.04 主机、默认的 GCC 11 作为 HOSTCC、老版本的 U-Boot 源码树。这里要特别提醒U-Boot 编译分成两套工具链给 ARM 目标板编译固件用的是交叉编译链比如 arm-linux-gnueabihf-gcc而编译过程中生成的辅助工具比如设备树编译器 DTC、mkimage用的是主机 GCC。这次报错发生在scripts/dtc目录它是用主机 GCC 编的所以问题出在 HOSTCC 而不是交叉编译链。1.2 为什么偏偏是 iTOP4412 栽在这里iTOP4412 是三星 Exynos 4412 方案Cortex-A9 四核厂商提供的 U-Boot 分支很老里面捆绑了一个相当古老的 DTCDevice Tree Compiler源码。正常流程下U-Boot 需要先把 DTC 编译出来用它把设备树源文件.dts编译成.dtb再打包进最终的 u-boot.bin 或者单独的 boot 分区。问题在于这个老 DTC 里的 Flex/Bison 生成代码用到了yylloc、yylval这类全局符号。在早年 GCC 的默认行为下这类符号会被链接器宽容合并到了 GCC 10 以后默认参数从-fcommon变成了-fno-common多个目标文件里出现同名定义就直接报错。这不是 iTOP4412 的独有问题而是整个老工具链生态的共同痛点圈子里流传的各种 U-Boot 编译失败案例里yylloc出现的频率相当高。1.3 这个问题的波及范围不只是 U-BootLinux 内核的scripts/dtc目录也有完全一样的坑。从 2020 年 Ubuntu 20.04 带 GCC 10 开始大量编译旧内核的用户都撞见过multiple definition of yylloc当时很多发行版通过给HOSTCFLAGS加-fcommon来打补丁。所以这篇文章的解决思路不只是修一个板子而是通用的老项目适配方法你以后编任何带旧版 DTC 的项目都可以套用。2. 从 yylloc 说起Flex/Bison 与链接器符号语义要彻底搞懂这个报错不能只停留在“加个参数就好了”的层面。把yylloc的来历、链接器符号规则、GCC 版本差异这三件事串起来以后遇到类似符号冲突就能举一反三。2.1 yylloc 到底是谁yylloc是 Flex词法分析器生成器和 Bison语法分析器生成器之间传递位置信息的一个全局变量。DTC 要解析.dts文件第一步是词法扫描把字符流拆成 token第二步是语法分析根据语法规则构建抽象语法树。词法分析器要知道每个 token 在源文件里的行列位置语法分析器在做语法检查、报错定位时也需要这些信息于是双方约定通过全局变量yylloc来传递位置信息。在 Bison 生成的dtc-parser.tab.c里通常有类似下面的定义YYLTYPE yylloc; YYSTYPE yylval;而在 Flex 生成的dtc-lexer.lex.c里会通过头文件或 extern 声明引用它。老版本生成代码的写法比较随意同一个符号既可能在 parser 里有一份定义也可能在 lexer 里有一份定义甚至在两个文件里都出现了“看起来像定义”的代码。2.2 多重定义链接器视角的解读要理解什么叫“多重定义”得先分清编译和链接两个阶段。编译阶段每个.c文件单独变成.o文件编译器只关心单文件内部符号是否一致链接阶段链接器把所有.o文件拼到一块这时候要求每个全局符号只能有一个“强定义”。问题就出在“强定义”的判定上。在老式 C 语言环境下如果一个全局变量没有显式初始化比如YYLTYPE yylloc;它会被当作“暂定定义”tentative definition。多个目标文件里出现相同的暂定定义时链接器会默认把它们合并成一个不会报错。这种宽松行为对应 GCC 的-fcommon参数。但是 GCC 10 之后默认改成了-fno-common。在这个模式下多个目标文件里的同名暂定定义不再被宽容合并而是被当成多个强定义处理于是链接器毫不留情地抛出multiple definition of yylloc。用大白话说以前是“大家共用一块地皮谁先占就算谁的”现在是“所有人拿出来的地契都必须唯一重了就是问题”。2.3 GCC 版本差异到底改了什么严格来说GCC 10 并没有删掉对-fcommon的支持只是把默认值改掉了。GCC 9 及更早版本编译 C 代码时默认允许 common symbolGCC 10 开始默认禁止。用一个最小例子就能说明// a.c int value;// b.c int value;int main(void) { return 0; }在 GCC 9 下编译链接gcc a.c b.c能直接通过两个暂定定义被合并。在 GCC 11 下编译链接就会报multiple definition of value。实际项目中不会有人故意写这么蠢的代码但 Flex/Bison 这类代码生成工具生成的代码不受开发者控制历史包袱就这么留下来了。所以你看技术演进经常会把老代码的“隐疾”变成“明病”修法无非两个方向让新工具链迁就老代码或者让老代码迁就新工具链。3. 解决方案三招让老项目在新工具链上编译通过面对yylloc多重定义错误我整理了三套方案按推荐程度排序。第一招是打补丁改动最小第二招是改源码相对治本第三招是换编译器适合不想折腾代码的人。3.1 最省事的一招给 HOSTCFLAGS 追加 -fcommon思路很简单既然报错是因为链接器不合并 common symbol那就把老编译器的宽容行为重新打开。U-Boot 在编译scripts/dtc时使用HOSTCFLAGS作为主机 C 编译器的参数所以我们只需要在 Makefile 里给它追加-fcommon。具体操作分两步。第一步打开顶层Makefile找到HOSTCFLAGS相关定义一般长这样HOSTCFLAGS -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer \ -include $(srctree)/include/libfdt_env.h直接在这一行末尾追加HOSTCFLAGS -fcommon或者更精准一点只修改scripts/dtc/Makefile在文件顶部加一行HOSTCFLAGS -fcommon两种写法效果一样区别是作用范围。我建议优先改scripts/dtc/Makefile因为你只希望 DTC 这个工具用老规矩编译避免-fcommon影响整个 U-Boot 的其他部分虽然实际影响很小但能少一个变量就少一个。第二步清理后重新编译make distclean make ARCHarm CROSS_COMPILEarm-none-eabi- 你的板级配置 make ARCHarm CROSS_COMPILEarm-none-eabi- -j4这里的CROSS_COMPILE要替换成你自己用的交叉编译链前缀iTOP4412 官方 BSP 一般给的是arm-none-eabi-或者arm-linux-gnueabihf-具体看你环境里装了哪个。改完之后DTC 编译阶段就会重新使用 common symbol 语义yylloc和yylval会被正常合并这个报错直接消失。我在多台机器上试过是最快最稳的办法。3.2 治本的一招修改 DTC 源码中 yylloc 的声明方式如果你不想依赖-fcommon或者以后要提交代码给别人用那就得从源码层面消除多重定义。思路是把全局符号改成单一定义或者改成每个编译单元私有的静态符号。常见做法是打开scripts/dtc/dtc-lexer.lex.c和scripts/dtc/dtc-parser.tab.c找到yylloc、yylval的定义处。由于不同版本的 Bison 生成的代码格式不一样我不给死板字符你自己搜索yylloc就能看到。比较稳妥的改法是把其中一个文件里的普通定义改成extern声明只保留一个真正的定义。比如在dtc-lexer.lex.c里extern YYLTYPE yylloc; extern YYSTYPE yylval;同时在dtc-parser.tab.c里保留YYLTYPE yylloc; YYSTYPE yylval;如果两个文件里都有带初始化的定义那就把多余的那个删掉只留一份。另一种更省心的改法是直接把两个文件里的定义都改成static。但要注意yylloc和yylval是词法分析器和语法分析器之间通信用的如果两边都改成 static它们就彼此看不见了编译会报“未定义符号”。所以除非你确认两个文件内部都能自洽否则还是推荐“一个文件 extern、一个文件定义”的写法。注意改生成代码属于“手工补丁”如果以后重新运行 Bison/Flex 生成改的内容会被覆盖。所以如果你要长期维护最好把这些改动写进 Makefile 里的 sed 命令或者在代码注释里标明这是针对 GCC 10 的手动适配免得后人莫名其妙。3.3 绕路的一招直接用旧版 GCC 构建如果上面两种方法你都觉得麻烦还有一个接地气的办法装一个老版本 GCC 专门用来编 U-Boot。Ubuntu 等发行版提供了多版本 GCC 共存方案以 Ubuntu 23.04 为例sudo apt install gcc-9 g-9然后用 HOSTCC 参数指定老版本make ARCHarm CROSS_COMPILEarm-none-eabi- HOSTCCgcc-9 板级配置 make ARCHarm CROSS_COMPILEarm-none-eabi- HOSTCCgcc-9 -j4这里只指定了HOSTCC因为 DTC 是用主机 GCC 编译的交叉编译链继续用你原来的工具链互不影响。GCC 9 默认还是-fcommon语义所以能直接绕过这个报错。这个方法适合“我只想赶紧把固件编出来”的场景。缺点是治标不治本而且如果你的系统源里没有 gcc-9你得手动处理软件源或者装老版本工具链稍微麻烦一点。3.4 针对 iTOP4412 的具体操作从厂商 BSP 到生成 u-boot.biniTOP4412 的 BSP 结构千差万别有的厂商把 U-Boot、内核、文件系统全堆在一个目录里有的又把 U-Boot 单独拆出来。我建议你拿到源码后先确认两件事一是 U-Boot 顶层目录里有没有scripts/dtc二是板级配置文件在include/configs/下叫什么名字。我手上这份 iTOP4412 BSP 的 U-Boot 板级配置不是标准的smdk4412_config就是厂商自定义的名字你可以用ls include/configs/查看里面带有 4412 关键字的文件就是目标。配置命令一般是make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- smdk4412_config或者厂商自定义的make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- itop4412_config配置完之后不要直接 make先把-fcommon按 3.1 节的方式加好再执行编译。产物最终会生成u-boot.bin然后在 SDK 里用厂商提供的打包工具把u-boot.bin和其他镜像合成为 SD 卡或 eMMC 烧录文件。4. 实操过程从报错到 uboot.bin 全程记录光说理论不落地等于白说我把自己在 Ubuntu 22.04 iTOP4412 BSP 上的完整实操过程整理出来你跟着走一遍就能复现。4.1 准备一个干净的构建目录老项目最忌讳在脏环境下反复编译因为旧的.o文件、旧配置会掩盖问题。我习惯先彻底清理make distclean如果你不确定之前有没有人改过源码可以对比一下 Git 状态。没有版本管理的 BSP 就认命反正以后编译出问题也是常事整理好存档就行。顺便检查交叉编译链是否可用arm-linux-gnueabihf-gcc --version如果这里报“命令找不到”后面什么编译都做不了先去装交叉工具链这一步不用多讲。4.2 修改前后的具体命令与 Makefile 片段我选了scripts/dtc/Makefile作为修改点因为它只影响 DTC 编译。原始文件里可能有一行HOSTCFLAGS -I$(src) -I$(src)/libfdt我直接在后面追加HOSTCFLAGS -fcommon改完可以cat确认cat scripts/dtc/Makefile然后再执行配置和编译make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- smdk4412_config make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j4这次 DTC 编译阶段不会再有yylloc报错整个 U-Boot 编译流程能顺利走完。如果你的 CPU 核心多-j可以加大但老项目的 Makefile 偶尔有并行依赖问题遇到莫名其妙报错就退回到-j1试试。4.3 验证结果与烧录提醒编译完成后u-boot.bin会在顶层目录生成。可以用file命令确认一下架构file u-boot.bin正常输出会包含 ARM 相关字样。如果你在 iTOP4412 上还要生成 SD 卡启动镜像通常还要用厂商的脚本在 Windows 或者 Linux 下打包这一步不同厂商差异很大就不好一概而论了。重要提醒烧录前务必确认你用的是正确配置编译出来的固件尤其注意内存型号、启动介质类型。我见过有人拿错了.bin文件刷进去板子直接变砖折腾半天才用 SD 卡引导救回来。编译能过只是第一步烧录前多看两眼板级配置没坏处。5. 编译过程中其他高频报错与排查心得解决了yylloc不代表 U-Boot 编译就能一路绿灯。老项目在新时代编译链上还会遇到不少别的问题我把常见的高频报错和排查思路整理成速查表方便你遇到类似情况时快速定位。5.1 高频报错速查表报错关键词常见原因快速处理multiple definition of yyllocGCC 10 默认 -fno-commonHOSTCFLAGS 加 -fcommonmultiple definition of yylval同上Flex/Bison 全局符号同上unrecognized command line option -fstack-protector-strong老交叉编译链不支持新参数升级交叉编译链或删除该参数fatal error: linux/compiler-gcc.h: No such file or directory内核源码与 GCC 版本不匹配给 GCC 打补丁或加-D宏Error: selected processor does not supportsmc 0in ARM mode老工具链不支持新指令升级交叉编译链cannot find -lstdc主机缺少 32 位库或多架构库安装 lib32stdc 或相应依赖Recipe for target u-boot.bin failed前面的链接步骤失败往前翻日志找根因这张表是我长期编各种开发板 BSP 过程中攒下来的发现没有很多问题本质都是“工具链版本错配”跟你写的代码关系不大。所以遇到编译错误第一步永远是看编译日志里第一个 error而不是最后一个。5.2 我的几个独家避坑心得第一个心得永远先分清“主机编译”和“目标编译”。U-Boot 构建过程里一部分工具给主机编一部分固件给目标板编。报错位置在scripts/dtc、tools/这些目录基本都是主机编译问题报错位置在arch/arm、board/samsung这类目录才是交叉编译链问题。方向搞错后面全白费。第二个心得加参数前想清楚作用范围。-fcommon加到HOSTCFLAGS和加到CFLAGS效果完全不同。我见过有人图省事直接在顶层 Makefile 里给全局 CFLAGS 加了-fcommon虽然也能编译过但老式 common symbol 的松散语义可能掩盖一些真实问题建议能限定范围就限定范围。第三个心得编译报错时先看linux发行版版本和 GCC 版本。Ubuntu 20.04 默认 GCC 9不会报 yyllocUbuntu 22.04 默认 GCC 11必报。所以同样的源码在这两个系统上的处理方式不一样。写博客或教程时永远把“系统版本 GCC 版本”写清楚别人才能复现你的经验。5.3 如何判断一个报错是工具链问题还是源码问题这个判断能力很值钱。我的经验是先看报错文件路径如果在scripts/、tools/或者某个自动生成的.c文件里八成是工具链兼容问题如果在你自己写的板级文件里八成是源码逻辑问题。再看报错类型multiple definition、undefined reference、unrecognized command line option这类基本都是工具链或链接参数问题。而implicit declaration of function、expected declarator这类才是源码语法或 API 变化问题。把这两条结合起来基本能定位八成的 U-Boot 编译问题。6. 收尾把老代码搬到新环境的几个通用建议最后分享一个通用排查清单不限于 iTOP4412老项目在新工具链上出问题时都可以按这个顺序走。6.1 通用检查清单先确认主机系统与默认 GCC 版本记录到一个文本里。然后确认源码自身版本U-Boot 顶层目录的Makefile里会有版本号。接着确认交叉编译链版本执行CROSS_COMPILE-gcc --version查看。如果报错是链接器多重定义优先搜索项目是否涉及 Flex/Bison 生成代码再考虑加-fcommon。如果报错是某个编译选项不认识检查 GCC 和 G 版本是否过旧或过新。如果报错来自自动生成的代码考虑重新生成或手工打补丁。这个清单我用过很多次不能说 100% 解决所有问题但能避开绝大多数弯路。6.2 一点个人体会我在实际修复这次 iTOP4412 编译问题时最深的感觉是老代码不是“错的”只是它活在它那个时代的 C 语言规范里。GCC 从-fcommon到-fno-common的转变是编译器变得更严谨的表现但对于并不需要这种严谨性的老嵌入式项目来说一个-fcommon就能换来一夜好眠。以后再遇到multiple definition of yylloc希望你能想起这篇文章的结论先看 HOSTCFLAGS再想 Flex/Bison最后祭出-fcommon。嵌入式开发就是这样很多问题并不高深只是时代留下的裂缝我们用手边最合适的材料把它填平就够了。
返回列表