This is a text-only version of the following page on https://raymii.org:
---
Title : Logging all C++ destructors, poor mans run-time tracing
Author : Remy van Elst
Date : 21-09-2024 23:59
URL : https://raymii.org/s/software/Logging_all_Cpp_destructors_poor_mans_run-time_tracing.html
Format : Markdown/HTML
---
I recently faced a challenging issue with an application that wasn't shutting down correctly, either segfaulting or terminating without an active exception. Running the program via `valgrind` to check for memory leaks wasn't possible because the program couldn’t perform its cleanup if it didn't shut down correctly. This article covers adding runtime instrumentation provided by `gcc` to log destructors. This helped me figure out what was still left over from the closed-source framework in use preventing correct shutdowns or causing segfaults. It includes example code, setup instructions and insights into handling shutdown issues in large, multi-threaded codebases.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below. It means the world to me if you show your appreciation and you'll help pay the server costs:
GitHub Sponsorship
PCBWay referral link (You get $5, I get $20 after you've placed an order)
Digital Ocea referral link ($200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!)
This is an embedded application that normally never exits, so shutdown
behavior hadn't been a focus. Given the large codebase with many threads,
pinpointing the shutdown issues was difficult.
After extensive debugging, I confirmed that all my code was destructing in the
correct order, but segfaults persisted. The codebase uses an external,
closed-source framework, complicating the debugging process.
To gain more insight, I decided to log all destructors and trigger a `SIGTRAP`
to debug with `gdb` after my code had stopped. This approach would help me
understand the order of destruction and identify potential issues.
### Run time instrumentation to log destructor

> Destructor logging without stdlib filter

> Destructor logging with stdlib filter
Using [run time instrumentation](https://web.archive.org/web/20240918213544/https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)
provided by `gcc` (and `clang`) I added a simple logging function to log
whenever a user-defined class's destructor is called. The code filters out
`std::` and other prefixes, but you can remove this filter if needed.
To set this up, link your program to `dl` and add the `-finstrument-functions` CXX
flag. Also, pass the `-rdynamic` option to resolve function names and include
`cxxabi.h` and `dlfcn.h`.
Implement the functions `void __cyg_profile_func_enter(void* this_fn, void*)`
and `void __cyg_profile_func_exit(void* this_fn)`, prefixing them with
`extern "C"` and using the attribute `__attribute__(
(no_instrument_function))` to avoid recursive crashes.
I used C-style code (`const char*`, `free` instead of `std::string`) to
prevent recursive crashes in the logging methods.
Note that this logging adds performance impact since the filtering and logging
code is added for each function. Making the code a shared library
(`-rdynamic`) and linking to `dl` also has a performance impact so I
recommend you only add this whenever you really need to.
This also only was tested under Linux with GCC. MSVC on Windows [also has
hook](https://web.archive.org/web/20240921181607/https://learn.microsoft.com/en-us/cpp/build/reference/gh-enable-penter-hook-function?view=msvc-170&redirectedfrom=MSDN)
mechanisms (`Gh` and `/GH` for ` _penter()` and `_pexit()` ) but I do not
need to run this on MSVC. Not tested on MinGW either.
### Example code
The following example application has one class in a separate file and has the logging set up.
#### main.cpp
#include
#include
#include
#include
#include
#include
#include "MyClass1.h"
extern "C" __attribute__((no_instrument_function)) void __cyg_profile_func_enter(void* this_fn, void* call_site) {}
extern "C" __attribute__((no_instrument_function)) void __cyg_profile_func_exit(void* this_fn, void* call_site)
{
Dl_info info;
if (dladdr(this_fn, &info) && info.dli_sname != nullptr)
{
int status;
char* demangled_name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
if (status == 0 && demangled_name != nullptr)
{
bool is_excluded_func = false;
const char* exclude_prefixes[] = {"std::", "__gnu_cxx::", "abi::__cxa", "llvm::", "clang::", "boost::"};
for (const char* prefix : exclude_prefixes)
{
if (strncmp(demangled_name, prefix, strlen(prefix)) == 0)
{
is_excluded_func = true;
break;
}
}
if (!is_excluded_func && strstr(demangled_name, "::~") != nullptr)
{
fprintf(stderr, "Destructor called for: %s\n", demangled_name);
}
}
free(demangled_name);
}
}
int main() {
auto obj1 = std::make_unique();
obj1->print();
return 0;
}
#### MyClass1.h
#pragma once
class MyClass1 {
public:
~MyClass1() {};
void print() const;
};
#### MyClass1.cpp
#include "MyClass1.h"
#include
#include
#include
void MyClass1::print()const
{
std::srand(std::time(nullptr));
int a = (std::rand() % 999999 + 1);
fprintf(stderr, "Hellorld %i\n", a);
}
#### CmakeLists.txt
cmake_minimum_required(VERSION 3.30)
project(destructorLogging)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -finstrument-functions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -finstrument-functions-exclude-function-list=printf")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
add_executable(destructorLogging main.cpp
MyClass1.cpp
MyClass1.h)
target_link_libraries(destructorLogging dl)
### Output
You have seen the screenshots at the top, with and without the filtering. Here
is some example output:
Hellorld 881802
Destructor called for: MyClass1::~MyClass1()
Destructor called for: std::unique_ptr >::~unique_ptr()
For fun I added [a C++ json library](https://github.com/nlohmann/json) and
with just the following `json` object the amount of logging exploded.
#include
using json = nlohmann::json;
json j;
j["string"] = "Ex Ample";
j["int"] = 30;
j["bool"] = false;
j["list"] = {"C++", "Memory usage"};
fprintf(stderr, "Hellorld %s %i\n", j.dump().c_str(), a);
The output:
Destructor called for: std::__cxx11::basic_string, std::allocator >::_M_construct(char const*, char const*, std::forward_iterator_tag)::_Guard::~_Guard()
Destructor called for: std::unique_ptr, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::create, std::allocator >, char const (&) [9]>(char const (&) [9])::{lambda(std::__cxx11::basic_string, std::allocator >*)#1}>::~unique_ptr()
Destructor called for: std::__new_allocator, std::allocator > >::~__new_allocator()
Destructor called for: std::allocator, std::allocator > >::~allocator()
Destructor called for: std::__cxx11::basic_string, std::allocator >::_M_construct(char const*, char const*, std::forward_iterator_tag)::_Guard::~_Guard()
Destructor called for: std::unique_ptr, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::create, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >>()::{lambda(std::map, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >*)#1}>::~unique_ptr()
Destructor called for: std::__new_allocator, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > > >::~__new_allocator()
Destructor called for: std::allocator, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > > >::~allocator()
Destructor called for: std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >, std::_Select1st, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > >, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >::_Auto_node::~_Auto_node()
Destructor called for: nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::data::~data()
Destructor called for: nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::~basic_json()
Destructor called for: std::__cxx11::basic_string, std::allocator >::_M_construct(char const*, char const*, std::forward_iterator_tag)::_Guard::~_Guard()
Destructor called for: std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >, std::_Select1st, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > >, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >::_Auto_node::~_Auto_node()
Destructor called for: nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::data::~data()
Destructor called for: nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::~basic_json()
Destructor called for: std::__cxx11::basic_string, std::allocator >::_M_construct(char const*, char const*, std::forward_iterator_tag)::_Guard::~_Guard()
Destructor called for: std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >, std::_Select1st, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > >, std::less, std::allocator, std::allocator > const, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >::_Auto_node::~_Auto_node()
Destructor called for: nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::data::~data()
Destructor called for: nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::~basic_json()
Destructor called for: std::__cxx11::basic_string, std::allocator >::_M_construct(char const*, char const*, std::forward_iterator_tag)::_Guard::~_Guard()
Destructor called for: std::unique_ptr, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::create, std::allocator >, char const (&) [4]>(char const (&) [4])::{lambda(std::__cxx11::basic_string, std::allocator >*)#1}>::~unique_ptr()
Destructor called for: std::__new_allocator, std::allocator > >::~__new_allocator()
Destructor called for: std::allocator, std::allocator > >::~allocator()
Destructor called for: std::__cxx11::basic_string, std::allocator >::_M_construct(char const*, char const*, std::forward_iterator_tag)::_Guard::~_Guard()
Destructor called for: std::unique_ptr, std::allocator >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::create, std::allocator >, char const (&) [13]>(char const (&) [13])::{lambda(std::__cxx11::basic_string, std::allocator >*)#1}>::~unique_ptr()
Destructor called for: std::__new_allocator, std::allocator > >::~__new_allocator()
Destructor called for: std::allocator, std::allocator > >::~allocator()
Destructor called for: std::__new_allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >::~__new_allocator()
Destructor called for: std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >::~allocator()
Destructor called for: std::__new_allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >::~__new_allocator()
Destructor called for: std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> >::~allocator()
Destructor called for: std::unique_ptr, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > >, nlohmann::json_abi_v3_11_3::basic_json, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>::create, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > >, nlohmann::json_abi_v3_11_3::detail::json_ref, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > const*, nlohmann::json_abi_v3_11_3::detail::json_ref, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > const*>(nlohmann::json_abi_v3_11_3::detail::json_ref, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > const*&&, nlohmann::json_abi_v3_11_3::detail::json_ref, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > const*&&)::{lambda(std::vector, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > >*)#1}>::~unique_ptr()
Destructor called for: std::__new_allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void> > > >::~__new_allocator()
Destructor called for: std::allocator, std::allocator >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::vector >, void>, std::allocator