NAME
febug::controlled_socket,
febug::wrapper,
febug::formatters,
febug::debug_handler() —
User-space debugfs ABI wrapper library
for C++
SYNOPSIS
#include
<libfebug.hpp>
c++
-lfebug++
…
#define FEBUG_DONT 0
#define FEBUG_SOCKET "/var/run/febug.sock"
#define FEBUG_SIGNUM SIGUSR2
getenv("FEBUG_DONT");
getenv("FEBUG_SOCKET");
struct febug::controlled_socket;
const febug::controlled_socket
febug::global_controlled_socket;
struct febug::wrapper;
febug::wrapper::wrapper(const
T & data, const char
* name, ...);
febug::wrapper::wrapper(const
T & data, uint8_t
signal, const char *
name, ...);
febug::wrapper::wrapper(const
T & data, uint8_t
signal, const char *
name, va_list
ap);
std::map<size_t, void (*)(int, size_t)> febug::formatters;
void
febug::debug_handler(int);
DESCRIPTION
Simplifies writing C++ programs debuggable with febug(8) by presenting a high-level interface to febug-abi(5).
There are three compile-time macros that allow customising
libfebug++ behaviour:
FEBUG_DONT- If non-zero, all symbols become static, functions turn into no-ops, and therefore no symbols from libfebug++.a|.so are imported at link-time; this is intended as a way to easily disable febug(8) integration completely on release builds.
FEBUG_SIGNUM- The signal to request from
febug(8) when using
febug_wrap(). Defaults toSIGUSR2. FEBUG_SOCKET- The path to connect to febug(8) on. Defaults to /var/run/febug.sock.
There are two environment variables that allow a user to customise its behaviour:
FEBUG_DONT- If set, don't try to connect to febug(8), so all library functions become no-ops.
FEBUG_SOCKET- If set, use its value instead of the built-in
FEBUG_SOCKETto connect to febug(8).
The febug::controlled_socket structure is defined as follows:
struct febug::controlled_socket {
int fd = -1;
inline operator int() const noexcept { return this->fd; }
controlled_socket(const char * path = FEBUG_SOCKET) noexcept;
~controlled_socket() noexcept;
};
There is a global instance at
febug::global_controlled_socket which, if
path (FEBUG_SOCKET) isn't the
null pointer, attempts to connect to
febug(8)
and will set fd, if successful. Similarly, destroying
it will hang up and reset fd.
The program needs to install
febug::debug_handler()
(or a wrapper around it) as the signal handler for
FEBUG_SIGNUM (and any other signals, if different
ones are explicitly requested); if notifications are disabled (by requesting
SIGKILL), some event loop that answers on
febug::global_controlled_socket must be in place. It's
a no-op if febug::global_controlled_socket is
-1.
The program should register handlers
for types of variables it wishes to handle by adding entries to
febug::formatters — the key is
typeid(std::decay_t<T>).hash_code(), so if
this yields different results for two types that should have the same
handler, multiple entries need to be registered. If no handler was
registered for a type,
febug::debug_handler()
will instead return a generic "not found" message. The handler
takes the write end of the pipe as the first argument, and the variable ID
as the second; it shouldn't close the pipe, as that is done by
febug::debug_handler() regardless, and the program
would then run the risk of closing another file with the same descriptor
simultaneously opened by another thread.
The febug::wrapper structure is defined as follows:
template <class T>
struct febug::wrapper {
const T * data;
wrapper(const T & data, const char * name, ...) noexcept;
wrapper(const T & data, uint8_t signal, const char * name, ...) noexcept;
wrapper(const T & data, uint8_t signal, const char * name, va_list ap) noexcept;
~wrapper() noexcept;
};
If the program wishes to debug a variable,
it should construct a febug::wrapper referencing it;
the constructor will send a febug_message with the
type corresponding to
typeid(std::decay_t<T>).hash_code(), ID
corresponding to the pointer to data, signal being
either specified or defaulting to FEBUG_SIGNUM, and
name formatted according to
printf(3). The destructor will send a
stop_febug_message.
Both become no-ops if febug::global_controlled_socket
is -1.
EXAMPLES
The following program sorts a
std::vector<int> with
std::sort() but waits a second between each
comparison; the vector and the amount of comparisons can be inspected via a
febug(8)
mount:
// SPDX-License-Identifier: MIT
#include <libfebug.hpp>
#include <algorithm>
#include <cstring>
#include <errno.h>
#include <unistd.h>
#include <vector>
int main() {
if(febug::global_controlled_socket != -1) {
febug::formatters.emplace(
typeid(std::vector<int>).hash_code(), [](int retpipe, std::size_t vid) {
const std::vector<int> & data = *(const std::vector<int> *)vid;
for(auto num : data)
dprintf(retpipe, "%d ", num);
write(retpipe, "\n", 1);
});
febug::formatters.emplace(
typeid(std::size_t).hash_code(), [](int retpipe, std::size_t vid) {
const std::size_t & data = *(const std::size_t *)vid;
dprintf(retpipe, "%zu\n", data);
});
}
struct sigaction handler {};
handler.sa_handler = febug::debug_handler;
if(sigaction(FEBUG_SIGNUM, &handler, nullptr) == -1)
std::fprintf(stderr, "sigaction: %s\n", std::strerror(errno));
{
std::vector<int> data{-1, -2, -3, 0, 1, 2, 3, -1, -2, -3, 0, 1, 2, 3,
-1, -2, -3, 0, 1, 2, 3, -1, -2, -3, 0, 1, 2, 3,
-1, -2, -3, 0, 1, 2, 3, -1, -2, -3, 0, 1, 2, 3};
std::size_t comparisons_done{};
febug::wrapper data_w{data, "cool_data"};
febug::wrapper comparisons_done_w{comparisons_done, "comparisons"};
std::sort(data.begin(), data.end(), [&](auto lhs, auto rhs) {
sleep(1);
++comparisons_done;
return lhs < rhs;
});
}
sleep(2);
}
SEE ALSO
febug-abi(5) — the ABI wrapped by this library.
libfebug(3),
libfebug.rs(3), and
libfebug.py(3), — equivalent C, Rust, and Python
libraries.
SPECIAL THANKS
To all who support further development, in particular:
- ThePhD
- Embark Studios
- Lars Strojny
- EvModder
REPORTING BUGS
febug mailing list: <~nabijaczleweli/febug@lists.sr.ht>, archived at https://lists.sr.ht/~nabijaczleweli/febug