debugging
gdb
This will be mostly about getting gdb to handle C++ programs.
stepping c++
The step command is one of the most useful in gdb, but its is horrible to use in c++ since it will step into each and every constructor and inlined function.
Mostly we are not interested in any code coming from /usr/include/** For instance :
| changetitle |
|---|
| 459 cmd = vehicle_ins_cmd(db, vehicle);
(gdb) s
std::shared_ptr[visit](Vehicle)::shared_ptr (this=0x7fffffffd2c0)
at /usr/include/c++/10/bits/shared_ptr.h:149
149 shared_ptr(const shared_ptr&) noexcept = default; ///< Copy constructor
(gdb)
|
We want to enter function vehicle_ins_cmd() and not the copy constructor of shared_ptr. Of course we could set a breakpoint on vehicle_ins_cmd but that defeats the whole point of the step operation.
gdb 7.12 has a solution for this, it can be ordered to skip certain directories from being used. Put these in your ~/.gdbinit file :
| ~/.gdbinit |
|---|
| skip -gfi /usr/include/c++/*/*/*
skip -gfi /usr/include/c++/*/*
skip -gfi /usr/include/c++/*
|
Now the debug session becomes :
| ~/.gdbinit |
|---|
| File(s) /usr/include/c++/*/*/* will be skipped when stepping.
File(s) /usr/include/c++/*/* will be skipped when stepping.
File(s) /usr/include/c++/* will be skipped when stepping.
Reading symbols from ./test/test...
...
459 cmd = vehicle_ins_cmd(db, vehicle);
(gdb) s
vehicle_ins_cmd[abi:cxx11](Database*, std::shared_ptr[visit](Vehicle)) (
db=0x5555558d2a60,
vehicle=std::shared_ptr[visit](Vehicle) (use count 3, weak count 1) = {...})
at test/../vehicle.cpp:431
431 std::stringstream command;
|
431 is indeed the first line of vehicle_ins_cmd() ;) To see the skipped directories use :
incomplete type
Still searching for a solution here....
Tryout compiling everything with -D_GLIBCXX_DEBUG
visit
Compiling with the flag results in :
| debugging |
|---|
| Program received signal SIGSEGV, Segmentation fault.
__memcpy_ssse3 () at ../sysdeps/x86_64/multiarch/memcpy-ssse3.S:132
132 ../sysdeps/x86_64/multiarch/memcpy-ssse3.S: No such file or directory.
(gdb)
|
So that's even worse, but the gdb site states that the size of containers etc change so it is likely ALL code has to be recompiled with this flag for it to work.
If you print the stack trace it will point to osrm:: functions so at least that should be recompiled.
Another thing to try is : visit It says adding debug/string instead of just string would help. But strings are printed just fine with or without this header. Anyway there is no debug/sstream to include so that won't help.
The one thing that helps is this :
| solution |
|---|
| apt-get install libstdc++6-10-dbg # or find you version in aptitude
g++ -g foo.cpp
ldd a.out # this will give a hint about what libstd++ is used
# find the debug version
find /usr/lib | grep stdc++ | grep debug
# one of the hits should be :
/usr/lib/x86_64-linux-gnu/debug/libstdc++.a
# g++ -g foo.cpp /usr/lib/x86_64-linux-gnu/debug/libstdc++.a
gdb ./a.out
|
Now printing a stringstream will dump a whole lot more ! The content is in some submember (_M_string in my case), but this is faster:
| debug stringstream |
|---|
| p ss.str();
$2 = "abx 42 xyz 3.1415n"
|
For temporary compiles I added this to make backend makefile :
| makefile |
|---|
| LINKDEBUG=/usr/lib/x86_64-linux-gnu/debug/libstdc++.a
debug/backend: backend.cpp debug/libbackend.a
$(CXX) $(CXXFLAGS) $(CFLAGS) $(DEBUGFLAGS) $^ -o $@ -L debug -lbackend $(LDFLAGS) $(LINKDEBUG)
|
Just always compile test and debug like this.
exception breakpoint
When you want to stop the debugger at the point an exception occurs, you can set a 'breakpoint' like this :
| break on exception |
|---|
| (gdb) catch throw
(gdb) run
|
Very handy !!
pretty printing
Very short guide from here : visit
Download the pretty-print python file here
| download pretty-print |
|---|
| svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python
mv python ~/Install/gdb_printers
|
Then put this inside .gdbinit :
| ~/.gdbinit |
|---|
| python
#sys.path.append("/usr/share/gcc-10/python/");
import sys
sys.path.insert(0, '/home/kees/Install/gdb_printers')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end
|
It should now work, but ut could also say :
ModuleNotFoundError: No module named 'libstdcxx'
If so, you should point the python code to the location of libstdcxx, in the snippet above it was already added in comments. Of course you first need to check the exact path, then uncomment. The error should now be gone.
fuzzing
In particular American Fuzzy Lob (AFL).
Installation is simply downloading the tarbal and :
| fuzzing |
|---|
| wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
tar zxvf afl-latest.tgz
cd afl-2.52b
make
|
You can instrument the program by simply changing the compiler in your makefile to the compilers you just created :
| instrumenting compilers |
|---|
| CC = /home/kees/Install/afl-2.52b/afl-gcc
CXX = /home/kees/Install/afl-2.52b/afl-g++
|
You should now be able to run afl-fuzz but...
| run |
|---|
| mkdir testcases
mkdir findings
/home/kees/Install/afl-2.52b/afl-fuzz -i test_case -o findings -- ./test/test
|
But it will spawn this error :
| error |
|---|
| afl-fuzz 2.52b by [visit](lcamtuf@google.com)
[+] You have 8 CPU cores and 4 runnable tasks (utilization: 50%).
[+] Try parallel jobs - see docs/parallel_fuzzing.txt.
[*] Checking CPU core loadout...
[+] Found a free CPU core, binding to #0.
[*] Checking core_pattern...
[*] Checking CPU scaling governor...
[-] Whoops, your system uses on-demand CPU frequency scaling, adjusted
between 1367 and 3906 MHz. Unfortunately, the scaling algorithm in the
kernel is imperfect and can miss the short-lived processes spawned by
afl-fuzz. To keep things moving, run these commands as root:
cd /sys/devices/system/cpu
echo performance | tee cpu*/cpufreq/scaling_governor
You can later go back to the original state by replacing 'performance' with
'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ
to make afl-fuzz skip this check - but expect some performance drop.
[-] PROGRAM ABORT : Suboptimal CPU scaling governor
Location : check_cpu_governor(), afl-fuzz.c:7337
|
Which explains in detail how to solve this you should note down the way to turn on scaling again.
libfuzzer
To be done
See here for a good start : visit
address sanitizer
This is actually a google project but it is closely related to the clang suite where libfuzzer
Both are also activated in a similar way :
| sanitizer |
|---|
| clang-9 -fsanitize=fuzzer test.c
clang-9 -fsanitize=address test.c
|
You can also specify both comma separated.
Here is an example that caused problems in detail :
| example problem |
|---|
| char *normalize_zip(const char *src)
{
if (!src) return NULL;
char *dst= strdup(src);
char *result = dst;
while (*src && len--) {
if (isspace(*src)) src++;
else *dst++ = toupper(*src++);
}
*dst = '0';
return result;
}
|
This seems like a normal function with some safe guards but actually the first test case with libfuzzer crashes it.
| output |
|---|
| SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/kees/projects/san/a.out+0x431128) in strdup
|
It took some time to see how fuzzer works. Here is a complete walk trough. The function above is tested with this 'wrapper' for libfuzzer.
I created a directory structure for fuzzer tests :
| directory structure |
|---|
| fuzzer/
└── util
└── normalize_zip
├── corpus
│ ├── 03d5f51f38198db51103d878a552a4611eb4e07f
│ ├── 04038448ce858e0cd698944e27c8532a44001693
│ ├── 043d8b913be7c47d30af08f6badd2094ccbb5c76
│ ├── fde6174fcdfe91fa0ad584cca8914d8d05786428
│ └── ffe0cfa36ba87655a7317cf05a10eca0b2b4f8ac
└── fuzz.c
|
The corpus directory is built up for you with more and more inputs. They are just the binary input for your function that are tried. You can put your own files there to start it of on a good foot. The fuzz.c file looks like this :
| fuzz.c |
|---|
| #include "../../../util.c"
int LLVMFuzzerTestOneInput(const char *data, size_t size) {
char *res = normalize_zip(data,size);
return 0;
}
|
LLVMFuzzerTestOneInput() is always the same and libfuzzer feeds it's test data through it. You can then relay that into your function to be tested.
This can be compiled and instrumented with :
| instrument compile |
|---|
| clang-9 -fsanitize=fuzzer,address fuzz.c -o fuzz
./fuzz
|
When you run this it crashed and dumps crash file with length 0. If you start fuzz in the debugger and break at normalize_zip you will see :
| gdb |
|---|
| where
#0 normalize_zip (src=0x53a400 [visit](fuzzer::TPC) "", len=0)
at fuzzer/util/normalize_zip/../../../util.c:14
#1 0x00000000004fdb47 in LLVMFuzzerTestOneInput (data=0x602000000010 "276",
size=0) at fuzzer/util/normalize_zip/fuzz.c:4
#2 0x000000000043d6d2 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) ()
#3 0x000000000043f1ff in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::vector[visit](fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile) >&) ()
#4 0x000000000043f981 in fuzzer::Fuzzer::Loop(std::vector[visit](fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile) >&) ()
#5 0x000000000042d45c in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) ()
#6 0x0000000000456053 in main ()
|
You see that LLVMFuzzerTestOneInput feeds data "276" with size 0. This explains the crash but also the crash file of 0 bytes.
If you see the report it says :
| diagnose |
|---|
| 0x602000000011 is located 0 bytes to the right of 1-byte region [0x602000000010,0x602000000011)
|
So it looks like a 1 byte region that is not null terminated. Something like this :
| example |
|---|
| char *s = malloc(1);
s[0] = '276';
normalize_zip(s);
|
This would also crash on strdup(). In fact you can do nothing to test if the input is valid or not. This would be valid :
| valid |
|---|
| char *s = malloc(10);
s[0] = '276';
s[9] = 0;
normalize_zip(s);
|
But you cannot test that from within normalize_zip (or strdup) without also specifying the valid length and using strndup !! But if we do not check the length again in the loop we again get a crash. So our final version would become :
| normalize_zip |
|---|
| char *normalize_zip(const char *src, int len)
{
if (!src) return NULL;
char *dst= strndup(src,len);
char *result = dst;
while (len> 0 && *src) {
if (isspace(*src)) src++;
else *dst++ = toupper(*src++);
len--;
}
*dst = '0';
return result;
}
|
Note that at first the loop test was
But that also gave an error because you test *src first and the len second. It does mean we have to pass along len all the time, will this become a performance issue. On the other hand strlen() suffers from the same deficit so we can't use that either and maybe we should switch to GString altogether since that carries it's length around.
I will start fuzzing things and see how much effort is needed to get it concrete safe !