使用GDB+QEMU调试Cosmos内核代码
1. 生成带调试符号的elf文件
修改编译选项:
-
GCC 的-O2参数要修改成O0 -g参数:-O0是告诉 GCC 编译器,在编译时不要对代码做优化,这么做可以避免在 GDB 调试时源码和实际程序对应不上的问题;-g参数是为了告诉编译器带上调试符号。
使用grep命令查找需要修改的文件,使用sed命令批量替换,命令如下:
grep -i '\-o2' -r //-i 代表不区分大小写
sed -i 's/-O2/-O0 -g/' ./initldr/build/krnlbuidcmd.mh ./script/krnlbuidcmd.S sed -i 's/-Os/-O0 -g/' ./initldr/build/krnlbuidcmd.mh ./script/krnlbuidcmd.S
-
去掉 ld 的-s参数:-s是告诉 ld 程序链接时去掉所有符号信息,其中包括了调试符号。
grep '\-s ' -r
使用 sed 命令批量去掉 ld 的-s参数,命令如下:
sed -i 's/-s / /g' ./initldr/build/krnlbuidcmd.mh ./script/krnlbuidcmd.S
编译生成“带调试符号的 elf 文件"
执行make就可以编译出带有调试符号的 elf 文件,如下图:这里的“not stripped”就表示文件带有调试符号。
- Cosmos.elf:当需要调试“内核代码”时,可以在 GDB 中执行symbol-file ./build/Cosmos.elf加载调试符号
- initldrkrl.elf:当需要调试“二级加载器代码”时,可以在 GDB 中执行symbol-file ./initldr/build/initldrkrl.elf加载调试符号。
制作 hd.img, 用于QEMU运行Cosmos内核
-
打包生成内核映像文件Cosmos.eki
将要打包的文件copy到同一个文件夹下,执行下列命令:./lmoskrlimg -m k -lhf initldrimh.bin -o Cosmos.eki -f initldrkrl.bin initldrsve.bin Cosmos.bin background.bmp font.fnt logo.bmp
-
如果已有 hd.img,则需要先将其挂载到临时文件,然后替换新生成的Cosmos.eki
cd mount hd.img /tmp/ cp Cosmos.eki /tmp/boot/ umount /tmp/
-
第一次生成hd.img需要按如下命令制作
使用dd命令生成100MB的纯二进制文件,也就是我们要用到的虚拟硬盘文件hd.img:
dd bs=512 if=/dev/zero of=hd.img count=204800 ;bs:表示块大小,这里是512字节 ;if:表示输入文件,/dev/zero就是Linux下专门返回0数据的设备文件,读取它就返回0 ; of:表示输出文件,即我们的硬盘文件。 ; count:表示输出多少块
格式化虚拟硬盘并挂载到本地目录下:
sudo losetup /dev/loop0 hd.img // losetup命令将hd.img变成Linux的回环设备 sudo mkfs.ext4 -q /dev/loop0 // mkfs.ext4格式化为ext4格式的文件系统 sudo mount -o loop ./hd.img ./hdisk/ //将hd.img当做块设备挂载到硬盘文件中 sudo mkdir ./hdisk/boot/ //建立boot目录
如果提示回环设备忙,则使用losetup -f查找第一个空闲的设备,替换即可:
安装GRUB:
sudo grub-install --boot-directory=./hdisk/boot/ --force --allow-floppy /dev/loop0
这时看到boot目录下多了一个grub目录,说明grub安装成功。
创建grub.cfg文件:
set timeout=2 menuentry 'Cosmos' { insmod part_msdos insmod ext2 set root='hd0' #我们的硬盘只有一个分区所以是'hd0' multiboot2 /boot/Cosmos.eki #加载boot目录下的Cosmos.eki文件 boot #引导启动 } set timeout_style=menu if [ "${timeout}" = 0 ]; then set timeout=10 #等待10秒钟自动启动 fi
将Cosmos.eki文件放置到boot目录下,解除挂载
cp Cosmos.eki /tmp/boot/ umount /hdisk/
-
如果要在virtualbox中启动,则需要转换成hd.vdi格式,命令如下:
VBoxManage convertfromraw ./hd.img --format VDI ./hd.vdi
2. qemu启动内核
qemu-system-x86_64 -drive format=raw,file=hd.img -m 512M -cpu kvm64,smep,smap -s // 一定要加-s参数,此参数可以打开调试服务。
3. 使用GDB加载调试符号
(gdb) symbol-file ./initldr/build/initldrkrl.elf // 加载调试符号,这样才能在显示源码、可以用函数名下断点
Reading symbols from ./initldr/build/initldrkrl.elf... //连接qemu-system-x86_64 -s选项打开的1234端口进行调试
4. 启动调试
(gdb) target remote :1234 // 连接qemu-system-x86_64 -s选项打开的1234端口进行调试
Remote debugging using :1234
0x0000000000200040 in _32bits_mode ()
5. 设置断点,使用GDB命令调试
root# od -tx4 ./initldr/build/Cosmos.eki | head -3
0000000 909066eb 1badb002 00010003 e4514ffb
0000020 04000004 04000000 00000000 00000000
0000040 04000068 90909090 e85250d6 00000000
根据GRUB 头结构,结合上面的 Cosmos.eki 文件头信息,我们很容易就能知道,_start符号地址是0x04000000,_entry符号地址是0x04000068。我们在这两个地址设置断点,通过 GDB 可以看到,程序不是在0x04000000断点暂停,而是直接在0x04000068 断点暂停,说明grub启动后会加载cosmos.eki 到0x04000000位置,但执行的第一条指令不是 _start 符号位置而是 _entry 符号位置。到 _entry 时,cr0 的 pe=1,表明此时保护模式已经打开了。
而在断点处执行的正是 _entry处的代码: ./initldr/ldrkrl/imginithead.asm