5.12 Procedures and the Stack


5.12 Procedures and the Stack

Because procedures use the stack to hold the return address, you must exercise caution when pushing and popping data within a procedure. Consider the following simple (and defective) procedure:

 procedure MessedUp; noframe; nodisplay; begin MessedUp;      push( eax );      ret(); end MessedUp; 

At the point the program encounters the ret instruction, the 80x86 stack takes the form shown in Figure 5-1.

click to expand
Figure 5-1: Stack Contents Before RET in "MessedUp" Procedure.

The ret instruction isn't aware that the value on the top of stack is not a valid address. It simply pops whatever value is on the top of the stack and jumps to that location. In this example, the top of stack contains the saved EAX value. Because it is very unlikely that EAX contains the proper return address (indeed, there is about a one in four billion chance it is correct), this program will probably crash or exhibit some other undefined behavior. Therefore, you must take care when pushing data onto the stack within a procedure that you properly pop that data prior to returning from the procedure.

Note

If you do not specify the @noframe option when writing a procedure, HLA automatically generates code at the beginning of the procedure that pushes some data onto the stack. Therefore, unless you understand exactly what is going on and you've taken care of this data HLA pushes on the stack, you should never execute the bare ret instruction inside a procedure that does not have the @noframe option. Doing so will attempt to return to the location specified by this data (which is not a return address) rather than properly returning to the caller. In procedures that do not have the @noframe option, use the exit or exitifstatements to return from the procedure.

Popping extra data off the stack prior to executing the ret statement can also create havoc in your programs. Consider the following defective procedure:

 procedure MessedUpToo; @noframe; @nodisplay; begin MessedUpToo;      pop( eax );      ret(); end MessedUpToo; 

Upon reaching the ret instruction in this procedure, the 80x86 stack looks something like that shown in Figure 5-2.

click to expand
Figure 5-2: Stack Contents Before RET in MessedUpToo.

Once again, the ret instruction blindly pops whatever data happens to be on the top of the stack and attempts to return to that address. Unlike the previous example, where it was very unlikely that the top of stack contained a valid return address (because it contained the value in EAX), there is a small possibility that the top of stack in this example actually does contain a return address. However, this will not be the proper return address for the MessedUpToo procedure; instead, it will be the return address for the procedure that called MessUpToo. To understand the effect of this code, consider the program in Listing 5-11.

Listing 5-11: Effect of Popping Too Much Data off the Stack.

start example
 program extraPop; #include( "stdlib.hhf" );      // Note that the following procedure pops      // excess data off the stack (in this case,      // it pops messedUpToo's return address).      procedure messedUpToo; @noframe; @nodisplay;      begin messedUpToo;           stdout.put( "Entered messedUpToo" nl );           pop( eax );           ret();      end messedUpToo;      procedure callsMU2; @noframe; @nodisplay;      begin callsMU2;           stdout.put( "calling messedUpToo" nl );           messedUpToo();           // Because messedUpToo pops extra data           // off the stack, the following code           // never executes (because the data popped           // off the stack is the return address that           // points at the following code.           stdout.put( "Returned from messedUpToo" nl );           ret();      end callsMU2; begin extraPop;      stdout.put( "Calling callsMU2" nl );      callsMU2();      stdout.put( "Returned from callsMU2" nl ); end extraPop; 
end example

Because a valid return address is sitting on the top of the stack, you might think that this program will actually work (properly). However, note that when returning from the MessedUpToo procedure, this code returns directly to the main program rather than to the proper return address in the EndSkipped procedure. Therefore, all code in the callsMU2 procedure that follows the call to MessedUpToo does not execute. When reading the source code, it may be very difficult to figure out why those statements are not executing because they immediately follow the call to the MessUpToo procedure. It isn't clear, unless you look very closely, that the program is popping an extra return address off the stack and, therefore, doesn't return back to callsMU2 but, rather, returns directly to whomever calls callsMU2. Of course, in this example it's fairly easy to see what is going on (because this example is a demonstration of this problem). In real programs, however, determining that a procedure has accidentally popped too much data off the stack can be much more difficult. Therefore, you should always be careful about pushing and popping data in a procedure. You should always verify that there is a one-to-one relationship between the pushes in your procedures and the corresponding pops.




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net