Print string using INT 0x10 in bootsector

4.4k views Asked by At

I want to create printl function that allow me to print string in the ax register. I am in 16-bit real mode and I can not find any way to print a message. I using int 0x10 to print a single letter.

I try pass argument (string to print) in bx register, then in a loop print letter by letter and then go back using popa and ret. My code didn't really work -- either it created a infinite loop or printed a strange sign.

If you know more efficient way to do it then it's not a problem. I would also like to ask about comment your code if you gave any

This is my code

boot.asm:

start:
    mov bx, welcome    ;put argument to bx
    call printl        ;call printl function in sysf.asm
    hlt                ;halt cpu

welcome db 'Hello', 0

include 'sysf.asm'
times 510 - ($-$$) db 0

db 0x55
db 0xAA

sysf.asm:

;print function
; al is one letter argument (type Java:char)
;
print:
        pusha
        mov ah, 0x0e
        int 0x10
        popa
        ret              ; go back

;printl function
; bx is argument of type Java:String
;
printl:
        pusha
        jmp printl001
printl001:
        lodsb             ; I was working with si register but i would like to use bx register
        or al,al
        jz printl002
        mov ah, 0x0e
        int 0x10
        jmp printl001 
printl002:
        popa
        ret
4

There are 4 answers

2
Ross Ridge On BEST ANSWER

The lodsb instruction loads the byte pointed to by the DS and SI registers but you haven't loaded either with a valid value. Since this a bootloader you also need to use the ORG directive, otherwise the assembler won't know where your code, and therefore the welcome message, gets loaded into memory. Try changing the start of of your program to:

ORG 0x7c00

start:
    push cs
    pop ds
    mov si, welcome
0
wallyk On

According to the documentation for BIOS int 0x10:

Teletype output: AH=0Eh, AL = Character, BH = Page Number, BL = Color (only in graphic mode)

If BH is not zero, it will write to a video page which is not displayed. Unless, of course, you have flipped to display whatever page is in BH. Probably you will want to modify your print function:

print:
        pusha
        mov ah, 0x0e
        xor bx, bx       ; BX = 0
        int 0x10
        popa
        ret              ; go back

If your output causes the screen to scroll, BP might be destroyed, though it should not cause a problem for your code because it preserves all the registers.

5
Nivas On

I'm totally new to this and I'm not sure if this is the efficient way to do this but it works for me

print_string:
    pusha
    mov ah, 0x0e
    mov al, [bx]
    loop:
        cmp al, 0
        je break
        int 0x10
        add bx, 0x01
        mov al, [bx]
        jmp loop
    break:
        popa
        ret
0
Sep Roland On

If, in your sysf.asm, you already had a single character print routine, why didn't you call it from within your printl routine?
Not that it matters, so read on...

Writing a bootloader

BIOS loads your bootloader program in memory at the address 7C00h and the only register that BIOS passes on to your code is the BootDrive number in the DL register. There are no other registers that you can trust to hold whatever value you would hope to find there!

Now, if you don't use the ORG directive on bootloader code, then the assembler (FASM) will conclude that you want an implicit ORG 0. You need to setup the segment register(s) accordingly. The code in the PrintString procedure depends on the DS segment register. Its correct value will be 07C0h.
However, most people will prefer to start bootloader code with an explicit ORG 7C00h (if only to make it recognizable in an instant). In this case the DS segment register needs to be loaded with 0000h.

Using the BIOS api

The BIOS.Teletype function expects the following arguments:

  • BL GraphicsColor; this is only used when the display is in a graphics mode
  • BH DisplayPage; the number of display pages depends on the video mode
  • AL CharacterCode; the ASCII code of the character to display
  • AH FunctionNumber; the number 0Eh (0x0E)

Because the BL and BH registers are part of the BX register, we should never use BX to address the string when writing this kind of PrintString procedure!

And because, when in a bootloader, the display will normally be in display page 0 of the text video mode, we can omit the BL GraphicsColor argument, but we should still set the BH DisplayPage. If we don't, then the characters could land on any of the alternative display pages or even nowhere at all.

What I would like

I want to create printl function that allow me to print string in the ax register.

A name like printl is hard to read! And a label like printl001 really hurts my eyes! Better use something like PrintString that conveys what is its purpose.

There's no benefit in passing the argument in the AX register. SI is generally the best choice in bootloader code because it facilitates the use of the lodsb string primitive instruction. This can reduce the code's footprint. But note that it will be wise to use the cld instruction once to be sure that the direction flag is reset so that SI can increment the way we want it.

        ORG     7C00h

        xor     ax, ax
        mov     ds, ax
        cld                 ; So `lodsb` will increment SI

        mov     si, welcome
        call    PrintString
        hlt
; --------------------------
; IN (si) OUT ()
PrintString:
        pusha               ; Preserving all registers
        mov     bh, 0       ; DisplayPage
        jmp     .While
.Do:    mov     ah, 0Eh     ; BIOS.Teletype
        int     10h
.While: lodsb
        test    al, al      ; Test for the end of the zero-terminated string
        jnz     .Do         ; Not yet
        popa
        ret
; --------------------------
welcome db 'Hello', 0
; --------------------------
times 510 - ($-$$) db 0
dw 0xAA55

Because this code must run in 512 bytes, it makes sense to preserve multiple registers using the 1-byte instructions pusha/popa. But if the program allows it, not preserving will shave off even those 2 bytes.

Below is an alternative way of passing strings that allows to not have to specify the address of the string explicitly, shaving off a further 3 bytes.

        ORG     7C00h

        xor     ax, ax
        mov     ds, ax
        cld                 ; So `lodsb` will increment SI

        call    PrintString ; -> (AX BX SI)
        db      'Hello', 0
        hlt
; --------------------------
; IN () OUT () MOD (ax,bx,si)
PrintString:
        pop     si          ; -> SI is the address of the message 'Hello', 0
        mov     bh, 0       ; DisplayPage
        jmp     .While
.Do:    mov     ah, 0Eh     ; BIOS.Teletype
        int     10h
.While: lodsb
        test    al, al      ; Test for the end of the zero-terminated string
        jnz     .Do         ; Not yet
        push    si          ; SI holds the address where execution resumes (here its `hlt`)
        ret
; --------------------------
times 510 - ($-$$) db 0
dw 0xAA55

Be adviced: All this "shaving off" is fine in the one-time bootloader code where codesize is everything. It's not meant to be used in your everyday-coding.