LIBFEBUG++(3) | Library Functions Manual | LIBFEBUG++(3) |
febug::controlled_socket,
febug::wrapper, febug::formatters,
febug::debug_handler
() —
User-space debugfs ABI wrapper library for C++
#include
<libfebug.hpp>
library “libfebug++”
#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);
libfebug++ allows a programmer to simplify 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 a program to customise its 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 to SIGUSR2
.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:
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 isn't the null pointer, attempts connection 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
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.
This finishes set-up.
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 write 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.
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 <vector> #include <algorithm> #include <cstring> #include <errno.h> #include <unistd.h> 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); }
febug-abi(5) — the ABI wrapped by this library.
libfebug(3) — an equivalent C library.
libfebug(3) — an equivalent Rust library.
Written by наб <nabijaczleweli@nabijaczleweli.xyz>
To all who support further development, in particular:
febug mailing list: <~nabijaczleweli/febug@lists.sr.ht> archived at https://lists.sr.ht/~nabijaczleweli/febug
January 20, 2021 | OpenBSD 6.8 |