Goals for today
- More experience with C strings (i.e. null-terminated char arrays)
- Intro to pointers/addresses in C, including the mysterious "const char *" from last time
- Intro to debugging with lldb
- Investigating the structure of stack frames as constructed by the C compiler
- Fun with buffer overflows
Debugging with lldb
The LLDB Debugger is a terminal-based debugger
that enables you to run your programs, stop them in the middle of execution (i.e. stop
at breakpoints), examine variables while the program is stopped, and continue
execution. Here's the very beginning of getting to know lldb.
Note that today we'll use the clang compiler and lldb instead of their more venerable friends
gcc and gdb, because clang + lldb supports what we want to do on a Mac without much trouble,
whereas the setup for gcc + gdb in our lab would be much more complicated. The steps here,
though, will be essentially the same with gcc + gdb on other systems (notably Linux).
First, let's watch the Fibonacci numbers evolve.
- Grab this slightly modified functions.c from the
other day. Take a quick look at it to remind yourself what it does. Note that
main() in this version just calls fibonacci(10) instead of calling fibonacci(k) for lots
of values of k.
- Compile it:
clang -g -o functions functions.c
That "-g" is essential; it generates "debugging symbols" in the executable so you can
use lldb effectively.
- Launch lldb:
lldb functions
- Run the program inside lldb:
(lldb) r
That "(lldb)" is the lldb prompt—you don't have to type it.
- List the contents of main:
(lldb) list main
You can then hit Enter as many times as you want to see more code beyond the first few
lines of main.
- List the contents of fibonacci. Take note of the line number that's given for
the line inside the loop that says "previous = current;".
- Set a breakpoint at that line number:
(lldb) b 43
(or whatever line number lldb is showing you—43 was what it showed me).
- Run the program up until the breakpoint:
(lldb) r
.
- Look at the current values of the local variables and the parameter:
p n
p previous
p current
p new
Did you get what you expected?
- Continue the program until it hits the breakpoint again:
(lldb) c
.
- Examine the variables again. Still looking good?
- Delete the breakpoint. First, list your breakpoints:
(lldb) breakpoint list
Then pick the number of the breakpoint from the list (probably 1 in this situation) and then do this:
(lldb) breakpoint delete 1
Next, let's watch a string change in squashCharacterInString.
- List the contents of the squashCharacterInString, and set a couple breakpoints:
one at the "if (string[k] == character) {" line, and one at the very bottom
of the function "}". This will make the program stop at the beginning of each iteration
of the loop, and also just before the function returns.
- Run the program up to the first breakpoint.
- Examine the values of character, n, k, and string.
- Continue the program up to the next time it stops. Examine k and string again. Are they
looking as expected?
- Do that again a few times.
- When you hit the breakpoint at the end of the function, does string look like it should?
- Done? Go ahead and quit lldb:
(lldb) q
Strings, pointers, and stack frames
Before jumping in here, think about these two ideas:
- Remember what a stack frame is for, and what kinds of information you put in one.
- When you declare a character array (e.g. char a[10];), the name of the array is, more or less,
synonymous with the address of the first byte in the array.
With those ideas in mind, keep working with functions.c.
Trouble in stackframeland
Finally, let's turn our attention to the function makeTrouble.