Debugging C code on macOS

I started to write C 25 years ago now, with many different tools over the year. As many open source developers, I spent most of my life working with the GNU tools out there.
As I've been using an Apple computer over the last years, I had to adapt to this environment and learn the tricks of the trade. Here are some of my notes so a search engine can index them β and I'll be able to find them later.
Debugger: lldb
I was used to `gdb` for most of years doing C. I never managed to install gdb correctly on macOS as it needs certificates, authorization, you name it, to work properly.
macOS provides a native debugger named lldb, which really looks like gdb to me β it runs in a terminal with a prompt.
I had to learn the few commands I mostly use, which are:
lldb -- myprogram -options
to run the program with optionsr
to run the programbt
orbt N
to get a backtrace of the latest N framesf N
to select frame Np V
to print some variable value or memory address
Those commands cover 99% of my use case with a debugger when writing C, so once I lost my old gdb
habits, I was good to go.
Debugging Memory Overflows
On GNU/Linux
One of my favorite tools when writing C has always been Electric Fence (and DUMA more recently). It's a library that overrides the standard memory manipulation function (e.g., malloc
) and instantly makes the program crash when an out of memory error is produced, rather than corrupting the heap.
Heap corruption issues are hard to debug without such tools as they can happen at any time and stay unnoticed for a while, crashing your program in a totally different location later.
There's no need to compile your program with those libraries. By using the dynamic loader, you can preload them and overload the standard C library functions.
LD_PRELOAD=/usr/lib/libefence.so.0.0 my-program
my-program
with Eletric Fence loaded on GNU/LinuxMy gdb
configuration has been sprinkle with my friends efence and duma, and I would activate them from gdb
easily with this configuration in ~/.gdbinit
:
define efence
set environment EF_PROTECT_BELOW 0
set environment EF_ALLOW_MALLOC_0 1
set environment LD_PRELOAD /usr/lib/libefence.so.0.0
echo Enabled Electric Fence\n
end
document efence
Enable memory allocation debugging through Electric Fence (efence(3)).
See also nofence and underfence.
end
define underfence
set environment EF_PROTECT_BELOW 1
set environment EF_ALLOW_MALLOC_0 1
set environment LD_PRELOAD /usr/lib/libefence.so.0.0
echo Enabled Electric Fence for underflow detection\n
end
document underfence
Enable memory allocation debugging for underflows through Electric Fence
(efence(3)).
See also nofence and efence.
end
define nofence
unset environment LD_PRELOAD
echo Disabled Electric Fence\n
end
document nofence
Disable memory allocation debugging through Electric Fence (efence(3)).
end
define duma
set environment DUMA_PROTECT_BELOW 0
set environment DYMA_ALLOW_MALLOC_0 1
set environment LD_PRELOAD /usr/lib/libduma.so
echo Enabled DUMA\n
end
document duma
Enable memory allocation debugging through DUMA (duma(3)).
See also noduma and underduma.
end
On macOS
I've been looking for equivalent features in macOS, and after many hours of research, I found out that this feature is shipped natively with libgmalloc
. It works in the same way, and its features are documented by Apple.
My ~/.lldbinit
Β file now contains the following:
command alias gm _regexp-env DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
This command alias allows enabling gmalloc
by just typing gm
at the lldb prompt and then run
the program again to see if it crashes with gmalloc
enabled.
Debugging CPython
It's not a mystery that I spend a lot of time writing Python code β that's the main reason I've been doing C lately.
When playing with CPython, it can be useful to, e.g., dump the content of PyObject
structs on the heap or get the Python backtrace.
I've been using cpython-lldb for this with great success. It adds a few bells and whistles when debugging CPython or extensions inside lldb
. For example, the alias py-bt
is handy to get the Python traceback of your calls rather than a bunch of cryptic C frames.
Now, you should be ready to debug your nasty issues and memory problems on macOS efficiently!
