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);

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 , 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 (). 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:

If set, don't try to connect to febug(8), so all library functions become no-ops.
If set, use its value instead of FEBUG_SOCKET to 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 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 () (or a wrapper around it) as the signal handler for (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, () 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 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 . 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 tracker

febug mailing list: <~nabijaczleweli/febug@lists.sr.ht> archived at https://lists.sr.ht/~nabijaczleweli/febug

January 20, 2021 OpenBSD 6.8