Up to now, we have talked about C code and X86 assembler code as two very distinct things. The C code is compiled and as part of that compilation, the C code is turned into X86 assembler, but we never mix our C code and X86 assembler code.
It turns out that sometimes, it's useful to communicate with the X86 underlying features in our C code, and the gcc compiler actually supports doing this. For complete documentation of this capability, the see gcc manual Extended Asm page.
Actually, I've done all the hard work for today's lab. I have created some subroutines to analyze the X86 stack frames using C, with hooks into X86 to get the values of the %esp and %ebp registers. To see this, download lab11.tar.gz, untar, and look at printStack.h and printStack.c. The printStack.h file contains the declarations of the structures used to map stack data, as well as declarations for variables ebp and esp to capture the values of registers %esp and %ebp respectively. The .h file also has some #define's to make life easier for the users of this function. Notice that SAVE_BASE_POINTER is a define that keeps the current value of %ebp in a global variable. The intention is to insert SAVE_BASE_POINTER; in your main function so that you know where the top stack frame is. Then, when you follow stack frame chains, you know where to stop.
The important code that actually prints a stack frame chain is in printStack.c. This C file contains two functions. The first is printFrameStack which prints the entire stack, starting at main's %ebp, going down until it reaches the current value of %ebp. Look at the code to see how this works. It actually uses a recursive function invocation to get to main's stack frame, and then unwinds the recursion, printing stack frames as it goes. The parameters to printFrameStack are the current %ebp value, the current %esp value, the number of parameters that should be printed, and a "prefix" value to keep track of how deep in the stack we are.
Take a look at printStackFrame as well. This takes almost the same parameter list as printFrameStack, but this only prints the current frame (and some fixed information in the frame above yours, like the return address, the pointer to the caller's ebp, and the parameters). This code also prints out the contents of the stack, from top to bottom, showing the offsets both from %esp and the negative offsets from %ebp.
Notice that in printStack.h, there are #defines that allow you to invoke printFrame and printStack as if they were functions, but the #define figures out what the first two parameters should be.
Once you get a feel for the printStack utilities, look at the third piece of code in the lab11 directory... fact.c. This is just a simple example of the recursive factorial function, like the one we used in Lab 1. However, the trick is to modify this code so we get an understanding of how the stack is actually working for us. We can do this as follows...
Compile, and run your code (e.g. run make test). It's easiest to capture the output in a file because there's so much of it (and make test does that for you - saving the outut in log.txt).
Now look at the log file you produced. Think about how the stack works, and what gets printed in the log. Specifically, think about what happens to the stack during a recursive call.
Can you spot where in the printout of fact's stack frames the local variable answer is kept? Did initializing answer to a recognizable value help? Can you see how answer gets updated when you compare the before and after instances of the stack?
When dealing with recursion, we often use the input parameter - in this case, the n value to keep track of where we are in the recursion. Can we look at the parameter n value in our log to keep track of where we are in the recursion? Does doing so help us understand the stack?
Change your fact.c program by calling printStack(1) instead of calling printFrame(1) in the fact function. Rerun make test and look at the output log again.
Remember that printStack prints stack frames upside down, so each section of the log starts with the bottom stack frame, then print the stack above that, and above that, and so on until the last stack frame that is printed is the stack frame for main. It's a little harder to read this log because there is so much redundant information, but you get a much better feel for how the stack grows by pushing new stack frames on the bottom of the stack when a function is called, and pops frames off the bottom of the stack when the function returns.
Code up your own main function with calls to sub-functions (either recursive or not)... Insert printStack in this code and see if the calls make sense.
Download and edit the following file: lab11_report.txt. Then submit your editted file on Blackboard in the Lab 11 submission area.