COMP 40 Laboratory: GNU (GDB)

Purpose of this Lab

The purpose of today’s lab is to give you familiarity with GDB and Explorer, two tools that COMP 40 students use to defuse the binary bomb. It’s imperative that you understand how these tools works before you start working on the bomb! Today’s lab will also help reinforce some concepts surrounding Intel assembly and how it relates to code. To get direct experience with these tools, we suggest that you and your partner each do this lab individually. Feel free to converse with your partner as you go to discuss confusing aspects or unexpected results.

Getting the Lab Materials

To get the code for today’s lab, run the following: pull-code gdb-lab cd gdb-lab

Getting Started

C to Assembly

Before we run any code through GDB, we are going to look at the Compiler Explorer tool which will show you any code you type and its corresponding assembly right beside it. This tool should help you make the connection between code and assembly and is a great tool to experiment with if you get confused.

1. Go to the Compiler Explorer at godbolt.org. 2. In the left pane, change the upper right tab from ++ to C. You do not need to change anything in the right panel, but you can see from the drop down in the top left of this panel that we are seeing -64 assembly compiled with gcc 9.2 (as of Spring 2020). 3. Copy and paste the following into the left panel of the Compiler Explorer:

void loop() { int i; for (i = 0; i < 5; i++) { printf("%d\n", i); } }

4. Let’s take a look at the assembly and how it relates to the code on the left. You can hover over an instruction (i.e. mov) to get a better understanding of what is happening. Looking at the instructions and the values in the for loop, try to answer the following questions (check with the TAs before moving on): • What machine instruction increments i? • What machine instruction corresponds to the loop’s i < 5 test?

1 Using GDB

1. First run make all which should produce three executables: loop, linkedlist, and do times16. 2. To open up gdb with the linked list code, run: gdb linkedlist 3. Lets get familiar with a few commands in gdb for running your code. Try some of these out, but note that some won’t work yet because they rely on other gdb commands being run first.

help command # ask gdb to tell you about the named gdb command run [arg1 ... argn] # run your code (and add command line arguments if you have any) next # proceed to next line of current function nexti # proceed to next assembly instruction step # like next but will "step" into any stepi # steps at the assembly instruction level continue # continues the program after you start running it

4. The next very important thing for you to know is how to set breakpoints. These are spots in the code where gdb will stop your code after you run it, or you are stepping through it. You can set break points in multiple ways:

break function_name # set a breakpoint when you know the function name break *address # set a breakpoint at an address

5. Now that you are in gdb type the following:

break makeList # telling gdb to stop when makeList is called in list-main.c run

6. You should see something like what is below in your terminal now. You can see that we have stopped the first time makeList has been called and that it is taking the argument n=10.

Breakpoint 1, makeList (n=10) at linkedlist.c:22 22 { (gdb)

7. You can view more of the code around your current location with the list command. Try running each of the following commands and note the differences:

list # prints the code around your current location list makeList # prints the code around the beginning of the named function list 21, 40 # prints the code between the two given line numbers

8. Let’s get more information with commands that will show us the current values of the registers in a summary or individually. Some of these useful commands are shown below:

info registers # will print the current value of all the registers print $eax # prints values in register eax in decimal print /x $eax # prints same value but in hex

9. Run the following: info registers

Remember that register rdi stores the first argument to the function. What value is stored there? The mid- dle column (with 0x in front of the numbers) is in hex, while the right-most column is in decimal. Does this value make sense?

2 10. Let’s set another break point in the makeList function at the function newNode and step into the makeList function to get a sense for what that does: break newNode step # you should see we’re now in the first line of makeList continue # keep running the program continue # do a second continue to get to the second call of newNode

11. Remember that register eax often stores the return value of the function. Let’s see what value is in eax by doing an: info registers or print $eax. The last function call that was made was the first call to newNode, so what it returned should be in the register eax right now 12. If we look at the function newNode in linkedlist.c, we can see that it returns a LinkedList which is a pointer to a Node struct as defined in linkedlist.h. Therefore, what is currently in eax is a pointer to the first node in the linked list. 13. On a piece of paper, draw this pointer to the start of the linked list and then run the following to print what the pointer is pointing at: print *(value_at_eax) # since we have a pointer, we can dereference it with *

14. The value printed should be 0 and correspond to the data field in the struct. The next field in the struct points to the next node. Right now we only have one node on the linked list so lets run continue again to run through newNode again and build another node. 15. Now we have two nodes. We can follow our way from the first node to the second node with the current pointer we have to the first node. To do this, we will add 8 to the pointer to the start of the list. You add 8 and not 4 (the size of an int) due to the way the struct is ordered; there are 4 bytes of padding between the data field and the next pointer since pointers are 8 bytes. You can run the following: print *(old_value_at_eax + 8)

16. Adding to your pointer drawing, draw a box for the first node of the linked list with corresponding fields and pointers. The value you just printed is the pointer to the second node, per the way the Node struct is laid out. Let’s print out the value of the second node with the pointer you just found and printed: print *(value_just_printed)

17. The value printed should be 1. Continue to add this second node to your drawing. You are now traversing your linked list! Build more nodes and traverse in this pattern or start over and set new break points entirely and next/step your way through the code. If you are traversing your list, draw it out as you go!

Using Your New Knowledge

1. Create a function incrementList which takes in a list and adds one to each element. 2. When you’re confident that your code works, run linkedlist in gdb, setting a breakpoint before your call to incrementList. As you build or traverse your list, make note of the pointer to the start of the linked list. Then, as you step through your incrementList, watch as the values increase before your eyes!

More and Final Thoughts

1. Next, try running gdb with the loop executable or compile one of the other files in the directory. You can try adding break points to function calls such as the printf in loop and examine the parameters passed in by examining the registers. Play around with both nexti and stepi. 2. There are more gdb commands that you may find helpful. Please refer to the gdb documentation, one of the many guides on the internet, and the helpful image on the next page about examining memory contents (much thanks to Professor Ming Chow for the image).

3 4