Minimal working example source:
use16
org 0x7c00
jmp 0x0000:@start
@start:
cli
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0x7c00
sti
mov bp,sp
call @fails
jmp @start
@fails:
nop
retn
times 510-($-$$) db 0
dw 0xAA55
The above is assembled to a binary via FASM or NASM and written to the MBR of a VHD (x.vhd). QEMU is started with debugging support enabled:
qemu-system-i386.exe -m 512 -boot c -net nic -net user -hda x.vhd -no-acpi -s -S -cpu 486
In Cygwin, with GDB 9.1-1, the following commands are then issued to run GDB and attach to QEMU, identify the architecture, skip over the BIOS code and set a breakpoint at the start of the MBR, loaded at 0x7c00, before continuing:
gdb
target remote localhost:1234
set architecture i8086
stepi 11
break *0x7c00
continue
nexti 10
At this point, the $ip is at the nop
line.
@fails:
nop ; <---
retn
Here is where the problem is encountered. Using stepi
, a temporary breakpoint is placed on the retn
line, execution continues through the nop
and breaks where it should. Using stepo
or nexti
, however, execution does continue without problems, but no breakpoint is reached. GDB just enters an endless wait. If a breakpoint is inserted manually after the nop
(on the retn
or at jmp @start
, the instruction to which the call returns with retn
, for example), GDB breaks at that one without issues. Listing all breakpoints at this point produces no new ones, so it seems nexti
did not put a temporary breakpoint anywhere (or temporary breakpoints are not listed?).
After a lot of investigation, here are the strange "fixes" for the issue that let nexti
automatically just skip over nop
and break there as it should: (1) set $ebp = 0x7bfa
and then nexti
or (2) comment out the line mov bp,sp
.
The questions are:
- Why does
nexti
seem to break the breakpoint mechanism at that particular location? Perhaps a breakpoint is put between instructions? Ornexti
has issues with 16-bit code somehow? - Why do the strange "fixes" outlined above work? How can $ebp be at all related to anything if it is not used at any point after the initialization? Does GDB use it internally?