调试配置
昨天写完printk函数后很有可能遇到bug,当遇到bug的时候怎样来调试呢,现在就来配置一下
gdb调试
qemu可以以调试模式启动配合gdb来进行调试,当然cgdb更加好用一些
qemu的调试模式命令是
1 | qemu -S -s -fda floppy.img -boot a |
-S是让qemu不要继续运行,等待gdb的运行指令,-s是开启1234端口等待gdb连接
开启gdb后就执行一下命令
1 | file time_kernel |
第一句是指定可执行文件,第二句是连接本地的1234端口,第三句是在kern_entry函数处设置断点,最后c是continue执行到断点处
这些可以写到一个脚本里,然后开启cgdb后自动执行
1 | cgdb -x scripts/gdbinit |
注意gdb加载脚本的时间一定要在qemu打开以后,不然会报错
打印函数调用栈
我们现在来实现一个当内核出现致命错误时自动打印函数调用栈的函数
在boot/boot.s里的start函数调用kern_entry函数之前,我们把ebx寄存器的值赋给了一个全局变量glb_mboot_ptr。这是一个指向了multiboot_t类型结构体的指针,这个结构体存储了GRUB在调用内核前获取的硬件信息和内核文件本身的一些信息。我们先给出具体的结构体的定义如下:
include/multiboot.h
1 |
|
我们主要关心ELF那一段
1 | /** |
我们先添加elf.h这个头文件
include/elf.h
1 |
|
这段结构体定义了ELF的区段头符号表等内容,然后我们要从multiboot_t结构体中提取ELF相关信息
kernel/debug/elf.c
1 |
|
说实话这些我没太弄懂,想弄明白自己去看文档吧,我不太懂没啥能解释的2333
用objdump文件反汇编生成的内核
1 | objdump -M intel -d time_kernel |
可以简化kern_entry函数的内容来让分析更简单
具体的分析过程文档写得很详细,我就不复述了,需要理解的就是函数开头那一块
1 | 100028: 55 push ebp |
esp是栈顶指针,在函数一开始先保存原来的ebp,然后将当前栈顶指针赋值给ebp,然给局部变量分配空间(移动栈顶指针),这样一来在EBP上方分别是原来的EBP,返回地址和参数EBP下方则是临时变量
然后返回时只需要将栈顶指针移动回来(mov esp ebp) ,然后恢复ebp的值(pop ebp) 最后ret就行了
所以我们只需要拿到当前ebp的值就可以沿着调用链获取所有函数
include/debug.h
1 |
|
kernel/debug/debug.c
1 |
|
改写init/entry.c测试一下
1 |
|
最后的效果