LIBFEBUG(3) Library Functions Manual LIBFEBUG(3)

febug_start(), febug_end(), febug_register_type(), febug_wrap(), febug_unwrap() — User-space debugfs ABI wrapper library

#include <libfebug.h>

library “libfebug”


#define FEBUG_DONT 0
#define FEBUG_SOCKET "/var/run/febug.sock"
#define FEBUG_SIGNUM SIGUSR2


getenv("FEBUG_DONT")
getenv("FEBUG_SOCKET")


int febug_global_controlled_socket = -1;


void febug_start();

void febug_start_path(const char * path);

void febug_debug_handler(int);

void febug_register_type(uint64_t type, void (*formatter)(int, size_t));

void febug_wrap(uint64_t type, const void * data, const char * name, ...);

void febug_wrap_signal(uint64_t type, const void * data, uint8_t signal, const char * name, ...);

void febug_wrap_signalv(uint64_t type, const void * data, uint8_t signal, const char * name, va_list ap);

void febug_unwrap(const void * data);

void febug_end();

allows a programmer to simplify writing 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)

To be debugged, a program needs to, first, call () (likely via (), which simply passes FEBUG_SOCKET thereto) to connect to febug(8), which, if successful, will set febug_global_controlled_socket appropriately.

The program needs to install () (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 calling () — those type numbers should be consistent across the program, lest the wrong handler is called. 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. It's a no-op if febug_global_controlled_socket is -1.

At any time, when the program wishes to expose a variable, it can call () (likely via () (likely via febug_wrap(), which passes FEBUG_SIGNUM thereto)), which will send a with the specified type and signal numbers, ID equal to the data pointer, and name formatted according to printf(3). It's a no-op if febug_global_controlled_socket is -1.

When the variable goes out of scope, the program should call () to send a with the same data pointer as it did febug_wrap(), to prevent reading random data that might no longer be mapped, or make sense. It's a no-op if febug_global_controlled_socket is -1.

When it wishes to stop being debugged, the program may call () which will shutter and reset febug_global_controlled_socket, if any, and deallocate the type->handler map. The program may omit this if it'd be the last thing it did before exiting, since the kernel will close all FDs and free all mappings anyway.

The following program sorts a string with qsort(3) but waits half a second between each comparison; the string can be inspected via a febug(8) mount:

// SPDX-License-Identifier: MIT


#define _POSIX_C_SOURCE 200809L

#include <libfebug.h>

#include <signal.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>


#define CSTRING_FEBUG_TP 420
static void cstring_febug_formatter(int fd, size_t data) {
	const char * str = (const char *)data;
	dprintf(fd, "%s\n", str);
}


static int char_comp(const void * lhs, const void * rhs) {
	const struct timespec half_second = {0, 500 * 1000 * 1000};
	nanosleep(&half_second, 0);

	return *(const char *)lhs - *(const char *)rhs;
}


int main() {
	febug_start();
	febug_register_type(CSTRING_FEBUG_TP, cstring_febug_formatter);


	struct sigaction handler;
	memset(&handler, 0, sizeof(handler));
	handler.sa_handler = febug_debug_handler;
	if(sigaction(FEBUG_SIGNUM, &handler, 0) == -1) {
		fprintf(stderr, "sigaction: %s\n", strerror(errno));
		return 1;
	}


	{
		__attribute__((__cleanup__(febug_unwrap))) char data[] = "JVLOkgsYmhCyEFxouKzDNajivGlpWqbdBwnfTAXQcreRHPIUSMtZQWERTYUIOPqwertyuiop1234567890";
		febug_wrap(CSTRING_FEBUG_TP, data, "cool_data");

		qsort(data, strlen(data), 1, char_comp);
	}

	sleep(2);

	febug_end();
}

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