Print string using INT 0x10 in bootsector
Asked Answered
R

4

5

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
Requisition answered 14/6, 2015 at 19:7 Comment(3)
Awesome question, I don't have that much knowledge of assembly but I wish I did (Degree in CompSci).Awfully
Which assembler are you using? Your code won't work with TASM, it looks more like NASM, but include 'sysf.asm' doesn't work with NASM either.Anatase
sorry my mistake i use FASM not TASMRequisition
A
4

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
Anatase answered 14/6, 2015 at 19:31 Comment(2)
I tried but it didnt work. I get nothing printed out. The code of printl001 was find on internet and I dont understand it muchRequisition
This example assumes that the loader is entered with cs:ip equal to 0:7C00h. This is not necessarily true however, cs may be 7C0h.Joachim
N
3

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.

Nariko answered 14/6, 2015 at 20:14 Comment(0)
T
2

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
Trincomalee answered 6/8, 2021 at 13:5 Comment(5)
For efficiency, you could only push bx / pop bx (much more efficient than pusha/popa), and let the function overwrite AX. Also, inc bx is more compact than add bx, 1. You could also save instructions inside the loop by putting the condition at the bottom (Why are loops always compiled into "do...while" style (tail jump)?), although the int 0x10 takes much more time than this loop body. Still, it's about equal for total code size if you do it with an unconditional jmp to a load + test al,al / jnz loop at the bottom.Anaesthesiology
It's generally recommended to set BH and BL before using ah=0x0e/int 0x10, in case the code is used with the VGA in graphics mode. (I can't print out a character in assembly shows an example of setting them in a useful way). So you could pass the pointer in SI instead of BX (enabling lodsb as a code-size optimization if you want, although slower on modern CPUs than movzx and inc si.)Anaesthesiology
@PeterCordes Thanks for the suggestions on optimization. Can you give a little more info on how to use BL and BH for VGA output?Trincomalee
I don't know much about obsolete legacy BIOS interfaces, especially in combination with use BIOS video-mode setting calls (int 10h/ah=00), that's why I linked Sep's answer for an example of sensible values to pass. See ctyme.com/intr/rb-0106.htm for Ralf Brown's interrupt list entry which covers the general case and quirks of some real-world BIOSes.Anaesthesiology
Also, wallyk's answer on this same question links the same docs and has some description. Not sure if the BH page number is actually significant in all video modes, or if that might be BIOS-dependent.Anaesthesiology
A
2

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.

Atrocious answered 7/8, 2021 at 20:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.