VESA mode, OSDEV - c

I am currently writing an OS from complete scratch(making my own bootloader etc), and I am attempting to adapt VESA mode.
I have read documentation, it all makes sense..all but just a few things.
This is directly from the documentation(I have it implemented differently):
vbe_set_mode:
mov [.width], ax
mov [.height], bx
mov [.bpp], cl
sti
push es ; some VESA BIOSes destroy ES, or so I read
mov ax, 0x4F00 ; get VBE BIOS info
mov di, vbe_info_block
int 0x10
pop es
cmp ax, 0x4F ; BIOS doesn't support VBE?
jne .error
mov ax, word[vbe_info_block.video_modes]
mov [.offset], ax
mov ax, word[vbe_info_block.video_modes+2]
mov [.segment], ax
mov ax, [.segment]
mov fs, ax
mov si, [.offset]
.find_mode:
mov dx, [fs:si]
add si, 2
mov [.offset], si
mov [.mode], dx
mov ax, 0
mov fs, ax
cmp [.mode], 0xFFFF ; end of list?
je .error
push es
mov ax, 0x4F01 ; get VBE mode info
mov cx, [.mode]
mov di, mode_info_block
int 0x10
pop es
cmp ax, 0x4F
jne .error
mov ax, [.width]
cmp ax, [mode_info_block.width]
jne .next_mode
mov ax, [.height]
cmp ax, [mode_info_block.height]
jne .next_mode
mov al, [.bpp]
cmp al, [mode_info_block.bpp]
jne .next_mode
; If we make it here, we've found the correct mode!
mov ax, [.width]
mov word[vbe_screen.width], ax
mov ax, [.height]
mov word[vbe_screen.height], ax
mov eax, [mode_info_block.framebuffer]
mov dword[vbe_screen.physical_buffer], eax
mov ax, [mode_info_block.pitch]
mov word[vbe_screen.bytes_per_line], ax
mov eax, 0
mov al, [.bpp]
mov byte[vbe_screen.bpp], al
shr eax, 3
mov dword[vbe_screen.bytes_per_pixel], eax
mov ax, [.width]
shr ax, 3
dec ax
mov word[vbe_screen.x_cur_max], ax
mov ax, [.height]
shr ax, 4
dec ax
mov word[vbe_screen.y_cur_max], ax
; Set the mode
push es
mov ax, 0x4F02
mov bx, [.mode]
or bx, 0x4000 ; enable LFB
mov di, 0 ; not sure if some BIOSes need this... anyway it doesn't hurt
int 0x10
pop es
cmp ax, 0x4F
jne .error
clc
ret
.next_mode:
mov ax, [.segment]
mov fs, ax
mov si, [.offset]
jmp .find_mode
.error:
stc
ret
.width dw 0
.height dw 0
.bpp db 0
.segment dw 0
.offset dw 0
.mode dw 0
What I am confused about is, why does it assign the segment to the video modes pointer plus 2?
I get that the video mode pointer has an offset:segment, but I am just confused as to why we assign video mode pointer + 2 to the segment, and why we add si by two after we assign the offset and segment to the dx register.

mov ax, word[vbe_info_block.video_modes]
mov [.offset], ax
mov ax, word[vbe_info_block.video_modes+2]
mov [.segment], ax
why does it assign the segment to the video modes pointer plus 2? I get that the video mode pointer has an offset:segment, but I am just confused as to why we assign video mode pointer + 2 to the segment
A far pointer is stored in memory with a word-sized offset followed by a word-sized segment.
The offset is stored at vbe_info_block.video_modes, and the segment is stored in the following word which has an address that is 2 more, so at [vbe_info_block.video_modes + 2]
mov dx, [fs:si]
add si, 2
mov [.offset], si
and why we add si by two after we assign the offset and segment to the dx register.
We don't assign the offset and segment to the DX register!
The far pointer we retrieved (and put in FS:SI) points to a list of word-sized mode numbers. It's a mode number that we load in the DX register. And the add si, 2 mov [.offset], si is there so the loop can iterate over all the words in the list.

Related

Efficiency when swapping array contents

I'm new to assembly and I am trying to swap the contents between two arrays. I have this code so far and after testing it, I've verified it works. However, I am wondering if this is the most efficient way to get the desired result or if there is another possible solution to this that is more efficient?
arrW WORD 100h, 200h, 300h
arrSW SWORD -140, 200, -300
mov ax, arrW
xchg ax, arrSW
xchg ax, arrW
mov ax, [arrW +2]
xchg ax, [arrSW +2]
xchg ax, [arrW +2]
mov ax, [arrW + 4]
xchg ax, [arrSW +4]
xchg ax, [arrW +4]
mov ax, arrW
xchg ax, arrSW
xchg ax, arrW
mov ax, [arrW +2]
The first thing that struck me is that second xchg. There's no sense in loading the AX register right before the other load of AX in the following instruction. The first rewrite that also gives a 20% speed increase on 8086 therefore is:
mov ax, [arrW]
xchg ax, [arrSW]
mov [arrW], ax
mov ax, [arrW + 2]
xchg ax, [arrSW + 2]
mov [arrW + 2], ax
mov ax, [arrW + 4]
xchg ax, [arrSW + 4]
mov [arrW + 4], ax
A solution where you avoid using the xchg instruction doesn't pay on 8086, but is the way to go on x86 in general. eg. Next snippet is 10% slower on 8086:
mov ax, [arrW]
mov bx, [arrSW]
mov [arrW], bx
mov [arrSW], ax
A loop can't beat your current unrolled code, but if the arrays should become larger then next is what it could look like:
mov cx, 3
mov si, OFFSET arrW
mov di, OFFSET arrSW
More:
mov ax, [si]
mov dx, [di]
mov [si], dx
mov [di], ax
add si, 2
add di, 2
dec cx
jnz More
In case the arrays arrW and arrSW follow each other in memory, the same loop is better written as:
mov bx, OFFSET arrW
More:
mov ax, [bx]
mov dx, [bx + 6]
mov [bx], dx
mov [bx + 6], ax
add bx, 2
cmp bx, OFFSET arrSW
jb More
If the CPU supports 32-bit registers, then working with these dwords can halve the number of iterations that are required. If the count of elements is odd, we peel off one word sized swap:
mov cx, 39
mov si, OFFSET arrW
mov di, OFFSET arrSW
shr cx, 1
jnc More ; Count was even
mov ax, [si]
mov dx, [di]
mov [si], dx
mov [di], ax
add si, 2
add di, 2
More:
mov eax, [si]
mov edx, [di]
mov [si], edx
mov [di], eax
add si, 4
add di, 4
dec cx
jnz More
The above code peels off one word sized swap at the start of the loop. As #PeterCordes wrote in his comment below this answer, it is generally beter to put the peeled-off swap at the end (for reasons of data alignment). Next is that version:
mov cx, 39
mov si, OFFSET arrW
mov di, OFFSET arrSW
shr cx, 1 ; -> CF is set if count is odd
jz Next \
More: |
mov eax, [si] |
mov edx, [di] |
mov [si], edx |
mov [di], eax | Nothing changes the CF
lea si, [si + 4] |
lea di, [di + 4] |
dec cx |
jnz More |
Next: /
jnc Done ; (*) Count was even
mov ax, [si]
mov dx, [di]
mov [si], dx
mov [di], ax
Done:

Input and print an array of strings in Assembly 8086

I am trying to make a 8086 program in which I input an array of string and then the program prints them. But the program prints only the 1st characters of input strings and jumbled text.
This is the code where I attempted to do it.
data segment
; Definicija podataka
poruka1 DB "Input array length: $"
strN DB " "
N DW 0
poruka2 DB "Input string: $"
strM DB " "
niz1 DB 16 dup(?)
ends
; Deficija stek segmenta
stek segment stack
dw 128 dup(0)
ends
; Ucitavanje znaka bez prikaza i cuvanja
keypress macro
push ax
mov ah, 08
int 21h
pop ax
endm
; Isis stringa na ekran
writeString macro s
push ax
push dx
mov dx, offset s
mov ah, 09
int 21h
pop dx
pop ax
endm
; Kraj programa
krajPrograma macro
mov ax, 4c02h
int 21h
endm
code segment
; Novi red
novired proc
push ax
push bx
push cx
push dx
mov ah,03
mov bh,0
int 10h
inc dh
mov dl,0
mov ah,02
int 10h
pop dx
pop cx
pop bx
pop ax
ret
novired endp
; Ucitavanje stringa sa tastature
; Adresa stringa je parametar na steku
readString proc
push ax
push bx
push cx
push dx
push si
mov bp, sp
mov dx, [bp+12]
mov bx, dx
mov ax, [bp+14]
mov byte [bx] ,al
mov ah, 0Ah
int 21h
mov si, dx
mov cl, [si+1]
mov ch, 0
kopiraj:
mov al, [si+2]
mov [si], al
inc si
loop kopiraj
mov [si], '$'
pop si
pop dx
pop cx
pop bx
pop ax
ret 4
readString endp
; Konvertuje string u broj
strtoint proc
push ax
push bx
push cx
push dx
push si
mov bp, sp
mov bx, [bp+14]
mov ax, 0
mov cx, 0
mov si, 10
petlja1:
mov cl, [bx]
cmp cl, '$'
je kraj1
mul si
sub cx, 48
add ax, cx
inc bx
jmp petlja1
kraj1:
mov bx, [bp+12]
mov [bx], ax
pop si
pop dx
pop cx
pop bx
pop ax
ret 4
strtoint endp
; Konvertuje broj u string
inttostr proc
push ax
push bx
push cx
push dx
push si
mov bp, sp
mov ax, [bp+14]
mov dl, '$'
push dx
mov si, 10
petlja2:
mov dx, 0
div si
add dx, 48
push dx
cmp ax, 0
jne petlja2
mov bx, [bp+12]
petlja2a:
pop dx
mov [bx], dl
inc bx
cmp dl, '$'
jne petlja2a
pop si
pop dx
pop cx
pop bx
pop ax
ret 4
inttostr endp
start:
; postavljanje segmentnih registara
ASSUME cs: code, ss:stek
mov ax, data
mov ds, ax
; Mesto za kod studenata
writeString poruka1
; Unos broja elemenata
push 3
push offset strN
call readString
; Pretvaranje strN u broj
push offset strN
push offset N
call strtoint
call novired
writeString strN
; Unos elemenata niza
mov cx, N
mov di, 0
mov si, 0
unos:
call novired
writeString poruka2
push 6
push offset strM
call readString
mov al, strM
mov niz1[di], al
add di, 2
add si, 1
loop unos
call novired
mov cx, N
mov di, 0
mov si, 0
ispis:
writeString niz1[di]
add di, 2
add si, 1
loop ispis
krajPrograma
ends
end start
I input an array of string and then the program prints them. But the program prints only the 1st characters of input strings ...
What else could the program print if the 1st character is all that you store in memory?
mov al, strM
mov niz1[di], al
add di, 2
... and jumbled text.
With add di, 2 you leave an undetermined byte between any two of these 1st characters. That's not going to look nice. Make that byte the DOS string terminator $.
Your writeString macro does not load an address but a word from memory. That's not going to work. The invokation writeString niz1[di] gets expanded into mov dx, offset niz1[di]. What you need here is LEA instead of MOV.
Try the following:
....
unos:
call novired
writeString poruka2
push 6
push offset strM
call readString
mov al, strM
MOV AH, "$"
mov niz1[di], AX
add di, 2
loop unos
call novired
mov cx, N
mov di, 0
ispis:
LEA DX, niz1[di]
MOV AH, 09h
INT 21h
add di, 2
loop ispis
...

Why does this ASM function not print properly?

I am trying to write a little function to print either a null-terminated or fixed-length string in a specific memory location. Here is my code:
vbrstart:
xor eax, eax
mov sp, 0x7a00
mov bp, 0x6000
mov ss, ax
mov ds, ax
mov es, ax
xor ebx, ebx
xor ecx, ecx
push strict dword initializing
push strict word 0x0000
call printmsg
jmp end
;push strict dword memloc
;push strict word length
;call printmsg
printmsg:
pop strict dword [store_ret]
mov [store_cx], cx
mov [store_esi], esi
pop cx
pop esi
push eax
push ebx
mov ah, 0x0E
mov bx, 0x0007
cmp cx, 0x0000
je printnullterm
printgivenlen:
lodsb
cmp cx, 0x0000
je printdone
int 10h
dec cx
jmp printgivenlen
printnullterm:
lodsb
cmp al, 0x00
je printdone
int 10h
jmp printnullterm
printdone:
pop ebx
pop eax
mov esi, [store_esi]
mov cx, [store_cx]
push strict dword [store_ret]
ret
printdata:
store_cx dw 0
store_esi dd 0
store_ret dd 0
end:
hlt
jmp end
initializing db 10,13,'Initializing...',0
When tested, it prints indefinately, and doesn't stop at the null byte. Where did I make a mistake?
I see 2 problems with your code:
You've written a bootloader. Such a program runs in the 16-bit real address mode. This implies that the return address from a call will be a word and not a dword like your code is expecting.
printmsg:
pop word [store_ret]
You setup the stack in a risky manner. You need to first change the SS register and immediately after that the SP register.
xor ax, ax
mov ss, ax <<< Keep these together
mov sp, 0x7a00 <<< and in this order!
Since this is 16-bit code there's no need to push/pop the address as a dword.
push word initializing
...
pop si
Your stack is not properly aligned with all that pushes and pops. Check parameters passing and it's usage in the routine.
To start, here's a hint:
vbrstart:
xor eax, eax
mov sp, 0x7a00
mov bp, 0x6000
mov ss, ax
mov ds, ax
mov es, ax
xor ebx, ebx
xor ecx, ecx
push strict dword initializing
push strict word 0x0000
call printmsg ; near call, only 16 bits ret addr stored
jmp end
;push strict dword memloc
;push strict word length
;call printmsg
printmsg:
pop strict dword [store_ret] ; pop dword (ret address + 16 bits- 1st argument)
mov [store_cx], cx ; store_cx = 0
mov [store_esi], esi ; store_esi = ??
pop cx ; cx = initializing
. . .

Pass array as parameter to proc in TASM?

I'm trying to create a proc in TASM that accepts arrays as parameters. Problem is, the result is all messed up. This is the code:
TextParameter equ [bp+8]
MenuColorParameter equ [bp+6]
RowToPrint equ [bp+4]
PrintBar proc
push bp
mov bp, sp
mov dh, RowToPrint
mov dl, 0
mov bh, 0
mov ah, 2
int 10h
mov si, 0
mov cx, 1
##Print:
mov ah, 9
mov al, [TextParameter+si]
mov bl, [MenuColorParameter+si]
int 10h
mov ah, 2
inc dl
int 10h
inc si
cmp si, MENU_LEN ; const in DATASEG
jc ##Print
mov ah, 2
mov bh, 0
mov dl, 0
mov dh, 2
int 10h
pop bp
ret 6
PrintBar endp
When referencing the array through the DATASEG (as in mov al, Array[si]), the proc works as expected.
I'm assuming that you're passing the arrays by-address (i.e. their offsets), since the arguments seem to be 2 bytes each. If so, you need to first load that address into a register, and then add si and do another read from memory:
mov bx, TextParameter ; expands to mov bx,[bp+8]
mov al, [bx+si]
mov bx, MenuColorParameter ; expands to mov bx,[bp+6]
mov bl, [bx+si]
In addition to Michael's answer, I found the problem - int 10h ah=9 and int 10h ah=2 expect bh as the video page number. After assigning al and bl, I needed to mov bh, 0. The ##Print label is now this:
##Print:
mov ah, 9
mov bx, TextParameter
mov al, [bx+si]
mov bx, MenuColorParameter
mov bl, [bx+si]
mov bh, 0
int 10h
mov ah, 2
inc dl
int 10h
inc si
cmp si, MENU_LEN ; const in DATASEG
jc ##Print

Assembler read full numbers from txt file

I am stuck at point where I need to read 3 numbers from created buffer of text file. I've tryed reading every byte, but no luck if the number is bigger than 9.
Steps that I do:
Open text file
Read file's content into created buffer
Put every buffers byte into different register (ax, bx, cx, dx).
The problem is, it reads 1 byte (mov ax, buffer[0]) at a time: if my text file is (10 10 1), it reads 1 then 0 then space symbol(ascii 20) and so on. Should I do cycle that converts and adds every byte to one register while it doesn't detect space symbol? Or is there a possibility to read whole number at one time? Here's the code:
.model small
bufferLen equ 16
.stack 100h
.data
duom db "duom.txt", 0
fident dw 0
buffer db bufferLen dup (?)
.code
start:
mov dx, #data
mov ds, dx
mov bx, 81h
tikrinam: ; not important
mov ax, es:[bx]
inc bx
cmp al, 13
je openf
cmp al, 20h
je tikrinam
cmp ax, "?/"
jne openf
mov ax, es:[bx]
cmp ah, 13
je abouthlp
jmp openf
abouthlp:
mov dx, offset about
mov ah, 09h
int 21h
jmp ending
openf:
mov ah, 3Dh
mov al, 0
mov dx, offset duom
int 21h
mov [fident], ax
readf:
mov ah, 3Fh
mov bx, [fident]
mov cx, bufferLen
mov dx, offset buffer
int 21h
mov al, buffer[0]
mov bl, buffer[1]
mov cl, buffer[2]
I've found the solution if anyone is having same problem:
changeNumbers:
push ax
mov ax, 0
cmp cl, 0
je change
temp1:
mov ch, 0
mov cl, buffer[si]
inc si
cmp cl, 32
je changeNumbers
cmp cl, 0
je changeNumbers
sub cl, 48
mul abc
add ax, cx
jmp temp1
Basically what I did was read every byte and if the number is >9 then add cx to ax and multiply it by 10. Then just push it to stack for further usage. Brain is an amazing thing, I'd say.

Resources