首页 / 资讯中心 / 文章详情

CSAPP Architecture Lab 环境安装详解:依赖编译与排错

CSAPP Architecture Lab 环境安装详解:依赖编译与排错 ★ FEATURED ARTICLE
打开终端进入解压出来的 sim 目录敲下make屏幕滚过几十行编译日志然后一个醒目的 error 砸在眼前——这个场景我见得太多了。做《深入理解计算机系统》对应配套实验的同学往往不是被实验本身难倒而是先被 Y86-64 实验四 Architecture Lab 的环境安装折磨一遍要么缺少 flex 和 bison要么编译报错看不懂要么 tcl/tk 的头文件路径找不到。这篇文章就围绕这个实验的环境安装把我自己在不同 Linux 发行版上编译这套模拟器工具链的完整过程、背后的依赖逻辑、以及踩过的坑系统讲一遍目标是让你从零开始把环境跑通并且知道每一步操作到底在做什么。内容以 Ubuntu/Debian 系的 Linux 环境为主线macOS 和 WSL2 的差异我也会顺带说明。1. 装环境前先想清楚这套工具链到底要解决什么问题很多人拿到实验安装包就一路 make我的建议是相反先花十分钟把工具链的角色搞清楚后面排错能省几小时。Architecture Lab 不是普通编程作业它要求你在硬件描述层面去修改一台虚拟处理器让这台处理器支持新的 Y86-64 指令。教材第四章用 HCL 语言描述处理器“什么信号什么时候有效”而实验包里那套工具链就是把 HCL 这份“图纸”施工成一台能跑的模拟器。这里有一个关键认知环境安装不是“装个软件”而是“构建一条能把 HCL 图纸变成可执行模拟器的流水线”。你以为你在执行 make实际上你驱动的是这样一条链条先由汇编器 yas 把你写的.ys汇编程序编译成.yo目标文件再由指令集模拟器 yis 或者处理器模拟器 ssim、psim 去执行你修改的.hcl文件则通过 HCL 编译器转换成 C 代码最终编译进模拟器。链条上任何一环断了实验都进行不下去。所以安装过程本质上是在校验这条链路上的每个环节是否都能正常工作。1.1 安装工作的最终交付物四个关键可执行文件等编译结束你会在 sim 目录树的各个子目录里看到几个可执行文件它们就是本次实验的“施工队”。我把它们的作用整理成下面这张表建议你先记个印象后面验证环境时会用到。可执行文件所在目录作用yassim/miscY86-64 汇编器把.ys汇编源文件翻译成.yo目标文件yissim/misc指令集模拟器不涉及处理器流水线细节执行.yo文件得到指令语义级结果ssimsim/seq顺序处理器模拟器基于seq.hcl或seq-full.hcl描述实现psimsim/pipe流水线处理器模拟器基于pipe.hcl或pipe-full.hcl描述实现后面做实验时你会反复用 ssim 和 psim 验证自己改写的 HCL 逻辑先用 yas 汇编出.yo再扔给模拟器执行观察寄存器、内存和状态码的变化。安装阶段如果这四个文件都编译成功并且能执行一个最简单的汇编程序你的环境就算真正就绪了。另外部分实验包还会尝试编译seq-sim和pipe-sim这是带图形界面的教学演示版本依赖 Tk 库跟最终提交无关编译不成功一般不影响做实验。1.2 三层编译依赖系统工具、解析器生成器、HCL 编译器把安装拆开看你会发现整个过程是在解决三层依赖。第一层是系统级依赖包括 gcc、make 这类基础工具以及 flex 和 bison 这两个解析器生成器还有 tcl/tk 的开发库。flex 也叫 lexbison 也叫 yacc它们是用来生成词法分析器和语法分析器的老牌工具。实验包里的 yas 和 yis 源码其解析器部分就是用 flex/bison 描述的编译时会自动调用这两个工具生成 C 代码。第二层是实验包自带的工具源码编译比如 misc 目录下的 yas 和 yis编好以后变成可执行文件。第三层是 HCL 编译器与模拟器本体。HCL 编译器读取.hcl文件生成等价的 C 代码再连同模拟器主程序一起编译成 ssim 和 psim。在这个实验里你动手改的是.hcl文件所以这一层是离你最近、也最需要理解的依赖关系。我之所以强调这三层是因为安装报错绝大多数发生在第二层和第三层的交界处。实验包源码写作年代比较早而你现在的 Linux 发行版、编译器和工具链版本比当年新了好几代老代码里依赖旧行为的地方就会暴露出来。理解了这一层你排错时就会明白方向不是去修改自己的实验代码而是让老代码适配新工具链。1.3 系统选型Linux、macOS 还是 Windows这个实验包是为类 Unix 环境准备的最顺滑的方案是用 Linux 发行版Debian 系或者 Red Hat 系都行。macOS 上也能编译核心逻辑一样只是安装系统依赖时用的包管理器命令不同。Windows 原生环境几乎没法直接编译建议直接用 WSL2 或者虚拟机装一个 Linux这也是现在主流做法。动手之前先进终端确认三件事系统是什么版本gcc 装没装make 装没装。一条命令就能看全uname -a gcc --version make --version如果 gcc 和 make 都不存在先把这个最基础的编译链补上。这一步往往被很多人忽略等到执行 make 时报gcc: command not found才会意识到连编译器都没有。基础编译环境不齐后面一切都是空谈。2. 安装前必须搞懂的文件结构与编译逻辑在敲 make 之前我建议你花几分钟把实验包的文件结构看一遍。很多人编译失败后束手无策就是因为不知道某个输出文件该出现在哪里遇到报错也不知道是哪一层依赖出了问题。我把 sim 目录的常见布局和它们的关系展开讲一下。2.1 sim 包目录布局速览拿到手的实验材料通常是一个sim.tar压缩包解压后得到 sim 目录。里面的子目录大致分三类工具源码目录、模拟器目录、示例程序目录。misc目录存放 yas 和 yis 的源码是整个工具链的入口。seq目录顺序处理器模拟器所在位置里面有seq.hcl、seq-full.hcl、ssim.c等文件。pipe目录流水线处理器模拟器所在位置里面有pipe.hcl、pipe-full.hcl、psim.c等文件。y86-code目录预置的 Y86-64 示例程序通常以.ys结尾配套提供.yo目标文件可以用来快速测试环境。ptrace等目录一些辅助教学小工具和本次实验无直接关系。我自己判断目录是否正常时会先看 seq 和 pipe 目录里有没有对应名字的.hcl文件。因为实验核心就是让你在seq-full.hcl和pipe-full.hcl里修改指令的控制逻辑。如果你打开这两个目录发现连 hcl 文件都没有那肯定是包没拿对或者解压不完整。2.2 从 HCL 到模拟器的三层转换这一节是整个理解的重点。HCL 语言描述的是处理器的组合逻辑比如“当某条指令执行时alufun 控制信号到底取哪个值”。HCL 编译器 hcl2c 会把这种描述翻译成一个求值条件的 C 函数集再与模拟器主程序ssim.c或psim.c链接最终形成可执行模拟器。模拟器主程序的工作方式是不断从内存取出指令、编码、调用 HCL 逻辑产生控制信号、执行状态更新如此循环。你可以把 HCL 描述想象成“交通规则”把模拟器主程序想象成“在路上按规则行驶的车辆”。你修改 HCL本质上是修改交通规则而环境安装就是要确保“车辆”能读懂新改写的“规则”。所以在安装阶段验证模拟器能正常执行一个最小编译程序其实就是在确认这条“车辆-规则”通路没有断。2.3 为什么老教程在新系统上容易翻车网上关于这个实验的安装教程不少但很多都是很多年以前写的用的还是那个年代的 Ubuntu 版本、GCC 版本、flex/bison 版本。那时/usr/include/tcl8.5可能是存在的GCC 默认也没有开启-fno-commonflex 生成的代码行为也和现在有差异。所以你现在照着老教程敲很容易出现报错信息完全对不上号的情况。这不是教程作者不负责而是软件生态一直在演进。比如 GCC 从 10 版本开始默认把 C 语言的多个全局符号定义行为改成了严格模式老代码如果头文件里定义变量且被多个源文件引用链接时就会报 multiple definition 错误。这种问题在当年不存在在新系统上却几乎必现。你如果不懂背后的原因就不知道该在 Makefile 里加-fcommon更不知道该去哪里查。所以这篇文里我会把这类高频坑单独列一章附上每个报错对应的解决思路。3. 完整安装步骤实录一次装到能跑通环境准备已经讲透下面开始实际操作。我按照一套典型的 Debian 系 Linux 发行版流程来写你照着执行即可。命令会涉及包管理器操作如果当前用户没有 sudo 权限可以先和系统管理员确认或者改用 root 用户执行。3.1 第一步安装系统级依赖先更新软件源缓存然后安装编译工具和解析器生成器sudo apt update sudo apt install -y gcc make flex bison tcl tcl-dev tk tk-dev逐个解释这些包的作用。gcc 和 make 负责编译整个 C 代码工程。flex 和 bison 负责生成 yas/yis 的语法解析器。tcl 和 tk 是图形界面模拟器依赖的库其中 tcl-dev 和 tk-dev 是配套开发头文件编译时连接 C 代码需要用到。严格来说如果只做核心实验不碰图形界面模拟器tcl/tk 也可以不装但有些 Makefile 会自动探测它们缺少时可能多出额外报错所以我建议一次性全装上。如果你用的是 Red Hat 系发行版对应安装命令大约是yum install gcc make flex bison tcl tcl-devel tk tk-devel单词后缀略有差异。macOS 的话用 Homebrew 安装 flex、bison、tcl-tk 即可但要注意 macOS 自带的 bison 老版本可能会有兼容问题安装时尽量装较新版本。3.2 第二步获取并解压实验材料实验包一般从课程配套资源页或者教材实验资源目录获取文件名通常是sim.tar。建议优先使用官方渠道的原始压缩包避免网上下载的改包引入额外问题。下载完成后放进一个独立的工作目录解压tar -xvf sim.tar cd sim解压后先用ls确认关键文件存在。重点看有没有Makefile以及有没有misc、seq、pipe、y86-code这几个子目录。这一步是低成本的自检能避免把解压错误或下载不完整的问题带进编译阶段。3.3 第三步执行 make 编译整个工具链进入 sim 顶层目录后先执行清理再全量编译make clean makemake clean的作用是删除可能遗留的旧编译产物。实验包如果是在 Windows 上解压再拷贝进 Linux有时文件权限或时间戳会异常清理一下能避免增量编译时出现莫名其妙的缓存干扰。make会进入各个子目录依次编译日志输出会很长。正常情况下它最终会在 misc 下生成 yas 和 yis在 seq 下生成 ssim在 pipe 下生成 psim。如果你只想先编译某一层也可以手动进入子目录执行make比如cd seq make ssim。但第一次装环境时建议直接编译全部让所有问题都暴露出来。3.4 第四步立即验证安装产物编译完成后不要急着庆祝先做一次最小验证。进入 misc 目录查看可执行文件是否存在cd misc ls -l yas yis cd ../seq ls -l ssim cd ../pipe ls -l psim有文件只是第一步还得确认它能真正运行。拿 yas 举例执行cd ../misc ./yas --version如果提示缺少动态库或者段错误说明环境还有问题。更可靠的验证方式是跑一个最简汇编文件我放到第五部分专门讲。这里先确保四个可执行文件都成功生成并且不带参数执行时不会直接崩溃。注意不要跳过验证直接开始做实验。有一次我在一个精简系统上编译时所有模拟器都生成了但一跑.yo文件就段错误回头排查发现是那个系统缺少某个运行时库。先花五分钟验证环境后面能省一下午。4. 高频报错与排查方案五个坑一次摆平这一章是本文的重头戏。环境安装报错翻来覆去就是那几类我把它们汇总成一张速查表再逐个展开讲原理和解决方式。报错现象根本原因快速解法yacc: command not found系统没有安装 bison或命令名不叫 yacc安装 bison必要时建立 yacc 软链接flex: command not found缺少 flex安装 flexmultiple definition of ...GCC 10 及以上默认-fno-common编译参数加-fcommontcl.h: No such file or directory缺少 tcl 开发头文件安装 tcl-dev或修正 Makefile 中的 TCLINC 路径undefined reference to Tcl_CreateInterp缺少 tcl 链接库或库顺序不对安装 tcl 库检查 TCLLIB 参数4.1 yacc、flex、bison 三兄弟的历史包袱这个坑非常经典。实验包源码写在一个 yacc 还是主流叫法的年代Makefile 或源码里可能直接写yacc命令而现代系统安装的 bison 默认不一定提供名为yacc的命令。报错通常是make: yacc: command not found解决方法很简单确认 bison 已经安装如果还需要 yacc 命令可以手动建一个命令行软链接sudo ln -s /usr/bin/bison /usr/local/bin/yaccflex 和 bison 同时还会带来另一个问题新版 flex 生成的解析器代码和老代码的类型声明不完全兼容。比如源码里声明extern char yytext[]而新版 flex 生成的yytext是一个指针编译时就会出现类似conflicting types for yytext的报错。这时候需要把源文件里的声明改成与当前 flex 版本匹配的extern char *yytext;或者在 Makefile 里调整相关宏定义。这一类问题在不同机器上表现不同但思路是一致的安装新工具链就要让它和老代码的声明兼容。4.2 GCC 10 之后的 -fcommon 多重定义问题现代 Linux 发行版很多默认使用 GCC 10 及以上版本这时候最容易遇到的就是链接阶段大量报错/usr/bin/ld: seq.o:(.bss0x0): multiple definition of f_name; sim_main.o:(.data0x0): first defined here根因是 GCC 10 开始默认使用-fno-common编译选项多个源文件如果都包含同一个全局变量定义常见于头文件里直接定义变量链接器就会认为这是重复定义。而实验包这种年代久远的代码恰恰习惯在头文件里直接定义全局变量所以几乎必踩。解决办法有四种我按推荐程度排序在 make 时通过变量覆盖 CFLAGS例如make CFLAGS-Wall -g -fcommon修改各个子目录 Makefile 里的 CFLAGS加上-fcommon。在顶层 Makefile 统一追加 CFLAGS。如果不想改编译选项就去重构代码把所有只在一个文件里使用的全局变量前加上static。但对于做实验的场景改编译参数明显更高效。我推荐第一种方式因为不用改任何源码文件后续清理也方便。如果你发现改了 CFLAGS 还是报 repeat 相关错误多半是其他地方没有同步应用可以进入子目录单独再执行一次带参数的 make。4.3 tcl/tk 头文件或链接库路径对不上图形界面版本的模拟器依赖 Tcl/Tk但实验包的 Makefile 里可能写死了某个版本的包含路径例如/usr/include/tcl8.5。现代系统上这个路径可能根本不存在编译时就会报fatal error: tcl.h: No such file or directory解决思路是先查一下系统里到底装的哪个版本ls /usr/include/ | grep tcl ls /usr/lib/ | grep tcl根据输出结果把 Makefile 里的 TCLINC 和 TKINC 路径改成实际路径。比如新版路径是/usr/include/tcl8.6就对应修改。如果连 tcl.h 都没有那就是上一节说的 tcl-dev 没装先补包装再改路径。链接阶段报undefined reference to Tcl_CreateInterp也有两种常见原因一是没有链接-ltcl库二是库放在链接参数后面导致顺序问题。GCC 链接静态库时对顺序敏感被依赖的库要放在依赖它的对象文件之后。改 Makefile 时注意把库参数放在末尾。4.4 64 位系统与指针相关小坑实验包本身是为 Y86-64 指令集设计的在 64 位系统上通常没有太大问题但老代码偶尔会有指针和整数之间的隐式转换警告。新版 GCC 默认把这些当作错误处理部分场景会失败。如果你看到类似cast to pointer from integer of different size的报错可以检查是不是某些宏定义了不正确的类型或者简单地给编译参数追加-fpermissive或-Wno-int-to-pointer-cast临时消除警告。实验包源码本身一般不会出现严重的 64 位问题这一类更多是提示不用过度紧张。4.5 改 Makefile 的几个干净姿势排错过程中你可能必须动 Makefile。我建议尽量用变量覆盖、命令行参数的方式避免在源码目录里留下太多改动。具体的做法是优先用make CFLAGSxxx或者make TCL_INC-I/usr/include/tcl8.6这类变量传入方式。只有在变量传入无效时才考虑直接修改 Makefile并且只改必要的变量不要顺手改掉编译优化级别。修改前先把原 Makefile 复制一份备份。这样做的原因是实验提交时有些课程会要求附带修改过的文件列表额外改动 Makefile 可能会干扰抽查脚本。5. 环境验证进阶写一个最小 Y86-64 程序并观察运行四个可执行文件都编译完成后你最应该做的是手动写一个极小的 Y86-64 汇编程序完整走一遍“汇编-模拟执行”流程。这个过程能确认整条工具链是真的通的而不仅仅是文件存在。5.1 写一个能跑的 .ys 汇编文本在 misc 目录下新建一个文本文件min.ys内容如下.pos 0 irmovq $10, %rax halt这两行的作用解释一下。.pos 0指定程序加载到内存地址 0 处。irmovq $10, %rax是一条 Y86-64 指令把立即数 10 移动到寄存器 %rax。halt让模拟器停止执行。这是你能写出的最短完整程序非常适合用来验证环境。5.2 用 yas 汇编并解读 .yo 文件在 misc 目录执行./yas min.ys正常情况下会生成min.yo文件。用cat或者编辑器打开你会看到类似这样的内容0x0000: 30f00a00000000000000 | .pos 0 0x0000: 30f00a00000000000000 | irmovq $10, %rax 0x000a: 00 | halt左边是内存地址中间是指令的机器编码右边是对应的汇编文本。从这里可以看到irmovq的编码是0x30后面跟着寄存器字段和 8 字节立即数这正好和教材第四章讲的一致。如果.yo文件内容为空或者报错说明 yas 没有正常工作需要回到前面的编译排查。5.3 用 ssim 或 psim 执行并观察寄存器变化接着用顺序模拟器执行cd ../seq ./ssim ../misc/min.yo输出里会包含 PC 的初始值、寄存器的最终状态、内存状态和状态码。重点观察%rax最终是否变成 0x000000000000000a也就是十进制的 10以及状态码是否为HLT。如果一切正常说明从汇编器到模拟器的整条链路已经彻底打通。psim的执行验证类似cd ../pipe ./psim ../misc/min.yo同样观察寄存器变化。这里顺便可以留意一下 ssim 和 psim 输出格式的差别流水线模拟器还会输出流水线寄存器的状态不过对于一个 halt 程序来说差异并不大。真正开始做实验之后你会在更复杂程序上观察 psim 的流水线冒险处理那时候对环境的理解会更深一层。6. 装好环境后真正动手做实验时的一点心得环境装通之后先别急着改 HCL。我自己的习惯是趁热把原始seq-full.hcl和pipe-full.hcl各复制一份备份因为后续改指令时很容易改乱有一份干净原版能让你随时回退。另外建议先用 yas 去汇编 y86-code 目录里的官方示例比如asum.ys再用 psim 执行一遍观察流水线模拟器的完整输出。这个过程会让你对“HCL 改动会如何影响模拟器行为”建立直觉。我在实际安装中最大的体会是环境问题几乎都是老代码和新工具链之间的兼容性问题而解决这类问题的关键不是死记硬背报错信息而是搞清楚这条编译链路哪里依赖了旧行为。比如知道了-fcommon的作用以后遇到任何老工程的 multiple definition 都能举一反三理解了 flex/bison 的角色也就理解了为什么安装包必须带上这两个工具。最后再说一个小技巧如果你改完 hcl 文件后重新执行make发现模拟器没更新多半是 Makefile 的依赖关系没有把.hcl文件纳入追踪此时手动清理一下再全量编译就能解决。这个坑几乎人人都会遇到提前知道能省不少折腾时间。
阅读完成 · 觉得有帮助?
咨询建站