DOS, how to break on EP (qemu, bochs) (last update: 2013-11-20, created: 2013-10-20) back to the list ↑
A problem I've recently encountered:
Having a CPU-level debugger (bochsdbg, qemu+gdb stub), how to break on the Entry Point of a DOS program?

Intro
The above task is obviously trivial while using standard DOS debuggers - you run the debugger with the program executable as the argument, then magic happens, and you end up looking at the debugger having already hit the breakpoint at the EP.

On CPU-level debuggers (or actually: CPU emulators with embedded debugging features) that's a little more complicated, since there is no notion of program or an application - that's strictly OS stuff which is a layer way above the CPU emulation. So, if there is no notion of program, then there is no notion of a program EP, and so it's hard to know where to break.

(One exception here is DOSBox debugger, which has a "debug" command inside the emulated environment which understands programs and their EPs.)

So, one could just take a look at the MZ file, determine the entry point (IMAGE_DOS_HEADER.e_ip), and place a "hardware" breakpoint on that address - right?
The answer is of course "yes and no".

The missing piece of information here is the CS segment, which is actually chosen by the DOS MZ loader.
So "no", you can't just place the breakpoint without knowing what CS will the EP end up on.
And "yes", if you can set a breakpoint as "ip==e_ip && cs==whatever", then you are all set (though that might hit a few false positives at first too).

Unfortunately neither the bochsdbg, nor qemu+gdb stub, seemed to be able to place that kind of breakpoints (though you still could do it with bochs instrumentation of course).

Getting the break on EP
One of the ideas I had was to analyze the DOS MZ loader (or actually the INT 21h/AH=4Bh interrupt aka EXEC - LOAD AND/OR EXECUTE PROGRAM), find how it transfers the control to the program and set a breakpoint there.

The prerequisites:
- DOS on some floppy image or hard disk image
- a CPU-level debugger (I used qemu+gdb stub+gdb)
- IDA for code analysis

I started (after DOS was up and running) with looking up the address of the INT 21h handler in the IVT - that's basically displaying 2 16-bit words at physical address 0x21*4:

(gdb) x/2hx 0x21*4
0x84:   0x40f8  0x000f

That basically means that the handler is at 000F:40F8. I proceeded with dumping the whole 000F:* segment to a file and loading it in IDA.

(gdb) dump binary memory /tmp/int21.bin 0xf0 0xf0+0xffff
Now I needed to find the handler for the 4Bh function. The code at the beginning looks like this:

seg000:40F8                 cli
seg000:40F9                 cmp     ah, 6Ch ; 'l'
seg000:40FC                 ja      short loc_41C0
seg000:40FE                 cmp     ah, 33h ; '3'
seg000:4101                 jb      short loc_420B
seg000:4103                 jz      short loc_4197
seg000:4105                 cmp     ah, 64h ; 'd'

And it's followed by what looks like an environment setup for executing more complex code. The interesting part start about 1-2 pages below:

seg000:4198                 mov     bl, ah          ; AH holds the function number
seg000:419A                 shl     bx, 1
...
seg000:41EA                 mov     bx, cs:[bx+3E9Eh]
seg000:41EF                 xchg    bx, ss:5EAh
...
seg000:41F9                 call    word ptr ss:5EAh

So this code basically picks up the handler function address from the lookup table placed at 000F:3E9E (NOTE: in case of other DOS versions this can be a totally different address).
Looking at 000F:3E9E+4B*2 I got the address of the "EXEC" implementation.

seg000:3E9E int_handler_table dw 0A1F7h
...
seg000:3F34                 dw 9B5Fh                ; <-- 21h/AH=4B

The function at that address (000F:9B5F) is very very long, but... the important thing is that at the very bottom there are two retf instructions, which one can assume do the transfer (well, it would be either a jmp far, a call far, or a ret far).

seg000:9B5F                 mov     byte ptr ss:85h, 0
seg000:9B65                 cmp     al, 5
...
long long way down
...
seg000:A0D2                 push    ax
seg000:A0D3                 mov     ax, 1115h
seg000:A0D6                 push    ax
seg000:A0D7                 mov     ax, es
seg000:A0D9                 retf  ; <--------
...
seg000:A0E8                 mov     es, dx
seg000:A0EA                 mov     ds, dx
seg000:A0EC                 mov     ax, bx
seg000:A0EE                 retf  ; <-------- 

So, setting the breakpoints at 000F:A0D9 and 000F:A0EE (in case of my DOS version) will allow you to:
1. Read full EP (CS:IP) from the stack.
2. Single-step into the EP ( it seems that qemu gdb stub does not support single stepping though actually the problem was a known bug with breakpoints - if a breakpoint is hit, you cannot continue execution in qemu/rmode until you disable the breakpoint, or it would just re-hit; this is probably because GDB doesn't care about segments, and doesn't take them into account while calculating which breakpoint to remove at given CS:IP).

Let's test it:

(gdb) break *($cs*0x10 + 0xa0d9)
Breakpoint 2 at 0xa1c9
(gdb) break *($cs*0x10 + 0xa0ee)
Breakpoint 3 at 0xa1de
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000a0ee in ?? ()
3: x/i $eip+$cs*0x10
   0xa1de:      retf   
(gdb) x/2hx $ss*0x10+$sp
*0x2769c:       0x0000  0x1fac* 

So in my case the EP was 1FAC:0000. Now I could easily put a breakpoint there and continue debugging through the code.

Random notes
To switch gdb into disassembling 16-bit code it seems you have to do this:

(gdb) set architecture i8086
To switch back into 32-bit code:

(gdb) set architecture i386
And that's about it.

Even more random notes
If you like these kind of setups (emulator+debugger), check out Heisenberg project - it's basically qemu + gdb + volatility, quite a cool idea.
【 design & art by Xa / Gynvael Coldwind 】 【 logo font (birdman regular) by utopiafonts / Dale Harris 】