Lab 3. Stack/Procedure Call

For the activities in this lab, first launch your Linux. Perform a git pull on your repository to make sure it is up to date. Also, print the Question Sheet in hard copy.

Get Lab 3 on your VM and keep your work in Git

Your VM is the Linux computer where you compile and run this lab. Your Gitter repository is the shared copy that moves your work between the VM and your own computer. Do not copy the lab3 directory manually between computers: commit and push from one clone, then pull in the other clone.

  1. On your own computer, connect to the VM you claimed (replace XX with its two-digit number):

    ssh csse@csse132-XX.csse.rose-hulman.edu
    
  2. On the VM, clone your repository once if you have not done so already. Use your Rose username in both places:

    git clone ssh://YOUR_ROSE_USERNAME@gitter.csse.rose-hulman.edu:13022/repos/2627a-csse132-YOUR_ROSE_USERNAME
    cd 2627a-csse132-YOUR_ROSE_USERNAME
    

    If you already cloned it on the VM, enter that directory instead.

  3. Before starting Lab 3, update the VM copy:

    git status
    git pull
    cd lab3
    

    git status should say that there are no uncommitted changes before you pull. If it does not, commit your earlier work first or ask for help.

  4. Do the lab on the VM. When you want to save or transfer your work, return to the repository root and push it:

    cd ..
    git add lab3
    git commit -m "Complete Lab 3 work"
    git push
    
  5. On your personal computer, open the separate clone of the same repository (or clone it there once with the command in Step 2) and run:

    git pull
    

    Your pushed VM work will now appear locally. Likewise, after committing and pushing from your personal computer, run git pull on the VM before continuing there.

After this lab, you will learn:

Answer the questions in the Question Sheet as you go through the lab!

Part 1. Examine Program with GDB

Install GEF (GDB Enhanced Features)

GEF (pronounced ʤɛf - "Jeff") is the old school GDB (GNU debugger) but with fancy features. To install GEF,

Understand the GEF Interface

Once the installation finishes, you can go to lab3/part1 folder. Then type vim part1.c and read the source code to understand the functions in the code.

Quit vim and return to the terminal. First, run the following command to compile the code.

gcc -g -fomit-frame-pointer part1.c -o part1

The -g flag tells the compiler to provide hints to GDB about how the C code corresponds to the Assembly code. You should use that flag when debugging. The -fomit-frame-pointer option is not required, but it simplifies the Assembly code.

Then, debug your compiled code by running

gdb ./part1

This should start GDB. But your code is not running yet. Then type

start

To tell GDB to run the program (this is a macro for creating a breakpoint at the beginning of main with b main followed by run).

If this is successful, you should be able to see some colorful output similar to the screenshot shown below:

From top to bottom, you should see the four panels which show 1) registers, 2) stack, 3) ARM code and 4) C code. These panels reflect the current state of the machine, and will be automatically updated as we step through the code.

Stack Panel

The stack is the space in main memory that stores information for function calls, including local variables, input arguments, etc. As shown in the picture below, GEF labels two kinds of addresses for data stored on the stack.

All addresses are in hexadecimal. The absolute address is the address of the data in the entire memory space. The relative address is the address with respect to the value $sp in the stack pointer register. For example, a relative address

0x0004 means the address is $sp + 4.

Reading 4-byte data addresses on the stack: Each row in the stack panel shows 8 bytes of data. For 4-byte data variables, two variables are stored in one row. The addresses of the two variables can be illustrated as follows:

In this example, the two integer variables with values 5 and 7 are stored at the row with absolute address 0x0000fffffffff9a8 and relative address 0x0018. In each row, the right half (4 bytes) has the lower address, and the left half (4 bytes) has the higher address. Therefore, the variable with value 7 (labelled in the red rectangle) is stored at absolute address 0x0000fffffffff9a8 (relative address 0x0018), and the variable with value 5 (labelled in the blue rectangle) is stored at absolute address 0x0000fffffffff9a8 + 4 = 0x0000fffffffff9ac (relative address 0x0018 + 4 = 0x001c).

ARM Panel

The ARM panel shows the Assembly code for the program.

The Assembly code corresponds to bytecode (machine instructions) stored in the memory space. Therefore, each ARM instruction has a memory address. The green arrow indicates the current line that is about to run but not running yet.

Source Code Panel

This panel shows the C code of the program. You can see the line number of the code. Also, similar to the ARM panel, the green arrow indicates the current line that is about to run but not running yet.

Debug with GEF/GDB

Once the program is running in gdb, we can use various commands to debug the code. Check this GDB Quick Reference for usages/instructions of various GDB commands.

Try out these commands and answer the questions on the Question Sheet in Part1.

Part 2: Stack/Procedure Call: Local Variables

The goal of this part of the lab is to discover how each function's local variables are organized.

  1. cd into the lab3/part2 directory.
  2. Open the do_nothing.c file using vim.
  3. Pay attention to the values of the local variable a and b in both main and do_nothing function. They have the same variable names but with different values. How? Let's find out.
  4. Type make do_nothing.s to generate the Assembly code. Open the do_nothing.s file using vim. Skim through the code to get a rough idea of how the Assembly stores variables in memory space.
  5. Type make to compile the program to executable do_nothing. Start debugging with gdb ./do_nothing.
  6. Set break points in the following places:

    There are questions you need to answer on the Question Sheet for EACH of the break points below. Please complete all questions for a break point before running to the next break point in GDB.

    • The beginning of main: Line 9
    • After assigning a, b in main: Line 11
    • The beginning of do_nothing: Line 4
    • The end of do_nothing: Line 6
    • The end of main: Line 12
  7. Debug the code and answer the questions on the Question Sheet in Part 2.

Part 3: Stack/Procedure Call: Function Input Arguments

The goal of this part of the lab is to discover input arguments are passed to functions.

  1. cd into the lab3/part3 directory.
  2. Open the add.c file using vim.
  3. Pay attention to the values of the input arguments a and b of add function passed from main.
  4. Type make add.s to generate the Assembly code. Open the add.s file using vim. Skim through the code to get a rough idea of how the Assembly stores variables in memory space.
  5. Type make to compile the program to executable add. Start debugging with gdb ./add.
  6. Set break points in the following places:

    There are questions you need to answer on the Question Sheet for EACH of the break points below. Please complete all questions for a break point before running to the next break point in GDB.

    • Right before calling add function in main: Line 12
    • The beginning of add: Line 4
    • The end of add: Line 7
  7. Run the code and answer the questions on the Question Sheet in Part3.

Part 4: Stack/Procedure Call: Function Return

The goal of this part of the lab is to discover how a function returns to where it was called after finishing running the function.

  1. cd into the lab3/part4 directory.
  2. Open the simple.c file using vim. Skim through the code to get a basic idea of the functions.
  3. Pay attention to callers and callees of different functions, i.e., which function(s) is called by which function(s).
  4. Type make simple.s to generate the Assembly code. Open the simple.s file using vim. Skim through the code to get a rough idea of how the Assembly stores variables in memory space.
  5. Type make to compile the program to executable simple. Start debugging with gdb ./simple.
  6. Set break points in the following places:

    There are questions you need to answer on the Question Sheet for EACH of the break points below. Please complete all questions for a break point before running to the next break point in GDB.

    • Right before calling the add function in main: Line 17
    • At the beginning of the add function: Line 8
    • Right before calling the do_nothing function in add: Line 10
    • At the beginning of the do_nothing function: Line 4
    • At the end of the do_nothing function: Line 5
  7. Run the code and answer the questions on the Question Sheet in Part4.

Finishing the Lab

  1. Scan and Upload the signed Question Sheet to Gradescope.