Debugging
Overview
Teaching: 10 min
Exercises: 10 minQuestions
How do I debug everything?
Objectives
Know how to find problems in CMake
Know how to set up builds for debugging
Debugging is easy with CMake. We’ll cover two forms of debugging: debugging your CMake code, and debugging your C++ code.
CMake debugging
First, let’s look at ways to debug a CMakeLists or other CMake file.
Printing variables
The time honored method of print statements looks like this in CMake:
message(STATUS "MY_VARIABLE=${MY_VARIABLE}")
However, a built in module makes this even easier:
include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
If you want to print out a property, this is much, much nicer! Instead of getting the properties one
by one of of each target (or other item with properties, such as SOURCES
, DIRECTORIES
, TESTS
,
or CACHE_ENTRIES
- global properties seem to be missing for some reason), you can simply list them
and get them printed directly:
cmake_print_properties(
TARGETS my_target
PROPERTIES POSITION_INDEPENDENT_CODE
)
Tracing a run
Have you wanted to watch exactly what happens in your CMake file, and when? The
--trace-source="filename"
feature is fantastic. Every line run in the file that you give will be
echoed to the screen when it is run, letting you follow exactly what is happening. There are related
options as well, but they tend to bury you in output.
Watching a build
Let’s try this out. Let’s go to the code/04-debug folder and configure with trace mode on:
cmake -S . -B build --trace-source=CMakeLists.txt
Try adding
--trace-expand
too. What is the difference? How about replacing--trace-source=CMakeLists.txt
with--trace
? {.:challenge}
C++ debugging
To run a C++ debugger, you need to set several flags in your build. CMake does this for you with
“build types”. You can run CMake with CMAKE_BUILD_TYPE=Debug
for full debugging, or
RelWithDebInfo
for a release build with some extra debug info. You can also use Release
for an
optimized release build, or MinSizeRel
for a minimum size release (which I’ve never used).
Debug example
Let’s try it. Go to
code/04-debug
, and build in debug mode. Our program has a bug. Let’s try it out in a debugger.cmake -S . -B build-debug cmake --build build-debug gdb build-debug/simple_example
Now, since we think there’s a problem in
my_sin
, let’s set a breakpoint inmy_sin
. Note that I’m providing the gdb commands on the left, and lldb commands on the right.# GDB # LLDB break my_sin breakpoint set --name my_sin r r
Now, let’s watch what happens to the sign variable. Set a watchpoint:
# GDB # LLDB watch sign watchpoint set variable sign c c
Keep running continue (
c
). Do you see the problem?
Aside: Linking to math
You may find that the example provided does not work unless it’s linked with the math library “m”, which looks like
-lm
when linking with gcc (llvm does not seem to need to link to it). Let’s look for the “m” library:# Does -lm work? (notice this is find_library, not find_package) > find_library(MATH_LIBRARY m)
If it is found, this saves the location of the m library in a variable that we gave it the name of, in our case,
MATH_LIBRARY
. We can add the path (not a target) using the sametarget_link_libraries
command. It is very unfortunate that this command happens to accept both targets and raw paths and linker flags, but it’s a historical leftover.# If there is a -lm, let's use it if(MATH_LIBRARY) target_link_libraries(simple_lib PUBLIC ${MATH_LIBRARY}) endif()
Note that CMake defaults to an “empty” build type, which is neither optimized nor debug. You can
fix this manually, or always
specify a build type. The empty build type uses the environment variables CFLAGS
and CXXFLAGS
,
allowing CMake to integrate with Linux package managers. Otherwise, you can set the release and
debug flags separately.
Key Points
CMake is great for different builds