You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

GDB配合QEMU调试16位引导加载程序时指令解析异常的解决咨询

GDB配合QEMU调试16位引导加载程序时指令解析异常的解决咨询

我之前调试MBR引导程序时踩过完全一样的坑,指令解析错位、符号对应不上的问题折腾了好久,下面给你讲清楚问题根源和两种可行的解决办法:

问题根源

你遇到的核心问题有两个:

  1. ELF32文件的元数据干扰:即使源码里指定了BITS 16,NASM生成的ELF32文件头会标记这是32位目标,GDB加载时默认会用32位模式解析指令,后续手动设置set architecture i8086也无法完全抵消ELF头的初始影响。
  2. 地址不匹配:你的引导程序必须在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 startbreak 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会在对应的源码行暂停,指令解析完全和源码一致,调试体验好很多。


额外调试小技巧

  1. 查看寄存器:GDB设置i8086架构后,寄存器都是16位的(axbx而非eaxebx),用info registers可以查看所有16位寄存器状态。
  2. 核对指令:如果还是不确定指令对应关系,可以用x/i $pc查看当前PC指针指向的指令,和bootloader.lst里的内容对照。
  3. 查看内存:用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

火山引擎 最新活动