
简介华中科技大学计算机系统基础课程实验报告围绕计算机内部数据表示与处理展开重点涵盖整数原码、反码、补码与IEEE 754浮点数编码以及移位、按位与、按位或、按位异或、逻辑运算等位操作核心知识点。实验在Linux环境使用C语言完成包含数据表示、二进制炸弹Binary Bombs与缓冲区溢出攻击三个模块数据表示实验要求用有限运算符实现将最低有效位置零、对指定字节取反、字节比较、逻辑与、循环左移、奇偶校验等函数并附详细注释二进制炸弹实验通过逆向分析可执行程序训练汇编阅读与调试排错能力缓冲区溢出攻击实验演示栈溢出覆盖返回地址的原理与防护思路。整个压缩包仅有1个docx文档体积1.6MB报告中包含完整源码、设计思路、实验总结与清晰目录结构适合计算机系统基础、计算机组成原理或安全入门课程的学生参考。已有1385人学习该报告是深入理解数据编码、位运算与系统安全底层的实用材料。1. 计算机系统基础实验报告一门课的实验为什么决定总评成绩提起“计算机系统基础”这门课很多华科学长学姐的第一反应不是教材多厚而是期末前一周还在调实验的血压。这份实验报告之所以值得反复琢磨是因为它不考背诵考的是对位级表示、汇编、链接、进程、缓存这套底层逻辑的真实理解实验成绩通常占到总评三到五成一份证据链完整的报告能把“做出来了但说不清”的六十分拉到八十五分。本文会从一个能落地的视角把这类报告的实验类型、工具链、破题思路和验收避坑拆开讲。无论你是正在修这门课、准备重修还是跨考想提前看门道照着下文走一遍至少能少走一个学期的弯路。血泪经验是实验可以靠运气跑通报告不能靠运气拿分。2. 实验框架全景四条能力线把数据表示和系统编程串成一体华科这门课以 CSAPP 体系为蓝本实验设置在不同学期略有调整但主线始终稳定数据表示、汇编与调试、系统编程、性能优化。这四条能力线不是并列的四道题而是层层递进的关系——先看懂位再读懂指令再理解操作系统接口最后回到性能。下面把每一类实验验证什么、卡在哪里讲清楚。2.1 数据表示实验位运算、浮点格式与第一个门槛数据表示实验通常是这门课的“开门题”也是淘汰率最高的地方。它要求你在限定规则下用位运算实现整数和浮点函数常见题目包括绝对值、相等判断、浮点数乘 2、浮点数转整数。实验的核心约束有三条不能用条件语句、不能定义局部变量数组、不能使用除位运算以外的运算符有些版本还会限制运算符总数。这个限制不是为了刁难人而是强迫你回到数的最底层去理解补码的符号位和数值位如何共同参与运算理解 IEEE 754 浮点数的符号位、阶码和尾数三段位模式如何决定真实数值。新手最容易卡在“凭什么(x ^ mask) - mask能做绝对值”这类问题上。我的建议是别急着看题解先画一张 8 位补码的真值表把x、~x 1、x 31逐行写出来观察负数右移后掩码是 0xFFFFFFFF、正数是 0 的规律。这张表一旦建立后面所有位运算题都会变成模式匹配。遇到浮点题先写一个打印函数把任意 float 的内存按十六进制打出来再人工拆分符号位、阶码、尾数比在脑子里想象二进制快得多。2.2 汇编与调试实验用 objdump 和 gdb 反推程序的真实行为汇编类实验最常见的形态是“二进制炸弹”或“反汇编分析”给一个编译好的程序要求你通过读汇编找到输入口令、拆解程序行为。很多人第一反应是暴力枚举或者用调试器单步碰运气。这个思路能过但学不到东西而且一旦程序加了时间戳或反调试逻辑就会翻车。正确的切入点是先跑objdump -d把程序反汇编出来从main函数的调用关系入手一层层往下梳理函数边界和比较指令再对照汇编里的硬编码常量反推输入的约束条件。这类实验的评分点往往不只是“拆出来”还包括你在报告里能不能画出函数调用关系、指出每个跳转对应的分支逻辑。GDB 在这里的核心价值是验证假设而不是代替思考。比如你推断某条cmpl $0x1a, %eax表示输入值等于 26就可以在断点处打印eax寄存器来验证。汇编实验是后面所有实验的放大器因为缓冲区溢出、性能优化甚至系统编程里最终都要落到“某个寄存器此刻存的是什么”这个基本动作上。2.3 系统编程实验进程、信号与 shell 里的系统调用重走系统编程实验通常要求实现一个迷你 shell支持外部命令执行、路径查找、重定向、管道和前后台任务管理。它比前两类实验更“工程化”因为代码量从几十行涨到几百行评分脚本也会模拟多种合法与非法输入。这个实验真正想考察的是 fork、exec、waitpid 和 signal 之间的关系为什么 fork 之后要用 execvp、什么时候需要 waitpid、收到 SIGINT 时是终止前台子进程还是 shell 自身这些在教材里是几句话在实验里是必须手工处理的细节。最常见的翻车点是僵尸进程。子进程退出后父进程如果没及时 waitps里会残留一堆 defunct 进程。不少人的第一反应是“加一个无限循环 sleep”这会让 grader 脚本直接超时挂掉。正确做法是在信号处理函数里用while (waitpid(-1, status, WNOHANG) 0)循环收割并注意系统调用被信号打断时EINTR的处理。另一个隐蔽问题是管道左侧命令和右侧命令的退出码传递时刻记住 shell 的退出码来自最后一个前台命令而不是第一个。2.4 性能优化实验Cache、流水线与拿数据说话的习惯性能优化实验一般分两种形态一种是给定一个 Cache 模拟器计算命中率另一种是直接优化指定函数的运行时间。无论是哪种核心考察点都是局部性原理——时间局部性对应循环复用变量空间局部性对应按顺序访问连续内存。以矩阵转置为例按列访问原矩阵、按行写入新矩阵的方式会让 Cache 行刚刚加载就被替换调换内外层循环顺序后命中率可能从不足五成升到九成以上。这类实验最看重数据对比。评测系统会给出优化前后的分数或周期数但你必须在报告里解释为什么这个变换、这些参数能生效。比如循环展开为什么选 4 而不是 8过度展开会让指令缓存溢出反而变慢临时变量为什么能提升性能因为减少了重复寻址和内存往返。我的习惯是每做一次改动只保留一个变量重新计时并记录避免“一次改了三个地方不知道是哪步起效”的糊涂账。3. 实验工具链搭建编译、反汇编、调试与版本管理四件套很多实验做不下去不是因为你不会写实验代码而是工具链没准备好。这一章用最小可复现的方式把从零到能写完一份实验报告的工具环境搭起来覆盖编辑、编译、反汇编、调试、版本管理五个环节。3.1 环境准备虚拟机、gcc 和 make一次装齐大多数实验要求 Linux 环境常见做法是在本机装虚拟机或直接用 Windows Subsystem for Linux。系统选 Ubuntu 或 Debian 系即可因为课程评测脚本大多以 Ubuntu 为主。装好后先更新源并安装基础工具sudo apt update sudo apt install -y build-essential gdb git vim安装完成后验证编译工具链是否可用gcc --version make --version gdb --versionbuild-essential 会一次性带来 gcc、g 和 make避免后面前编译缺stdio.h或缺ld的尴尬。gdb 是必装的没有它数据表示实验的位模式验证和汇编实验的寄存器查看都无从谈起。git 可能看上去不是必需但实验代码改错后想回退时它是唯一的后悔药。3.2 编译与反汇编用 gcc -S 和 objdump 把 C 拉回指令级编译是实验的第一步但很多人对 gcc 参数不够敏感。实验课常用的是-O1不是-O0也不是-O2。-O2会把局部变量优化掉导致调试时打印不出值-O0的汇编太过冗长性能实验又测不出真实水平。写一个小文件验证编译链int main(void) { int a 3, b 5; return a b; }gcc -O1 -g -Wall -o test test.c objdump -d test | less-g生成调试信息-Wall打开常见警告二者在实验报告里几乎是必加项。objdump -d反汇编所有可执行代码段找到main后就能看到movl、addl、ret的完整过程。gcc -S test.c可以直接生成汇编源文件适合对比不同优化级别下的指令差异。需要强调反汇编时要区分 ATT 风格和 Intel 风格GDB 默认 ATTset disassembly-flavor intel可以切到 Intel选一种习惯就好。3.3 GDB 调试五个常用姿势解决九成玄学问题调试是实验报告的护城河。所谓“玄学问题”九成是寄存器或内存状态和你预期不一致用 GDB 直接看现场就原形毕露。下面五个命令是最低配break main # 在 main 入口下断点 info registers # 查看全部寄存器值 x/20xw $rsp # 以十六进制查看栈顶 20 个字 bt # 打印调用栈 si # 单步执行一条指令使用流程一般是先break main再run程序停在入口后用info registers看寄存器初值用x/20xw $rsp看栈布局用si慢慢走。x命令的格式值得说清楚x/20xw $rsp表示从$rsp开始、以四字节为单位、十六进制形式打印 20 个值改成x/20wd就是十进制x/20xg是八字节一块。缓冲区溢出实验里返回地址的位置全靠这个命令逐步推进确定数据表示实验里浮点数的位模式也靠它按内存字节还原。3.4 版本控制给“改坏了”留后悔药的最小 git 用法很多同学不需要 git 是因为觉得“代码量不大改坏了重写”。但性能优化实验里你可能会在某个时刻发现三个小时前的一版比现在快 20%没有版本管理就只能凭记忆重敲。最小用法只需要四个命令git init git add . git commit -m baseline: naive version git diff每次实验开始前先提交一个 baseline每完成一个优化步骤就提交一次。git diff在写报告时尤其好用可以清晰看到两个版本之间的代码改动正好对应报告里“方案演进”部分。我一般还会用git stash临时搁置当前改到一半的代码快速切回上一个可用版本跑评测。这不是额外负担是保护血汗的最短路径。4. 三个高频实验的破题思路位运算、栈破坏与访存优化不同学校的实验名称可能不同但核心考察点高度重合。这一章从算法和策略层面拆三个最有代表性的实验方向不贴完整实现只讲能直接迁移到你自己实验里的方法论。4.1 位运算实验从位级逻辑推回整数和浮点的数学性质位运算实验的破题原则是“先数学后位操作”。以绝对值为例补码的绝对值可以拆成两步构造符号掩码mask x 31然后用(x ^ mask) - mask得到结果。当x为正时mask 0结果就是x当x为负时mask 0xFFFFFFFFx ^ mask是取反再减-1等于加 1正好是补码的取反加一。这段逻辑用代码写出来int abs_val(int x) { int mask x 31; return (x ^ mask) - mask; }注意右移是算术右移还是逻辑右移这由编译器决定int类型在有符号数下通常是算术右移。检查符号位是否扩散可以写一个用例分别用正负奇数验证。浮点题建议把 float 转成 int 的位模式再操作运算符限制内多用unsigned进行无符号右移避免符号扩展带来的意外结果。若题目限制运算总数优先优化常量的构建方式比如用0xFF 23构造阶码掩码而不是每个位单独或运算。4.2 缓冲区溢出实验栈布局、返回地址与攻击串构造缓冲区溢出实验的目标通常是用精心构造的输入串覆盖栈上的返回地址让程序跳转到预期位置比如打开某个隐藏文件或调用某个隐藏函数。这里不需要理解高深的漏洞利用只需要理解函数调用时栈的生长方向函数栈帧从高地址向低地址生长局部变量在低地址返回地址在更高地址两者之间隔着旧的栈底指针和可能的对齐填充。实验第一步是用objdump -d查看目标反汇编找到目标函数的地址比如0x4015b4。第二步根据反汇编里的栈分配指令sub $0x48, %rsp算出偏移量0x48即 72 字节那返回地址就位于输入缓冲起始地址加 72 字节处。构造输入串时前 72 字节是填充之后按小端方式写入目标地址\xb4\x15\x40\x00。字符串输入场景要额外注意strcpy会在末尾补0这会影响后续字节需要把攻击串末尾的地址排列调整到不被截断的布局。这类实验报告的高分点在于你画出栈布局图并标注每段偏移来源而不是只说“用脚本试出来了”。4.3 性能优化实验优化等级、循环展开与 Cache 局部性性能优化的思路是有优先级的先改算法再改访存最后才扣细节。以矩阵转置为例朴素实现的双层循环一旦让访存方向与存储顺序相反Cache 命中率会低到个位数。第一步先把循环顺序调整为“按行遍历、按行写入”让每行数据加载进 Cache 后能用完再替换。第二步做分块典型参数是 8×8 或 16×16 的 tile使子矩阵能留在 L1 Cache 内。第三步才是循环展开把步长定为 4 或 8减少循环控制开销。for (i 0; i N; i 8) for (j 0; j M; j 8) for (ii i; ii i 8; ii) for (jj j; jj j 8; jj) dst[jj][ii] src[ii][jj];这里的8不是拍脑袋定的而是 L1 Cache 行大小 64 字节、元素 4 字节相除得到的理想值。如果你不知道目标 Cache 参数就做一组4 / 8 / 16 / 32的实验记录时间画成趋势图写进报告。这个实验里最常见的误区是盲目开-O2以为能“自动优化”但评测系统通常要求固定的优化级别真正的差距来自访存模式而不是编译器。5. 实验报告写作与验收避坑证据链比玄学更管用代码跑通只算完成一半另一半是把运行结果变成可复现、可验证的证据链。评分人看报告时追求的不是“你好强”而是“你的结论我能相信”。下面按报告结构、常见问题、提交前检查三个层面展开。5.1 报告四段式写给评分人看的结构我见过很多实验报告写得像流水账先贴代码、再贴输出、最后写“本次实验我学会了……”。这种报告最大的问题是缺少逻辑链。评分人看一份报告的时间有限建议按四段式来组织实验目的与原理、设计思路与关键代码、实验结果与分析、遇到的问题与解决过程。原理部分不要抄教材只写这个实验验证了什么知识点设计思路部分强调“为什么这样设计”而不是“是什么”结果分析部分必须给出具体数据比如“优化前 12.3s优化后 4.1s性能提升 67%”。实验数据要以表格或截图形式保留。截图要截关键命令和输出不要截整个终端。终端背景色建议调成白底黑字避免打印后截图模糊不清。若评测脚本输出较长优先用重定向保存到文件再做对比./solver trace1.txt result1.txt cat result1.txt这种保存方式在成绩申诉时非常有用因为你可以直接向老师展示“当时跑的原始 log”。5.2 五个常见踩坑现象、原因与解决路径第一坑本地运行正常评测脚本报错。常见原因是可执行文件名和脚本预期不一致或者代码里用了system(pause)一类的阻塞调用。解决方法是先看脚本源码确认预期的输入输出格式再用脚本跑一次最小用例。第二坑GDB 打印变量显示optimized out。原因是编译时开了-O2变量被寄存器替换或者直接常量传播。解决方法是改用-O0 -g重新编译调试版本保留一份-O2用于最终评测。第三坑浮点数输出NaN或Inf。常见于数据表示实验可能是符号位变成 1、阶码变成全 1 时被解析为特殊值。解决方法是打印十六进制位模式对照 IEEE 754 结构逐段检查。第四坑系统编程实验出现僵尸进程和管道卡死。原因通常是没有处理SIGCHLD或管道读端未关闭导致子进程阻塞。解决方法是统一用waitpid(-1, status, WNOHANG)收割并在所有 fork 出的子进程里显式关闭不需要的管道端。第五坑报告截图成绩和最终评分对不上。原因可能是实验版本更新评测标准变了或者截图没有俯视出完整记录。解决方法是提交报告前重新跑一遍全部用例并把log文件一起提交让评分人能复现。5.3 交报告前的自查清单提交前十分钟按下面这个清单过一遍能省掉大量低级扣分第一确认代码能重新编译make clean make要能一次通过第二确认所有测试用例都能跑通包括边界输入第三报告内的代码片段必须和提交的源码一致不能出现“报告里写 A源码里是 B”第四数据截图有时间戳或有命令上下文避免被怀疑是旧结果第五报告文件命名符合课程要求常见格式是学号加实验名。这份清单每次实验都能用把它贴在终端上方比临时翻通知可靠得多。6. 贯穿所有实验的一个习惯先看汇编再动代码最后一个技巧也是我做完所有实验后最想强调的一个习惯遇到任何不确定的行为不要猜编译成汇编看。这个习惯在四个方向都救过我数据表示实验里不确定右移行为看sar还是shr缓冲区实验里不确定返回地址偏移看sub $0x??, %rsp性能优化实验里不确定循环是否被展开看跳转指令密度系统编程实验里信号处理是否正确看中断返回路径。具体流程很简单gcc -O1 -S experiment.c -o experiment.s grep -A 30 main experiment.s如果想知道某一行的 C 代码生成了什么指令就在对应编号行旁注释# LINE 12便于对照。需要看运行时的栈布局再用 GDB 在main下断点执行si一条条走。汇编与源码对照不是装模作样的严谨而是把“我感觉应该是这样”变成“指令明确写着这样”。我自己的一个典型教训是做浮点数位操作实验时连续两个小时在调逻辑右移和算术右移的区别代码看起来毫无问题可结果总是负数偏大。后来用objdump一看问题根源是我把一个unsigned变量赋值给int返回编译器按有符号数做了右移符号位被复制。找到那一刻我才意识到所谓玄学其实都是没有先看汇编的浮躁。从那以后我的实验流程固定成三件事编译、反汇编、调试顺序不乱。希望这个习惯也能让你的实验报告少几次返工多几分底气。本文还有配套的精品资源点击获取