I’ve written before about how you can use qemu + gdb to debug a guest. Today I was wondering how I was going to debug a problem in a BIOS option ROM, when Stefan Hajnoczi mentioned this tip: Insert
1: jmp 1b
into the code as a “poor man’s breakpoint”. In case you don’t know what that assembly code does, it causes a jump back (b
) to the previous 1
label. In other words, an infinite loop.
After inserting that into the option ROM, recompiling and rebooting the virtual machine, it hangs in the boot, and hitting ^C
in gdb gets me straight to the place where I inserted the loop.
(gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x0000fff0 in ?? () (gdb) set architecture i8086 The target architecture is assumed to be i8086 (gdb) cont Continuing. ^C Program received signal SIGINT, Interrupt. 0x00000045 in ?? () (gdb) info registers eax 0xc100 49408 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0x6f30 0x6f30 ebp 0x6f30 0x6f30 esi 0x0 0 edi 0x0 0 eip 0x45 0x45 eflags 0x2 [ ] cs 0xc100 49408 ss 0x0 0 ds 0xc100 49408 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) disassemble 0xc1000,0xc1050 Dump of assembler code from 0xc1000 to 0xc1050: ... 0x000c103c: mov %cs,%ax 0x000c103e: mov %ax,%ds 0x000c1040: mov %esp,%ebp 0x000c1043: cli 0x000c1044: cld 0x000c1045: jmp 0xc1045 0x000c1047: jmp 0xc162c 0x000c104a: sub $0x4,%esp 0x000c104e: mov 0xc(%esp),%eax End of assembler dump.
Look, my infinite loop!
I can then jump over the loop and keep single stepping*:
(gdb) set $eip=0x47 (gdb) si 0x0000062c in ?? () (gdb) si 0x0000062e in ?? () (gdb) si 0x00000632 in ?? ()
I did wonder if I could take Stefan’s idea further and insert an actual breakpoint (int $3
) into the code, but that didn’t work for me.
Note to set breakpoints, the regular gdb break
command doesn’t work. You have to use hardware-assisted breakpoints instead:
(gdb) hbreak *0xc164a Hardware assisted breakpoint 1 at 0xc164a (gdb) cont Continuing. Program received signal SIGTRAP, Trace/breakpoint trap. 0x0000064a in ?? ()
Note:
* If you find that single stepping doesn’t work, make sure you are using qemu in TCG mode (-M accel=tcg
), as KVM code apparently cannot be single-stepped.
Years ago I tried to use this trick. Namely, I was trying to debug some hairy mode switch code (well, is there any other kind of mode switch code?) in edk2, that is, going from real mode to protected mode to long mode. Something was wrong and the mode switching caused the VM to reboot; I didn’t know which step exactly.
Of course such assembly is mostly undebuggable, so IIRC I tried to add the kind of infinite loop you mention, between the adjacent steps, with the argument being, if the VM hangs (spinning) instead of rebooting, then the steps before the loop are okay — they don’t cause the reboot.
My debugging attempts failed. With Paolo’s help (I think — I’m fuzzy by now) it became clear that the jump instruction *itself* caused a reboot (triple fault IIRC) *even if* the instructions right before it were okay. (Something about the code segment being incorrectly set up, or whatever.) So, the behavior was indistinguishable from the symptoms.
That’s when I decided, paraphrasing a Hungarian saying in English, that mode switching be best written by whoever has two moms (“írjon ilyet, akinek két anyja van”) 🙂
Hm, wait, since we’re doing open source, I can actually give you a link, if you are interested:
http://thread.gmane.org/gmane.comp.bios.tianocore.devel/5343/focus=5386
I do agree that this technique can be useful whenever a jump instruction is supposed to work. (Which is “almost always”, I guess :))
Instruction prefetch buffer is filled and can run instructions, but as soon as you jump it needs to refill that, which fails because %cs not set up mumble mumble?