调试指南¶
本指南介绍 NoobKernel 的调试方法,包括 GDB 调试、日志系统和常见问题排查。
GDB 调试¶
启动调试会话¶
make debug
此命令会: 1. 启动 QEMU,暂停等待 GDB 2. 自动连接 GDB 3. 在 GDB 中等待命令
常用 GDB 命令¶
执行控制¶
continue # 继续执行
step # 单步执行(进入函数)
next # 单步执行(不进入函数)
finish # 执行到当前函数返回
断点¶
break main # 在 main 函数设置断点
break kerneltrap # 在 kerneltrap 设置断点
break *0x80200000 # 在地址设置断点
delete 1 # 删除断点 1
info breakpoints # 查看断点
查看信息¶
backtrace # 调用栈
info registers # 所有寄存器
print var # 打印变量
print/x var # 十六进制打印
print *ptr # 打印指针指向的值
x/10x 0x80200000 # 查看内存(10 个十六进制值)
RISC-V 特定¶
info registers sstatus # 查看 sstatus 寄存器
info registers satp # 查看 satp 寄存器
print $sp # 打印栈指针
print $ra # 打印返回地址
VSCode 调试¶
make vs-debug
然后在 VSCode 中按 F5 开始调试。
.vscode/launch.json 配置:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Kernel",
"type": "gdb",
"request": "launch",
"target": "./build/QEMU/kernel",
"cwd": "${workspaceRoot}",
"gdbpath": "gdb-multiarch",
"preLaunchTask": "Start QEMU"
}
]
}
调试技巧¶
调试启动问题¶
break *0x80200000 # 内核入口
break main
continue
调试中断处理¶
break kerneltrap
break handle_timer
break handle_external
调试内存分配¶
break kmalloc
break buddy_alloc
break kmem_cache_alloc
调试调度¶
break sched_yield
break context_switch
日志系统¶
日志级别¶
#define LOG_LEVEL 3 // 当前级别
#define LOG_DEBUG 4 // 调试信息(绿色)
#define LOG_INFO 3 // 一般信息(蓝色)
#define LOG_WARN 2 // 警告(黄色)
#define LOG_ERROR 1 // 错误(红色)
日志函数¶
debugf("debug message: %d", value); // 绿色
infof("info message: %s", str); // 蓝色
warnf("warning: %d", warning); // 黄色
errorf("error: %d", error); // 红色
panic("fatal error: %s", msg); // 红色 + 关机
修改日志级别¶
在 include/config.h 中修改:
#define LOG_LEVEL 4 // 显示所有日志
#define LOG_LEVEL 0 // 只显示 panic
调试特定模块¶
在模块源文件中定义局部日志宏:
#undef LOG_LEVEL
#define LOG_LEVEL 4 // 本模块显示所有日志
infof("mm: allocating %d pages", n);
QEMU 调试选项¶
详细日志¶
qemu-system-riscv64 \
-d guest_errors,unimp,cpu_reset \
-D qemu.log \
...
-d 选项:
- guest_errors:客户机错误
- unimp:未实现指令
- cpu_reset:CPU 复位
- int:中断
- exec:执行跟踪(大量输出)
监视器¶
QEMU 运行时按 Ctrl+A C 进入监视器:
(qemu) info registers # 查看寄存器
(qemu) info mtree # 内存映射
(qemu) info qtree # 设备树
(qemu) quit # 退出
反汇编调试¶
生成反汇编文件:
make # 会生成 kernel.asm
查看反汇编:
less build/QEMU/kernel.asm
查找函数地址:
grep "kerneltrap>" build/QEMU/kernel.asm
符号表¶
查看符号表:
riscv64-unknown-elf-nm build/QEMU/kernel | sort
查找符号地址:
riscv64-unknown-elf-nm build/QEMU/kernel | grep main
常见问题¶
内核崩溃¶
症状:QEMU 无响应或输出异常。
排查步骤:
- 查看
kernel.asm找到崩溃地址 - 使用 GDB 定位崩溃位置
- 检查
scause和stval寄存器
break kerneltrap
continue
# 触发崩溃后
info registers scause
info registers stval
页故障¶
症状:页面错误异常。
排查:
# 在 kerneltrap 中
print/x ktf->scause
print/x ktf->stval # 故障地址
常见原因: - 访问空指针 - 访问未映射地址 - 权限错误
死锁¶
症状:内核停止响应。
排查:
- 启用
SPINLOCK_DEBUG - 检查锁持有情况
break panic
continue
backtrace
内存泄漏¶
症状:内存逐渐耗尽。
排查:
- 在
kmalloc和kfree添加日志 - 统计分配和释放次数
static int alloc_count = 0;
static int free_count = 0;
void *kmalloc(size_t size) {
void *p = ...;
if (p) alloc_count++;
debugf("kmalloc: %d allocated, %d freed", alloc_count, free_count);
return p;
}
void kfree(void *ptr) {
free_count++;
debugf("kfree: %d allocated, %d freed", alloc_count, free_count);
}
调试宏¶
assert¶
#include <misc/log.h>
void foo(int *ptr) {
assert(ptr != NULL); // 如果 ptr 为 NULL,触发 panic
...
}
BUG_ON¶
BUG_ON(condition); // 如果条件为真,触发 panic
断点触发¶
#define BREAKPOINT() asm volatile("ebreak")
void debug_here() {
BREAKPOINT(); // GDB 会停在这里
}
内存检查¶
检查内存分配器状态¶
void kalloc_test(void) {
infof("Free pages: %d", get_free_pages_num());
infof("Buddy free pages: %d", buddy_free_pages);
}
检查缓冲区缓存¶
void bcache_stat(void) {
int used = 0;
for (int i = 0; i < BCACHE_SIZE; i++) {
if (bcache[i].refcnt > 0) used++;
}
infof("bcache: %d/%d used", used, BCACHE_SIZE);
}
性能分析¶
简单计时¶
u64 start = r_time();
// 要测量的代码
u64 end = r_time();
infof("Time: %d cycles", end - start);
函数调用计数¶
static int call_count = 0;
void my_function() {
call_count++;
debugf("my_function called %d times", call_count);
}