5.3 Saving the State of the Machine


5.3 Saving the State of the Machine

Take a look at the program in Listing 5-2. This section of code attempts to print 20 lines of 40 spaces and an asterisk. Unfortunately, there is a subtle bug that creates an infinite loop. The main program uses the repeat..until loop to call PrintSpaces 20 times. PrintSpaces uses ECX to count off the 40 spaces it prints. PrintSpaces returns with ECX containing zero. The main program then prints an asterisk, calls a newline, decrements ECX, and then repeats because ECX isn't zero (it will always contain $FFFF_FFFF at this point).

The problem here is that the PrintSpaces subroutine doesn't preserve the ECX register. Preserving a register means you save it upon entry into the subroutine and restore it before leaving. Had the PrintSpaces subroutine preserved the contents of the ECX register, the program in Listing 5-2 would have functioned properly.

Listing 5-2: Program with an Unintended Infinite Loop.

start example
 program nonWorkingProgram; #include( "stdlib.hhf" );      procedure PrintSpaces;      begin PrintSpaces;           mov( 40, ecx );           repeat                stdout.put( " ); // Print 1 of 40 spaces.                dec( ecx );      // Count off 40 spaces.           until( ecx = 0 );      end PrintSpaces; begin nonWorkingProgram;      mov( 20, ecx );      repeat           PrintSpaces();           stdout.put( '*', nl );           dec( ecx );      until( ecx = 0 ); end nonWorkingProgram; 
end example

You can use the 80x86's push and pop instructions to preserve register values while you need to use them for something else. Consider the following code for PrintSpaces:

      procedure PrintSpaces;      begin PrintSpaces;           push( eax );           push( ecx );           mov( 40, ecx );           repeat                stdout.put( ' );      // Print 1 of 40 spaces.                dec( ecx );           // Count off 40 spaces.           until( ecx = 0 );           pop( ecx );           pop( eax );      end PrintSpaces; 

Note that PrintSpaces saves and restores EAX and ECX (because this procedure modifies these registers). Also, note that this code pops the registers off the stack in the reverse order that it pushed them. The last-in, first-out operation of the stack imposes this ordering.

Either the caller (the code containing the call instruction) or the callee (the subroutine) can take responsibility for preserving the registers. In the example above, the callee preserved the registers. The example in Listing 5-3 shows what this code might look like if the caller preserves the registers:

Listing 5-3: Demonstration of Caller Register Preservation.

start example
 program callerPreservation; #include( "stdlib.hhf" );      procedure PrintSpaces;      begin PrintSpaces;           mov( 40, ecx );           repeat                stdout.put( ' ' ); // Print 1 of 40 spaces.                dec( ecx );        // Count off 40 spaces.           until( ecx = 0 );      end PrintSpaces; begin callerPreservation;      mov( 20, ecx );      repeat           push( eax );           push( ecx );           PrintSpaces();           pop( ecx );           pop( eax );           stdout.put( '*', nl );           dec( ecx );      until( ecx = 0 ); end callerPreservation; 
end example

There are two advantages to callee preservation: space and maintainability. If the callee (the procedure) preserves all affected registers, then there is only one copy of the push and pop instructions, those the procedure contains. If the caller saves the values in the registers, the program needs a set of push and pop instructions around every call. Not only does this make your programs longer, it also makes them harder to maintain. Remembering which registers to push and pop on each procedure call is not something easily done.

On the other hand, a subroutine may unnecessarily preserve some registers if it preserves all the registers it modifies. In the examples above, the code needn't save EAX. Although PrintSpaces changes AL, this won't affect the program's operation. If the caller is preserving the registers, it doesn't have to save registers it doesn't care about (see the program in Listing 5-4).

Listing 5-4: Demonstrating That Caller Preservation Need Not Save All Registers.

start example
 program callerPreservation2; #include( "stdlib.hhf" );      procedure PrintSpaces;      begin PrintSpaces;           mov( 40, ecx );           repeat                stdout.put( ' ' ); // Print 1 of 40 spaces.                dec( ecx );        // Count off 40 spaces.           until( ecx = 0 );      end PrintSpaces; begin callerPreservation2;      mov( 10, ecx );      repeat           push( ecx );           PrintSpaces();           pop( ecx );           stdout.put( '*', nl );           dec( ecx );      until( ecx = 0 );      mov( 5, ebx );      while( ebx > 0 ) do           PrintSpaces();           stdout.put( ebx, nl );           dec( ebx );      endwhile;      mov( 110, ecx );      for( mov( 0, eax ); eax < 7; inc( eax )) do           push ( eax );           push ( ecx );           PrintSpaces();           pop( ecx );           pop( eax );           stdout.put( eax, " ", ecx, nl );           dec( ecx );      endfor; end callerPreservation2; 
end example

This example in Listing 5-4 provides three different cases. The first loop (repeat..until) only preserves the ECX register. Modifying the AL register won't affect the operation of this loop. Immediately after the first loop, this code calls PrintSpaces again in the while loop. However, this code doesn't save EAX or ECX because it doesn't care whether PrintSpaces changes them. Because the final loop (for) uses EAX and ECX, it saves them both.

One big problem with having the caller preserve registers is that your program may change. You may modify the calling code or the procedure so that they use additional registers. Such changes, of course, may change the set of registers that you must preserve. Worse still, if the modification is in the subroutine itself, you will need to locate every call to the routine and verify that the subroutine does not change any registers the calling code uses.

Preserving registers isn't all there is to preserving the environment. You can also push and pop variables and other values that a subroutine might change. Because the 80x86 allows you to push and pop memory locations, you can easily preserve these values as well.




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