7

For educational purposes, I have adapted this bootloader from mikeos.berlios.de/write-your-own-os.html rewriting it to specifically load at address 0x7c00.

The final code is this:

[BITS 16]           ; Tells nasm to build 16 bits code
[ORG 0x7C00]        ; The address the code will start

start: 
    mov ax, 0       ; Reserves 4Kbytes after the bootloader
    add ax, 288 ; (4096 + 512)/ 16 bytes per paragraph 
    mov ss, ax 
    mov sp, 4096 
mov ax, 0   ; Sets the data segment 
    mov ds, ax 
    mov si, texto   ; Sets the text position 
    call imprime    ; Calls the printing routine
jmp $       ; Infinite loop 
    texto db 'It works! :-D', 0 
imprime:            ; Prints the text on screen
    mov ah, 0Eh     ; int 10h - printing function 
.repeat: 
    lodsb           ; Grabs one char 
    cmp al, 0 
    je .done        ; If char is zero, ends 
    int 10h         ; Else prints char 
jmp .repeat 
.done: 
ret 
times 510-($-$$) db 0 ; Fills the remaining boot sector with 0s 
dw 0xAA55             ; Standard boot signature

I can step through the program and see the registers changing, along with the instruction being executed, stepping with gdb (si) and inspecting with QEMU monitor (info registers, x /i $eip, etc).

After I get into int 10h (the BIOS printing routine), things get a little strange. If I step 500 instructions at once, I can see the character "I" (the first of char of my text string) printed on the screen. So I restarted again and stepped 400 steps (si 400) and then I did one step at a time to see in which exact step "I" got printed. It never happened. I actually stepped 200 steps one by one and nothing happened. As soon as I stepped 100 steps at once (si 100) I got "I" printed on the screen again.

So, I wonder if there is a timing issue (some system interrupt gets in the way as I do a step by step debug). What else could this be?

Anyway, is there a way of skipping the whole BIOS interrupt and other functions and just go back and keep stepping the bootloader code? As suggested by Peter Quiring in the comments, I tried using next. This did not work.

(gdb) next 
Cannot find bounds of current function

So I tried nexti and it just behaves as si.

Thanks!

Community
  • 1
  • 1
Cesar Brod
  • 190
  • 2
  • 13
  • Maybe the qemu screen update works differently if you are single stepping. – Jester Jun 30 '14 at 14:09
  • Why not use 'next' instead of 'step'ing into the interrupt? 'next' will allow the interrupt to finish and then stop the app at the line after the int 10h – Peter Quiring Jun 30 '14 at 19:25
  • Umm, Isn't the first character of your text string (as given)... 'I'? (not 'F'?), and second char is 't', (not 'I'?) As for why 500,400,200 ... steps into the BIOS gives varying results, haven't a clue. Unless you're morbidly curious, I'd skip debugging the BIOS routines and enjoy the fact that your boot sector runs properly. Be careful that the `int 10h` calls don't corrupt your `SI` register, perhaps a push/pop surrounding the `int 10h`? – lornix Jul 01 '14 at 08:53
  • Thanks for the correction, lornix! I've edited the text to change the 'F' by the correct 'I'. I will try using 'next', Peter. Thanks! – Cesar Brod Jul 03 '14 at 15:20
  • I have tried next. It does not work: `(gdb) next` `Cannot find bounds of current function` So I tried `nexti`, It has the same effect of `si`. Is there a way of skip the whole BIOS interrupts and functions and just return the sample bootloader code? I am editing my question to include this. – Cesar Brod Jul 04 '14 at 12:30
  • That seems perfectly normal: the BIOS firmware gets run by the CPU, and GDB sees it, and it contains a lot of instructions. How to skip `int`, is also something I want to do :-) ! – Ciro Santilli Путлер Капут 六四事 Oct 18 '15 at 21:32
  • 1
    Use this set of [16-bit macros](http://ternet.fr/?p=gdb_real_mode). In particular `stepo` will automatically set the breakpoint after the _int_ and run until it is reached and then the breakpoint is automatically cleared. – Michael Petch Oct 18 '15 at 22:37
  • @MichaelPetch thanks for the tip! I have tried that out on a BIOS hello world by it gave me `Invalid cast.`. We should also open an issue with GDB: `nexti` should not enter `int` calls just as it does not enter `call`... – Ciro Santilli Путлер Капут 六四事 Oct 19 '15 at 09:40

2 Answers2

5

I've automated your procedure with a Python script that:

  • calculates the length of the current instruction
  • sets a temporary breakpoint on the next instruction
  • continues

This will also work for any other instruction, but I don't see many other use cases for it, since nexti already jumps over call.

class NextInstructionAddress(gdb.Command):
    """
Run until Next Instruction address.

Usage: nia

Put a temporary breakpoint at the address of the next instruction, and continue.

Useful to step over int interrupts.

See also: http://stackoverflow.com/questions/24491516/how-to-step-over-interrupt-calls-when-debugging-a-bootloader-bios-with-gdb-and-q
"""
    def __init__(self):
        super().__init__(
            'nia',
            gdb.COMMAND_BREAKPOINTS,
            gdb.COMPLETE_NONE,
            False
        )
    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        arch = frame.architecture()
        pc = gdb.selected_frame().pc()
        length = arch.disassemble(pc)[0]['length']
        gdb.Breakpoint('*' + str(pc + length), temporary = True)
        gdb.execute('continue')
NextInstructionAddress()

Just drop that into ~/.gdbinit.py and add source ~/.gdbinit.py to your ~/.gdbinit file.

Tested on GDB 7.7.1, Ubuntu 14.04.

3

This is actually a work around that fits my purposes. What I did was setting breakpoints so I can use "continue" on gdb along with "si" and follow the message being printed on the screen, one character at a time. Here are the steps.

In the first run, I do step my bootloader, so I can actually check the memory positions where the instructions are stored.

Linux shell:

# qemu-system-i386 -fda loader.img -boot a -s -S -monitor stdio
QEMU 1.5.0 monitor - type 'help' for more information
(qemu) 

Other Linux shell (some lines have been supressed [...]):

# gdb
GNU gdb (GDB) 7.6.1-ubuntu
[...]
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
(gdb) set architecture i8086
[...]
(gdb) br *0x7c00
Ponto de parada 1 at 0x7c00
(gdb) c
Continuando.
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) si
0x00007c03 in ?? ()

In the terminal I am running QEMU monitor, I find the address of the instructions executing this command after every si on gdb:

(qemu) x /i $eip
0x00007c03:  add    $0x120,%ax

For those new to QEMU, x display the contents of a register, /i translates it into an instruction and $eip is the instruction point register. By repeating these steps, I find out the addresses for the lodsb and int 10h instructions:

0x00007c29:  lods   %ds:(%si),%al 
0x00007c2e:  int    $0x10 

So, on gdb I just set the breakpoints for these aditional positions:

(gdb) br *0x7c29
Ponto de parada 2 at 0x7c29
(gdb) br *0x7c2e
Ponto de parada 3 at 0x7c2e

Now I can use a combination of "continue" (c) and stepi (si) on gdb and skip through the whole BIOS stuff.

There is probably better ways to do this. However, for my pedagogical purposes, this method works quite well.

Cesar Brod
  • 190
  • 2
  • 13