Page 1 of 1

Double character is printing in a routine (solved)

Posted: Fri Sep 28, 2007 7:15 pm
by brunsa2
I am trying to work on the roots of a video driver, so I am implementing the following routine:

posn: dw 160*1+2*1 ; row 2 column 2

putc: push bp ; prolouge
mov sp, bp
sub sp, 4
push bp
push bx

mov bx, 0xB800 ; color video segment
mov es, bx
mov bp, [posn] ; position offset
mov byte [es:bp], al ; copy character
inc bp
mov byte [es:bp], 0xE0 ; make black on yellow
inc bp
mov [posn], bp ; save new position

pop bx
pop bp
mov bp, sp ; epilouge
pop bp
ret

I invoke the routine as follows:

mov al, 'x'
call putc

to print x

Whenever I print a string with it the first character prints twice like:
'HHello World!' starting at row 2 column 2. I am sure that I am not accidentally calling putc twice with H. I have used different letters.

I am assembling under nasm in FreeDOS in QEMU. What is going on? Shouldn't the bp be incremented each time the routine is called?

Thanks.

Posted: Sat Sep 29, 2007 4:57 am
by Combuster
I find it rather awkward to reuse bp when you also use it for the stack frame and you have other registers at your disposal (bx, si, di)

next, you neglect to preserve ES and your epilogue makes no sense as you write to BP before popping it again.

Based on that, I suspect that you have more programming issues. Time to start the debugger.

Posted: Sat Sep 29, 2007 5:48 am
by brunsa2
I had to use bp instead of bx because nasm kept returning illegal effective address errors. Could this be the problem?

Posted: Sat Sep 29, 2007 9:23 am
by Combuster
[es:bx] can be used just as well as [es:bp], so I don't see the problem other than a assembler supposedly complaining about something it should be capable of doing.

The four effective addresses you can use in 16-bit mode are:
[bx]
[bp]
[si]
[di]
[bx+si]
[bx+di]
[bp+si]
[bp+di]
if one of those can't be used, you should get a better assembler.

Posted: Sat Sep 29, 2007 1:35 pm
by jnc100
Your prolog and epilog are wrong. In Intel syntax use

Code: Select all

push bp
mov bp, sp
and

Code: Select all

pop bp
ret
and avoid using bp to store stuff other than the stack frame pointer. Actually, as you don't seem to need to use local variables, I don't see the need of the

Code: Select all

sub sp, 4
line at all (you can just use a register to store [posn], e.g. bx as already stated), and if not using local variables or arguments, you shouldn't need to set up a stack frame at all.

Regards,
John.

Posted: Sat Sep 29, 2007 4:45 pm
by brunsa2
I was able to make it use bx this time and it worked. However, nasm will often assemble mov bx, [posn] fine and all of the sudden decide it's illegal. The prologue and epilogue were obtained from Wikipedia, but something didn't seem right, and now I see why. I have pretty much rewritten the whole thing several times and tried many different things but it keeps displaying the first character twice.

Right now, putc is as follows:

Code: Select all

		; al holds the character, cx holds offset
putc:		push bp												; prolouge
		mov sp, bp
		sub sp, 2
		push bx
		push es
				
		mov bx, 0xB800								; video segment
		mov es, bx
		mov bx, cx								; where on the screen
		mov byte [es:bx], al					; save character
		
		mov byte [es:bx+1], 0xE0			; save attribute (black on yellow)
		add cx, 2
				
				
		pop es
		pop bx
		mov bp, sp										; epilouge
		pop bp
		ret
I pass the character in al and the offset on the screen in cx. After each call I need to move the offset, but when I increment twice, the characters have spaces between them, and when I increment once, the characters are wrong and of different colors (because I'm writing attributes and characters in the wrong bytes); however, the double printing stopped in this routine.

I've tried

Code: Select all

mov bx, 0xB800
mov es, bx
mov bx, [posn]
mov byte [es:bx], al
mov byte [es:bx+1], al
add [posn], 2


but this still double prints.

I became frustrated with C and assembly together recently, but should I use C again (I was hoping for an all-assembly kernel)? I've seen this implemented easily in C, but it stopped working for me. Meanwhile, the assembly version still is double printing or impractical.

Posted: Sat Sep 29, 2007 5:08 pm
by jnc100
So despite what I said, you're still setting up your stack pointer wrong. And I ask again, why do you need a stack frame in this function? Also, C calling convention states that you don't need to preserve bx, so unless you're using a different one, you don't need to push/pop it.

Something like

Code: Select all

; character in al, offset ((x + y * 80) * 2) in cx, which is incremented by function
push es
push di
mov bx, 0xb800
mov es, bx

mov di, cx
mov ah, 0xe0 ; attribute
mov word [es:di], ax
add cx, 2

pop di
pop es
ret
should work, but considering you're still getting errors with a simple version of your putc, maybe the problem is in your string printing function instead?

Regards,
John.

Posted: Sat Sep 29, 2007 9:07 pm
by brunsa2
The code was before I read the post because I made it earlier today.

Code: Select all

; character in al, offset ((x + y * 80) * 2) in cx, which is incremented by function
push es
push di
mov bx, 0xb800
mov es, bx

mov di, cx
mov ah, 0xe0 ; attribute
mov word [es:di], ax
add cx, 2

pop di
pop es
ret
worked. Thank you.

I think that the problem may have been in the wrong prologue/epilogue from Wikipedia and my confusion between bp and si/di (I have been studing RISC processors lately, and that probably led to my confusion, or something...)

Posted: Sun Sep 30, 2007 8:12 am
by jnc100
Just noticed a small mistake in what I told you: C calling convention dictates you must preserve bx, but not the other gprs. Guess I wrote that late at night or something... :oops:

Regards,
John.

Posted: Sun Sep 30, 2007 3:25 pm
by Combuster
jnc100 wrote:C calling convention dictates you must preserve bx, but not the other gprs.
For completeness sake, you need to preserve 5 registers: si, di, sp, bp and bx. You don't need to preserve ax, cx or dx (ax holds the return value)