Qemu与GDB调试内核

Qemu是一款计算机模拟器,与Vmware和VirtualBox类似,但是Qemu能够模拟多种硬件,同时支持调试。本文主要介绍Qemu与GDB结合调试操作系统的引导。

引导扇区

引导用nasm编写,代码如下

mov ah, 0x88
int 0x15		; 调用BIOS中断,获取虚拟内存大小,结果保存在ax,单位为kb

mov bx, ax

mov ah, 0x0e 	
mov al, 'H'
int 0x10 		; 调用BIOS中断,ah=0e代表打印al的内容到控制台
mov al, 'i' 
int 0x10 		

jmp $ 			; $ 指当前指令的位置,跳回自己,表示永久循环

times 510-($-$$) db 0
dw 0xaa55

编译引导

nasm boot.asm -o boot.bin	# 编译到二进制
nasm boot.asm -l boot.lst 	# 编译可查看的字节码对照表

生成的lst文件显示了编译后的二进制代码以及对应的地址,汇编源码

     1 00000000 B488                    mov ah, 0x88
     2 00000002 CD15                    int 0x15                        
     4 00000004 89C3                    mov bx, ax                       
     6 00000006 B40E                    mov ah, 0x0e 	
     7 00000008 B048                    mov al, 'H'
     8 0000000A CD10                    int 0x10 		
     9 0000000C B069                    mov al, 'i' 
    10 0000000E CD10                    int 0x10 		                            
    12 00000010 EBFE                    jmp $ 			                                  
    14 00000013 00<rept>                times 510-($-$$) db 0
    15 000001FE 55AA                    dw 0xaa55

这里的第二列是汇编后二进制指令的地址,在调试的时候会用到。

启动系统

qemu-system-i386 -s -S -m 512 -hda boot.bin -nographic 

qemu-system-i386 是目标硬件架构

-S表示“freeze CPU at start up”,即启动后CPU停止等待,此时需要使用gdb来恢复运行。

-m 指定运行RAM大小,单位为MB

使用 -hda-hdb-hdc-hdd 来引用 Parallel ATA (PATA) 硬盘,-cdrom 指定 CD 或 DVD 镜像文件或设备, 这些选项都采用文件名作为参数。光驱占用了 -hdc 的位置,因此不能同时使用这两个选项。

-boot 参数用于指定启动设备,c代表从第一块硬盘启动,d代表从CD-ROM启动

-nographic 用于不显示图形界面,在后台运行

GDB调试引导

qemu将boot.bin作为内核启动,开机时CPU处于实模式,CS=0xFFFF,IP=0x0000,所以PC=CS:IP=0xFFFF0,CPU执行0xFFFF0(ROM BIOS),这里后面的代码是固化在芯片中的,包括检查RAM、键盘、显示器、硬软磁盘,最后一步最重要,将磁盘0磁道0扇区(引导扇区,即boot.bin中的前512字节)读入到内存的0x7c00处,然后设置CS=0x07c0, IP=0x0000, 此时CPU就开始执行0x7c00的指令了,也就是引导扇区的指令。本文要调试的是内核引导,也就是从0x7c00处开始调试。

由于使用了-S参数,此时PC还处在0xFFFF0处。

打开gdb,连接远程调试,qemu打开的gdbserver默认端口为1234

chiyiw@IdeaY400:~$ gdb
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
(gdb) 

连接成功后即可进行调试,可以看出目前pc=0xfff0,使用si可以单步执行,我们要调试的是0x7c00处的代码,所以直接设置断点到0x7c00,然后使用c继续运行,CPU运行到PC=0x7c00时会停止等待,不会执行0x7c00的指令。

(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.

Breakpoint 1, 0x00007c00 in ?? ()
(gdb) i r
eax            0xaa55   43605
ecx            0x0      0
edx            0x80     128
ebx            0x0      0
esp            0x6ef0   0x6ef0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x7c00   0x7c00
eflags         0x202    [ IF ]
cs             0x0      0
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb)

使用i r命令可以查看当前寄存器的值,我们在0x7c00处的代码是mov ah, 0x88,此时可以看到ah还是0xaa,使用si进行单步调试,再查看寄存器的值

(gdb) si
0x00007c02 in ?? ()
(gdb) i r
eax            0x8855   34901
ecx            0x0      0
edx            0x80     128
ebx            0x0      0
esp            0x6ef0   0x6ef0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x7c02   0x7c02
eflags         0x202    [ IF ]
cs             0x0      0
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb)

此时可以看到在执行了mov ah, 0x88指令后,ah被置为了0x88,同时下一条指令的地址为0x00007c02。我们的目标是获取到虚拟内存的大小,即要执行int 0x15指令,该指令位于0x7c02,这个指令的地址可以从boot.lst中看到,即0x7c00+0x0002,中断执行的结果会保存在ax中,单位是kb。使用si单步执行。

(gdb) si
0x0000f85c in ?? ()
(gdb) i r
eax            0x8855   34901
ecx            0x0      0
edx            0x80     128
ebx            0x0      0
esp            0x6eea   0x6eea
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0xf85c   0xf85c
eflags         0x97     [ CF PF AF SF ]
cs             0xf000   61440
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb)

此时可以看到0x7c02被执行,但是下一条指令不是0x7c04,同时ax中的值也没有变化,这是由于BIOS中断处理是一系列的操作,但是最后还是会回到当前指令处,即int 0x13的一系列指令执行完后会回到0x7c04处,因此,我们只要在0x7c04处设一个断点即可查看int 0x13的结果。

(gdb) b *0x7c04
Breakpoint 2 at 0x7c04
(gdb) c
Continuing.

Breakpoint 2, 0x00007c04 in ?? ()
(gdb) i r
eax            0xfc00   64512
ecx            0x0      0
edx            0x80     128
ebx            0x0      0
esp            0x6ef0   0x6ef0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x7c04   0x7c04
eflags         0x202    [ IF ]
cs             0x0      0
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb)

在执行0x7c04前,打印出寄存器的值,此时可以看到,虚拟内存的值已经被读入到了ax中,它的值是0xfc00,十进制是64512。也就是说,当前机器的虚拟内存为64512kb=63M。(使用0x88最多能够探测到64M)

至此,gdb调试内核的过程结束。以下是一些记录:

参考: 使用 QEMU 进行跨平台开发 - IBM developerWorks