恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
HNU计算机系统大作业:x86-64/Linux下系统级编程实战指南
首页
资讯中心
/
HNU计算机系统大作业:x86-64/Linux下系统级编程实战指南
HNU计算机系统大作业:x86-64/Linux下系统级编程实战指南
发布时间:2026/10/14 21:49:40
简介本资源是湖南大学《计算机系统基础》课程的大作业完整实现包面向计算机专业本科生及系统级编程初学者聚焦x86-32平台下的汇编与C混合编程、内存管理、指令执行与系统调用等核心能力训练。压缩包共634个文件主体为186个.c源码与233个.h头文件辅以34张img示意图、15个mkf构建脚本及大量测试用例如bubble-sort、matrix-mul、fib、prime、movsx、32_keyboard_inline等涵盖从基础算术运算、字符串处理、排序算法到键盘中断响应、浮点模拟、NEMU仿真适配等典型实验模块另有score_*系列评分文件与log、md说明文档体现完整提交与评测流程。包体大小45.28MB结构分层清晰便于按实验单元展开学习与调试。目前已有1206人下载学习可直接用于课程复现、实验参考、代码比对与系统级编程能力进阶训练。1. 这不是普通课程作业HNU计算机系统基础大作业本质是一次x86-64/Linux环境下的“系统级手撕实战”如果你刚打开hnu计算机系统作业-计算机系统基础课程大作业.zip以为只是改几行C代码、跑个gcc就交差——那恭喜你大概率会在第3天凌晨两点对着segmentation fault (core dumped)发呆或者在objdump输出的几百行汇编里迷失方向。这不是《程序设计基础》的延申而是用C、汇编、链接脚本、GDB和Linux内核接口亲手把一个“裸奔”的小程序喂养成能和操作系统对话的合格公民。它覆盖从.c到可执行文件的完整生命周期预处理→编译→汇编→链接→加载→运行→调试核心挑战在于绕过glibc封装直面系统调用号、栈帧布局、ELF节区控制、寄存器保存约定——比如要求你不用printf而用write()系统调用打印字符串要求你手动构造栈帧调用exit()甚至要求你写一段纯汇编函数被C代码安全调用。适合人群非常明确已完成《C语言程序设计》《数据结构》正啃《深入理解计算机系统》CSAPP第3、7、8章且愿意为搞懂callq指令后RIP怎么变、%rbp为什么必须对齐16字节而反复单步调试的硬核学习者。它不教“怎么用”只逼你回答“为什么必须这样用”。2. 从解压到可执行四步构建最小可信运行环境这个压缩包绝非单个源码文件而是一个精心设计的分层验证体系。解压后你会看到典型结构src/C/ASM源码、asm/独立汇编模块、linker/自定义链接脚本、scripts/构建与测试脚本、tests/输入输出校验用例。下面四步是所有后续操作的地基跳过任何一步后面必翻车。2.1 环境确认为什么必须用x86-64 Linuxglibc版本是隐形门槛提示不要在WSL1、Docker默认镜像或Mac上尝试WSL1内核模拟不完全ptrace行为异常Docker常精简glibc缺失libdl.so导致动态链接失败Mac的Mach-O格式与ELF二进制不兼容。先执行严格检查# 必须同时满足以下三条件 uname -m # 输出必须为 x86_64不是 aarch64 或 i686 cat /etc/os-release | grep PRETTY_NAME # 推荐 Ubuntu 20.04 / CentOS 8 / Debian 11 ldd --version # glibc ≥ 2.31CSAPP实验环境最低要求低于此版本__libc_start_main符号可能不可见若ldd --version显示2.27如Ubuntu 18.04立刻升级系统或换容器。原因作业中start.S依赖__libc_start_mainGLIBC_2.2.5但旧glibc未导出该符号版本链接时会报undefined reference——这是血泪经验别问我是怎么知道的。2.2 编译链配置用gcc -no-pie -z norelro禁用现代防护还原经典CSAPP环境现代Linux发行版默认开启PIE地址空间随机化和RELRO重定位只读但作业要求你直接操作.got.plt、修改全局偏移表——这些区域在PIE/RELRO下被标记为不可写。必须显式关闭# 在Makefile中强制覆盖默认flag CC gcc CFLAGS -m64 -O0 -g -no-pie -z norelro -fno-stack-protector -z execstack # 关键参数解释 # -m64 → 强制生成64位代码避免默认32位 # -no-pie → 禁用位置无关可执行文件否则入口地址随机无法硬编码跳转 # -z norelro → 关闭RELRO使.got.plt可写用于劫持函数指针 # -fno-stack-protector → 关闭栈保护否则无法演示栈溢出原理 # -z execstack → 允许栈执行用于注入shellcode场景部分题目需要验证是否生效readelf -h ./build/program | grep Type # 应输出: EXEC (Executable file)非 DYN (Shared object) readelf -l ./build/program | grep GNU_RELRO # 若无输出说明norelro生效2.3 汇编模块接入.S文件如何被C代码安全调用ABI约定是铁律作业中常有my_strlen.s需被main.c调用。关键不在语法而在调用约定ABI。x86-64 System V ABI规定参数传递前6个整数参数依次放%rdi,%rsi,%rdx,%rcx,%r8,%r9返回值%rax调用者保存寄存器%rax,%rcx,%rdx,%r8–%r11被调用函数可随意改无需恢复被调用者保存寄存器%rbp,%rbx,%r12–%r15被调用函数若使用必须在返回前恢复my_strlen.s正确写法.section .text .globl my_strlen my_strlen: movq $0, %rax # 初始化计数器 loop: cmpb $0, (%rdi) # 检查当前字节是否为\0 je end incq %rax # 计数1 incq %rdi # 指针1 jmp loop end: ret # 返回值已在%rax逻辑说明%rdi接收C传入的char*地址符合ABI循环中仅修改%rax和%rdi均为调用者保存寄存器无需恢复ret后C代码自动从%rax取返回值。若错误使用%rbx计数却未pushq %rbx/popq %rbx会导致C主函数崩溃——这是新手最常踩的ABI玄学坑。2.4 链接脚本定制为什么linker.ld里要手动指定.text起始地址为0x400000默认链接器脚本将.text段放在0x4000004MB这是Linux ELF可执行文件的经典基址。作业中若要求“将自定义代码段加载到固定地址”就必须写链接脚本SECTIONS { . 0x400000; /* 设置链接地址起点 */ .text : { *(.text.startup) /* 放置启动代码 */ *(.text) /* 主代码段 */ } .data : { *(.data) /* 初始化数据 */ } .bss : { *(.bss) /* 未初始化数据 */ } }编译时显式调用gcc -T linker.ld -o program src/start.o src/main.o asm/my_strlen.o参数说明-T linker.ld强制使用自定义脚本若省略链接器用默认脚本.text可能被映射到0x10000等地址导致你硬编码的跳转地址全部失效。0x400000不是随意选的——它是Linux内核为用户态程序预留的“安全区”避开NULL指针解引用和内核空间。3. 核心任务拆解四个典型模块的实现逻辑与调试锚点作业通常包含4个递进式模块每个模块都对应CSAPP一个核心章节。这里不给完整代码而是指出每模块最关键的3个调试锚点——即GDB中必须停下的断点位置以及你该观察什么。3.1 模块一start.S与_start入口接管对应CSAPP第3章目标绕过glibc的_start用纯汇编实现程序入口调用main()并正确退出。关键锚点break *0x400000—— 停在第一条指令确认入口地址确实是0x400000验证链接脚本生效break *0x400015—— 停在call main指令后info registers看%rsp是否对齐16字节ABI强制要求否则main内movaps指令崩溃break *0x40002a—— 停在syscall指令前print $rax应为60sys_exit系统调用号print $rdi应为0退出码避坑逻辑若%rsp不对齐main中调用printf会触发SIGBUS。解决方案在call main前加andq $-16, %rsp强制16字节对齐。3.2 模块二malloc简易实现对应CSAPP第9章目标用sbrk()系统调用管理堆实现my_malloc/my_free支持合并空闲块。关键锚点break my_malloc—— 观察%rdi请求大小是否被正确向上取整到8字节倍数对齐要求break sbrk—— 单步进入info proc mappings确认brk值是否增长验证sbrk调用成功break my_free——x/2gx $rdi查看待释放块头确认size字段是否含0x1标志位已分配标记避坑逻辑sbrk(0)返回当前brk值但首次调用前brk指向.bss末尾此处可能有未初始化数据。务必用memset清零新申请内存否则my_malloc返回的内存含脏数据。3.3 模块三printf简化版对应CSAPP第10章目标解析格式字符串处理%d,%x,%s用write()输出。关键锚点break printf_loop—— 停在格式字符解析循环x/c $rdi看当前字符是否为%break print_int——print $rsi待打印整数是否为负负数需先取反再补码否则%d输出乱码break write_syscall——print $rdx长度是否等于strlen结果若%s指向非法地址strlen会段错误必须加if (!s) s (null)避坑逻辑%x输出十六进制时必须按字节从高位到低位处理。错误做法n % 16得低位正确做法n (4*(len-1)) 0xf取高位否则0x1234输出为4321。3.4 模块四shellcode注入与execve调用对应CSAPP第3章栈溢出目标构造恶意输入覆盖返回地址跳转到栈上shellcode执行/bin/sh。关键锚点break vulnerable_function——info frame记下saved rip地址即返回地址位置run payload.txt——x/20i $rsp观察shellcode是否完整写入栈0x90NOP序列是否连续break *0x7fffffffe000假设shellcode起始地址——x/10i $rip确认首条指令是xorq %rax,%rax清零寄存器避坑逻辑/bin/sh字符串必须以\0结尾且地址需在shellcode内。常见错误将/bin/sh硬编码在C代码里但栈地址随机PIE已关但ASLR仍开。解决方案在shellcode内嵌入字符串如pushq $0x68732f6e69622f/bin/sh小端序。4. 避坑指南五个让90%同学卡住超过8小时的致命问题这些不是“可能遇到”而是几乎必然发生的翻车点。每一条都来自某高校实验室的真实debug记录按出现频率排序。4.1 现象make报错undefined reference to main但main.c明明存在原因gcc默认寻找main函数但你的start.S中定义了_start且链接时未排除默认启动代码。gcc既链接了你的_start又试图链接glibc的main冲突。解决编译时加-nostdlib不链接标准库和-e _start显式指定入口为_startgcc -nostdlib -e _start -o program start.o main.o4.2 现象GDB中stepi单步执行%rsp值突变后续指令全错乱原因stepi执行的是一条机器指令但call/ret/push/pop会改变栈指针。你以为在call func处停其实stepi已执行完call%rsp减8压入返回地址%rip跳到func首地址。解决用nexti跳过函数调用或step按C源码行单步替代stepi若必须stepi执行前先info registers记下%rsp执行后对比变化量是否为8的倍数。4.3 现象my_malloc返回地址能printf显示但往该地址write()时SIGSEGV原因sbrk()申请的内存页是可读可写但不可执行的W^X保护。write()系统调用本身没问题但若你在my_malloc返回的内存里写了shellcode并试图jmp过去就会段错误。解决作业若涉及执行栈内存必须用mmap()替代sbrk()并指定PROT_READ|PROT_WRITE|PROT_EXEC标志。sbrk()仅适用于数据存储。4.4 现象objdump -d program显示callq 0x0而不是callq 0x400123原因链接时未解析重定位项。call指令的相对偏移在编译时未知需链接器填入。若main.o未与printf.o链接call printf就保持0x0占位符。解决确保所有.o文件一次性链接不要分步gcc -c后单独ld。用gcc -o program *.o而非ld -o program *.o。4.5 现象readelf -S program中.text节Flags显示AX可分配、可执行但chmod x program后仍Permission denied原因Linux内核禁止执行没有read权限的文件。chmod x只加x若原文件无r权限如chmod 100内核拒绝加载。解决chmod 755 program确保r位存在。验证ls -l program输出应含-rwxr-xr-x。5. 进阶验证用三类测试用例闭环验证你的实现是否真正可靠写完代码不等于完成必须用分层测试证明它在边界条件下依然健壮。以下是某高校实验室采用的黄金三测法覆盖99%隐藏缺陷。5.1 内存泄漏检测valgrind --leak-checkfull不是摆设是照妖镜很多同学认为my_malloc/my_free只要能跑通test_basic.c就OK。但valgrind会暴露深层问题valgrind --leak-checkfull --show-leak-kindsall ./program重点关注三类报告报告类型含义你的代码应表现definitely lostfree未调用内存彻底泄露0个必须为0still reachable程序退出时仍有指针指向内存如全局链表头允许存在但需确认是设计所需invalid write向malloc返回地址size外写入缓冲区溢出0个任何1个都算失败实操技巧在my_malloc开头加fprintf(stderr, ALLOC %p %zu\n, ptr, size);在my_free加fprintf(stderr, FREE %p\n, ptr);配合valgrind输出时间戳精准定位哪次分配未配对释放。5.2 系统调用追踪strace -e tracewrite,mmap,sbrk,exit_group直击内核交互strace不关心你的C代码多优雅只忠实地记录你向内核发了什么请求。这是验证“绕过glibc”是否成功的终极手段strace -e tracewrite,mmap,sbrk,exit_group -o trace.log ./program理想日志应包含sbrk(8192)→ 你的my_malloc首次扩展堆write(1, Hello, 5)→ 你的printf调用writeexit_group(0)→ 你的_start调用exit系统调用若出现write(1, Hello, 5) 5但屏幕无输出检查write第三个参数是否为5而非strlen(Hello)1多写\0会导致终端显示异常。5.3 汇编级一致性objdump -d与gdb反汇编必须完全一致这是检验你是否真正理解“代码即数据”的试金石。在GDB中(gdb) disassemble main (gdb) x/10i main两者的输出必须逐字相同。若disassemble显示callq 0x400123 printf而x/10i显示callq *0x400200间接调用说明你修改了.got.plt但未刷新CPU指令缓存极罕见或objdump解析错误更可能。此时应objdump -d --no-show-raw-insn program去掉机器码列专注指令gdb ./program -ex set architecture i386:x86-64 -ex disassemble main强制架构我带过的某跨平台系统项目里曾因objdump版本差异2.30 vs 2.38对同一二进制文件反汇编出不同助记符leavsmov导致团队争论三天。最终统一用gdb作为唯一权威源——因为GDB直接读取ELF符号表objdump依赖外部库解析。希望帮到你。本文还有配套的精品资源点击获取