The letter S in a light blue, stylized speech bubble followed by SpeakBits
SpeakBitsThe letter S in a light blue, stylized speech bubble followed by SpeakBits
Trending
Top
New
Controversial
Search
Groups

Enjoying SpeakBits?

Support the development of it by donating to Patreon or Ko-Fi.
About
Rules
Terms
Privacy
EULA
Cookies
Blog
Have feedback? We'd love to hear it!

How to build highly-debuggable C++ binaries

dhashe.com
submitted
a year ago
byjustadevtoprogramming

Summary

C++ has a notoriously complicated compilation model. It can be an ordeal to even get a C++ project to compile. It is even harder to configure one to produce debuggable binaries. I want to help regular C++ programmers improve their debugging experiences. Follow this link to skip the intro text and jump directly to the advice.

Interactive debugging is most useful in large unfamiliar legacy projects written in familiar languages. Printf-style debugging is oddly attractive in C++ simply because you can generally expect it to work. It is usually possible to generate highly-debuggable C++ binaries that work well with an interactive debugger without sacrificing too much performance.

In this week's Daily Discussion, we look at some of the most important issues of the day. This week, we discuss the issue of the best way to make the most of the time we have left on this planet. We also look at ways to improve the quality of our communication.

The sanitizers are not all compatible with each other, so you will need to test multiple builds with different subsets enabled. There is currently no production-quality C++ toolchain that promises to alert on all undefined behavior. Correctness issues from undefined behavior are currently an unavoidable risk.

If using libc++: Add this define to your CXXFLAGS if you are able to recompile your dependencies from source. If using libstdc++: -DLIBCPP_HARDENING_MODE=_LIBPP HARDENing_MODE_DEBUG.

This will make printing backtraces faster and more reliable. This made sense for release builds on 32-bit x86. In practice, this never worked very well. For x86_64, there are many more registers and it is worth it to always include the frame-pointer.

Ref Link with whole-archive so that the entire static archive is available. Historically, reverse debuggers have not supported all x86_64 instructions (e.g. AVX). x86-64 is the baseline 64-bit x86 architecture without extensions.

C++ is often used for code that has to be fast. Unoptimized C++ code can be very slow, especially in large projects. It is possible to link together optimized and unoptimized TUs. This will always make our backtraces more informative.

Confusingly, gdb will suggest that the function "may have been inlined", when the actual problem is that the template member function was never generated in the first place. G++ recommends using -Og instead of -O0 for the best debugging experience.

Some classes cannot be explicitly instantiated for all valid arguments. Be aware that it may be a bad idea to leave explicit ifdefs in your code. Run unifdef to evaluate preprocessor ifdef. Run ifdef to run in-place on a subset of your codebase.

If you want to use a new macro, you need to add a new line of code to the top of the macro. If you want the old line to stay the same, you must add the new line to the bottom of the current line. If the newline is not the same as the old one, you have to add another line.

The way that gdb sets a breakpoint is to temporarily replace an instruction with the single byte int $3 instruction. This can be very slow because it only requires overwriting a single byte of the binary, which it knows how to do safely. Using volatile means that the program will always read the variable from memory before using it.

Gdb can be configured to always step-over arbitrary files and directories. It can also be used to blacklist code that we don't usually want to step-into. For example: the stdlib, third-party dependencies, utility classes, custom string or enumeration classes.

Use the gdb Python API to write pretty-printers for your project's frequently-used classes. Run the following command inside gdb while attached to your running process to see if thepretty-printer is installed and available. This is analogous to writing a custom str function on a Python object.

Thank you to Eliot Robson for providing feedback on drafts of this post. All mistakes are my own. C++20 concepts are the partial solution to template hell for compilers. They complement C++15 concepts, which are the full solution to the template hell problem.

13

5 Comments

2
iareunique
a year ago*
This whole piece just gave me nightmares of my first job trying to debug things in C++. Exact reason why I pivoted as best I could away from it. Really love the tool chains in newer languages like Rust that bake this stuff in.
1
justadevOP
a year ago
Did you do it for long and professionally? I did C++ for my degree and moved to Java right away
2
iareunique
a year ago
I did it for a few years and started getting tired of fighting the environment so I moved on. I've gone through Java, .Net, and Rust throughout the years. It's been much funner!
2
justadevOP
a year ago
Any preference between those three? I've had my eye on rust for a while but not sure if worth the effort.
2
iareunique
a year ago
Java has my heart but Rust is a close second!