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:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $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
![img](/s/inc/img/75a83ad3c355467a90c7ca2a906a0bcc.png)
> Destructor logging without stdlib filter
![img](/s/inc/img/ed9a795cd42e43f482fcd87c3467767e.png)
> 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