radare2/doc/devdebug.md

5.0 KiB

Developer's Guide for Debugging Radare2

This document explains common development practices used to debug radare2 in order to find, identify and fix bugs, memory corruptions, leaks or race conditions.

Compilation

When changing your code you need to rebuild r2 to test it. In case you use ACR/Make you just need to type make in the directory where you changed the code. But if you use meson you must run ninja -C ../../b instead.

Bear in mind that meson tracks source dependencies more deeply than acr/make, so it's possible that for some changes in .h files meson builds will require a full recompilation, while make could just be faster and handier.

The reason why source build changes are available system wide is because the default installation method ./sys/install.sh uses sudo make symstall which creates symlinks instead of copying the executables, libraries and support files to $PREFIX.

Other important CFLAGS are:

  • -Wall - show more warnings useful to improve code quality
  • -Wshadow - show if you have variables with the same name in nested scopes
  • -w - in case -Werror is set, stop treating warnings as errors

Multiple r2

In order to develop for r2 it is common to have two or more builds of radare2, so you can compare master behaviour with your patched code at any time.

You can achieve this in different ways, but bear in mind that using --with-rpath for meson or acr builds is important to avoid having one r2 using the other r2's libraries.

  • Use ACR source build for your patched code
  • Keep a --with-rpath meson build in a custom directory for running it
  • Statically build r2 (sys/static.sh)
  • Use r2env to switch between release or git builds

Symbols

By default radare2 is compiled with debug symbols, but in case the source-line information is not available you may want to set the CFLAGS=-g before calling ./configure or meson b.

ASAN

Stands for Address Sanitizer and it's a compiler plugin available for clang, gcc and msvc that adds extra checks in the generated binary, which permits to identify many types of bugs.

  • undefined: behaviour (binary operations on signed variable)
  • memory: uninitialized variables (compiler warnings may catch that too)
  • thread: sanitizer - detect race conditions and deadlocks
  • address: memory corruption, read and write overflows
  • leak: track memory usage and detect memory leaks

In case you are coming from another branch or another non-sanitized build you may want to have a clean build dir. To do this, reset your git state into a clean state.

$ make mrproper
$ git clean -xdf
$ rm -rf shlr/capstone

Works like valgrind, but faster. Use sys/sanitize.sh to get that build ready to use.

$ export SANITIZE=$leak memory address"
$ sys/sanitize.sh

Now you can just run r2 and get a nice crash report without the need of a debugger.

But it is also compatible with any debugger like gdb or lldb.

Valgrind

It's a Linux tool that pseudo-emulates programs in order to detect runtime memory corruption problems and other types (like the ones listed above).

Valgrind was working on macOS and other BSDs, but it lacks for that porting. So we may want to use it only when we are on Linux and we dont want to modify the binary we want to execute.

There are different plugins that can be used for different purposes:

Memory Profiling

By default valgrind will identify undefined behaviour, memory corruptions, race conditions and more. Just call it like this:

$ valgrind r2 /bin/ls

Time Profiling

Identifying bottlenecks is an important thing to do when looking for optimizations. This can be achieved with callgrind which is a plugin for tracing function calls and profile the time spent on them, specifying how many times are called.

$ valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --collect-jumps=yes \
    /usr/local/bin/r2 /bin/ls

To visualize the generated logs use kcachegrind

kcachegrind callgrind.*

GPerfTools

This package can be used on macOS or Linux systems and uses tcmalloc to track heap allocations and create a graph with the memory allocated by each function during the execution.

$ brew ls gperftools
$ DYLD_INSERT_LIBRARIES=/opt/homebrew/Cellar/gperftools/2.9.1_1/lib/libtcmalloc.4.dylib \
  HEAPPROFILE=/tmp/b.txt \
  r2 /bin/ls
$ pprof --web /usr/local/bin/radare2 /tmp/b.txt*

Frida-Trace

One of the tools shipped with frida is frida-trace, and allows us to trace function calls easily, and visualize their arguments, what they return and its backtraec.

$ frida-trace -i 'r_core_cmd*' r2 /bin/ls

Debuggers

You can also debug r2 using gdb, lldb, x64dbg or any other debugger you like.

Launching lldb and r2 in the same terminal it's usually handy, unless you are going to debug a bug in visual or panels mode, because the terminal configuration may be different. In those cases you may want to attach to the process by calling it with lldb -p <pidofr2>

PD

Read the DEVELOPERS.md document for more development and debugging tips!

--pancake