Page 1 of 1

Stuck on BIOS INT10 WriteString!!

Posted: Sat May 24, 2008 12:47 pm
by indiocolifa
I'm trying to start creating some toy OS just for fun. All from scratch, so I began writing the bootloader of course. My first test was to write a BOOTING... message in red, at top of the cleared screen, this is my primitive code:

Code: Select all

	
;; ITCHIES/OS
	;;
	;; Boot loader
	;; =============================================================

	format binary
	use16

	SECTOR_SIZE = 512

	org	0x7c00
	jmp	start

	;; -- message strings -----------------------------------------

	bootmsg		db	'BOOTING...'
	bootmsglen	equ	$-bootmsg

	;; -- boot code -----------------------------------------------
	
start:	mov	ax, 0003h
	int	10h		; clear screen
	mov	ax, 1300h	; moving cursor
	mov	bx, 0004h	; page 0 / redcolor
	xor	dx, dx		; print at (0,0)
	mov	cx, bootmsglen	; string length
	mov	bp, bootmsg	; es:bp --> msg (ES=0 at boot)
	int	10h		; call BIOS
	hlt

	;; -- boot signature ------------------------------------------
	
	db	SECTOR_SIZE-2-($-$$) dup 0
	dw 	0xaa55

The problem is that CX is loaded with wrong count of characters (0x17) so I get the message plus garbage. I don't know where the error is, this is FASM code and the bootmsglen calculation is OK, I think.

Note that I'm assuming that ES is set at 0000 by the BIOS, it's correct to assume this?

Thx for your help.

Posted: Sat May 24, 2008 1:01 pm
by AJ
Hi,

Code: Select all

mov cx, bootmsglen
Moves the memory address 'bootmsglen' in to cx.

Code: Select all

mov cx, WORD [bootmsglen]
Moves the data pointed to by 'bootmsglen'.

Cheers,
Adam

Posted: Sat May 24, 2008 1:02 pm
by AJ
Oh, and by ending your boot message with a NULL terminator, you don't need to store the message length.

HTH,
Adam

Posted: Sat May 24, 2008 1:36 pm
by indiocolifa
AJ wrote:Hi,

Code: Select all

mov cx, bootmsglen
Moves the memory address 'bootmsglen' in to cx.

Code: Select all

mov cx, WORD [bootmsglen]
Moves the data pointed to by 'bootmsglen'.

Cheers,
Adam
Hey Adam, thanks for your reply.

But EQU does reserve some memory? How you can take the address of a symbolic constant?

From what I understand (I know assembler but not how FASM works in particular), FASM does:

Pass 1: Calculates CURRENT_OFFSET - BOOTMSG address
Pass 2: Replaces this offset diff in the mov instruction (number of chars) and that's what CX expects...

Can you give me some light on this "address of equates" thing? :wink:

Posted: Sat May 24, 2008 1:49 pm
by indiocolifa
What I'm trying to say is that it's not bad to use

MOV cx, bootmsg_1

since if I define

bootmsg_1 equ 10 ; (correct value=10 chars)

the message works properly. I don't know why FASM calculates 0x17 as the resulting message length!
:twisted:

Posted: Sat May 24, 2008 1:51 pm
by indiocolifa
Ha! I solved it using FASM constants:

BOOTMSGLEN = $-bootmsg

but I don't understand why this not works with:

BOOTMSGLEN equ $-bootmsg :?

Posted: Sat May 24, 2008 7:22 pm
by svdmeer
indiocolifa wrote:Ha! I solved it using FASM constants:

BOOTMSGLEN = $-bootmsg

but I don't understand why this not works with:

BOOTMSGLEN equ $-bootmsg :?
It's not hard to understand why you got 0x17 as length instead of 10.

Your definition behaved like a macro. On the place where you use it, $ is calulated. So $ is not calculated from the position directly after the string, but in the middle of your code.

Code: Select all

bootmsg      db   'BOOTING...'               [10 bytes]
   bootmsglen   equ   $-bootmsg 

   ;; -- boot code ----------------------------------------------- 
    
start:   mov   ax, 0003h                      [ 3 bytes ]
   int   10h      ; clear screen                [2 bytes]
   mov   ax, 1300h   ; moving cursor     [3 bytes] 
   mov   bx, 0004h   ; page 0 / redcolor   [3 bytes]
   xor   dx, dx      ; print at (0,0)          [2 bytes]
mov   cx, bootmsglen   ; string length
Before the instruction with bootmsglen there are after the label bootmsg 10 bytes of data and 13 bytes of code. 10 + 13 = 23 = 0x17.

Posted: Sat May 24, 2008 7:28 pm
by svdmeer
mov bp, bootmsg ; es:bp --> msg (ES=0 at boot)
I don't think it's safe to assume the value of segment-registers at boot. You'd better initialize them.

From my bootsector:

Code: Select all

xor ax,ax		; ax=0
cli
mov ss,ax       	; ss=0
mov sp,7C00h   ; Top of stack
sti
mov bp,sp ; bp=7c00h for access to filesystem info in the first 127 bytes 
                ; of the bootsector (like FAT parameters) with smaller code
mov ds,ax		; ds=0
mov es,ax		; es=0
...

Posted: Sun May 25, 2008 2:28 pm
by indiocolifa
Thanks, I thought that addresses present on EQU definitions were calculated and later replaced literally, seems that works like a plain literal text replacement.

I fixed some "assumptions", hehe, like segment registers. Now it's working ok, the only question is that I left SS at 0 (it's OK) but I did not specify stack pointer. This is OK?

Posted: Sun May 25, 2008 2:39 pm
by svdmeer
indiocolifa wrote:Thanks, I thought that addresses present on EQU definitions were calculated and later replaced literally, seems that works like a plain literal text replacement.

I fixed some "assumptions", hehe, like segment registers. Now it's working ok, the only question is that I left SS at 0 (it's OK) but I did not specify stack pointer. This is OK?
No. You can't set SS to a value without knowing the value of SP. SS=0, it's the same segment as the MBR/bootsector. In the worst case the stack overwrites your code. So set SS and SP both like in my example.
It doesn't matter where you place the stack (outside code and important data, so keep also away from the first 1536 bytes in RAM), but you should know where it is.
SS and SP both zero is also ok, your stack grows down from 0000:FFFF.
I use SP 7C00h, stack is right under the bootsector/MBR code and grows down, in the unused memory space between realmode interrupt/bios data and the bootsectorMBR code.

Posted: Sun May 25, 2008 4:55 pm
by chezzestix
Is using this Interrupt function really all that worth it? I wrote a custom string out function in nine lines... its as short as what its taking to set up the variables for the int function. All you have to do is set di to the address of the string you want to print and call the function.

Code: Select all

sb_strout:
mov al,[di]
cmp al,0
je sbstrout_end
mov ah,0Eh
int 10h
add di,1h
jmp sb_strout
sbstrout_end:
ret

Posted: Sun May 25, 2008 5:06 pm
by svdmeer
chezzestix wrote:Is using this Interrupt function really all that worth it? I wrote a custom string out function in nine lines... its as short as what its taking to set up the variables for the int function. All you have to do is set di to the address of the string you want to print and call the function.

Code: Select all

sb_strout:
mov al,[di]
cmp al,0
je sbstrout_end
mov ah,0Eh
int 10h
add di,1h
jmp sb_strout
sbstrout_end:
ret
Number of lines is not important. Important is the amount of code it generates, especially when it's part of the bootsector/mbr with very limited space. Instead of "mov al,[di]" and "add di,1h" you can use "lodsb".

Your function has a bug: It doesn't set BX for color attribute and page. Value of BX is uncertain in your function, so textcolor will be unpredicable.

This is my small and safe version, specially written for bootsectors:

Code: Select all

; **********************
; *** Function Print ***
; **********************
;
;  Usage: The string must follow directly after the call
;         to the function "Print".
;
;  For example: 
;               call Print	; Call printfunction
;               db 'Put your message here...',0
;               nop             ; Program execution continues..
;
Print:          cld                     ; Directionflag=0, for lodsb
                pop si                  ; ds:si -> string
                push bp                 ; BP on stack, buggy bioses
                lodsb                   ; Char from string in AL
NextChar:       mov bx,7                ; Color 7 (ordinary light gray)
                mov ah,0x0e             ; Function 0x0e: Teletype output
                int 0x10                ; Call video-BIOS with interrupt 10h
                lodsb                   ; Char from string in AL
                or al,al                ; Value 0 ?
                jnz NextChar            ; If not, print this char
                pop bp                  ; Restore BP
                push si                 ; Put the right value of IP on the stack
                ret                     ; Return from function
The way of calling is somethign different: Instead of setting SI or another register for addressing the string, you have to put the string directly after the function call. That save a MOV SI,... instruction (3 bytes of code). The function has a PUSH SI and POP SI instruction to manage the pointer to the string and a goot return from the function, but it makes a call to the function take less (3 bytes) of code macause MOV SI,.. is not necessary.

Posted: Mon May 26, 2008 1:39 pm
by indiocolifa
I've already used LODSB instruction in a loop in my boot sector. It's really tight. I've also wrote a function to output integers using STOSB (needs AX as a parameter) but that's another story.