NAME
febug_start()
,
febug_end()
,
febug_register_type()
,
febug_wrap()
, febug_unwrap()
— User-space debugfs ABI wrapper
library
SYNOPSIS
#include
<libfebug.h>
cc
-l
febug
…
#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
();
DESCRIPTION
Simplifies writing 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_SOCKET
to connect to febug(8).
To be debugged, a program needs to, first,
call
febug_start_path
()
(likely via
febug_start
(),
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
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 calling
febug_register_type
()
— 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 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. 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
febug_wrap_signalv
()
(likely via
febug_wrap_signal
()
(likely via febug_wrap
(), which passes
FEBUG_SIGNUM
thereto)),
which will send a
febug_message
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
febug_unwrap
()
to send a stop_febug_message
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
febug_end
()
which will shut 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 file
descriptors and free all mappings anyway.
EXAMPLES
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 <errno.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.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(void) { 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[] = "JVLOkgsYmhCyEFxouKzDNajivGlpWqbdBwnfTAXQcreRHPIUSMtZQWERTYUIOPqwertyuiop" "1234567890"; febug_wrap(CSTRING_FEBUG_TP, data, "cool_data"); qsort(data, strlen(data), 1, char_comp); } sleep(2); febug_end(); }
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