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.
include 'sysf.asm'
doesn't work with NASM either. – Anatase