GDB配合QEMU调试16位引导加载程序时指令解析异常的解决咨询
GDB配合QEMU调试16位引导加载程序时指令解析异常的解决咨询
我之前调试MBR引导程序时踩过完全一样的坑,指令解析错位、符号对应不上的问题折腾了好久,下面给你讲清楚问题根源和两种可行的解决办法:
问题根源
你遇到的核心问题有两个:
- ELF32文件的元数据干扰:即使源码里指定了
BITS 16,NASM生成的ELF32文件头会标记这是32位目标,GDB加载时默认会用32位模式解析指令,后续手动设置set architecture i8086也无法完全抵消ELF头的初始影响。 - 地址不匹配:你的引导程序必须在
0x7C00地址运行(BIOS会把MBR加载到这个地址),但ELF32文件默认的加载地址是0x08048000左右,GDB加载ELF时会用这个默认地址映射符号,和QEMU里的实际运行地址完全脱节,导致指令和源码行对应不上。
解决办法一:用Flat Binary文件+手动加载符号(简单直接)
步骤1:编译生成Flat Binary文件
用NASM编译成纯二进制的MBR文件,同时生成列表文件方便对照地址:
nasm -f bin bootloader.asm -o bootloader.bin -l bootloader.lst
-l bootloader.lst会生成一个包含每一行源码对应内存地址的列表文件,调试时可以用来核对指令和地址的对应关系。
步骤2:启动带调试端口的QEMU
启动QEMU并暂停CPU,同时开启GDB调试端口:
qemu-system-i386 -hda bootloader.bin -S -s
-S:启动后立即暂停CPU,等待GDB连接-s:默认在localhost:1234开启GDB调试服务器
步骤3:配置GDB并连接调试
打开新终端启动GDB,执行以下命令:
# 强制设置为8086(16位x86)架构 set architecture i8086 # 连接到QEMU的调试端口 target remote localhost:1234 # 将Flat Binary文件加载到内存地址0x7C00(MBR的运行地址) add-symbol-file bootloader.bin 0x7C00
现在你可以用break *0x7C00设置断点,执行continue后QEMU会运行到断点,此时nexti/stepi就能看到和源码完全对应的指令了。
解决办法二:用ELF32文件带符号表(调试更便捷)
如果想直接用break start、break init这样的符号断点,推荐用ELF32文件带符号表,再强制GDB映射到正确地址:
步骤1:编译生成ELF32文件
用NASM编译成ELF32格式,保留符号表:
nasm -f elf32 bootloader.asm -o bootloader.elf
步骤2:启动QEMU(和办法一相同)
qemu-system-i386 -hda bootloader.bin -S -s
注意:QEMU还是加载Flat Binary文件,因为ELF32格式不被BIOS识别,无法作为MBR引导。
步骤3:配置GDB并映射符号到正确地址
set architecture i8086 target remote localhost:1234 # 关键:告诉GDB,ELF文件中的符号实际运行在0x7C00地址 add-symbol-file bootloader.elf 0x7C00
现在你可以直接用符号设置断点,比如:
break start break init break load_kernel
执行continue后,GDB会在对应的源码行暂停,指令解析完全和源码一致,调试体验好很多。
额外调试小技巧
- 查看寄存器:GDB设置
i8086架构后,寄存器都是16位的(ax、bx而非eax、ebx),用info registers可以查看所有16位寄存器状态。 - 核对指令:如果还是不确定指令对应关系,可以用
x/i $pc查看当前PC指针指向的指令,和bootloader.lst里的内容对照。 - 查看内存:用
x/10x 0x7C00查看MBR所在内存区域的内容,验证是否和编译出的bin文件一致。
按照上面的方法操作,你就能解决GDB指令解析错位的问题,顺利调试你的16位引导加载程序了!
附你的引导程序源码(格式化后):
; No EDD bootloader BITS 16 ORG 0x7C00 start: jmp 0x0000:init init: mov [DRIVE_ID], dl ; Saves the drive ID of the media in the DRIVE_ID variable cli xor ax,ax mov ds, ax mov es, ax mov ss, ax mov sp, 0x7C00 sti mov ax, 0x0003 int 10h jmp load_kernel ; ----------------------------------------------------------------------- ; | Data | ; ----------------------------------------------------------------------- DRIVE_ID: db 0x00 error_msg db 0x0D,0x0A,"Read Error. Error Code: ",0x00 load_msg db "Loading kernel...",0x0D,0x0A,0x00 ; ----------------------------------------------------------------------- ; | Functions | ; ----------------------------------------------------------------------- print_string: ; Routine: output string in SI to screen cld mov ah, 0x0E ; int 10h 'print char' BIOS function .repeat: lodsb ; Get the character from string cmp al, 0x00 je .done ; If the char obtained is the 00, end of string int 10h ; Otherwise, print it jmp .repeat .done: ret load_kernel: mov si, load_msg call print_string mov ax,0x0100 ; Destiny address (0x0100 * 16 = 0x1000) mov es, ax mov bx, 0x0000 mov ah, 0x02 ; BIOS Function: Read sectors mov al, 0x05 ; Number of sectors to read mov ch, 0x00 ; Cylinder 0 mov dh, 0x00 ; Head 0 mov cl, 0x02 ; Sector 2 (sector 1 is bootloader) int 13h jc short .read_error mov ah, 0x86 mov cx, 0x0000F mov dx, 0x4240 int 15h jmp 0x0100:0x0000 .read_error: mov si, error_msg call print_string push ax mov al, ah shr al, 4 call print_hexdigit pop ax mov al, ah and al, 0x0F call print_hexdigit cli hlt print_hexdigit: add al, '0' cmp al, '9' jle .is_digit add al, 7 .is_digit: mov ah, 0x0E int 10h ret ; ------------------------------------------------------------------------ ; | MBR Structure | ; ------------------------------------------------------------------------ times 440-($-$$) db 0x00 db 0x00,0x00,0x00,0x00 dw 0x0000 ; ------------------------------------------------------------------------ ; | MBR Partitions | ; ------------------------------------------------------------------------ ; Format: ; Off: 0x00 (1B, status; 0x80 active), 0x01 (3B, CHS first sector of partition; 1024 C, 255 H, 53 S), 0x04 (1B, partition type), 0x05 (3B, CHS final sector of partition) ; 0x08 (4B, Logical block address of first sector of partition), 0x0C (4B, Partition length, in sectors of 512B at least) ; Partition 1: Active, type 0x0C ( FAT32 LBA ), starts at sector 1, max size db 0x80, 0x21, 0x03, 0x00, 0x0C, 0x54, 0xC8, 0xFC, 0x00, 0x08, 0x00, 0x00, 0x00, 0x80, 0xEB, 0x00 ; Partitions 2,3,4: Empty times 16 * 3 db 0x00 dw 0xAA55 ; The standard PC boot signature




