gcc-3.3.6 安装全流程:宿主选型、bootstrap 与动态库落地 简介gcc-3.3.6.tar.gz 是 GNU 编译器集合 3.3.6 版的完整源码包面向需要构建交叉编译工具链的嵌入式开发者、操作系统与移动设备底层工程师以及希望研读编译器实现的学习者用于解决在一种系统上编译另一种目标平台程序时的环境搭建问题。压缩包约 30.03MB文件总数标注为 2000类型以 C 源码、Java 源码和头文件为主并包含 Ada、C、Fortran 等语言前端以及 i386、rs6000 等后端相关实现此外还有 configure 配置脚本、Makefile.in 模板、测试用例、说明文档和 cpp、gcc、g、as、ld 等工具相关文件。已有 221 人学习下载适合具备 Unix/Linux 基础、想深入 GCC 构建流程的中高级读者。解压后可得到完整的 gcc-3.3.6 目录便于按目标架构定制配置选项、编译并安装交叉工具链也能用于分析词法语法处理、表达式与声明解析、寄存器分配、调试信息生成等编译环节配合测试集和安装说明可辅助验证与排错。1. gcc-3.3.6 不是装不上卡住的多半是能编译它的那台机器gcc-3.3.6 的安装难难在它挑宿主环境不在它本身。很多人把 gcc-3.3.6.tar.gz 解开就敲./configure make半小时后对着一屏报错怀疑源码包损坏。3.3 这个分支的老编译器要的是当年的编译器、当年的 glibc 头文件、当年的 C98 语义宿主机哪一样太新都可能让它在半路停住。真正麻烦的不是编不出来而是编到一半停住stage1 的产物留在构建目录里重跑 make 又是一批新错像黑匣子。这篇记录下来的是从宿主选型、依赖补齐、configure 参数取舍到 bootstrap、装完验证的完整路线适合要给老内核、老工具链、老嵌入式 SDK 配编译环境的人。2. 宿主环境选型哪一套 gcc 和 glibc 组合能一次编过 3.3.62.1 先定宿主老系统里编比在新系统上打补丁划算gcc-3.3.6 的源码主体是 C真正会跟现代工具链打架的地方集中在 libstdc-v3 那批 C 代码上。那份代码写于 C98 还没完全定型的年代里面有一堆依赖隐式转换、依赖std::名字可见性、依赖老cmath宏定义的写法。现代 g 默认走-stdgnu17名字查找规则收紧了模板两阶段查找也严了编译器会一口气报出几十个“在你看来完全没道理”的错误。你可以一个个加-fpermissive、一个个补#include cstddef但补丁是叠着来的补完 A 文件B 文件又冒出来运气不好还要改 Makefile 来绕过某条测试。我一般不会在现代发行版上干这件事而是直接把宿主降级到“比 3.3.6 晚几年、但不至于太晚”的那一代。判断标准很简单宿主自带的 g 版本落在 4.x 中段glibc 还在 2.x 的早期版本基本上 configure 和 make 就能一路走到底。宿主组合宿主 gglibc 量级需要手工打补丁的量我的取舍当前主流发行版11 以上2.3x多且随发行版变化不推荐除非只编 C 前端上一代发行版792.2x2.3x少量主要在 libstdc-v3可用但要留时间排错老一代企业发行版 6.x4.42.12基本为零首选一次过老一代 Debian 系 7.x4.72.13基本为零备选包管理更好用这不是说新系统绝对不行。如果你的目标只是拿 3.3.6 编一段纯 C 的老内核模块--enable-languagesc关掉 C新系统上大概率能过。但只要目标里有 Clibstdc-v3 就绕不开宿主降级的收益远大于在新系统上死磕补丁。2.2 依赖清单release 包到底要装什么不要多装也不要少装网上不少教程上来就让你先编译 GMP、MPFR、MPC 三件套。这套流程是 GCC 4.3 之后才成为硬性依赖的gcc-3.3.6 根本不查这三个库照抄只会白白多花一小时还可能因为装了个新版 GMP 干扰 configure 的探测结果。同理flex 和 bison 也不需要官方发布的 release tarball 里已经带上了预生成的语法分析器源码只有从版本库直接导出的树才要求你自己跑 bison。真正需要提前装好的是下面这几样缺一样都会在某个阶段突然停下# 老一代企业发行版系的装法一次把构建链补齐 yum install -y gcc gcc-c make binutils glibc-devel texinfo gzip tar sed gawk # Debian 系对应写法 # apt-get install -y gcc g make binutils libc6-dev texinfo gzip tar sed gawk逐项说一下为什么gcc和gcc-c是宿主编译器负责 stage1 把所有 C 源码编出来少了gcc-cconfigure 阶段不会立刻报错而是在编 libstdc-v3 时才提示找不到 C 编译器。make必须是 GNU makeBSD make 会在解析 GCC 的 Makefile 时报语法错。binutils提供 as 和 ld版本不要太老2.17 以上就行。glibc-devel给的是系统头和 crt 启动文件少了它链接阶段会找不到crt1.o。texinfo这一项最容易漏GCC 在 make 的最后阶段要跑makeinfo生成 info 文档没装就直接中断而且报错位置在整轮编译快结束时前面几十分钟全白等。提示texinfo的版本也不能太新。部分新版 texinfo 对老 .texi 文件的语法检查变严会把 gcc 3.3.6 的文档判成错误。遇到这种情况最省事的做法是让 make 跳过文档生成而不是去降级 texinfo。2.3 用容器把构建环境固定下来宿主降级最干净的做法是容器不是真的去找一台十年前的老机器。把源码目录挂进去整个构建过程都跑在容器里编完把/opt/gcc-3.3.6整个目录拷出来宿主机上不留任何痕迹也不会污染系统自带的编译器。# 拉一个老一代发行版镜像做宿主构建全过程都在容器内完成 docker run -it --name gcc336-build \ -v /data/build:/build \ -w /build \ centos:6 bash这里有三处要注意。-v /data/build:/build把宿主机目录挂进容器源码包和最终的构建目录都放这里容器删了成果还在。-w /build让进来就在工作目录省得每次 cd。镜像这一段老镜像基本都是 amd64 架构如果你的宿主机是 arm 平台需要显式加--platform linux/amd64否则拉取阶段就会失败。进去之后装依赖时可能撞上另一个坑这类老发行版的官方软件源早已下线yum install会直接报找不到仓库。常见做法是把源地址改成归档地址或者干脆换成局域网里现成的内部源。这一步绕不过去先把yum repolist能正常列出来再谈后面的编译。3. 配置 gcc-3.3.6configure 参数逐个拆别照抄网上那串3.1 解压与独立构建目录不要在源码树里直接 configureGCC 支持在源码目录外构建这个习惯必须养成。在源码树里直接 configure 之后任何一次参数改动都没法干净重来只能删掉整棵树重解压更麻烦的是源码树被中间产物污染后报错信息里的路径会变得很难读排查时容易看错文件。# 解压到固定位置源码树保持只读 tar xzf gcc-3.3.6.tar.gz -C /build # 构建目录与源码目录分离改参数时直接删掉重建 mkdir -p /build/build-3.3.6 cd /build/build-3.3.6解压完先做一件事确认config.guess认不认你的机器。这个脚本是 2003 年前后的产物里面的架构对照表非常老。在老镜像里跑一般没问题但如果你的构建机是较新的架构configure 会直接甩出一句cannot guess build type然后退出。遇到这种情况不必去改config.guess直接在 configure 时显式指定三元组更稳# 遇到 cannot guess build type 时手动指定构建三元组 ../gcc-3.3.6/configure --buildx86_64-pc-linux-gnu ...三元组的格式是CPU-厂商-系统--build指的是“编译 gcc 的这台机器”--host指的是“gcc 将来跑在哪台机器”--target指的是“gcc 编出来的程序跑在哪台机器”。本机自用的情况下三者一致只需要补--build就够另外两个让 configure 自己推。3.2 configure 参数每个开关都要知道它为什么在那儿下面这串是我在这类老版本上反复用过的一套参数不多但每一个都有明确目的../gcc-3.3.6/configure \ --prefix/opt/gcc-3.3.6 \ --enable-languagesc,c \ --disable-multilib \ --enable-shared \ --enable-threadsposix \ --disable-nls \ --with-gnu-as \ --with-gnu-ld--prefix/opt/gcc-3.3.6决定安装位置这一条是整个安装里最重要的决定。绝对不能装到/usr否则会覆盖系统自带的gcc、g、cpp、libstdc把宿主机搞成半瘫状态恢复起来极其痛苦。装到独立前缀用的时候靠 PATH 切换不用的时候改回来进退都有余地。--enable-languagesc,c限定只构建 C 和 C 前端。3.3.6 默认会把 Fortran 77、Objective-C、Java 一并编出来Java 前端尤其慢而且你八成用不上。砍掉这些能让整轮 bootstrap 的时间缩短一大截。--disable-multilib关掉多库支持。64 位宿主上如果不关构建过程会尝试同时产出 32 位版本而 32 位的 glibc 头和crt文件在老镜像里通常没装全最后卡在找不到crtbeginS.o之类的文件上。除非你明确需要 32 位产物否则一律关掉。--enable-shared让 libstdc 生成动态库。这一条直接决定了后面libstdc.so.5会不会存在而大量依赖 3.3.6 的老二进制程序链接的正是这个 SONAME静态库满足不了它们。--enable-threadsposix指定线程模型。3.3.6 的默认值在不同平台上不一致显式指定可以避免 configure 探测出一个奇怪的模型导致后面 C 程序链接时找不到 pthread 符号。--disable-nls关掉国际化支持减少对 gettext、libintl 的依赖也少一批可能的编译问题。--with-gnu-as和--with-gnu-ld告诉 configure 用 GNU 的汇编器和链接器而不是系统默认位置上的其他实现。参数作用不写的后果--prefix隔离安装路径覆盖系统编译器宿主机不可用--enable-languages只编指定前端多编 Java 等前端耗时翻倍--disable-multilib只产出当前位宽卡在缺失的 32 位 crt 文件--enable-shared生成 libstdc.so.5老二进制程序无法链接--enable-threadsposix固定线程模型C 程序链接期报 pthread 未定义--disable-nls关掉国际化引入多余的 libintl 依赖3.3 configure 跑完后必须确认的几行输出configure 跑完不要急着 make把输出翻一遍。它会写一份config.log绝大多数失败原因都在里面。需要确认的东西不多但都是关键第一行看三元组。checking build system type...和checking host system type...两行给出的值应该是你预期的架构。如果这里出现了i686而你以为在 64 位机器上构建说明 configure 被某个环境变量带偏了后面产出的东西都是 32 位的跟你的预期完全对不上。第二处看checking for gcc...那几行。这里探测的是 stage1 用的宿主编译器输出应该是一个已经存在的 gcc 可执行文件路径。如果这一项探测失败说明宿主环境里根本没有可用的 C 编译器后面的 make 一步都走不了。第三处看Thread model:这一行它应该显示posix。如果显示的是别的值说明--enable-threads没生效得回头看参数有没有写错。最后确认 configure 正常退出config.status文件已经生成。没有这个文件说明 configure 在中途失败退出了只是错误信息被前面的输出淹没了。4. 编译与安装bootstrap 三个阶段和 make install 的收尾动作4.1 bootstrap 到底在做什么为什么不能随便并行GCC 构建自己这件事本质上是一个自举过程分三段走。stage1 用宿主编译器把 GCC 的源码编成一个能用的xgcc。这个xgcc还没有经过验证只是“看起来能工作”。stage2 用 stage1 产出的xgcc再把同一份源码编一遍得到第二套产物。stage3 再重复一次然后用make compare逐字节比对 stage2 和 stage3 的目标文件。两者一致说明这个编译器编出来的代码是稳定可复现的整个自举才算通过。所以启动命令是这一条# 完整的自举构建同时把全部输出落盘方便中断后回查 make bootstrap 21 | tee /build/build-3.3.6/bootstrap.logmake bootstrap会自动跑完 stage1 到 stage3 并执行比较。如果比较失败最后的报错是Bootstrap comparison failure!后面跟着一长串不一致的目标文件名。这个错误在 3.3.x 上并不罕见多半是因为并行度过高、某几个目标文件的编译顺序乱了或者中间某一步用了不同的优化选项。遇到它先别怀疑源码降并行度重跑往往就消失了。关于-j这台老编译器在 bootstrap 阶段对并行不是完全免疫的。我一般不直接上-j$(nproc)而是从-j4起步机器核多再往上加。真的追求速度可以把日志重定向和并行一起用# 并行构建日志单独存一份出问题按关键字回查 make bootstrap -j8 bootstrap.log 218 核机器上整轮 bootstrap 大致是几十分钟的量级主要时间花在 stage2 和 stage3 各编一遍上。这段时间不要走开中途任何一次报错都会让后面的阶段失去意义。4.2 编译中断了怎么救三种就地处理的办法在宿主较新的环境里最常见的报错集中在 libstdc-v3 的 C 文件上表现通常是某个名字“没有声明”、某个模板参数“不匹配”、某处size_t找不到定义。处理方式按代价从低到高排能低就不高。第一种放宽编译器的严格程度。老代码里有大量依赖隐式转换的写法现代 g 不再接受加上-fpermissive能让这些降级为警告继续编下去# 通过环境变量把宽容选项透传给构建过程只影响编译参数不改 Makefile make bootstrap CFLAGS-g -O2 -fpermissive CXXFLAGS-g -O2 -fpermissive第二种把语言标准钉回 C98。现代 g 默认标准太高-stdgnu98能让名字查找和模板规则回到 3.3.6 熟悉的年代# 显式指定老的 C 标准减少因语言规则变化导致的报错 make bootstrap CXXFLAGS-g -O2 -stdgnu98第三种补上头文件。某些文件用了size_t却没有包含cstddef在老编译器上能靠间接包含蒙混过关现在不行了。这种只需要在报错定位到的那个文件顶部加一行#include cstddef不要全局撒改动越少越好。改完之后回到构建目录重新 make前面的 stage 产物还在能省不少时间。# 只给报错定位到的那一个文件补声明改完直接续跑 sed -i 1i #include cstddef /build/gcc-3.3.6/libstdc-v3/src/locale.cc make bootstrap三种办法的优先级是从上往下试先-fpermissive不行再钉标准最后才动源码。因为动源码意味着你的构建不再能被原样复现以后换台机器还得再来一遍。注意构建过程中途失败后不要急着make clean或者删目录重来。先看bootstrap.log里第一条 error 的位置很多失败是单点的改一处续跑就能过。删目录重来的代价是几十分钟的重复劳动。4.3 make install 与 libstdc.so.5 的落地bootstrap 比较通过之后安装这一步相对简单# 装到独立前缀不碰系统自带的编译器目录 make install装完先确认产物在哪、叫什么名字# 检查安装目录结构和库文件名 ls /opt/gcc-3.3.6/bin | head ls -l /opt/gcc-3.3.6/lib/libstdc.so*关键在libstdc.so后面那个数字。3.3.6 这一代的 C ABI 对应的是 SONAMElibstdc.so.5跟现在的libstdc.so.6完全不兼容。如果你看到软链指向的是.so.5.x.x说明--enable-shared生效了如果只有静态库.a那说明动态库没编出来回头检查 configure 参数。新装的库默认不在系统库搜索路径里直接运行刚编出来的 C 程序会报找不到libstdc.so.5。加上路径并刷新缓存# 把自编的库目录登记到动态链接器搜索路径 echo /opt/gcc-3.3.6/lib /etc/ld.so.conf.d/gcc-3.3.6.conf ldconfigldconfig之后用ldconfig -p | grep libstdc应该能看到三条记录.so.5和.so.6各占一条互不干扰。这一步做扎实了后面跑任何依赖 3.3.6 的老程序都不会因为库路径出问题。5. 排查清单gcc-3.3.6 安装里最容易翻车的五个地方5.1 configure 阶段的两个坑坑一cannot guess build typeconfigure 直接退出。现象是../gcc-3.3.6/configure刚跑几秒就报configure: error: cannot guess build type; you must specify one后面什么都没生成。原因很明确源码包里的config.guess是 2003 年的版本里面的架构识别表覆盖不到这台机器的 CPU 类型脚本只能放弃。解决方式是显式传三元组--buildx86_64-pc-linux-gnu让它不再去猜。三元组的系统部分要与实际一致写错了一样会在后面链接阶段出问题。坑二makeinfo: command not found编译到后半段才失败。现象是前面 compiling 一切正常突然在某一行停下报makeinfo: command not found位置在gcc/doc相关的目录里。原因是构建机没装 texinfo而 GCC 在 make 的最后阶段要生成 info 文档。这时候 stage1 到 stage3 已经全部编完就差最后一步重来一遍非常不划算。解决方法有两条装 texinfo或者直接告诉 make 不要生成文档。# 方案一补装 texinfo 后从断点续跑前面编好的产物不会重来 yum install -y texinfo make # 方案二直接跳过文档生成适合根本不需要本地 info 手册的场景 make MAKEINFOtrue5.2 编译阶段的坑坑三libstdc-v3 的 C 文件集体报错一屏几十个 error。现象是 bootstrap 跑到 libstdc-v3 目录时突然爆发报错内容是各种“名字未声明”“模板参数不匹配”“conversion 不允许”。原因是宿主 g 版本太新默认语言标准比 3.3.6 的代码晚了好几个版本名字查找规则变了。解决方式按 4.2 节的顺序来先加-fpermissive还不行就钉-stdgnu98最后才考虑补#include cstddef。如果三种都试过还在报说明宿主选得太新了换老镜像的成本比继续打补丁更低。坑四Bootstrap comparison failure!三阶段比对不一致。现象是 stage2 和 stage3 都编完了最后比对时报出一串目标文件不一致整个 make 以失败退出。原因通常有三个并行度太高导致构建顺序竞争、两次编译用了不同的优化选项、构建过程中文件时间戳被外部改动过。解决方式是先降并行度重跑-j1或者-j4如果还失败检查有没有人动过源码目录里的文件。# 串行重跑排除并行竞争导致的比对失败 make bootstrap -j1 bootstrap-serial.log 21真到串行也过不了才需要怀疑是在较新 glibc 下编译导致的产物不确定性这时候换宿主是唯一稳的路。5.3 安装与运行阶段的坑坑五程序编出来了一运行就报找不到libstdc.so.5。现象是/opt/gcc-3.3.6/bin/g编出来的程序能链接、能生成可执行文件但启动时立刻报error while loading shared libraries: libstdc.so.5: cannot open shared object file。原因是自编的库目录不在动态链接器的默认搜索路径里系统只认得自带的.so.6。解决是登记路径并刷新缓存echo /opt/gcc-3.3.6/lib /etc/ld.so.conf.d/gcc-3.3.6.conf然后ldconfig。验证手段是ldd 可执行文件 | grep libstdc输出里应该出现你刚装的那个绝对路径而不是not found。顺便提一句不要图省事把/opt/gcc-3.3.6/lib直接追加进LD_LIBRARY_PATH全局生效。这个目录里除了.so.5还有一批老版本的运行库全局挂上之后可能影响系统里其他程序对库版本的判断正确做法永远是 ldconfig 登记不要用环境变量兜底。6. 装完怎么验证五条命令确认 3.3.6 的 C 和 C 都真的通了装完不验证等同于没装。我的习惯是按顺序跑下面这一组从版本号到实际编译产出、从 C 到 C 逐层确认。第一步确认版本和配置。/opt/gcc-3.3.6/bin/gcc -v输出里的gcc version必须是3.3.6Configured with:后面那串要和你传的参数对得上。这一步能立刻发现装错版本或者参数没生效的问题。-dumpmachine输出的是三元组跟 configure 阶段看到的一致才算前后自洽。第二步用 C 编一个最小程序验证 C 前端和 crt 文件都正常/* ver.c只依赖 stdio确认 3.3.6 的 C 前端能产出可执行文件 */ #include stdio.h int main(void) { printf(built by %s\n, __VERSION__); return 0; }编译并运行/opt/gcc-3.3.6/bin/gcc -o ver ver.c ./ver。预期输出是built by 3.3.6。如果这里报的是crt1.o not found说明构建机缺 libc 开发包回去补glibc-devel。第三步走 C这一条才是真正验证 libstdc-v3 有没有编成功// hello.cc走 iostream验证 C 前端和 libstdc.so.5 的链接 #include iostream int main() { std::cout c ok std::endl; return 0; }编译时显式指定 rpath避免测试结果被环境变量干扰/opt/gcc-3.3.6/bin/g -Wl,-rpath,/opt/gcc-3.3.6/lib -o hello hello.cc。然后ldd hello | grep libstdc输出必须是/opt/gcc-3.3.6/lib/libstdc.so.5。如果这里出现的是系统自带的.so.6说明链接时用错了库目录检查g -print-search-dirs的输出顺序。验证项命令期望结果版本号/opt/gcc-3.3.6/bin/gcc -vgcc version 3.3.6目标三元组/opt/gcc-3.3.6/bin/gcc -dumpmachine与 configure 输出一致C 编译链gcc -o ver ver.c ./ver打印built by 3.3.6C 编译链g -o hello hello.cc ./hello打印c ok运行库绑定ldd hello | grep libstdc指向libstdc.so.5第四步真正拿一份目标源码编一遍。前面三步只能证明工具链本身没坏最后一关得用你当初装它的理由来验要编老内核模块就编一个最简单的 hello 模块要编老 SDK 就挑一个最小 demo。这一步经常能暴露出前几步看不出来的问题比如某些头文件路径被系统版本抢先命中或者-m32这类参数在这种组合下不生效。第五步把这一整套流程写进脚本。从那以后我每次装这类老工具链都会强制走一遍这五条验证哪怕这次只是改了个--prefix。经验是看起来只差一个路径的改动出问题的概率一点不低早发现比等到业务侧跑起来再回头查便宜得多。希望这套走法能帮你少走点弯路。本文还有配套的精品资源点击获取