The purpose of this lab is to become familiar with standard command-line debugging tools that can be used in Unix/Linux systems (with equivalents on other operating systems). These tools allow you to investigate a piece of binary code, even if you do not have the source code available.
First refresh your basic gdb skills by working through my lab from CMPS 223 for using gdb to debug a core dump and to step through an executable: gdb lab
For this lab, let's focus on different ways to debug a core dump from a C/C++ program. Grab the file lab1.cpp from the CMPS 223 lab for the rest of this lab. Don't forget to use the command
ulimit -c unlimitedto allow core dump files.
There are several ways to compile a program. If the programmer is debugger-friendly, they will compile with debugging information, which is the -g option for gcc/g++. A program with debugging information has access to the source code, the symbol table, and other pieces of information that makes it very easy to trace through the code line-by-line.
Using the -g option is what you should have done in the review from Lab 1 from CMPS 223.
The next way a program could be compiled is the "don't care" or default mode, which is to omit the debugging info option from the compiler command line, but to also omit any post-processing of the executable. For example, the command
g++ lab1.cppwould create an executable named
a.out
that does not have the
debugging information available.
Run that code to get the core dump and then use gdb to investigate the core file. You'll notice that the commands you could use from CMPS 223 Lab 1 no longer work because the executable does not have debugging info available.
But also notice that backtrace does give some information about the executable. You can see the information about the library call (frame 0) and the address for the main() function (frame 1).
In fact, you can do assembly debugging from within gdb, assuming you know
the register names for your architecture. The most important register to start
with is the instruction pointer. On Sleipnir, we are using the Intel x86-64
architecture, so the instruction pointer is stored in $rip
.
To see all the registers in use by your core file, use the gdb command
info registers
To print out the assembly instruction at the instruction pointer, use the following gdb command:
x/i $ripThis tells gdb to translate (the "x" portion of the gdb command) the information at memory address
$rip
into an instruction (the "i"
portion of the gdb command).
As long as you are still in frame 0, you should see that the assembly command
mov $eax,0($rbp)
was where the segmentation fault occurred. If
you use the following commands, you can see that $rbp
(which
should have the base address of the array) is the null pointer:
print $eax print $rbpYou can use the
x/i <address>
command to investigate other
assembly instructions.
You can use the command
disassembleto display the assembly for that frame.
There are even instruction-level equivalents to the step
and
next
gdb commands, named appropriately stepi
and
nexti
.
The final way a program could be compiled is to intentionally remove all source
code information from the file by using the strip
command. For
example, you could do the following:
g++ -o lab1 lab1.cpp strip lab1
The stripped executable can be debugged with gdb in a similar fashion to the "don't care" executable.
If you have an unknown executable, you can get information about its architecture and whether or not it has been stripped using the command:
file <filename>
Take Away: gdb - It's not just for source code.
The simplest way to start is to see what human-readable information is stored
in the executable using the strings
command:
strings <filename> | lessThis may or may not reveal much information beyond what
file
revealed. For example, on our executable from Part 1, it really doesn't give
you much of an insight beyond what libraries the executable uses and the
prompts the program has.
The real "old school" way of debugging an executable is to fire up a command line hex reader or hex editor and break out your binary to assembly manual. While this can be a tiresome task for debugging a large program, it is still useful to know how to view a binary file in hexadecimal (or binary) format.
To simply dump the contents of a file in hex, use the following:
hexdump <filename>This can be rather wordy, so another common technique is to invoke the
xxd
command from within the vi editor to display the file in
hex (with a sidebar of ASCII). Open the binary file in vi and then issue the
following vi command to convert it to hex:
:%!xxdYou can then use standard vi commands to move around (and change, although I don't recommend trying to make changes at this point) the binary file. When you are done, you can issue the following vi command to convert back to binary:
:%!xxd -rOr if you've made no changes, you can just use
:q!
to quit vi
without saving.