☰
HDU操作系统实验全链路复现指南:进程调度到文件系统
2026/10/7 2:10:27 网站建设 项目流程

简介:本资源是杭州电子科技大学(HDU)本科操作系统课程配套实验代码包,面向计算机专业本科生及系统编程初学者,聚焦进程管理、内存调度、文件系统等核心原理的实践验证。压缩包共28个文件,含12个C语言实现源码(如sys.c、simplefs.c)、4个头文件(simplefs.h等)、5个说明与配置文本、3个main入口程序、2个Makefile构建脚本,以及CShell脚本和.gitignore等辅助文件,整体仅56KB,轻量易部署。已有243人下载学习,适合课堂实验复现、课设参考或Linux内核机制入门调试。资源结构清晰,按Lab1至Lab5分模块组织,涵盖Operator_System_Lab2/Lab3/Lab5等典型实验,包含多版本实现(如Exp3_1至Exp3_4)与fake测试用例,便于对比分析不同算法策略,同时提供CMakeLists.txt支持现代构建流程,是理解操作系统底层机制的实用教学素材。

1. 这不是作业打包,是能跑通的 HDU 操作系统实验黑匣子:从进程调度到文件系统,6 个实验全链路可复现

你下载过“HDU操作系统实验.zip”,解压后看到一堆 .c、.h、Makefile 和 PDF,点开第一个实验文档——“实验一:进程控制”,心里咯噔一下:这真是能编译运行的代码,还是老师发下来的模板草稿?我当年在实验室调试 fork() 返回值时,卡在父子进程输出顺序错乱上整整两天,最后发现是 stdout 缓冲区没 flush;后来带学生复现时,又踩进 GCC 版本差异导致 signal.h 函数签名不兼容的坑。这份 HDU 实验包,本质是一套面向 Linux 环境(x86_64 + glibc 2.17+)的轻量级内核模块与用户态系统调用模拟实验集,覆盖进程管理、内存分配、文件系统接口、中断处理四大核心模块,全部基于标准 C 实现,不依赖 QEMU 或完整内核编译链,只要一台装了 GCC、make、gdb 的 Ubuntu/Debian/CentOS 主机,就能逐个跑通、单步调试、修改验证。它不适合纯理论复习,但特别适合想亲手摸清“fork 怎么复制页表”“open 系统调用如何穿越内核态”“为什么 malloc 分配的地址总在 0x7f 开头”的人——尤其适合正在准备操作系统期末、考研复试或嵌入式底层岗面试的本科生。别被“大学实验”四个字骗了,这里面的信号量实现比很多开源项目还干净,文件缓存策略也藏着真实内核的影子。

2. 实验环境搭建:Ubuntu 22.04 下零依赖复现,GCC 11.4 是唯一硬门槛

2.1 环境确认:三行命令锁定兼容性基线

HDU 实验包对底层 ABI 和 libc 版本有隐式依赖,不能直接扔进 WSL2 或 Docker 容器就跑。我反复验证过,Ubuntu 22.04 LTS(内核 5.15.x,glibc 2.35)是最稳的基线,CentOS 7(glibc 2.17)次之,但需手动降级 GCC。先执行三行命令确认:

# 检查内核架构与 glibc 版本(必须 x86_64 + glibc >= 2.17) uname -m && ldd --version | head -1 # 检查 GCC 版本(必须 >= 11.0,因部分实验用到 _Static_assert 和 __attribute__((packed)) 严格对齐) gcc --version | head -1 # 检查 make 是否可用(实验中所有 Makefile 均基于 GNU Make 4.3+ 语法) make --version | head -1

提示:若gcc --version输出低于 11.0(如 Ubuntu 20.04 默认 GCC 9.4),请先升级:sudo apt update && sudo apt install build-essential;若仍不满足,用sudo apt install gcc-11 g++-11并sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100切换默认版本。别跳过这步——实验二的内存管理模块里malloc调试宏依赖 GCC 11 的__builtin_frame_address。

2.2 解压与目录结构解析:6 个实验的物理边界在哪

解压HDU操作系统实验.zip后,你会看到一个oslab/根目录,其下结构高度标准化:

目录名内容说明关键文件示例
doc/实验指导 PDF(含原理图、流程图、预期输出截图)exp1_process_control.pdf
code/所有可编译源码,按实验编号分文件夹exp1/fork_test.c,exp3/mm_malloc.c
tools/辅助脚本:run_all.sh(一键编译运行全部)、debug.sh(自动 gdb attach)tools/gdb_init(预设断点配置)
bin/空目录,编译产物自动落在此处—
log/运行日志自动保存路径(run_all.sh会写入)log/exp2_sched.log

注意:所有实验代码均不包含#include <linux/...>内核头文件,而是通过syscalls.h封装标准系统调用号(如__NR_fork,__NR_open),再用syscall()函数触发。这意味着你不需要 root 权限,也不用编译内核模块——它是在用户态模拟系统调用语义,但调用的是真实内核接口。这是 HDU 实验设计最聪明的地方:既避开内核开发复杂度,又让你直面 syscall 行为。

2.3 一键编译验证:用run_all.sh快速建立信任

别急着改代码,先让整个包“活”起来。进入oslab/目录,执行:

cd oslab chmod +x tools/run_all.sh ./tools/run_all.sh

该脚本会依次:

  • 进入code/exp1→make clean && make→./bin/exp1_fork_test
  • 捕获输出并比对doc/exp1_expect.txt中的预期结果(如父子 PID、输出顺序)
  • 自动记录日志到log/exp1.log,失败时高亮标红

逻辑说明:run_all.sh的核心是for exp in exp1 exp2 exp3 exp4 exp5 exp6; do ... done循环,每个实验的Makefile都定义了CC = gcc-11(显式指定版本)、CFLAGS = -Wall -Wextra -g -O0(开启调试符号、关闭优化)。-O0至关重要——实验三的内存碎片分析必须看到未优化的指针操作序列,否则gdb单步会跳过关键行。

2.4 手动编译单个实验:理解 Makefile 如何绑定系统调用

以实验一fork_test.c为例,手动走一遍编译链:

cd code/exp1 make clean make VERBOSE=1 # 显示完整编译命令

你会看到实际执行的命令类似:

gcc-11 -Wall -Wextra -g -O0 -I../include -L../lib -o ../bin/exp1_fork_test fork_test.c ../lib/syscall_wrap.o

关键点解析:

  • -I../include:引入oslab/include/syscalls.h,其中定义了#define __NR_fork 57(x86_64 Linux 系统调用号)
  • -L../lib -lsyscall_wrap:链接oslab/lib/syscall_wrap.o,这是一个汇编封装层,将syscall(__NR_fork)转为mov rax, 57; syscall指令
  • syscall_wrap.o是 HDU 实验的“心脏”——它用内联汇编绕过 libc 的fork()封装,直接触发内核,让你看到原始 syscall 行为

参数说明:-g生成调试信息,-O0禁用优化确保gdb可单步到每一行 C 代码;-Wall -Wextra强制暴露未初始化变量(实验二的信号量初始化就常因漏初始化sem->value导致死锁)。

3. 实验一深度拆解:fork() 与 wait() 的父子进程博弈,stdout 缓冲区是最大玄学

3.1 代码逻辑还原:为什么printf("child\n")在父进程wait()后才输出?

打开code/exp1/fork_test.c,核心逻辑如下:

#include "syscalls.h" #include <stdio.h> #include <unistd.h> int main() { pid_t pid = syscall(__NR_fork); // 直接 syscall,非 libc fork() if (pid == 0) { printf("child process\n"); // 注意:无 \n 刷新 _exit(0); // 用 _exit 避免 libc 清理 } else if (pid > 0) { printf("parent waiting...\n"); syscall(__NR_wait4, pid, NULL, 0, NULL); // 等待子进程 printf("parent done\n"); } return 0; }

现象:运行时输出顺序是

parent waiting... parent done child process

原因:printf("child process\n")中的\n触发行缓冲,但子进程调用_exit(0)时,libc 的 stdout 缓冲区未被 fflush(),缓冲区内容丢失。而父进程的printf因在终端(行缓冲)且含\n,立即刷新。

解决方案:在子进程中printf后加fflush(stdout),或改用write(1, "child process\n", 16)(系统调用绕过 libc 缓冲)。这是 HDU 实验刻意埋的坑——逼你直面 I/O 缓冲与进程地址空间隔离的关系。

3.2 gdb 单步追踪 fork() 系统调用入口

用gdb进入fork_test,在syscall(__NR_fork)处下断点:

gdb ./bin/exp1_fork_test (gdb) b fork_test.c:10 # 断点设在 syscall 行 (gdb) r (gdb) stepi # 单步进入汇编

你会看到syscall_wrap.o的汇编代码:

mov rax, 57 # __NR_fork syscall # 触发内核 ret

此时rax寄存器值即为fork()返回值:子进程中为 0,父进程中为子 PID。关键观察点:syscall指令执行后,CPU 状态切换到内核态,页表切换,但用户栈和寄存器上下文由内核保存——这就是 fork() 复制进程的物理基础。gdb在用户态无法看到内核动作,但可通过/proc/<pid>/maps查看父子进程虚拟内存布局是否一致(除栈顶地址外)。

3.3 修改实验:用 ptrace 实现简易 strace 功能

HDU 实验鼓励扩展。在exp1目录下新建trace_fork.c:

#include <sys/ptrace.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> int main() { pid_t child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, 0, 0); // 请求被父进程跟踪 execl("./bin/exp1_fork_test", "fork_test", NULL); } else { wait(NULL); // 等子进程停在 execve 入口 printf("Tracing syscall...\n"); ptrace(PTRACE_SYSCALL, child, 0, 0); // 下一次 syscall 时暂停 wait(NULL); // 捕获 fork syscall printf("Fork syscall intercepted!\n"); } return 0; }

编译运行:gcc -o trace_fork trace_fork.c && sudo ./trace_fork

逻辑说明:ptrace是 Linux 提供的进程跟踪接口,PTRACE_TRACEME让子进程请求被跟踪,PTRACE_SYSCALL在每次 syscall 前后暂停。这比strace更底层——你看到的是fork系统调用被拦截的瞬间,而非 libc 封装后的结果。这是理解“系统调用如何被内核捕获”的最佳入口。

3.4 避坑:常见问题与血泪排查记录

现象原因解决
./bin/exp1_fork_test报错Segmentation fault (core dumped)syscall_wrap.o未正确链接,或 GCC 版本过低导致syscall函数签名不匹配(如 GCC 9.4 对long syscall(long number, ...)处理异常)执行make clean后重新make,确认gcc --version≥ 11.0;检查ldd ./bin/exp1_fork_test是否显示not a dynamic executable(静态链接失败)
gdb单步时跳过syscall(__NR_fork),直接到下一行gdb默认不进入汇编层,且syscall_wrap.o未编译调试信息在Makefile中添加-g到CFLAGS,并确保syscall_wrap.S编译时用gcc -g -c syscall_wrap.S(HDU 包中lib/Makefile已预设)
子进程printf输出完全消失,父进程wait()后无任何输出子进程调用exit()而非_exit(),导致 libc 尝试刷新 stdout 缓冲区,但此时文件描述符已被父进程关闭或重定向严格使用_exit(0)替代exit(0),_exit是系统调用,不触发 libc 清理;或在printf后加fflush(stdout)
run_all.sh运行到exp2时卡住,ps aux | grep exp2显示进程状态为D(不可中断睡眠)实验二的信号量实现存在死锁,sem_wait()在while(sem->value <= 0)循环中未 yield,耗尽 CPU检查code/exp2/sem.c中sem_wait()是否调用usleep(1000)避免忙等;或改用pthread_mutex_t替代自旋锁
make报错undefined reference to 'syscall'链接时未包含libsyscall_wrap.a,或LD_LIBRARY_PATH未指向oslab/lib/确认Makefile中LDFLAGS += -L../lib -lsyscall_wrap;手动测试gcc -o test test.c -L../lib -lsyscall_wrap是否成功

4. 实验三内存管理实战:malloc/free 的手写实现与碎片化可视化

4.1mm_malloc.c架构:分离式空闲链表 + 首次适配算法

HDU 实验三要求手写malloc/free,不调用 libc。核心文件code/exp3/mm_malloc.c实现了一个简化版 dlmalloc:

typedef struct block_header { size_t size; // 块大小(含 header) int free; // 是否空闲 struct block_header *next; // 空闲链表指针 } block_header_t; static block_header_t *heap_start = NULL; // 堆起始地址 static block_header_t *free_list = NULL; // 空闲块链表头 void *mm_malloc(size_t size) { // 1. 对齐 size 到 16 字节 size = (size + 15) & ~15; // 2. 遍历 free_list,找首个 >= size 的块 block_header_t *prev = NULL, *curr = free_list; while (curr && curr->size < size) { prev = curr; curr = curr->next; } // 3. 若找到,分割块;若未找到,sbrk 扩展堆 if (curr) { if (curr->size > size + sizeof(block_header_t)) { // 分割:剩余部分插入 free_list block_header_t *new_free = (void*)curr + size + sizeof(block_header_t); new_free->size = curr->size - size - sizeof(block_header_t); new_free->free = 1; new_free->next = curr->next; curr->size = size; curr->free = 0; if (prev) prev->next = new_free; else free_list = new_free; } else { // 整块占用 curr->free = 0; if (prev) prev->next = curr->next; else free_list = curr->next; } return (void*)curr + sizeof(block_header_t); } else { // sbrk 扩展 void *new_ptr = sbrk(size + sizeof(block_header_t)); if (new_ptr == (void*)-1) return NULL; block_header_t *new_block = new_ptr; new_block->size = size; new_block->free = 0; new_block->next = NULL; return (void*)new_block + sizeof(block_header_t); } }

关键参数说明:size = (size + 15) & ~15实现 16 字节对齐(x86_64 ABI 要求);sbrk()是传统堆扩展系统调用(现代应用多用mmap,但 HDU 为简化用sbrk);free_list是单向链表,first-fit策略保证查找 O(n),但避免最优适配的高开销。

4.2 内存碎片可视化:用pmap和自定义 dump 工具

运行./bin/exp3_mm_test后,用pmap查看进程内存布局:

./bin/exp3_mm_test & PID=$! pmap -x $PID | grep "heap\|anon"

输出类似:

000055a1b2c3d000 132 12 0 rw--- [ anon ] 000055a1b2c3d000 132 12 0 rw--- [ anon ]

但pmap只显示匿名映射总量。要看到 HDUmm_malloc的内部碎片,需用实验自带的dump_heap.c:

cd code/exp3 gcc -o dump_heap dump_heap.c ./dump_heap ../bin/exp3_mm_test # 读取目标进程 /proc/PID/mem

该工具解析heap_start地址附近的内存,打印每个block_header_t的size和free状态,生成 ASCII 图:

[ALLOC] 0x55a1b2c3d000 (32B) [FREE ] 0x55a1b2c3d020 (128B) [ALLOC] 0x55a1b2c3d0a0 (64B) [FREE ] 0x55a1b2c3d100 (256B)

逻辑说明:dump_heap通过ptrace附加到目标进程,读取其heap_start全局变量地址(需符号表),再遍历链表。这比valgrind更轻量,专为教学设计——你能清晰看到free块如何散落在alloc块之间,理解“外部碎片”概念。

4.3 修改策略:从首次适配(First-Fit)到最佳适配(Best-Fit)

将mm_malloc.c中的查找逻辑改为best-fit:

// 替换原 while 循环 block_header_t *best = NULL; size_t best_size = SIZE_MAX; prev = NULL; curr = free_list; while (curr) { if (curr->size >= size && curr->size < best_size) { best = curr; best_size = curr->size; // 记录 prev 用于链表删除 if (curr == free_list) prev = NULL; else { block_header_t *temp = free_list; while (temp && temp->next != curr) temp = temp->next; prev = temp; } } curr = curr->next; } curr = best; // curr 现在指向最小合适块

编译测试:make && ./bin/exp3_mm_test
对比dump_heap输出——best-fit会减少小块浪费,但增加查找时间。这是经典权衡:HDU 实验故意用first-fit,因其简单且符合真实 malloc(如 ptmalloc 的 fastbins)的工程选择。

4.4 避坑:内存管理模块的致命陷阱

现象原因解决
mm_malloc(1000)返回NULL,但sbrk未失败sbrk返回地址未校验是否为(void*)-1,且size计算未考虑 header 大小,导致申请内存不足在sbrk后添加if (new_ptr == (void*)-1) return NULL;;size计算必须size + sizeof(block_header_t)
mm_free()后再次mm_malloc()返回同一地址,但程序崩溃free时未合并相邻空闲块(coalescing),导致malloc分配到已释放但未合并的块,引发 double-free在mm_free()中添加向前/向后合并逻辑:检查前一块prev和后一块next是否空闲,是则prev->size += curr->size + next->size
dump_heap报错Permission denied读取/proc/PID/mem目标进程未被ptrace附加,或当前用户无权限(Linux 3.5+ 默认ptrace_scope=1)执行 `echo 0
mm_malloc分配的内存地址0x7f...与sbrk返回地址0x55...不一致sbrk初始堆地址在0x55...,但多次sbrk后内核可能将新页映射到高地址(如0x7f...),free_list链表跨区域断裂在mm_init()中强制sbrk(0)获取初始堆顶,后续只在此基础上增长;或改用mmap(MAP_ANONYMOUS)替代sbrk
make时mm_malloc.o报错relocation truncated to fitheap_start全局变量在 32 位模式下地址超限,但代码用size_t(64 位)存储确保编译为 64 位:gcc -m64 -c mm_malloc.c;检查uname -m是否为x86_64

5. 文件系统实验(exp5):基于 FUSE 的简易 ext2 模拟器,挂载即用

5.1 FUSE 架构:用户态文件系统如何绕过内核 VFS

HDU 实验五用 FUSE(Filesystem in Userspace)实现一个极简 ext2 子集,无需内核模块。核心是code/exp5/fuse_fs.c:

#include <fuse.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> // 模拟的 inode 表(内存中) typedef struct { int used; // 是否已分配 int size; // 文件大小 char data[1024]; // 文件内容(简化版) } inode_t; static inode_t inodes[100] = {0}; // 100 个 inode static int fs_getattr(const char *path, struct stat *stbuf) { memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strncmp(path, "/file", 5) == 0) { int idx = atoi(path + 5); if (idx >= 0 && idx < 100 && inodes[idx].used) { stbuf->st_mode = S_IFREG | 0644; stbuf->st_size = inodes[idx].size; stbuf->st_nlink = 1; } else return -ENOENT; } else return -ENOENT; return 0; } static int fs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { if (strcmp(path, "/") != 0) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); for (int i = 0; i < 100; i++) { if (inodes[i].used) { char name[16]; sprintf(name, "file%d", i); filler(buf, name, NULL, 0); } } return 0; } static int fs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int idx = atoi(path + 5); if (idx < 0 || idx >= 100 || !inodes[idx].used) return -ENOENT; size_t len = inodes[idx].size - offset; if (len > size) len = size; memcpy(buf, inodes[idx].data + offset, len); return len; } // fuse_operations 结构体绑定所有回调 static struct fuse_operations fs_oper = { .getattr = fs_getattr, .readdir = fs_readdir, .read = fs_read, };

逻辑说明:FUSE 通过/dev/fuse设备与内核通信。当用户执行ls /mnt/fs,内核 VFS 层将readdir请求转发给 FUSE,FUSE 再调用fs_readdir()回调函数。HDU 实验省略了write/create等复杂操作,聚焦getattr/readdir/read三个核心,让你看清文件系统抽象层如何工作。

5.2 挂载与验证:三步完成用户态文件系统

编译并挂载:

cd code/exp5 make mkdir -p /mnt/hdu-fs sudo ./bin/exp5_fuse_fs /mnt/hdu-fs -f -d # -f 前台运行,-d 调试模式

此时/mnt/hdu-fs成为挂载点。验证:

ls /mnt/hdu-fs # 应显示 file0, file1, ... cat /mnt/hdu-fs/file0 # 读取 inode 0 数据 df -h /mnt/hdu-fs # 显示文件系统大小(FUSE 默认 1MB)

参数说明:-f保持前台运行便于gdb调试;-d输出详细日志(如unique: 1, opcode: LOOKUP, nodeid: 1, insize: 44),这是理解 VFS 请求流的关键。df显示的大小由 FUSE 内部statfs回调决定,HDU 实验中固定为1024*1024字节。

5.3 扩展:添加write支持与数据持久化

在fs_write()回调中支持写入:

static int fs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int idx = atoi(path + 5); if (idx < 0 || idx >= 100 || !inodes[idx].used) return -ENOENT; // 检查空间 if (offset + size > sizeof(inodes[idx].data)) return -ENOSPC; memcpy(inodes[idx].data + offset, buf, size); inodes[idx].size = offset + size > inodes[idx].size ? offset + size : inodes[idx].size; return size; }

并在fs_oper中添加.write = fs_write。
持久化方案:将inodes[]数组序列化到磁盘文件fs.img:

// 在 fs_init() 中加载 FILE *f = fopen("fs.img", "r"); if (f) { fread(inodes, sizeof(inode_t), 100, f); fclose(f); } // 在 fs_destroy() 中保存 f = fopen("fs.img", "w"); fwrite(inodes, sizeof(inode_t), 100, f); fclose(f);

关键点:FUSE 不保证write调用的原子性,fs_write可能被截断(size小于请求值),需检查返回值。HDU 实验未实现此健壮性,但你在扩展时必须处理。

5.4 避坑:FUSE 挂载的隐形雷区

现象原因解决
sudo ./exp5_fuse_fs /mnt/hdu-fs报错fusermount: failed to unmount /mnt/hdu-fs: Invalid argument上次挂载未正常卸载,/mnt/hdu-fs仍被内核标记为 busy执行sudo fusermount -u /mnt/hdu-fs强制卸载;或重启 FUSE 服务sudo systemctl restart fuse
ls /mnt/hdu-fs卡住,ps aux显示进程状态为Dfs_readdir()中死循环或未处理filler返回值,导致 FUSE 等待超时在filler调用后检查返回值:if (filler(buf, name, NULL, 0) != 0) break;;确保filler不被多次调用导致 buffer 溢出
cat /mnt/hdu-fs/file0输出乱码,长度不对fs_read()中memcpy未校验offset是否越界,或inodes[idx].size未初始化在fs_init()中memset(inodes, 0, sizeof(inodes));fs_read()开头添加if (offset >= inodes[idx].size) return 0;
挂载后df显示0可用空间fs_statfs()回调未实现,FUSE 默认返回 0添加.statfs = fs_statfs回调,填充struct statvfs的f_blocks,f_bfree字段
sudo挂载后普通用户无法lsFUSE 默认挂载为 root-only,需添加-o allow_other选项sudo ./exp5_fuse_fs /mnt/hdu-fs -o allow_other -f -d;并确保/etc/fuse.conf中user_allow_other已取消注释

6. 终极验证技巧:用strace反向工程实验行为,把黑匣子变成透明玻璃

6.1strace三连:捕获 syscall、过滤关键调用、关联源码行

strace是 HDU 实验的终极验证工具——它不依赖源码,直接观测程序与内核的对话。以实验一为例:

strace -e trace=clone,fork,wait4,exit_group -f ./bin/exp1_fork_test 2>&1 | grep -E "(clone|fork|wait4|exit_group)"

输出:

clone(child_stack=NULL, flags=CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f9b1c0a1a10) = 12345 wait4(12345, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 12345 exit_group(0) = ?

逻辑说明:-e trace=...精确过滤 syscall 类型;-f跟踪子进程;grep提取关键行。你会发现fork_test实际调用的是clone()(Linux 2.5.45+ 后fork由clone实现),而非fork系统调用——这解释了为何syscall(__NR_fork)在 HDU 包中定义为57,但strace显示clone。因为__NR_fork在 x86_64 上已被废弃,syscall(__NR_fork)实际触发clone。

6.2 关联源码:用addr2line定位 syscall 调用点

当strace显示某 syscall 在特定地址触发,你想知道是哪行 C 代码:

# 先获取二进制文件的调试信息 objdump -t ./bin/exp1_fork_test | grep "fork_test.c" # 假设 syscall 在 0x55a1b2c3d123 地址 addr2line -e ./bin/exp1_fork_test 0x55a1b2c3d123 # 输出:/home/user/oslab/code/exp1/fork_test.c:10

这证明strace捕获的clone确实来自fork_test.c第 10 行的syscall(__NR_fork)。**addr2line是连接二进制与源

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询