This isn't a minimal complete example, but the issue is pretty common. Even without seeing the rest of the code I'd guess that you have a mismatch in the segment being used by the processor (or simulated processor) at boot up and the offset being used by your assembly code.
With 16-bit segment:offset addressing there are many combinations of segments and offsets that point to the same physical memory address. The most common ones used by BIOSes are 0x07c0:0x0000 and 0x0000:0x0000. (0x07c0 << 4)+0x0000 = physical address 0x07c00. (0x0000 << 4) + 0x7c00 = physical address 0x07c00 too. The idea is that you want your ORG directive to reflect the offset portion what you want to use and then manually set DS when your bootloader starts. The lack of an ORG directive in NASM (when building using -f bin
) defaults to org 0x0000
.
I have General Bootloader Tips that say this:
- When the BIOS jumps to your code you can't rely on CS,DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts. You can only be guaranteed that your bootloader will be loaded and run from physical address 0x00007c00 and that the boot drive number is loaded into the DL register.
[snip]
- The direction flag used by
lodsb
, movsb
etc could be either set or cleared. If the direction flag is set improperly SI/DI registers may be adjusted in the wrong direction. Use STD
/CLD
to set it to the direction you wish (CLD=forward/STD=backwards). In this case the code assumes forward movement so one should use CLD
. More on this can be found in an instruction set reference
You can't rely on the direction DF flag (needed for LODSB) and the segment registers (like DS) being a particular value (or the right one) when your bootloader starts. You need to set it explicitly at the start of your code. If you don't, the string you wish to print will be read from the wrong part of RAM and the result will be gibberish. The fact that you seem to be printing the same gibberish twice suggests another issue and that is that you don't have an infinite loop of some sort to end your bootloader. I have another Stackoverflow Answer that I believe is related.
I will fill out a missing bootloader that calls the print_string
function you have provided that uses 0x0000 as the segment and we use and org 0x7c00
as the starting offset:
; Create bootloader with: nasm -f bin boot.asm -o boot.bin
; To test in QEMU use: qemu-system-i386 -fda boot.bin
org 0x7c00
main:
xor ax, ax ; XOR register with itself zeroes the register
mov ds, ax ; We want DS:ORG to be 0x0000:0x7c00
cld ; Forward direction for string instructions like LODSB
mov si,Msg
call print_string ; Print the message
; This is a preferred way to do an infinite loop but
; you could also do: JMP $. This prevents falling through
; the code below and off to memory we don't intend to execute
cli
.endloop:
hlt
loop .endloop
print_string:
Pusha
mov ah,0Eh
xor bx,bx ; Ensure we are writing to page 0
; XOR register with itself zeroes the register
.repeat:
lodsb
cmp al,0
je .done
int 10h
Jmp .repeat
.done:
Popa
Ret
; Place data after the code but before the boot signature.
Msg db "hello",0 ; Nul terminated string
times 510 - ($ - $$) db 0 ; padding with 0 at the end
dw 0xAA55 ; PC boot signature
Alternatively we could have used a segment of 0x07c0 and used org 0x0000
(or omitted it) and it should still work. The beginning of the code could look like:
org 0x0000
main:
mov ax, 0x07c0
mov ds, ax ; We want DS:ORG to be 0x07c0:0x0000
cld ; Forward direction for string instructions like LODSB
This is the output I get using QEMU for both versions:
