简介gcc-3.3.6.tar.gz 是 GNU Compiler Collection 3.3.6 的完整官方源码压缩包面向需要构建交叉编译工具链的嵌入式开发者、系统软件学习者及底层工具链研究人员。压缩包约30.03MB内含近2000个文件以 C 源文件为主体同时覆盖头文件.h、C 源文件.cc/.cpp、汇编文件、configure 配置文件、Makefile.in 模板、多平台 t-* 规则文件、测试用例、TeX 文档等目录结构完整便于按模块检索与分析。该版本虽较旧但架构模块齐全适合学习 GCC 内部组成、配置流程和编译原理。已有 221 人学习下载。借助该源码包可深入分析配置脚本如何适配不同目标架构理解 GCC 在交叉编译环境中的构建方式并据此定制特定硬件平台的编译器为嵌入式系统开发与 OS 工具链搭建提供扎实基础。1. 绕不开 gcc-3.3.6老设备、老内核与老代码的复现基准如果你手上还有一台跑着 2.4/2.6 内核的老服务器或者一段必须原样交付的嵌入式 C/C 工程多半会遇到一个尴尬新装的 gcc 12、gcc 14 编出来的二进制要么体积暴涨要么直接报错警告刷屏链接符号都对不上。这时候翻箱倒柜找 gcc-3.3.6.tar.gz不是怀旧是刚需。这份资源就是 GCC 3.3.6 的源码完整包能解决老内核模块编不过、老 C 代码在 ABI 上翻车、以及需要复现十年前构建环境这三类问题。适合老系统维护工程师、想重构旧工具链的嵌入式开发以及做软件版本考古的人。我后面写的每一步都是在这类环境下踩过坑之后沉淀下来的操作顺序。2. 拆包确认版本结构先从 tar.gz 里看清楚能干什么2.1 解压后先摸清目录结构把包拿到手后别急着 configure先解开看看目录里到底有什么。tar xzf gcc-3.3.6.tar.gz cd gcc-3.3.6 ls -la解压后你会在根目录看到configure、gcc/、libstdc-v3/、libiberty/、zlib/这些核心子目录。gcc/里是 C/C/Java 前端和编译器主体libstdc-v3/是这个版本对应的 C 标准库源码libiberty/和zlib/是内部工具库构建时会自动编进去不需要你额外处理。说明一点GCC 3.3.6 属于 2004 年前后的 3.3.x 稳定分支。它后面没有像新版那样的 gmp、mpfr、mpc 依赖所有第三方库都以子目录形式打包在源码树里。这个特性让它在离线环境、老旧发行版上格外好使——不需要联网拉依赖解开就能编。2.2 构建前置依赖m4、bison、flex 一个都不能少老版本 GCC 对构建宿主机的依赖比新版本更“传统”。它在 configure 阶段会调用一系列 GNU 工具来生成解析器代码缺了直接中止。我在干净系统上编译时最常遇到的是这三个东西没装。which m4 bison flex make gcc m4 --version bison --version flex --version make --version逐条解释一下这些命令在做什么which检查命令是否在 PATH 中--version确认版本号是否够用。GCC 3.3.6 的构建需要 autoconf 生成的 configure 脚本运行需要 m4 处理宏需要 bison 重新生成gcc/c-parse.y相关的解析器需要 flex 处理词法扫描器。这些工具缺一个后面 configure 会在某个阶段莫名退出且报错信息不直接指向缺失的依赖。我在 Ubuntu 和 CentOS 上都试过最省事的做法是提前装好apt-get install -y m4 bison flex或yum install -y m4 bison flex。另外宿主机的 gcc 版本别太老建议 4.x 以上这样编 3.3.6 时不会有“编译器太旧编不了编译器”的尴尬。2.3 校验包完整性的两种方式源码包在传输过程中可能被截断或文件损坏直接编会报出莫名奇妙的 syntax error。我解包后习惯先做一次完整性校验。md5sum gcc-3.3.6.tar.gz tar -tzf gcc-3.3.6.tar.gz | head -20 tar -tzf gcc-3.3.6.tar.gz | wc -lmd5sum算出校验值如果你能找到官方发布时的 md5 摘要就对一下找不到也别慌至少换台机器重算一次数值一致说明传输没问题。tar -tzf的z参数在 3.3.6 时期还是主流能直接列出压缩包内部的文件清单wc -l统计文件数量正常情况下是几千个条目如果只列出几个文件说明包是坏的不要浪费时间继续。3. 编译安装的三条硬经验configure 参数、bootstrap 与安装验证3.1 建独立 build 目录别在源码树里直接编译老版本 GCC 官方推荐的是在源码树外建一个 build 目录来编译。这个习惯我保留到现在原因很简单源码目录只读build 目录随时可以删了重建编译失败不会把源码树弄脏。mkdir build cd build ../configure --prefix/opt/gcc-3.3.6 \ --enable-languagesc,c \ --disable-multilib \ --enable-threadsposix这一步的参数不要照抄完就过我逐个说清楚含义。--prefix/opt/gcc-3.3.6决定最终安装路径编译完成后编译器可执行文件会落到/opt/gcc-3.3.6/bin库落入lib头文件落入include。--enable-languagesc,c指定只编译 C 和 C 前端这个参数非常关键因为 3.3.6 默认会连 Javagcj、Objective-C、Fortran 一起编耗时翻倍不说Java 前端在部分新系统上还会编译失败直接拖垮整个构建。--disable-multilib是关闭 32 位和 64 位多库输出能力我一般建议老版本编译器关闭它因为多库模式下系统里缺哪边的库编译就断在哪边。--enable-threadsposix显式指定线程模型老系统上默认值不一定是 posix显式写死能避免后续 C 程序里 pthread 符号解析失败。这里补充一句如果你的宿主机器是 arm64 或 x86_64而目标环境是 32 位不要指望靠 multilib 一劳永逸那是泪的教训后面避坑章里细说。3.2 configure 参数逐项拆解哪些默认值会坑你把上面几个参数展开成一张对照表方便你按自己的场景取舍。参数不设时的默认行为我的建议--prefix多数发行版会装到/usr/local容易和系统自带 gcc 冲突独立目录如/opt/gcc-3.3.6--enable-languages编所有语言前端耗时且易挂显式写成c,c--disable-multilib尝试编 32/64 双库缺库即失败默认关闭--enable-threads可能退化成单线程或巨型锁模型显式写posix--disable-nls开启国际化翻译无实际收益建议加上--disable-nls是我后来才加的参数。它关掉 GCC 自身的多语言消息翻译老版本在部分 locale 环境下会报cannot find message catalog之类的运行时错误关了以后世界清净。configure 顺利结束后终端会打印Configuration ... done这样的字样。如果在这一步就挂了优先去看build/config.log的最后几十行绝大多数原因就写在里面。3.3 make bootstrap 的三阶段自举为什么要跑这个GCC 源码包不能像普通 C 项目那样直接make make install收工标准做法是make bootstrap。这个是老版本手册里的推荐流程底层逻辑是三个阶段第一阶段用宿主机现有的 gcc 把 GCC 3.3.6 源码编出一版基础编译器第二阶段用刚编出的 3.3.6 编译器再编一次完整源码第三阶段再用第二阶段的结果编第三遍用来验证编译器的自我一致性。make bootstrap -j2这里的-j2是指并行任务数。老版本 GCC 的 Makefile 对并行的支持没有现在成熟我在四核机器上开-j4遇到过头文件竞争导致编译中断稳妥起见用-j2如果你内存小于 2GB我建议直接-j1。整个 bootstrap 过程在当年的机器上要耗费数小时现在的硬件跑起来会快很多但也要预留二十分钟到一小时。编译过程中如果某个阶段报错留意是 stage1 还是 stage2。stage1 的问题多半是宿主 gcc 太老或太新stage2/stage3 的问题多半是 3.3.6 本身的代码与当前 binutils 或 glibc 头文件不兼容。这个问题在第五章展开写。3.4 安装后先做三件事确认路径、版本、库搜索bootstrap 结束后执行安装make install ls -la /opt/gcc-3.3.6/bin/gcc /opt/gcc-3.3.6/bin/gcc -v /opt/gcc-3.3.6/bin/g -v which gcc echo $PATH第一件事是用绝对路径直接调用gcc -v确认输出里出现gcc version 3.3.6字样第二件事是用which gcc看系统默认的 gcc 到底指向哪里。这一步非常关键因为绝大多数人在这里发现自己敲gcc时启动的还是系统自带的旧路径。如果你不想每次都用绝对路径可以临时把目录加到 PATH 最前面export PATH/opt/gcc-3.3.6/bin:$PATH gcc -v但注意这只是当前 shell 会话有效新开终端就失效了。要让所有会话生效需要写到~/.bashrc或/etc/profile.d/gcc336.sh里。我一般建议不要全局改保持系统默认 gcc 不动需要老版本时用脚本切环境第六章给你具体做法。4. 在新系统上把它用起来内核模块、C 老代码与静态产物4.1 给 2.x 老内核编模块Makefile 里显式指定 CC装好 gcc-3.3.6 之后最典型的落地场景是编译老内核模块。老内核源码里大量使用旧式语法和隐式声明新版 gcc 的默认告警级别与更严格的格式检查会让编译变成一场告警刷屏的灾难甚至直接把-Werror触发导致目标文件生成失败。我实际用过的一个编译 2.4 内核模块的 Makefile 长这样obj-m : oldmod.o KERNELDIR : /usr/src/linux-2.4.36 CC : /opt/gcc-3.3.6/bin/gcc PWD : $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M$(PWD) modules这个 Makefile 的逻辑解释一下obj-m声明要生成一个模块KERNELDIR指向老内核源码树里面必须有配置好的.config和编译内核时生成的include/linux/version.hCC被显式覆盖为老编译器绝对路径这是整个文件最值钱的一行——如果漏掉它make 会优先使用 PATH 里的新 gcc一切白干。执行前有一条前置检查要做grep CONFIG_MODULES /usr/src/linux-2.4.36/.config ls /usr/src/linux-2.4.36/include/linux/version.hgrep CONFIG_MODULES确认内核源码开启了模块支持version.h存在是模块编译的硬前提。缺这两样时先去补否则进到内核目录里 make 会报一堆找不到头文件的错排查起来特别费时间。4.2 libstdc.so.5 与老 C 程序的动态库依赖C 老程序比 C 程序多一层麻烦——标准库 ABI。GCC 3.3.6 对应的 libstdc 版本是 libstdc.so.5而现代系统上是 libstdc.so.6。拿老编译器编出来的 C 二进制运行时系统默认只找得到 .so.6直接报libstdc.so.5: cannot open shared object file。解决思路有两个我一般推荐先做软链接成本最低ln -s /opt/gcc-3.3.6/lib/libstdc.so.5 /usr/lib/libstdc.so.5 ldconfig ldd ./old_cpp_binary | grep libstdcln -s把老标准库软链到系统库目录ldconfig刷新动态链接器缓存ldd验证二进制实际找到了哪个版本的 libstdc。如果你不想动系统目录也可以在当前 shell 导环境变量export LD_LIBRARY_PATH/opt/gcc-3.3.6/lib:$LD_LIBRARY_PATH这个只是临时顶一下程序退出就失效。生产环境我建议前面那个软链接方案明白且不容易被环境变量覆盖。4.3 想要“一个二进制走天下”用 -static 静态链接如果你的老 C 程序不需要依赖系统库里任何东西静态链接是最省心的交付方式。我自己编过一台老设备的运维工具直接-static一把梭拷到任何同架构的机器上都能跑不需要在新机器上还原老库环境/opt/gcc-3.3.6/bin/gcc -static -O2 -o oldtool oldtool.c file oldtool ldd oldtool-static让链接器把所有依赖的 libc 代码都打包进二进制file oldtool看输出格式如果显示statically linked说明静态链接成功ldd此时应该提示not a dynamic executable。代价是产物体积比动态版大不少但换来的是从库依赖泥潭里彻底解脱。这里顺便回答一个高频问题在 macOS 下装了 clang 或 gcc能不能直接编出 Windows 的 exe答案是不能。要出 Windows 下的 PE 文件得靠 mingw-w64 这类交叉工具链单独一个 gcc-3.3.6 包没这个能力交叉编译需要配套的目标平台头文件和 binutils那是另一套工程别被“gcc 万能”的错觉带偏。5. 避坑记录装了还是旧版本、configure 失败、cc1 找不到5.1 敲 gcc 还是新版系统就是不认老编译器现象make install完成后在终端里执行gcc -v输出还是系统自带的 gcc 12仿佛新装的 3.3.6 不存在。原因PATH 环境变量里/usr/bin排在/opt/gcc-3.3.6/bin前面另外 bash 对命令路径有 hash 缓存同一个 shell 会话里之前执行过gcc之后就算 PATH 变了它还记着旧路径。解决先跑which gcc看实际路径再用hash -r清空 bash 缓存或者像我这样直接用绝对路径验证hash -r /opt/gcc-3.3.6/bin/gcc -v export PATH/opt/gcc-3.3.6/bin:$PATH which gcc从那以后我在验证环境时都强制走一遍“绝对路径优先”确定不是缓存锅再谈别的。5.2 configure 直接失败C compiler cannot create executables现象执行../configure没跑几步终端出现checking whether the C compiler works... no然后报C compiler cannot create executables全程没有更多线索。原因这个报错八成是宿主系统缺基础库或头文件。老版本 GCC 的 configure 会尝试编译一个微型测试程序如果系统里没有 libc 开发包或者默认头文件路径下找不到stdio.h就会在这步失败。现代精简系统里最容易缺的是libc6-dev。解决先看日志再装包不要瞎试参数tail -100 config.log | grep error apt-get install -y libc6-dev libc6-dev-i386tail -100 config.log能从 configure 日志里捞出真正出错的意图比如fatal error: stdio.h: No such file or directory然后对症装包。这个排查顺序我建议养成肌肉记忆我在银河麒麟这类国产发行版上也遇到过一次原因同样是缺 libc 开发包补上就好。5.3 bootstrap 走到一半崩溃找不到 libgcc 或 crtbegin.o现象make bootstrap进行到 stage2 或 stage3链接阶段报cannot find crtbegin.o或libgcc.a前面 stage1 明明已经成功了。原因这是老 GCC 与新版 binutils 的兼容性问题。GCC 3.3.6 发布时的 binutils 大致在 2.15 版本现代系统里的 binutils 2.3x 对目标文件与链接脚本的处理细节有变化导致部分内建库路径找不到。解决最有效的是把 binutils 也退到同期版本单独装到/opt/binutils-2.15然后 configure 时显式指定../configure --prefix/opt/gcc-3.3.6 \ --enable-languagesc,c \ --disable-multilib \ --with-gnu-as \ --with-gnu-ld--with-gnu-as与--with-gnu-ld告诉 GCC 使用 GNU 汇编器和链接器并让它的内部路径计算基于这个假设。如果你不想换 binutils还有一个折中做法把 stage1 产出的库目录手动指给编译器但这条路调试成本高不推荐新手走。5.4 vscode 一键编译运行报错找不到 cc1 或 cc1plus现象在 vscode 里装了 C/C 插件配置好编译器路径后点“一键编译运行”终端报gcc: error trying to exec cc1: execvp: No such file or directory。原因vscode 的 tasks.json 里指定的编译器是gcc但当前终端环境里 PATH 指向的老版本编译器安装不完整或者它的内部程序路径libexec/gcc/...没有正确暴露。新版 GCC 内部能找到cc1是因为安装路径规范老版本如果 prefix 设置不对cc1 会跑到奇怪的地方去。解决一个是检查 tasks.json 里把编译器写成完整绝对路径{ version: 2.0.0, tasks: [ { type: cppbuild, command: /opt/gcc-3.3.6/bin/gcc, options: { cwd: ${workspaceFolder} }, args: [-g, ${file}, -o, ${fileDirname}/${fileBasenameNoExtension}.out] } ] }另一个更稳妥的办法是给 vscode 的终端设置环境变量GCC_EXEC_PREFIX让 gcc 能定位到内部工具export GCC_EXEC_PREFIX/opt/gcc-3.3.6/lib/gcc/GCC_EXEC_PREFIX是 GCC 搜索cc1、cc1plus、collect2这些内部程序的前缀。它在老版本上尤其重要配好了 vscode 才能真的做到“一键编译”配不好就是这种字符串层面的翻车。5.5 并行编译过猛内存直接爆掉现象make bootstrap -j4跑了一会儿终端卡死然后 OOM Killer 把进程杀了或者直接报virtual memory exhausted: Cannot allocate memory。原因老版本 GCC 编译时内存占用不低每个并行任务都要在内存里撑起整个解析器状态。四核机器上开-j4加上系统本身占用2GB 内存很容易直接触顶。解决我现在的习惯是看内存选并行度1GB 内存用-j12GB 用-j24GB 及以上再考虑-j4free -m make bootstrap -j2free -m先看可用内存以 MB 为单位显示。如果开-j1编译时间太长就用 tmpfs 把源码放内存盘里换点速度这招在机械硬盘的老机器上效果特别明显。6. 与新版 gcc 并存一份脚本隔离环境三组命令验证产物6.1 switch-gcc.sh一键切到 3.3.6 环境老编译器最大的麻烦不是编不出来而是和新版系统环境乱串。我习惯写一个环境切换脚本用到老工具链时 source 一下把 PATH、库路径、内部工具前缀全部绑定用完退出 shell 就恢复#!/bin/bash # switch-gcc-336.sh export GCC336_HOME/opt/gcc-3.3.6 export PATH$GCC336_HOME/bin:$PATH export LD_LIBRARY_PATH$GCC336_HOME/lib:$LD_LIBRARY_PATH export GCC_EXEC_PREFIX$GCC336_HOME/lib/gcc/ export LIBRARY_PATH$GCC336_HOME/lib:$LIBRARY_PATH export C_INCLUDE_PATH$GCC336_HOME/include:$C_INCLUDE_PATH export CPLUS_INCLUDE_PATH$GCC336_HOME/include/c/3.3.6:$GCC336_HOME/include/c/3.3.6/i686-pc-linux-gnu:$CPLUS_INCLUDE_PATH gcc -v这个脚本干了几件事PATH置顶让 shell 优先找到老编译器LD_LIBRARY_PATH让运行时能找到 libstdc.so.5GCC_EXEC_PREFIX解决 cc1 查找问题LIBRARY_PATH与C_INCLUDE_PATH分别解决链接器和头文件搜索路径。脚本末尾实际执行一次gcc -v让你立刻确认当前环境已切换成功。注意这个脚本一定要在当前 shell 里 source 而不是直接执行source ./switch-gcc-336.sh直接./switch-gcc-336.sh会在子进程里改环境退出来什么都没变这是新手最容易踩的一脚。6.2 验证产物别只信 version 输出编译器版本号对了不代表产物就对。我有三组命令每次编完老代码都跑一遍file ./output_binary readelf -h ./output_binary | grep -E Class|Machine ldd ./output_binary | head -5file看二进制的整体格式确认是 32 位还是 64 位是动态还是静态readelf -h读 ELF 头Class字段显示ELFCLASS32或ELFCLASS64Machine字段确认目标架构ldd看动态依赖有没有混入新系统的库。对 C 编译产物我还会用一条命令确认符号修饰规则/opt/gcc-3.3.6/bin/nm -C ./output_binary | grep T | head -5nm -C把 C 符号 demangle 成可读形式。老版本编译器对模板实例的修饰规则和新版不完全一致如果这里显示名和预期相符说明确实是用 3.3.6 的 ABI 编出来的这部分对老库联动时尤为重要。还有一条习惯建议不要用系统自带的 g 去链接老编译器生成的.o文件两边 C ABI 不同链接时十有八九符号对不上。我就吃过一次亏混用之后程序跑到一半崩在某个析构函数里追查了半天才发现是编译器和链接器版本不一致。从那以后我每次涉及老代码都是强制走一遍完整流程先 source 环境脚本再确认gcc -v再编译再file和ldd双验证。这套顺序一次没换过。希望帮到你。本文还有配套的精品资源点击获取