3.9 The Stack Segment and the PUSH and POP Instructions


3.9 The Stack Segment and the PUSH and POP Instructions

This chapter mentions that all variables you declare in the var section wind up in the stack memory segment. However, var objects are not the only things in the stack memory section; your programs manipulate data in the stack segment in many different ways. This section introduces the push and pop instructions that also manipulate data in stack memory.

The stack segment in memory is where the 80x86 maintains the stack. The stack is a dynamic data structure that grows and shrinks according to certain needs of the program. The stack also stores important information about program including local variables, subroutine information, and temporary data.

The 80x86 controls its stack via the ESP (stack pointer) register. When your program begins execution, the operating system initializes ESP with the address of the last memory location in the stack memory segment. Data is written to the stack segment by "pushing" data onto the stack and "popping" or "pulling" data off of the stack. Whenever you push data onto the stack, the 80x86 decrements the stack pointer by the size of the data you are pushing, and then it copies the data to memory where ESP is then pointing. Therefore, the stack grows and shrinks as you push data onto the stack and pop data from the stack.

3.9.1 The Basic PUSH Instruction

Consider the syntax for the 80x86 push instruction:

 push( reg16 ); push( reg32 ); push( memory16 ); push( memory32 ); pushw( constant ); pushd( constant ); 

The pushw and pushd operands are always two or four-byte constants, respectively.

These six forms allow you to push word or dword registers, memory locations, and constants. You should specifically note that you cannot push byte values onto the stack.

The push instruction does the following:

 ESP := ESP - Size_of_Register_or_Memory_Operand (2 or 4)  [ESP] := Operand's_Value 

Assuming that ESP contains $00FF_FFE8, then the instruction "push( eax );" will set ESP to $00FF_FFE4, and store the current value of EAX into memory location $00FF_FFE4 as Figures 3-9 and 3-10 show.

click to expand
Figure 3-9: Before "PUSH( EAX );" Operation.

click to expand
Figure 3-10: Stack Segment After "PUSH( EAX );" Operation.

Note that the "push( eax );" instruction does not affect the value of the EAX register.

Although the 80x86 supports 16-bit push operations, their primary use in is 16-bit environments such as DOS. For maximum performance, the stack pointer's value should always be an even multiple of four; indeed, your program may malfunction under Windows or Linux if ESP contains a value that is not a multiple of four and you make an operating system API call. The only practical reason for pushing less than four bytes at a time on the stack is because you're building up a double word via two successive word pushes.

3.9.2 The Basic POP Instruction

To retrieve data you've pushed onto the stack, you use the pop instruction. The basic pop instruction allows the following different forms:

                     pop( reg16 );                     pop( reg32 );                     pop( memory16 );                     pop( memory32 ); 

Like the push instruction, the pop instruction only supports 16-bit and 32-bit operands; you cannot pop an 8-bit value from the stack. Also like the push instruction, you should avoid popping 16-bit values (unless you do two 16-bit pops in a row) because 16-bit pops may leave the ESP register containing a value that is not an even multiple of four. One major difference between push and pop is that you cannot pop a constant value (which makes sense, because the operand for push is a source operand while the operand for pop is a destination operand).

Formally, here's what the pop instruction does:

 Operand := [ESP] ESP := ESP + Size_of_Operand (2 or 4) 

As you can see, the pop operation is the converse of the push operation. Note that the pop instruction copies the data from memory location [ESP] before adjusting the value in ESP. See Figures 3-11 and 3-12 for details on this operation.

click to expand
Figure 3-11: Memory Before a "POP( EAX );" Operation.

click to expand
Figure 3-12: Memory After the "POP( EAX );" Instruction.

Note that the value popped from the stack is still present in memory. Popping a value does not erase the value in memory; it just adjusts the stack pointer so that it points at the next value above the popped value. However, you should never attempt to access a value you've popped off the stack. The next time something is pushed onto the stack, the popped value will be obliterated. Because your code isn't the only thing that uses the stack (i.e., the operating system uses the stack as do subroutines), you cannot rely on data remaining in stack memory once you've popped it off the stack.

As Chapter One notes, HLA provides an extended syntax for the mov instruction that allows two memory operands (that is, the instruction provides a memory-to-memory move). HLA actually generates the following two instructions in place of such a mov:

 // mov( src, dest );           push( src );           pop( dest ); 

This is the reason that the memory-to-memory form of the mov instruction only allows 16-bit and 32-bit operands — because push and pop only allow 16-bit and 32-bit operands.

3.9.3 Preserving Registers with the PUSH and POP Instructions

Perhaps the most common use of the push and pop instructions is to save register values during intermediate calculations. A problem with the 80x86 architecture is that it provides very few general purpose registers. Because registers are the best place to hold temporary values, and registers are also needed for the various addressing modes, it is very easy to run out of registers when writing code that performs complex calculations. The push and pop instructions can come to your rescue when this happens.

Consider the following program outline:

      << Some sequence of instructions that use the EAX register >>      << Some sequence of instructions that need to use EAX, for a          different purpose than the above instructions >>      << Some sequence of instructions that need the original value in EAX >> 

The push and pop instructions are perfect for this situation. By inserting a push instruction before the middle sequence and a pop instruction after the middle sequence above, you can preserve the value in EAX across those calculations:

      << Some sequence of instructions that use the EAX register >>      push( eax );      << Some sequence of instructions that need to use EAX, for a          different purpose than the above instructions >>      pop( eax );      << Some sequence of instructions that need the original value in EAX >> 

The push instruction above copies the data computed in the first sequence of instructions onto the stack. Now the middle sequence of instructions can use EAX for any purpose it chooses. After the middle sequence of instructions finishes, the pop instruction restores the value in EAX so the last sequence of instructions can use the original value in EAX.

3.9.4 The Stack Is a LIFO Data Structure

You can push more than one value onto the stack without first popping previous values off the stack. However, the stack is a last-in, first-out (LIFO) data structure, so you must be careful how you push and pop multiple values. For example, suppose you want to preserve EAX and EBX across some block of instructions. The following code demonstrates the obvious way to handle this:

         push( eax );         push( ebx );         << Code that uses EAX and EBX goes here >>         pop( eax );         pop( ebx ); 

Unfortunately, this code will not work properly! Figures 3-13 through 3-16 show the problem. Because this code pushes EAX first and EBX second, the stack pointer is left pointing at EBX's value on the stack. When the "pop( eax );" instruction comes along, it removes the value that was originally in EBX from the stack and places it in EAX! Likewise, the "pop( EBX );" instruction pops the value that was originally in EAX into the EBX register. The end result is that this code manages to swap the values in the registers by popping them in the same order that it pushes them.

click to expand
Figure 3-13: Stack After Pushing EAX.

click to expand
Figure 3-14: Stack After Pushing EBX.

click to expand
Figure 3-15: Stack After Popping EAX.

click to expand
Figure 3-16: Stack After Popping EBX.

To rectify this problem, you must note that the stack is a LIFO data structure, so the first thing you must pop is the last thing you push onto the stack. Therefore, you must always observe the following maxim:

  • Always pop values in the reverse order that you push them.

The correction to the previous code is

         push( eax );         push( ebx );         << Code that uses EAX and EBX goes here >>         pop( ebx );         pop( eax ); 

Another important maxim to remember is

  • Always pop exactly the same number of bytes that you push.

This generally means that the number of pushes and pops must exactly agree. If you have too few pops, you will leave data on the stack, which may confuse the running program: If you have too many pops, you will accidentally remove previously pushed data, often with disastrous results.

A corollary to the maxim above is, "Be careful when pushing and popping data within a loop." Often it is quite easy to put the pushes in a loop and leave the pops outside the loop (or vice versa), creating an inconsistent stack. Remember, it is the execution of the push and pop instructions that matters, not the number of push and pop instructions that appear in your program. At runtime, the number (and order) of the push instructions the program executes must match the number (and reverse order) of the pop instructions.

3.9.5 Other PUSH and POP Instructions

The 80x86 provides several additional push and pop instructions in addition to the basic push/pop instructions. These instructions include the following:

  • pusha

  • pushad

  • pushf

  • pushfd

  • popa

  • popad

  • popf

  • popfd

The pusha instruction pushes all the general purpose 16-bit registers onto the stack. This instruction exists primarily for older 16-bit operating systems like DOS. In general, you will have very little need for this instruction. The pusha instruction pushes the registers onto the stack in the following order:

 ax cx dx bx sp bp si di 

The pushad instruction pushes all the 32-bit (double word) registers onto the stack. It pushes the registers onto the stack in the following order:

 eax ecx edx ebx esp ebp esi edi 

Because the pusha and pushad instructions inherently modify the SP/ESP register, you may wonder why Intel bothered to push this register at all. It was probably easier in the hardware to go ahead and push SP/ESP rather than make a special case out of it. In any case, these instructions do push SP or ESP, so don't worry about it too much — there is nothing you can do about it.

The popa and popad instructions provide the corresponding "pop all" operation to the pusha and pushad instructions. This will pop the registers pushed by pusha or pushad in the appropriate order (that is, popa and popad will properly restore the register values by popping them in the reverse order that pusha or pushad pushed them).

Although the pusha/popa and pushad/popad sequences are short and convenient, they are actually slower than the corresponding sequence of push/pop instructions, this is especially true when you consider that you rarely need to push a majority, much less all the registers.[15] So if you're looking for maximum speed, you should carefully consider whether to use the pusha(d)/popa(d) instructions.

The pushf, pushfd, popf, and popfd instructions push and pop the (E)FLAGs register. These instructions allow you to preserve condition code and other flag settings across the execution of some sequence of instructions. Unfortunately, unless you go to a lot of trouble, it is difficult to preserve individual flags. When using the pushf(d) and popf(d) instructions it's an all-or-nothing proposition: You preserve all the flags when you push them; you restore all the flags when you pop them.

Like the pushad and popad instructions, you should really use the pushfd and popfd instructions to push the full 32-bit version of the EFLAGs register. Although the extra 16 bits you push and pop are essentially ignored when writing applications, you still want to keep the stack aligned by pushing and popping only double words.

3.9.6 Removing Data from the Stack Without Popping It

Once in a while you may discover that you've pushed data onto the stack that you no longer need. Although you could pop the data into an unused register or memory location, there is an easier way to remove unwanted data from the stack: Simply adjust the value in the ESP register to skip over the unwanted data on the stack.

Consider the following dilemma:

      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           // Whoops, we don't want to pop EAX and EBX!           // What to do here?      else           // No calculation, so restore EAX, EBX.           pop( ebx );           pop( eax );      endif; 

Within the then section of the if statement, this code wants to remove the old values of EAX and EBX without otherwise affecting any registers or memory locations. How to do this?

Because the ESP register simply contains the memory address of the item on the top of the stack, we can remove the item from the top of stack by adding the size of that item to the ESP register. In the preceding example, we wanted to remove two double word items from the top of stack. We can easily accomplish this by adding eight to the stack pointer (see Figures 3-17 and 3-18 for the details):

      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          add( 8, ESP ); // Remove unneeded EAX and EBX values      else          // No calculation, so restore EAX, EBX.          pop( ebx );          pop( eax );      endif; 

click to expand
Figure 3-17: Removing Data from the Stack, Before ADD( 8, ESP ).

click to expand
Figure 3-18: Removing Data from the Stack, After ADD( 8, ESP ).

Effectively, this code pops the data off the stack without moving it anywhere. Also note that this code is faster than two dummy pop instructions because it can remove any number of bytes from the stack with a single add instruction.

Caution

Remember to keep the stack aligned on a double word boundary. Therefore, you should always add a constant that is an even multiple of four to ESP when removing data from the stack.

3.9.7 Accessing Data You've Pushed on 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 ); 

click to expand
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 on the stack. Popping all the intermediate values and then pushing them back onto the stack is problematic at best and 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 four 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.

Caution

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 you first push data onto the stack and the point 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:

      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.

[15]For example, it is extremely rare for you to need to push and pop the ESP register with the PUSHAD/POPAD instruction sequence.




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