Jack M
Jack M

Reputation: 6035

Why does my bootloader not load a byte from memory correctly?

I have the following x86 program:

mov ah, 0x0e          ; Set up call to BIOS routine to print character

mov al, [character]   ; Stick the byte at label "character"
int 0x10              ; Display character in al

jmp $                 ; Loop forever

character:
db 0x41               ; Put the byte "A" at this position

times 510-($-$$) db 0 ; Pad with zeros and end with the magic number for a bootloader
db 0x55
db 0xaa

I'm running it in two different ways:

I use the following commands to run this code:

$ nasm -f bin -o boot.bin main.s
$ qemu-system-x86_64 boot.bin  # to test
$ dd if=boot.bin of=/dev/sda # to put it on a USB stick

The code as written above doesn't work in either case. On the hardware it displays a blinking cursur, and on qemu it prints a Cyrillic letter, rather than "A". So I change the second (non-empty) line to

mov al, [0x7c00 + character]

adding the offset 0x7c00 to the label, since according to some sources x86 puts your bootloader at 0x7c00 in memory. This works as expected in qemu, but continues to give me a blinking cursor on the hardware. Note that this has the same effect as putting [org 0x7c00] at the top, by which I mean the binaries produced by using the above line or by adding an org directive are identical (I compared their md5s).

To make sure my hardware doesn't have some weird character set where 0x41 isn't an "A", I tried

mov al, 0x41

and that works on both qemu and the hardware.

How can I properly reference the data stored at "character" so that my laptop will find the value that's supposed to be there? Note that because this is a bootloader the CPU is (if I understand correctly) in 16-bit real mode.

Upvotes: 3

Views: 341

Answers (2)

Jack M
Jack M

Reputation: 6035

The x86 features several segment registers containing memory offsets. In real mode (and other modes?), these registers are implicitly added to any memory reference you make. Which segment register is used depends on the context (in other words what kind of instruction the address is used in). In our case, when we try to get data from memory with

mov al, [character]

the processor will implicitly add the contents of the ds (for "data segment") register (multiplied by 16) to the memory offset character. Note that this happens at runtime, not compile-time, so you won't see this in your binary if you disassemble it.

The solution is to zero out ds at the top of the assembly program. However, note that you can't actually just say mov ds, 0 because x86 doesn't support writing constants to segment registers - you have to go via another register as in

mov ax, 0
mov ds, ax

For completeness, this is the full updated code which works on both my laptop and QEMU. Differences from the code in the question are commented below.

mov ax, 0  ; Zero out the data segment register
mov ds, ax ;

mov ah, 0x0e

mov al, [0x7c00 + character] ; Add 0x7c00 to the offset
                             ; As mentioned in the question, putting ORG 0x7C00 at the top of the file
                             ; also works (and is better, but this is clearer for demonstration purposes)
                             ; and in fact produces an identical binary to this explicit addition.
int 0x10

jmp $

character:
db 0x41

times 510-($-$$) db 0
db 0x55
db 0xaa

Clearly, what was happening here is that the ds register was zero by default on QEMU but not on my hardware. A real bootloader written by a professional would always zero out explicitly this sort of thing rather than assuming the BIOS put the registers in any particular state before loading its code.

If you've been reading "Writing a Simple Operating System - from Scratch" by Nick Blundell like me, he actually talks about this stuff a little later on in section 3.6.1 ("Extended memory access using segments"). Unfortunately I got stuck on this several pages before that and didn't read ahead.

Upvotes: 3

AlanCui
AlanCui

Reputation: 157

Maybe You lost some parameters and ORG command

Try This

org 0x7c00            ; Tell NASM ,This program begin at Address 0x7c00

mov ah, 0x0e          ; Set up call to BIOS routine to print character

mov al, [character]   ; Stick the byte at label "character"

mov bh,0              ; The PAGE 0

mov bl,0xff           ; White

int 0x10              ; Display character in al

jmp $                 ; Loop forever

character:

db 0x41               ; Put the byte "A" at this position

times 510-($-$$) db 0 ; Pad with zeros and end with the magic number for  a bootloader

db 0x55

db 0xaa

Upvotes: -1

Related Questions