ICS Lab 最终总结
ICS 全部的 Lab 在今日结束了,于是给这一个学期的工作写个总结。不得不说,这堆东西耗费了本学期里最多的精力。在期末季由于阳了影响复习,ICS 以 PF 告终(于是最后一个 Lab 也摆了)。算是个十分无奈的结局,不过考虑到选这门课的目的只是学些东西,倒足以自我安慰。
1. Data Lab
Data Lab 是一系列位运算Puzzle和整数/浮点数运算Trick。第一个Lab实属“技巧性太强”,有的不参考别人的解法几乎没法做。大概记忆是痛苦的寻思和抄答案,以及写的过程是在各个咖啡馆里,其他记不清了。
1 | Correctness Results Perf Results |
2. Bomb Lab
Bomb Lab 是“拆二进制炸弹”。反汇编得到汇编代码,然后通过分析它在干什么,或者用 gdb 在运行时跟踪它在干什么(也以此避免爆炸扣分),来找到每一个phase的答案:比如某处的字符串、跳转表、按某种要求的顺序排序的数等。当时还对程序运行时的内存结构一无所知,于是寻找这些信息是个神奇的过程。而通过每一个 phase 都很令人激动!
后面的 phase 变得很复杂,读代码十分困难(实属废了大劲),但是在运行时查看栈指针附近的内存却能很快地猜出它在干的事,这让我突然意识到 Lab 是一个“实验”。
还有些奇奇怪怪的背景设定和“mua ha ha ha”,可惜不如去年的答案中有“关注嘉然顿顿解馋”和“顶晚人和我会一直陪着你”,今年似乎只有原。
1 | BOOM!!! |
3. Attack Lab
这个更是神奇,要进行缓冲区溢出攻击。分为没啥防备的 Code injection 攻击,以及在有栈随机化、运行权限的控制下搞的 Return-oriented 攻击。要通过溢出覆盖掉原来的返回地址,跳到我们的代码运行,Code injection 就是可以直接把代码塞进栈里,而 Return-oriented 是利用一些别处的代码片段(Gadget)进行“断章取义”然后借助 ret
指令(C3)跳来跳去。
还是在玩汇编、玩机器指令,玩得比较花。像最后一个 phase 甚至加了栈保护者(canary),但还是要通过一个内存操作的漏洞,悄悄地改变它复制的范围,然后通过巧妙测算改变 canary 下面的东西。
写这个 Lab 的时候大概是光棒录制集中的时期。参与食南京大牌档和拍片,然后又匆匆跑回来接着整,对此印象很深刻。
1 | Cookie: 0xff114514 |
4. Arch Lab
这个比较怪。先是写些 y86 汇编,然后给一个 SEQ 处理器添加两条指令(写 HCL),这些都好办。之后是给一个流水线(PIPE)处理器下的数组复制算法优化。方法大概是循环展开、消除加载-使用冲突等等,结果按性能给分。那段时间有点过于忙,处理器那一章是匆忙看完的。后来这个 Lab 的解法看起来很丑,而且没追求满分——满分还要去魔改处理器(是针对程序改处理器,听起来就很离谱!)
搞完这个以后就告别了汇编。
1 | Autodriver: Job exited with status 0 |
5. Cache Lab
它跨越了期中季。“擦车” Lab 是写起来比较有成就感的一个。且这是第一回需要处理命令行参数,建立一个比较完整的命令行应用——一个高速缓存模拟器。自己设计一些操作来模拟 Cache 的行为:查找、替换(LRU策略)等等,摸清 Cache 在做的几件事就可以开写(当然,启动还是依赖了网上的版本),不过 Bug 还是在不易留意之处出现。写它的时候我在外面的自习室,因为一个 Bug 搞到1点都没注意。
后半部分是做一个矩阵转置算法的性能优化,这部分除了简单的循环展开、按 Cache 的大小分块之外,我自己很难想出别的什么,于是又参考了神奇算法。卡着边界得到满点。 令人感叹,这是封校前最后的 Lab。
1 | Cache Lab summary: |
6. Shell Lab
时间变得有点紧,我决定使用 Grace Day!这阵子也比较混乱,学异常处理这一章时我感觉我就需要一个异常处理程序,进入内核态处理完再回来。目的是写一个具有任务调度功能的 Tiny Shell,要能处理 SIGINT(ctrl+c) 一类的信号,很难上手,好在框架比较完整。需要实现的只有具体的运行以及几个信号处理。虽然 writeup 上说应当别急着使用测试程序,但跟据舍友给出的经验,测试用的 trace 是循序渐进的,实现某个功能就能通过某些 trace,于是就按这个顺序来。
感谢某人的 reference。在经历了比以前痛苦但不如之后痛苦的 debug 后,分数的提升很迅速,最后在autolab上是满点(虽然在我这里有时是124/128,因为 race 存在,可能之后会查出来扣掉)。
1 | ...... |
7. Malloc Lab
最大最恶的 Lab!写一个动态内存分配器,重点是堆上内存空间的管理,基本全看性能给分:包括空间利用率和吞吐量(即处理速度)。书上给出了隐式空闲链表的完整实现,简单提及显式链表和分离链表。但想要得分必须实现显式链表,满分则需要改成分离链表。这是一个逐步改进过程,而最大的问题在于 bug 无处不在。也因此,要求我们先写一个检查堆各项指标良好的函数(但这个也可能包含bug)。
我的第一阶段就是和 bug 最斗争,先是极其恶劣的 Segmentation Fault
,来自毫无容错的指针运算(比较草的是,我的sigsegv几乎全出在了checkheap
函数里);然后是链表的行为完全不符合设想,又是一通狂改,狂暴 printf 输出。当我终于调好了显式链表,拿到了80+,这天我离开了学校。
又实现了几个优化,舍友在这些优化的情况下达到了88。然而我提交以后竟只有71,是吞吐量比本机低上许多。当时我正在病发编程,我一句一句地寻找性能瓶颈,不论怎么调都对这个B性能没有改进。我麻了,在跟它较劲的第二天,它的 ddl 和后面的 Proxy Lab一起改为了1.6。于是放弃搞它。
在期末考试过后,再次拾起,又经历了一阵子相同的无用优化。那时我已经认定了和舍友的版本相比,我在一个 trace 上表现极差。我分析它,却整不出个所以然来。直到决定投向玄学调参,我通过把chunksize
改成1.5甚至2倍,竟一路把分数从71调到83;接下来修改寻找空闲块的find_fit
策略,由 first-fit 改成 前几个-fit,提高至87;再修改时写错了一个地方,结果竟达到 95。试图修改,可是改不得——实际上我以为写错了而毫无作用的,其实是对不同长度的链表做了不同的事。
于是收工。离ddl不远了。
1 | Using default tracefiles in ./traces/ |
8. Proxy Lab
PF之后摆了(Malloc 主要是不甘心)。我本来打算留2天给这个Lab,但5号也摸了,于是只留了一天。这个 Lab 要实现一个代理服务器,再加入多线程和 Cache,还有些针对 Real Page 的鲁棒性等(不太相干的内容)。我实现了基本功能,加上几句就有了线程化。之后因为没时间写 Cache 了,Lab就此结束,拿了63/90 。由于代码与书上给出的都比较近似,还算好写,但听说针对 Real Page 的边际成本很大。
1 | Basic: 40 / 40 |
后记
在大概 shell lab 进行时,class machine 经历了一次更新,更新到 ubuntu 22。而我在用的 wsl 还是 ubuntu 20。在 make
时,发现 GLIBC
的版本不匹配,于是尝试更新。当我正完成安装时,才在网上看到 GLIBC
不可轻易升级的文章——因为它可能导致 Linux 的内核紊乱。GLIBC
是最底层的C语言库,而内核基于C,所有功能都与它有关。当安装到最后报出两条错误,我意识到惨案发生。在那之后,我无论输入什么指令,得到的响应就只有那两条错误了。于是这个 linux machine 被我玩坏了。不过对它没多少留恋,继而重装了 ubuntu 22。
一些不得不尝的经历啊。