3.10 Accessing Data You've Pushed onto the Stack Without Popping It

Once in a while you will push data onto the stack and you will want to get a copy of that data's value, or perhaps you will want to change that data's value without actually popping the data off the stack (that is, you wish to pop the data off the stack at a later time). The 80x86 [reg32 + offset] addressing mode provides the mechanism for this.

Consider the stack after the execution of the following two instructions (see Figure 3-19):

push( eax );
          push( ebx );
Stack after pushing EAX and EBX

Figure 3-19. Stack after pushing EAX and EBX

If you wanted to access the original EBX value without removing it from the stack, you could cheat and pop the value and then immediately push it again. Suppose, however, that you wish to access EAX's old value or some other value even farther up the stack. Popping all the intermediate values and then pushing them back onto the stack is problematic at best, impossible at worst. However, as you will notice from Figure 3-19, each of the values pushed on the stack is at some offset from the ESP register in memory. Therefore, we can use the [ESP + offset] addressing mode to gain direct access to the value we are interested in. In the example above, you can reload EAX with its original value by using the single instruction

mov( [esp+4], eax );

This code copies the 4 bytes starting at memory address ESP+4 into the EAX register. This value just happens to be the previous value of EAX that was pushed onto the stack. You can use this same technique to access other data values you've pushed onto the stack.

Warning

Don't forget that the offsets of values from ESP into the stack change every time you push or pop data. Abusing this feature can create code that is hard to modify; if you use this feature throughout your code, it will make it difficult to push and pop other data items between the point where you first push data onto the stack and the point where you decide to access that data again using the [ESP + offset] memory addressing mode.

The previous section pointed out how to remove data from the stack by adding a constant to the ESP register. That code example could probably be written more safely as this:

push( eax );
          push( ebx );

          << Some code that winds up computing some values we want to keep
             into eax and ebx >>

          if( Calculation_was_performed ) then

             << Overwrite saved values on stack with new eax/ebx values
                (so the pops that follow won't change the values in eax/ebx). >>

               mov( eax, [esp+4] );
               mov( ebx, [esp] );

          endif;
          pop( ebx );
          pop( eax );

In this code sequence, the calculated result was stored over the top of the values saved on the stack. Later on, when the program pops the values, it loads these calculated values into EAX and EBX.