BIOS int 10h printing garbage on QEMU
Asked Answered
P

2

3

I have a problem while writing an x86 real mode assembly program that runs as a bootloader in QEMU. I'm trying to print text through BIOS interrupt 0x10. My code is:

print:
    pusha
.loop:
    mov AL, [SI]
    cmp AL, 0
    je .end
    call printChar
    inc SI
    jmp .loop
.end:
    popa
    ret

printChar:
    pusha
    mov AH, 0x0E
    mov BH, 0
    mov BL, 0x0F
    int 0x10
    popa
    ret

I'm using [ORG 0x7c00] as an origin point. I tested the printChar label and calling it with some letter in AL and it works fine. When I try to load a memory address to a message like this:

loadMsg      db "Loading",0
mov SI, loadMessage
call print

I get garbage like 'U' as output on QEMU emulator. Yesterday, I wrote a code really similar to this one and have no problem at all. What is causing my problem and how can it be fixed?

Posthaste answered 28/11, 2015 at 17:15 Comment(1)
Bootloaders are loaded at physical memory address 0x7c00 when the BIOS bootstraps loading the first sector (512 bytes) of a boot disk to RAM, and then jumps to it. You need to tell NASM that your code will be starting from location 0x7c00 so that it knows how to properly generate offsets to your data in memory. Because you can't guarantee that the DS contains the segment you expect when the bootloader jumps to your code - you need to set it manually.Quash
Q
6

I recently wrote some General Bootloader Tips in a Stackoverflow answer that may be of some use to you. Likely Tip #1 applies to your problem:

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.

Based on the fact that printChar works, and that writing an entire string out doesn't suggests that DS:SI is not pointing at the proper location in memory where your string resides. The usual cause of this is that developers incorrectly assume the CS and/or DS register is set appropriately when the BIOS jumps to the bootloader. It has to be manually set. In the case of an origin point of 0x7c00, DS needs to be set to 0. In 16-bit real mode physical memory addresses are computed from segment:offset pairs using the formula (segment<<4)+offset . In your case you are using an offset of 0x7C00. A value in DS of 0 would yield the correct physical address of (0<<4)+0x7c00 = 0x07c00 .

You can set DS to 0 at the start of your program with something like:

xor ax, ax       ; set AX to zero
mov ds, ax       ;     DS = 0  

In the case of QEMU, the BIOS jumps to 0x07c0:0x0000 . This also represents the same physical memory location (0x07c0<<4)+0 = 0x07c00 . Such a jump will set CS=0x07c0 (not CS=0). Since there are many segment:offset pairs that map to the same physical memory location, you need to set DS appropriately. You can't rely on CS being the value you expect. So in QEMU, code like this wouldn't even set DS correctly (when using ORG 0x7c00):

mov ax, cs
mov ds, ax       ; Make DS=CS

This may work on some emulators like DOSBOX and some physical hardware, but not all. The environments where this code would work is when the BIOS jumps to 0x0000:0x7c00 . In that case CS would be zero when it reaches your bootloader code, and copying CS to DS would work. Don't assume CS will be zero in all environments is the main point I am making. Always set the segment registers to what you want explicitly.

An example of code that should work is:

    BITS  16
    ORG   0x7c00
    GLOBAL main

main:
    xor ax, ax        ; AX = 0
    mov ds, ax        ; DS = 0
    mov bx, 0x7c00

    cli               ; Turn off interrupts for SS:SP update
                      ; to avoid a problem with buggy 8088 CPUs
    mov ss, ax        ; SS = 0x0000
    mov sp, bx        ; SP = 0x7c00
                      ; We'll set the stack starting just below
                      ; where the bootloader is at 0x0:0x7c00. The
                      ; stack can be placed anywhere in usable and
                      ; unused RAM.
    sti               ; Turn interrupts back on

    mov SI, loadMsg
    call print

    cli
.endloop:
    hlt
    jmp .endloop      ; When finished effectively put our bootloader
                      ; in endless cycle

print:
    pusha
.loop:
    mov AL, [SI]      ; No segment on [SI] means implicit DS:[SI]
    cmp AL, 0
    je .end
    call printChar
    inc SI
    jmp .loop
.end:
    popa
    ret

printChar:
    pusha
    mov AH, 0x0E
    mov BH, 0
    mov BL, 0x0F
    int 0x10
    popa
    ret

; Place the Data after our code
loadMsg db "Loading",0

times 510 - ($ - $$) db 0   ; padding with 0 at the end
dw 0xAA55                   ; PC boot signature
Quash answered 28/11, 2015 at 19:24 Comment(9)
Really thanks, it helped alot. So since i wasn't setting DS properly, it just used DS:SI and this can be whatever the BIOS on the machine likes. I have a small doubt, setting SS to 0 and SP to 0X7C00, you're not putting the Stack into 0000:7C00? and the Stack will not start to "overwrite" the bootloader?Posthaste
Glad you got your DS problem sorted out. As for SS:SP being 0000:7C00 . The way the stack works (in 16-bit real mode) is that SP is decremented by 2 first and then a a value written at SS:SP. This means that the first PUSH with SS:SP being 0000:0x7c00 will decrement to 0000:0x7BFE and then write a 2 byte WORD to that location. Because of the decrement occurs first it won't write ontop of our code. You could have placed SS:SP pretty much anywhere that didn't conflict with your data and code -I just used 0x0000:0x7c00 as an example.Quash
Dude, you're awesome. I was trying to find an answer for how the Stack works to find where to put the Stack on memory and your answer was perfect. So the Stack works "kinda backwards", it subtracts from SP before add values, for example, if i set SP to 7E00h (7C00h + 512d) then will stat to overwrite my bootloader (from the end, assuming that BIOS loaded my bootloader at 0000:7C00).Posthaste
@hoOmE Correct. One oddity though is if SP reaches 0 the stack will will wrap around at the top of the 64k segment. Let us say we set SS:SP to 0x9000:0x0000 to start, then the first push PUSH would subtract 2 from SP. Only SP is decremented (not the segment) so the first 16-bit value would be pushed on the stack at 0x9000:0xFFFE (because of the wraparound). If you had set SS:SP to 0x0000:0x0000 then the first 16-bit value would have been put at 0x0000:0xFFFE and worked downwards from there.Quash
This helps even more, basically the Stack has 64k and will only be decrement the Pointer until "reach 0" and restart back from the top. Thats explain alot and make things more easy.Posthaste
@hoOmE The stack segment is limited to a size of 64k - yes you are correct. And you are correct about the wraparound.Quash
@MichaelPetch - Note that mov ss,ax, disables interrupts for one instruction, the mov sp,bx, so CLI / STI should not be needed, unless as you mentioned the 8088 (real or virtual) is broken.Exerciser
@Exerciser If there is no chance you will be running on a defective 8088, it isn't needed. If you are possibly going to run on such a hardware it is reasonable to have if you have the space.Quash
@Exerciser : I am well aware that it disables it until end of next instruction (On real 8088/8086 hardware) it should disable the interrupts for any segment register (not just SS). This changed with 80286+where it was only done for SS . I specifically mentioned buggy 8088's for a reason. My code targets a wider variety of hardware that may exist. Having been doing assembly language development on real 8088 hardware since the mid 80s I also account for a well known bug. PC magazine in 1987 ran a story: books.google.com/… .Quash
A
1

This is a problem:

loadMsg    db "Loading",0
mov        SI, loadMessage
call    print

Unless the program jumps over the "loading" text, it executes whatever instructions which those bytes may (or may not) represent. Something like this fixes it:

    jmp      print_msg
    loadMsg    db "Loading",0
print_msg:
    mov        SI, loadMessage
    call    print
Allison answered 28/11, 2015 at 19:22 Comment(1)
Oh, my fault. I'm still learning and this part was just an example. I didn't put any data declaration inside the code ;) I didn't put data inside code by convention but reading what you says makes perfect sense why NOT put data in code segmente, because data will be execute and its rubbish to the processorPosthaste

© 2022 - 2024 — McMap. All rights reserved.