
简介面向天津大学计算机系统基础课程的实验资源覆盖位操作、整数与浮点数、拆弹专家、代码注入攻击和高速缓存五个主题适合计算机专业学生对照课程进度开展上机实践、理解底层运算与系统安全机制。资源共35个文件以14份PDF实验指导书、6个C源代码、4份DOC实验报告为主体另含asm汇编、bomb/ctarget/rtarget等可执行靶标及说明文档压缩包整体约112.48MB目录按实验编号清晰分模块存放。资料已吸引919名学习者下载参考价值较高。具体内容包含各实验指导书、学生完成的代码与报告样例以及高速缓存模拟器csim.c、拆弹实验phase文件等有助于快速掌握实验要求、规范报告书写并排查常见问题是天津大学该课程上机实验与复习备考的实用配套资料。1. 这门实验课到底在做什么先搞懂“计算机系统基础”是什么刚接触“计算机系统基础实验”的新手第一次翻开实验手册时几乎都会对着屏幕发一会儿呆这门课到底要我们干什么说的直白一点它就是配合《计算机系统基础》这门理论课来上的一门上机实践课。理论课讲的是“计算机内部是怎么运转的”实验课则逼着你亲手把那些运转过程实现一遍。你之前写C语言写完交给编译器就结束了这门实验课会把你丢进编译器后面那个黑匣子里让你亲眼看看数据在内存里怎么存、程序在CPU上怎么跑、系统调用背后发生了什么。它解决的问题是学了编程却没学过“程序为什么能跑起来”的人缺少的那块底层拼图。适合刚学完C语言、对底层好奇、想提升调试能力的零基础新手。读完这篇你不会再发怵至少知道第一步该落在哪。2. 为什么非得动手做实验黑匣子里的计算机没那么神秘2.1 你之前学C语言可能只用到“表面”回想一下你第一次写C语言的经历定义一个int a 1;然后a 1你理所当然地算出了2。但你有没有想过一个问题——这个a在内存里到底长什么样是一个比特一个比特地存还是一次性占一整块空间为什么int是4个字节而不是5个a 1在CPU里执行的时候到底经历了哪几个步骤大部分同学学到C语言的时候都会把这些细节当成“编译器的事跟我没关系”。确实你照样能把程序写出来跑出结果。但这种“黑匣子式”的学习方式到了学操作系统、学计算机组成原理的时候就会卡壳。你听不懂“栈帧”理解不了“内存对齐”更不明白“缓存命中率为什么会影响到程序快慢”。因为这些东西全在语言表面之下而你没有亲眼看过它们。“计算机系统基础实验”这门课做的事情就是把这些表面之下的东西掏出来摊在你面前让你亲手摆弄。它不是让你背概念而是让你像拆玩具一样把一个程序的运行过程拆开看看里面的齿轮是怎么咬合的。2.2 四个经典实验模块从数据到系统全程动手这门实验课通常设置四个经典的上机模块一个比一个深入也一个比一个“好玩”。为了方便你建立整体认识我用一张表概括它们分别练什么、对应哪块底层知识实验模块你要做什么对应底层知识数据表示实验只用手写位运算实现加减乘除、逻辑运算补码、浮点数、位运算规则二进制炸弹实验阅读一段汇编代码找出程序隐藏的密码汇编语言、反汇编、调试器使用缓冲区溢出实验人为制造一段越界写入理解黑客攻击原理函数调用栈、内存布局、指针缓存与性能优化实验调整代码访问顺序让程序跑得更快Cache原理、局部性原理、性能分析为什么把数据表示实验放在第一个因为它最容易上手。这个实验只要求你写几个C语言函数甚至不用系统调用不涉及进程和内存纯粹考察“一个整数在计算机里到底是怎么存的”。很多新手从它开始建立“原来底层长这样”的直观感受。到了二进制炸弹实验事情就开始变有趣了。实验会给你一个已经编译好的程序你不知道密码是什么必须用反汇编工具把它拆开一点一点猜出它的逻辑。这已经不是“做题”了更像是在解谜。我第一次做这个实验的时候花了整整一个下午看汇编代码最后靠一句printf的调用规律找到了答案那种成就感比期末考试拿高分还强。2.3 为什么实验比看书更重要强制动手看书学计算机系统有一个很难避免的问题你会产生“我懂了”的错觉。书里把补码规则写得再清楚那也是作者的思路不是你的思路。你合上书过了三天再让你自己实现一个位运算的异或函数x ^ y限制条件是只能使用和~两种操作符。你大概率会卡住。不是因为笨是因为“看懂”和“会写”之间隔着一条巨大的鸿沟。实验课就是用来填这条鸿沟的。它的残酷之处在于你写不出来测试脚本就报错电脑不会因为你看懂了教材就给你同情分。你必须真刀真枪地敲出代码跑通过测试才算是真的掌握了这个知识点。这种“被逼着动手”的学习方式比你单方面看书效率高得多。从工具使用的角度来说做实验也是在提前训练你以后工作要用的技能。调试器GDB、编译工具GCC、自动化构建Makefile这些工具在学校里很少有人系统教你但你在实验课上会天天用。等到你以后做项目、写工程代码的时候会发现这些工具比任何IDE插件都底层、都通用。2.4 这些实验对考试、面试和真实开发有什么用我在前面说过这门课的底层知识对你后续学操作系统、学编译原理很有帮助这一点是长期价值。短期价值也看得见很多技术面试喜欢问“int为什么是4字节”“一个函数调用过程中栈是怎么压入和弹出的”“为什么数组遍历按行比按列快”这些问题没有一个不落在这门实验课的射程之内。不过我建议你不要抱着“为了面试”的心态去做实验那样会很痛苦。更好的心态是把这些实验当成“给未来的自己积累直觉”。你以后写代码遇到程序崩溃第一反应是怀疑越界还是怀疑指针这种直觉就是从缓冲区溢出实验里练出来的。你以后写一个性能敏感的循环会不会下意识考虑数据的访问顺序这种习惯就是在缓存实验里养成的。做这些实验最好的方式是留好每一版代码和笔记。我到现在还记得我保留的第一份实验报告上面画满了当时看不懂的栈帧图后来每次复习都看一遍收获一次比一次多。2.5 我第一次学的时候也觉得这是玄学说句实话我刚拿到实验手册的第一周整个人是懵的。身边没几个人能完全靠自己把环境搭好大家互相讨论、互相帮忙才勉强跑通了第一个测试用例。那时候我觉得这门课太难、太“玄学”甚至怀疑自己适不适合学计算机。后来我才发现问题不是我的能力而是我把顺序搞反了——我急着去写代码却不先搞清楚实验包的结构、不先读README、不先理解评分脚本的规则。后面的章节我会带你按正确顺序一步步来把这些“玄学”变成一清二楚的操作流程。3. 迈出第一步搭好环境、装好工具跑通第一个小例子3.1 你需要一个Linux环境虚拟机是最省心的选择这门实验课的要求理论上说你在Windows上装一个C语言编译器也能完成一部分实验但越往后越别扭。因为实验课的评分脚本往往是Linux下的Shell脚本你在Windows上跑不了缓冲区溢出实验要观察程序崩溃的行为Windows的崩溃机制和Linux不同调试器方面实验课上讲的是GDB它在Windows下用起来也很费劲。所以我建议你直接使用Linux环境这是最稳的一条路。怎么获得一个Linux环境呢新手最推荐“虚拟机”这条路。笼统地说就是你在自己的电脑上装一个虚拟机软件然后在虚拟机里装一个Linux操作系统。它跟你现在用的Windows、macOS是互不影响的你可以在里面随便折腾折腾坏了重装就行不会影响你日常使用的电脑。这也是我强烈推荐虚拟机的原因——试错成本极低。配置虚拟机的时候有几个参数值得注意。内存给个4GB以上比较舒服太低的话编译稍大一点的文件就会卡。硬盘建议分配40GB以上因为实验过程中会装各种软件包、产生很多中间文件。网络模式不用改默认的“网络地址转换”模式就够用了。装好Linux系统之后建议先跑一遍sudo apt update sudo apt upgrade把系统更新到最新然后再装后续要用的开发工具。3.2 三个必备工具编译器、调试器、构建工具做这门实验课你不需要先学一堆工具先掌握三个就够用。第一个是GCC它是C语言的编译器负责把你写的.c源代码变成可执行文件。第二个是GDB它是调试器你可以让程序在你指定的那一行暂停下来看看每个变量的值是什么。第三个是Make它是自动化构建工具实验包里的评分脚本会调用它来编译你的代码。这三个工具分别解决你三个不同的痛点编译报错了你要会看懂GCC的报错信息程序崩溃了你要会用GDB找到崩溃位置项目文件多了你要会用Makefile一键编译而不是手动敲一堆命令。安装命令如下在Linux终端里执行sudo apt install build-essential gdb makebuild-essential这个包里包含GCC和相关编译工具gdb是调试器make是构建工具。装完之后可以用下面这段命令验证是否安装成功gcc --version gdb --version make --version如果每个命令后面都能输出版本号说明环境就绪了。我第一次装的时候就是靠这三条命令确认自己没装错。3.3 第一个可运行的小例子从写代码到调试环境准备好之后我们写一个简单的C程序完整走一遍“写代码→编译→运行→调试”的流程让你对实验课的操作节奏先有一个手感。先建一个工作目录然后创建源文件mkdir ~/lab0 cd ~/lab0 vim hello.c用你熟悉的文本编辑器在里面写下下面这段代码#include stdio.h int main() { int a 3; int b a 1; printf(b %d\n, b); return 0; }这段代码本身很简单就是定义变量a计算a 1存入b然后打印b的值。我们特意留出a和b两个变量是为了下一步用GDB观察它们的值。保存退出之后用GCC编译gcc -o hello hello.c这里-o hello的意思是“把编译出来的可执行文件命名为 hello”如果不写-o默认会生成a.out这个文件名。编译通过后运行它./hello在Linux当前目录下运行程序必须写成./hello前面的./表示“当前目录”下的程序这是Linux的一条安全约定。看到屏幕上打印b 4整条链路就通了。接下来我们用GDB看看程序内部发生了什么gdb ./hello进入GDB交互界面后执行三条命令(gdb) break main (gdb) run (gdb) nextbreak main是在main函数入口打断点让程序跑到这里暂停。run让程序开始运行它会停在你设置的断点处。next表示执行当前行并停在下一行。此时我们可以查看变量的值(gdb) print a (gdb) print b第一次执行print a的时候a的值应该已经是3因为next已经执行了int a 3;这一行但b可能还没有被赋值所以会显示一个随机值。这说明程序是一行一行按顺序执行的。这就是调试的基础逻辑让程序暂停你就能偷看到内部状态。3.4 第一次编译报错不要慌看懂三行报错信息新手第一次编译报错最容易干出“对着代码反复看也看不出问题”的事情最后只好删了重写。其实编译器已经告诉你它在哪里遇到了什么问题你只需要学会看它的提示。假设你把第三行写成了这样int a 3编译的时候GCC会输出hello.c:4:5: error: expected ‘;’ before ‘int’这条信息可以拆成三部分来看hello.c是文件名4:5表示“第4行第5个字符”附近error:后面则是对问题的描述。这里的意思是编译器在等你写一个分号结果等来了int于是它建议你回去看第三行是不是漏了分号。我给你的建议是报错之后先看行号再去数的前一行。因为C语言分号属于上一句编译器常常是在下一行才报出来。还要学会使用-Wall参数重新编译一次gcc -Wall -o hello hello.c-Wall的意思是“打开全部警告”它会把代码里潜在的问题以 warning 的形式列出来。很多实验代码虽然能编译通过但带着一堆警告去交作业可能导致评分脚本检测异常。所以从第一步开始就养成没有错误、没有警告的习惯能给你省下后面大量排查的时间。4. 第一个正式实验从读懂实验包到跑通数据表示上机4.1 先读懂分发到的实验包等到真正开始做第一个正式实验时你会拿到一个压缩包通常是.tar格式。你可能会迫不及待地想打开里面的代码开始改但这里我要劝你先别着急。拿到实验包的第一步永远是先看它的目录结构和说明文件。在Linux下解压它们的命令是tar -xvf lab1-handout.tar cd lab1-handouttar -xvf里面的参数含义是x表示解压extractv表示把解压的文件列表显示出来verbosef表示后面跟的是文件名file。解压之后先用ls -l看看里面都有什么再用cat README把一个叫README的文件从头读到尾。README会告诉你这次实验的规则和目标比如要填写哪些空、评分脚本怎么跑、有什么禁止事项。很多同学一上来直接改代码结果改到一半才发现自己漏看了“不允许使用循环”这种关键约束前功尽弃。我第一次就拿着一道题闷头写了一个下午位运算最后才发现题目要求里写明了“只能使用和~”但我的代码里用了直接违规。后来每次拿到实验包我都强制自己先读README、再动键盘。4.2 读懂Makefile和评分脚本实验包里通常会有一个Makefile文件它的作用是告诉make命令怎么编译你的代码。比如你在终端里直接敲make系统就会按照Makefile里的规则生成可执行文件敲make clean则会清理掉所有编译产生的临时文件让实验包恢复到初始状态。评分时通常会用make clean make保证代码被重新编译一次避免旧的可执行文件干扰结果。评分脚本一般以.pl、.sh或.py结尾它做的事是这样的先用make编译你写的代码然后运行一组编译好的测试程序用各种输入值调用你写的函数逐个比对输出结果。全部正确就是满分有一个不对就会扣分。你可能想偷偷看一下评分脚本的源码这完全没问题脚本本身通常不加密。但我也要提醒你评分的测试数据是一组固定输入你针对它去“特判”通过从学习角度没有意义而且换一批输入就挂了。老老实实把函数本身的逻辑写对才是最省力的通关方式。4.3 第一个函数怎么写位运算入门数据表示实验里最经典的一道入门题是让你实现“异或”用代码表达x ^ y但限制条件是你只能用和~。这个限制条件是故意的它逼着你去思考异或的本质关系而不是直接抄语言内置运算符。下面这个解法是经典思路/* bitXor - x^y using only ~ and */ int bitXor(int x, int y) { return ~(~x ~y) ~(x y); }我们一行一行拆开看。~x是取反把x的每一位0变1、1变0。~x ~y表示“x和y都为0的那些位”因为只有两个都为0时取反后都为1与运算结果才是1。对这一步整体取反就变成“至少有一个为1或都为1的位”。然后再看x y这表达的是“x和y同时为1的位”。最后用~(x y)表示“不同时为1的位”再和前面“至少有一个为1”的结果做与剩下的就是恰好一个为1、另一个为0的位——这正是异或的定义。新手不用强求自己一眼看懂每一步推导你可以拿x1, y3这种具体值拿笔在草稿纸上把每一位的二进制写出来手动走一遍这个表达式走完一遍你就彻底明白了。这种“用纸笔推演”的方法是我做实验时屡试不爽的笨办法。4.4 用测试脚本验证看到“全部通过”那一刻写完函数之后保存好文件回到终端执行make clean make的作用前一个命令成功之后才执行后面一条保证“先清理再编译”。编译器没有任何输出则代表编译成功。接下来运行评分./dlvdlv是这次实验专门用来打分的程序它的名称可以理解为“数据实验验证器”Data Lab Validator的缩写。它会输出一份评分报告如果全部正确你会看到类似于这样的信息Score: 100/100我印象很深我第一次看到这行的时候整个人从椅子上跳起来了。在这之前我连续三个晚上调试失败最接近的一次是87分有一个函数的边界用例始终不对。最后发现原因特别蠢我在判断负数时直接用了x 0但题目明确要求“只能使用位运算”任何关系比较运算符都是禁止的。改成x 31取出符号位之后瞬间就通过了。所以如果看到Score不是满的请把失败的那一行函数名记下来对照约束条件逐一检查。4.5 一个实验的终点是下一个实验的起点数据表示实验带给你最核心的价值是让你理解“数据在计算机里是二进制的”。这个直觉会在后面的每个实验里反复用到。大概做第二个实验——二进制炸弹的时候你要读汇编代码看到寄存器里存的就是一串十六进制数如果你懂得补码和位运算就能快速判断某个值到底是整数还是地址如果你不懂就只能干瞪眼。所以第一个实验不要急着赶进度宁可多花两天把它彻底吃透也不要赶鸭子上架。扎实做完它的收获相当于你提前预习了后面三个实验的共同基础。我自己的节奏是先花一天时间认真读README、弄懂Makefile和评分规则再花两天实现所有函数、反复调试直到满分最后花半天时间写实验笔记、把每道题的位运算推导留在纸上。这套流程走得慢但走到后面越来越快后面几个实验反而没有第一个那么吃力。5. 新手最容易踩的坑五种典型翻车现场的排查与修复做实验的过程本质上就是踩坑、填坑的过程。这里我把我自己和身边同学踩过频率最高的五种坑整理给你每一条都按“现象→原因→解决”的顺序来说方便你在翻车的时候对照着排查。5.1 现象一编译报错但代码“看起来”没问题现象你盯着编辑器里的代码觉得每个括号都是配对的、每个分号都写上了可是make一执行编译器照样报错。最气人的是报错的行号指向的代码你反复看都“没有毛病”。原因最常发生的其实是三种情况。函数名拼写错了比如把printf写成了print字符串里的引号没有闭合导致编译器把后面所有代码都当成了字符串还有一个特别隐蔽——用了中文输入法打出了全角分号或括号。这些一眼看过去几乎一样但编译器把它们当作完全不同的字符。解决编译报错时先看行号不要看整篇代码。报错的那一行如果看起来正常就检查它的上一行因为很多错误是上一行遗留下来的比如上一行漏了分号。然后再用gcc -Wall重新编译让所有警告全部暴露出来。如果怀疑是全角字符直接把那一行删掉重新敲一遍。5.2 现象二make之后冒出一堆“undefined reference”现象编译过程很快结束但是在最后链接阶段蹦出来几十行报错里面反复出现undefined reference to function_name这样的字样。你明明在代码里实现了这个函数但它就是不认账。原因这个报错的意思是“链接器找不到这个函数的定义”。最常见的原因是函数名拼写和头文件里的声明不完全一致或者你把函数的实现放在了另一个源文件里但Makefile的编译列表里没包含那个文件。解决看到undefined reference时先记住后面跟的函数名然后去头文件和源代码里搜这个名字。对比一下是不是大小写不同、参数列表不一致。新手最常见的错误是接口要求函数名为bitXor你在代码里写成了bitxor或者要求参数是int x, int y你写成了int a, int b但函数签名本身不匹配。这种错误用肉眼检查很难看出来最好的办法是搜出来直接对比着看。5.3 现象三测试脚本一直报错但自己手工测试都是对的现象你写了一个函数自己写了个main函数调用了一下输出结果完全正确。可一跑评分脚本这个函数就是被标记为失败。你会觉得评分脚本是不是坏了。原因绝大多数情况下评分脚本是好的问题出在你的代码违反了题目约束。数据表示实验最典型的约束是“禁止使用循环”“禁止使用关系运算符”“禁止使用条件语句”。你可能写着写着顺手用了for循环或if这些在编辑器里完全看不出来但脚本专门扫描了语法树一旦发现就直接判负。解决把题目要求再看一遍把你写的每一条语句列出来对照可以使用的操作符清单一张纸一样一条条勾掉。这种检查很笨但是极其有效。当年我那个“负数判断”的问题就是在这样逐条对照时发现的——我用if处理了一个边界分支而这一句直接让整题得分归零。记住测试脚本的检查标准往往比你想象的更严格。5.4 现象四一运行就Segmentation fault现象程序编译通过评分脚本一运行弹出来一行Segmentation fault整个测试直接中断。这在缓冲区溢出实验和后续涉及指针的实验里尤其常见。原因段错误的本质是“程序访问了不属于它的内存”。常见诱因包括数组越界访问、指针没有初始化就使用、malloc分配之后忘记做空指针检查、字符串没有结尾符导致strlen一直读到未知区域去。解决段错误最忌讳猜一定要用调试器。执行gdb ./程序名然后输入run程序崩溃后输入bt。btbacktrace的缩写会打印出函数调用栈直接指出崩溃发生在哪个函数、哪一行。看到行号之后回去检查那一段的指针和下标。比如你发现崩溃在arr[i] ...那就立刻去看i的值是不是越过了数组长度崩溃在strcpy就去数一数字符数组有没有留够结尾符的位置。5.5 现象五代码写好了结果没保存、没备份现象你熬了一个晚上终于把实验所有函数都写完、调试到满分。第二天早上开机发现昨天的工作全没了或者只剩下一个残缺的旧版本。原因实验机房的电脑可能做了还原设置重启之后所有文件被清空或者你自己的虚拟机磁盘空间满了写入失败还有一种最常见——用一个固定的实验目录连续几天在里面编辑文件后来某一次不小心把源码覆盖掉又没保存旧版本。解决我吃了这个亏之后养成了一个雷打不动的习惯每次离开电脑前至少做一次版本提交。最简单的方式是这样cd ~/lab1 git init git add . git commit -m 完成数据表示实验全部函数git init会在当前目录初始化一个版本仓库git add .把当前目录所有文件加入暂存区git commit则把这一整份内容永久保存下来。以后不管代码改成什么样只要执行过这一步你随时可以回到这个版本。不要嫌麻烦它花不了十秒钟但它是我能给你的所有预防针里最管用的一针。从那以后我每次结束实验前都强制自己走一遍这条命令再也没有丢过东西。6. 全部做完之后下一步学什么、怎么继续提升6.1 建议一把每个实验都整理成一份复盘笔记做完实验不要急着删除代码也别以为分数是满分就万事大吉。更值得做的是把每个实验沉淀成一份笔记写清楚三件事这个实验到底模拟了什么底层机制、中间卡在了哪一个技术点上、最后靠什么办法解决的。不用写得很长三到五段就够但一定要用自己的话说清楚。半年之后你开始准备面试或考研复习时再翻出这份笔记你会发现太多考题就是当年实验里踩过的坑。比如缓冲区溢出实验里你亲手制造的段错误其实就是面试官想问的“栈溢出攻击”的原型缓存实验里调整循环顺序带来的性能变化就是面试题“为什么要考虑局部性原理”的答案。6.2 建议二按这个顺序继续补底层知识计算机系统知识的学习是有顺序的建议你按下面这条路径往下走先巩固汇编语言能看懂mov、add、call、ret这些基本指令然后学习链接过程搞清一个程序从源代码到可执行文件之间发生了什么接着学内存管理理解虚拟内存、栈和堆的分工最后再碰并发与线程这个时候你已经具备足够的底层基础读起来不费力。找一本经典的计算机系统教材把实验里没展开的章节补读一遍。读的时候带着实验课上的问题去读比如“我之前写的那个位运算跟加法器电路有什么关系”一旦你带着问题读书效率就是被动的两三倍。6.3 建议三把实验代码改造成自己的小工具箱最后给你一个具体的技巧第一个实验里写出来的那些位操作函数别让它们躺在实验报告里吃灰。你可以把它们整理成一个头文件比如bit_utils.h放上注释规范命名以后写算法题或做其他项目时直接调用。如果面试时被问到底层原理你可以顺手写出bitXor的实现并说明它的推导过程这本身就是面试官想看到的“能讲清楚底层的候选人”。我第一次做完实验的时候根本没想过这些代码还能复用。直到后来有一次写算法题需要快速判断一个整数是不是2的幂次我第一反应就是x (x - 1)那一刻我才明白实验课上练出来的直觉真的会在你意想不到的地方帮你一把。希望帮到你。本文还有配套的精品资源点击获取