Lab 6 - Debugger and x86-64
Some terminology:
lldb
is a command-line debugger- “LLVM” is the compiler framework that includes many things, including the
clang
compiler that we are using, as well aslldb
gdb
is the debugger that was used in the past, and is often used elsewhere – it is analogous tolldb
in how it works
What is a Debugger?
A debugger is a utility program that allows you to run a program under development while controlling its execution and examining the internal values of variables. We think of a program running “inside” a debugger. The debugger allows us to control the execution of the program by pausing its execution and then resuming it. While paused, we can find out where we are in the program, what values variables have, reset the values of variables, etc. If a program crashes, the debugger can tell you exactly where the program crashed. The principles and commands described in this document are specific to the lldb debuggers under UNIX, but every debugger has similar commands.
Compiling for Debugging
We’ll use debuggers initially on binary files. When we get to writing our own C code, you will want to compile your code with the -g
flag to enable debugging symbols, which will make the debugger much more useful.
How to Start Using lldb
- Log into a computer with clang and LLDB that uses the x86-64 ISA (e.g. by SSHing into
portal.cs.virginia.edu
and runningmodule load clang-llvm
) - Invoke with
lldb program_to_debug
The following sections describe the important types of things you can do with lldb
, organized by “category” of activity.
Useful commands
The following all assume you are in a debugger
Commands controlling running
Command | Meaning |
---|---|
run | (re)start the program |
run x y z | (re)start the program with command line arguments x , y , and z |
step | step one source-code-line forward, entering functions if stepping on call |
next | step one source-code-line forward, skipping to return if stepping on call |
stepi | step one ISA-instruction forward, entering functions if stepping on call |
nexti | step one ISA-instruction forward, skipping to return if stepping on call |
finish | run until the next return |
continue | resume running after run was interrupted (e.g., after a breakpoint or step ). |
exit | leave the debugger |
You might also want to use Ctrl+C to interrupt a program if it is running too long (this works on the command line for programs run without a debugger too).
Commands controlling break points
A breakpoint is a program location where the debugger pauses when running so you can see what’s around it.
When run
, the debugger pauses right before executing the code on which you place a breakpoint.
Command | Meaning |
---|---|
br set -n main | set a breakpoint on the first line of main |
br foo.c:23 | set a breakpoint on the line 23 of foo.c (must be a line with code, not a comment, blank line, etc) |
br list | list all breakpoints |
br delete 1 | delete breakpoint number 1 (as indicated in the list) |
Looking around
To inspect the code and call stack,
Command | Meaning |
---|---|
bt | show a backtrace : a list of call s used to reach here |
up | select the stack frame of the caller of the current stack frame |
down | undo a previous up |
di -f | diassemble the code for the current call f rame. |
di -n main | diassemble the code for the function n amed main |
di -n main -b | diassemble the code for the function n amed main , with byte encoding of instructions included |
di -s 0x1234 -c 20 | diassemble 20 bytes starting at address 0x1234 |
If you need to peek inside registers or memory,
Command | Meaning |
---|---|
frame info | show information about the current stack frame |
register read | show the contents of the program registers |
register read --format i | show the contents of the program registers, formated as signed integers |
register read rax rdx | show the contents of rax and rdx (only) |
me rea -s4 -fx -c8 0x1234 | me mory rea d, with a c ount of 8 values, each value’s s ize being 4 bytes, f ormated in hex adecimal, from address 0x1234 |
Example: debugging cmdadd
See the cmdadd example for a detailed walkthrough.
Task: debug runsum
The program runsum
is supposed to print out the sum of numbers from 0 to \(n\) (the command line argument) inclusive, as e.g.
./runsum 0
Sum of values from [0, 0]: 0
./runsum 3
Sum of values from [0, 3]: 6
./runsum 4
Sum of values from [0, 4]: 10
However, the program sometimes prints the wrong numbers! If we call ./runsum 4
, it prints 4
. That’s not good! Try it out yourself.
You can download runsum by copy/pasting the following line into the terminal window. It will download the file and save to the current directory.
wget https://www.cs.virginia.edu/~jh2jf/courses/cs2130/fall2022/labs/files/runsum
After you get the file, you should make sure it is executable by issuing the following command. We’ll talk more about file permissions in CSO2!
chmod +x runsum
Your task: use lldb
to find the bug, then use a hex editor to fix it.
Hex editors
We used an online hex-editor in Lab 2, and that can be used again here. There are also various hex-editor applications, such as
- xxd (installed by default on some linux systems)
- hexedit
- ghex
- bless
- oketa
- wxHexEditor
- hexcurse
- dhex
- hexer
If editing binaries becomes a common part of your workflow (it won’t in this class, but might later in your careers) then picking a hex editor you like will be worth your while.
A Quick Walkthrough
Our programs normally start with main
, so that’s a good place to look first. Once you have started the debugger on runsum
, use di -n main
to disassemble and view the contents of main
.
- Questions to ponder:
-
What function calls do you see?
Since lldb gives us comments after the
;
, we can get some hints about where the functions may live. Symbol stubs will tell us about code elsewhere. It looks like we are calling a particular function! -
Do you notice any if statements (from the jumps)?
We can see a compare followed by a conditional jump, as well as an unconditional jump.
-
Do you notice the mix of 64-bit and 32-bit?
There are
movl
andmovq
instructions mixed in!
-
Set a break point at main
using br set -n main
. Then, you can run this code using the run
command, but don’t forget to give an argument! You can then step
through the code. At each step:
- lldb will show you the upcoming instructions (what will be executed next is at the arrow).
- You can see where you are in the entire frame with the
di -f
command. Remember, the next instruction to be executed will have an arrow next to it. - You can view the contents of any register using the
reg read
command. We can look at the contents of rax withreg read rax
. - You can view the contents of memory using the
me rea -s8 -c1 -fx ADDRESS
command. Remember that if the value is 64-bit (look for theq
), then we need 8-byte values (s8). You may need to view the contents of a register then do some math to get the address (see step 7 in the debugger example). Notice that even this assembly is doing a lot of what we saw in our own Toy ISA, including moving values from register to memory so that the registers can be used for other things.
It looks like there is one local function in our code. Disassemble and view the contents of that function. Where is it storing the added values? Do you see a loop or an if statement? Run through this function, review the register values as it works, and see if you can find where things might go awry. Once you’ve found the issue, fix it in a hex editor, and double check using the debugger again.
Tips
Linux has a notion of “file permissions” which we’ll learn more about in CSO2. One of those permissions is “executable”, meaning a file can be run as a program. Depending on how you download runsum
and get it onto portal
, it might or might not have that permission.
If lldb
’s run
command gives an error message like 'A' packet has an error
, try exiting lldb
and running chmod +x runsum
to enable executable permissions on the runsum
file.
Check-off
To check-off this lab, show a TA your working code. The TA may also request that you discuss how you used the debugger to solve the problem and/or that you do a few simple debugger actions for them.