Author Topic: Question regarding push, pop, ret interactions. [solved]  (Read 17280 times)

Offline jambli

  • New Member
  • Posts: 1
Question regarding push, pop, ret interactions. [solved]
« on: July 26, 2010, 08:49:07 PM »
Afternoon everyone,

NM - realized my problem, I wasn't popping them back off in the correct order. =)
For some reason I assumed each register had its own stack, my bad.

I'm running into an infinite loop after calling one of my functions from within another function.
This occurs in 16bit mode, its for a boot loader.

;This is the function were I hit an infinite loop.
Function_1:
    mov cd, 10 ; number of times to loop
.loop_section
    mov ax, cx ; Moving number to print into ax
    mov bx, 10 ; Number base of output
    call Print_Num
    loop .loop_section
    ret
; Here is the offending function that is causing the loop.
; register ax must contain the number to print
; register bx the number base to print.
Print_Num:
        ; Setup
   push ax    ; numerator
   push bx    ; number base.
   push cx    ; counter
   push dx    ; result

   xor cx, cx ; Clear counter

.Push_Digits:
   xor dx,dx        ; clear dx.
   div bx           ; ax/bx -> ax quotient, dx remainder
   push dx          ; save dx
   inc cx           ; increment counter
   or ax,ax         ; check for a quotient of 0
   jnz .Push_Digits ; if quotient is not 0 continue looping.

   mov ah, 14
.Pop_Digits:
   pop dx
   mov al, dl
   add al, 48 ; nasm has some special op for this I know.
   int 16        ; video interrupt to display the value in al to screen.
   loop .Pop_Digits
.Done:
   pop ax    ; numerator
   pop bx    ; denominator
   pop cx    ; counter
   pop dx    ; result
   ret

Now when I call Function_1 it repeatedly displays 9. I can't tell if it displays 10, it moves too quickly and I haven't figured out a way to debug my bootloader. =/

Now my understanding is that in Print_Num when I push the values at the beginning and pop them at the end it'll restore the registers to their original state.  however it doesn't seem like this is what is occuring. If I modify function_1 in the following way :
function_1:
    mov cx, 10
.loop_section
    push cx
    mov ax,cx
    mov bx, 10
    call Print_Num
    pop cx
    loop .loop_section
    ret

it works just fine. However I shouldn't have to do this since Print_Num was suppose to restore the values itself.
Basically I'm wondering why this is occurring, I checked the manual couldn't find anything that helped. The descriptions of the
instructions for nasm are practically worthless from what I saw, in appendix b, if there are better descriptions in the manual somewhere please let me know. =)
« Last Edit: July 26, 2010, 09:11:05 PM by jambli »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Question regarding push, pop, ret interactions. [solved]
« Reply #1 on: July 27, 2010, 12:35:03 AM »
The "old" Nasm manual had descriptions of the instructions - the "new" manual is just a list.

http://home.myfairpoint.net/fbkotler/nasmdocr.html

Note the disclaimer!

Quote
This is derived from the old, obsolete, and potentially buggy instruction set reference from the old Nasm Manual. The Nasm development team explicitly disclaims any responsibility for errors and omissions in this document!

In spite of that, it may be useful to you. Doesn't mention the effect on flags. There are other, potentially better, instruction set references floating around - perhaps best to go straight to the horse's mouth, the Intel/AMD manuals... but I "rescued" this one... 'cause I'm used to it...

Right, you need to pop registers in the opposite order from which you pushed 'em. And to complete the question in the topic, "call" puts the return address on the stack, and "ret" removes it, so you do need to pop everything you pushed before you hit the "ret"!

But... do you really need to preserve all those registers? You're reloading ax and bx before each call to Print_Num, and not really using dx (in the caller), so the only thing you need to preserve is cx. Doesn't do any harm, but in a bootsector, you may want to save every byte you can, since space is limited.

Another potential "savings":

Code: [Select]
Pop_Digits:
;   pop dx
;   mov al, dl
   pop ax
   add al, 48 ; nasm has some special op for this I know.
   int 16        ; video interrupt to display the value in al to screen.
   loop .Pop_Digits
.Done:

There's only one stack, and you need to pop everything you pushed, but you *can* pop it into a different register than you pushed...

Nasm doesn't have any "special op" that I know of, but you could write it different ways:

Code: [Select]
add al, 48
add al, 30h
add al, '0'

... all do the same thing. Also, speaking of writing things different ways, you've called "int 10h", "int 16". This will work fine - it's the same number (bit pattern) - but it's more "usual" to use hex for interrupt numbers. They're usually documented that way. For example:

http://www.ctyme.com/rbrown.htm

(if you haven't discovered Ralf Brown yet, he's your new best friend!) :)

You mention that it "moves too fast"... You can "hold" the display so you've got time to read it by waiting for a key-press:

Code: [Select]
mov ah, 0
int 16h ; note hex!

I understand that you can debug a bootsector by running it in an emulator - Bochs, or some other. I don't "trust" emulators to be exactly the same as "real hardware" (which differs from bios to bios), so I like to run my bootsectors on "real hardware"... so I can't advise you on emulators. :)

Since a bootsector is hard to debug, and for other reasons, it isn't an easy place for a beginner to start. I'd advise doing a few .com files first (if your OS supports it), but a lot of beginners like to start with a bootsector - go for it, if you wanna, but it isn't the easiest thing to do!

Best,
Frank