function in assembly

Programming, for all ages and all languages.
Haghiri75
Member
Member
Posts: 29
Joined: Fri Nov 16, 2012 4:16 am
Location: Iran
Contact:

function in assembly

Post by Haghiri75 »

Greetings. This is part of my code :

Code: Select all

__entry_point:
	mov edi, 0xb8000
	mov esi, string
	mov ah, 0x0f
	.displaying:
		lodsb
		stosw
		or al, al
		jnz .displaying
		jmp short $
and I wanna make the print part a function, but It doesn't work. My way was pushing and popping ax, and also adding a "ret" to the printing part.
Octocontrabass
Member
Member
Posts: 5513
Joined: Mon Mar 25, 2013 7:01 pm

Re: function in assembly

Post by Octocontrabass »

Are you calling this function from C?

If so, what calling conventions does the compiler expect?
Haghiri75
Member
Member
Posts: 29
Joined: Fri Nov 16, 2012 4:16 am
Location: Iran
Contact:

Re: function in assembly

Post by Haghiri75 »

Octocontrabass wrote:Are you calling this function from C?

If so, what calling conventions does the compiler expect?
No, it's part of assembly code, and I want to make it a function, in assembly.
User avatar
b.zaar
Member
Member
Posts: 294
Joined: Wed May 21, 2008 4:33 am
Location: Mars MTC +6:00
Contact:

Re: function in assembly

Post by b.zaar »

Make sure your memory is identity mapped and your data segments DS and ES are base 0 relative to your 0xb8000 mapped memory, lastly add a cld at the top of the function to make sure the string moves are in the right direction.
"God! Not Unix" - Richard Stallman

Website: venom Dev
OS project: venom OS
Hexadecimal Editor: hexed
Haghiri75
Member
Member
Posts: 29
Joined: Fri Nov 16, 2012 4:16 am
Location: Iran
Contact:

Re: function in assembly

Post by Haghiri75 »

Code: Select all

 
print_string:
    push ax
    mov ah, 0x0f
   .displaying:
      lodsb
      stosw
      or al, al
      pop ax
      jnz .displaying
      ret
Is this the right way?!
User avatar
b.zaar
Member
Member
Posts: 294
Joined: Wed May 21, 2008 4:33 am
Location: Mars MTC +6:00
Contact:

Re: function in assembly

Post by b.zaar »

Ok I think I didn't read the problem right the first time.

try this

Code: Select all

print_string:
    push eax
    push edi
    push esi
    mov edi, 0xb8000
    mov esi, string
    mov ah, 0x0f
   .displaying:
      lodsb
      stosw
      or al, al
      jnz .displaying
   pop esi
   pop edi
   pop eax
   ret
"God! Not Unix" - Richard Stallman

Website: venom Dev
OS project: venom OS
Hexadecimal Editor: hexed
Haghiri75
Member
Member
Posts: 29
Joined: Fri Nov 16, 2012 4:16 am
Location: Iran
Contact:

Re: function in assembly

Post by Haghiri75 »

Thanks. Now, I have another problem.

When I define strings :

Code: Select all

string db 'foo', 10, 13, 0
string2 db 'bar', 10, 13, 0
It doesn't print string 2 in a new line. What can I do?
Techel
Member
Member
Posts: 215
Joined: Fri Jan 30, 2015 4:57 pm
Location: Germany
Contact:

Re: function in assembly

Post by Techel »

you have to move to the current character location to a new line manually.
Haghiri75
Member
Member
Posts: 29
Joined: Fri Nov 16, 2012 4:16 am
Location: Iran
Contact:

Re: function in assembly

Post by Haghiri75 »

Techel wrote:you have to move to the current character location to a new line manually.
Can you explain how?
Techel
Member
Member
Posts: 215
Joined: Fri Jan 30, 2015 4:57 pm
Location: Germany
Contact:

Re: function in assembly

Post by Techel »

Here I have some code I wrote some time ago, it uses 32 bit instructions, prints text pointed to by esi, moves to a newline and scrolls everything when neccessary. It also updates the vga hardware cursor.
'CurrentPosition' is a memory address which stores a 4 byte position of the current character position (initialized with 0 obviously)

Code: Select all

;; esi: pointer to nullterminated string
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

PrintSomeText:
	pusha
	mov edi, 0xB8000 ;;video memory
	mov eax, dword[CurrentPosition]
	shl eax, 1
	add edi, eax ;;2 bytes per character
   .loopi:
	lodsb ;;get character
	cmp al, 0 ;;test if null
	je .done
	stosb ;;write to video memory
	mov al, 0x07 ;;grey on black
	stosb
	jmp .loopi
   .done:
	sub edi, 0xB8000
	shr edi, 1
	add edi, 80-1 ;;go one line below
	mov eax, edi
	xor edx, edx
	mov ebx, 80
	div ebx ;;x-offset = cursor pos%80
	sub edi, edx ;;sub x-offset
	cmp edi, 80*50;;check if out of screen
	jnae .inscreen ;;else move screen up
		push edi
		mov esi, 0xB8000+80*2 ;;begin at second line
		mov edi, 0xB8000 ;;to first line
		mov ecx, 80*49 ;;entire screen minus one line
		rep movsw
		mov edi, 0xB8000+49*80*2 ;;clear last line
		mov cx, 80
		mov ax, 0x0720 ;;character (space) + color
		rep stosw
		pop edi
		sub edi, 80
   .inscreen:
	mov dword[CurrentPosition], edi ;;save current cursor position
	mov al, 0x0F ;;update hardware cursor
	mov dx, 0x3D4
	mov al, 0x0F
	out dx, al
	mov eax, edi
	inc dx
	out dx, al
	mov al, 0x0E
	dec dx
	out dx, al
	mov eax, edi
	xchg al, ah
	inc dx
	out dx, al
	popa
	ret
glauxosdever
Member
Member
Posts: 501
Joined: Wed Jun 17, 2015 9:40 am
Libera.chat IRC: glauxosdever
Location: Athens, Greece

Re: function in assembly

Post by glauxosdever »

Hi,


I think you need to do more research if you are serious about this. Writing 5 lines of code and then asking why doesn't it work isn't going to help in long-term.

Hint: Have you checked the Intel manuals? Have you read the Ralph Brown's Interrupt List? Have you at least searched for some references about the behaviour of some instructions you aren't sure?


Regards,
glauxosdever
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: function in assembly

Post by Brendan »

Hi,
Haghiri75 wrote:Greetings. This is part of my code :

Code: Select all

__entry_point:
	mov edi, 0xb8000
	mov esi, string
	mov ah, 0x0f
	.displaying:
		lodsb
		stosw
		or al, al
		jnz .displaying
		jmp short $
Haghiri75 wrote:and I wanna make the print part a function, but It doesn't work. My way was pushing and popping ax, and also adding a "ret" to the printing part.
Note that assembly doesn't really have "functions" (as people are used to them in high level languages). It's better to think of them as something different ("routines"), that can return multiple values, and can have multiple entry points. There is also no calling convention, and you decide where each routine's entry point expects to find input parameters and leaves output parameters. People that don't understand these differences end up writing bad code (code restricted by limitations that exist for compiler generated code but don't exist in assembly).

To turn your code into a routine, you need to document its inputs, outputs and which registers it trashes; and mostly just put a "ret" at the end. For example, your original code might become:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
	mov edi, 0xb8000
	mov ah, 0x0f
.displaying:
	lodsb
	stosw
	or al, al
	jnz .displaying
	ret
Note that this has a bug - it prints the last "0x00" character. It's also more efficient to use "test al,al" (as it doesn't modify AL). To fix that:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
	mov edi, 0xb8000
	mov ah, 0x0f
	lodsb
.displaying:
	stosw
	lodsb
	test al, al
	jnz .displaying
	ret
Also note that it'd be trivial to add multiple entry points to make this more flexible:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
	mov edi, 0xb8000

;Print an ASCIIZ string with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
; edi  Address in display memory to put string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite:
	mov ah, 0x0f

;Print an ASCIIZ string
;______________________________________________________________________________
;
;Input
; ah   Attribute for string
; esi  Address of ASCIIZ string
; edi  Address in display memory to put string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString:
	lodsb
.displaying:
	stosw
	lodsb
	test al, al
	jnz .displaying
	ret
Of course these entry points naturally lead into each other. In some cases you can't do that and you need a "jmp" instruction. For example:

Code: Select all

;Print an ASCIIZ string at coords (x, y)
;______________________________________________________________________________
;
;Input
; ah   Attribute for string
; ebx  Y position
; esi  Address of ASCIIZ string
; edi  X position
;
;Output
; None
;
;Trashed
; eax, ebx, esi, edi
;______________________________________________________________________________

printString_atCoords:
    shl ebx,5                ;ebx = y * 32 = y * 16 * 2
    lea edi,[0xB8000+edi*2]  ;edi = 0xB8000 + x * 2
    lea ebx,[ebx*5]          ;ebx = y * 16 * 2 * 5 = y * 80 * 2
    add edi,ebx              ;edi = 0xB8000 + x*2 + y * 80 * 2 = 0xB8000 + (y * 80 + x) * 2
    jmp printString
Note that there's 2 ways to think about this. You could think of it as multiple entry points for the same routine. Alternatively you could think of it as multiple separate routines that use tail call optimisation combined with the removal of pointless "jmp" instructions.

For the latter, imagine you have something like this:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
    mov edi, 0xb8000
    call printString_blackAndWhite
    ret
Because we're not using the stack for arguments; the "tail call optimisation" mostly just means replacing "call then ret" with "jmp", like this:

Code: Select all

printString_blackAndWhite_top_left:
    mov edi, 0xb8000
;    call printString_blackAndWhite
;    ret
    jmp printString_blackAndWhite
If the target of a jump is the next instruction, then the "jmp" does nothing and can be removed too. That gives us the same code as before.

Of course it's easier to think of it as a routine with multiple entry points (and not need to optimise) than it is to think of it as multiple routines (and then do 2 extra optimisations).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: function in assembly

Post by Combuster »

Haghiri75 wrote:

Code: Select all

 
print_string:
    push ax
    mov ah, 0x0f
   .displaying:
      lodsb
      stosw
      or al, al
      pop ax
      jnz .displaying
      ret
Is this the right way?!
How many POPs are being executed here?
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
Haghiri75
Member
Member
Posts: 29
Joined: Fri Nov 16, 2012 4:16 am
Location: Iran
Contact:

Re: function in assembly

Post by Haghiri75 »

Brendan wrote:Hi,
Haghiri75 wrote:Greetings. This is part of my code :

Code: Select all

__entry_point:
	mov edi, 0xb8000
	mov esi, string
	mov ah, 0x0f
	.displaying:
		lodsb
		stosw
		or al, al
		jnz .displaying
		jmp short $
Haghiri75 wrote:and I wanna make the print part a function, but It doesn't work. My way was pushing and popping ax, and also adding a "ret" to the printing part.
Note that assembly doesn't really have "functions" (as people are used to them in high level languages). It's better to think of them as something different ("routines"), that can return multiple values, and can have multiple entry points. There is also no calling convention, and you decide where each routine's entry point expects to find input parameters and leaves output parameters. People that don't understand these differences end up writing bad code (code restricted by limitations that exist for compiler generated code but don't exist in assembly).

To turn your code into a routine, you need to document its inputs, outputs and which registers it trashes; and mostly just put a "ret" at the end. For example, your original code might become:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
	mov edi, 0xb8000
	mov ah, 0x0f
.displaying:
	lodsb
	stosw
	or al, al
	jnz .displaying
	ret
Note that this has a bug - it prints the last "0x00" character. It's also more efficient to use "test al,al" (as it doesn't modify AL). To fix that:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
	mov edi, 0xb8000
	mov ah, 0x0f
	lodsb
.displaying:
	stosw
	lodsb
	test al, al
	jnz .displaying
	ret
Also note that it'd be trivial to add multiple entry points to make this more flexible:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
	mov edi, 0xb8000

;Print an ASCIIZ string with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
; edi  Address in display memory to put string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite:
	mov ah, 0x0f

;Print an ASCIIZ string
;______________________________________________________________________________
;
;Input
; ah   Attribute for string
; esi  Address of ASCIIZ string
; edi  Address in display memory to put string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString:
	lodsb
.displaying:
	stosw
	lodsb
	test al, al
	jnz .displaying
	ret
Of course these entry points naturally lead into each other. In some cases you can't do that and you need a "jmp" instruction. For example:

Code: Select all

;Print an ASCIIZ string at coords (x, y)
;______________________________________________________________________________
;
;Input
; ah   Attribute for string
; ebx  Y position
; esi  Address of ASCIIZ string
; edi  X position
;
;Output
; None
;
;Trashed
; eax, ebx, esi, edi
;______________________________________________________________________________

printString_atCoords:
    shl ebx,5                ;ebx = y * 32 = y * 16 * 2
    lea edi,[0xB8000+edi*2]  ;edi = 0xB8000 + x * 2
    lea ebx,[ebx*5]          ;ebx = y * 16 * 2 * 5 = y * 80 * 2
    add edi,ebx              ;edi = 0xB8000 + x*2 + y * 80 * 2 = 0xB8000 + (y * 80 + x) * 2
    jmp printString
Note that there's 2 ways to think about this. You could think of it as multiple entry points for the same routine. Alternatively you could think of it as multiple separate routines that use tail call optimisation combined with the removal of pointless "jmp" instructions.

For the latter, imagine you have something like this:

Code: Select all

;Print an ASCIIZ string at the top left of the screen, with "black and white" text
;______________________________________________________________________________
;
;Input
; esi  Address of ASCIIZ string
;
;Output
; None
;
;Trashed
; eax, esi, edi
;______________________________________________________________________________

printString_blackAndWhite_top_left:
    mov edi, 0xb8000
    call printString_blackAndWhite
    ret
Because we're not using the stack for arguments; the "tail call optimisation" mostly just means replacing "call then ret" with "jmp", like this:

Code: Select all

printString_blackAndWhite_top_left:
    mov edi, 0xb8000
;    call printString_blackAndWhite
;    ret
    jmp printString_blackAndWhite
If the target of a jump is the next instruction, then the "jmp" does nothing and can be removed too. That gives us the same code as before.

Of course it's easier to think of it as a routine with multiple entry points (and not need to optimise) than it is to think of it as multiple routines (and then do 2 extra optimisations).


Cheers,

Brendan
Thank you very much! But I have another question, does "printString_atCoords" routine, changes cursor place? I tried it but it didn't work.
Techel
Member
Member
Posts: 215
Joined: Fri Jan 30, 2015 4:57 pm
Location: Germany
Contact:

Re: function in assembly

Post by Techel »

Writing to video memory directly doesn't affect the hardware cursor. Have a look at the local osdev vga wiki, there's some example code lingering around, or at my code I posted.
Post Reply