Skip to content

Commit d04ee13

Browse files
committed
libbpf-rs: Add set_print/get_print, to send libbpf output to a callback
This commit exposes libbpf_set_print to direct libbpf output to a user-defined callback. Signed-off-by: Andrii Nakryiko <[email protected]>
1 parent ebc55ba commit d04ee13

File tree

6 files changed

+295
-20
lines changed

6 files changed

+295
-20
lines changed

Cargo.lock

+74
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libbpf-rs/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ nix = "0.22"
2626
num_enum = "0.5"
2727
strum_macros = "0.21"
2828
vsprintf = "2.0"
29+
lazy_static = "1.4"
2930

3031
[dev-dependencies]
3132
libc = "0.2"
3233
plain = "0.2.3"
3334
scopeguard = "1.1"
35+
serial_test = "0.5"
36+
log = "0.4"

libbpf-rs/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod link;
7272
mod map;
7373
mod object;
7474
mod perf_buffer;
75+
mod print;
7576
mod program;
7677
pub mod query;
7778
mod ringbuf;
@@ -88,6 +89,7 @@ pub use crate::link::Link;
8889
pub use crate::map::{Map, MapFlags, MapType, OpenMap};
8990
pub use crate::object::{Object, ObjectBuilder, OpenObject};
9091
pub use crate::perf_buffer::{PerfBuffer, PerfBufferBuilder};
92+
pub use crate::print::{get_print, set_print, PrintCallback, PrintLevel};
9193
pub use crate::program::{OpenProgram, Program, ProgramAttachType, ProgramType};
9294
pub use crate::ringbuf::{RingBuffer, RingBufferBuilder};
9395
pub use crate::util::num_possible_cpus;

libbpf-rs/src/object.rs

+5-20
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,15 @@ impl ObjectBuilder {
2929
}
3030

3131
/// Option to print debug output to stderr.
32+
///
33+
/// Note: This function uses [`set_print`] internally and will overwrite any callbacks
34+
/// currently in use.
3235
pub fn debug(&mut self, dbg: bool) -> &mut Self {
33-
extern "C" fn cb(
34-
_level: libbpf_sys::libbpf_print_level,
35-
fmtstr: *const c_char,
36-
va_list: *mut libbpf_sys::__va_list_tag,
37-
) -> i32 {
38-
match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
39-
Ok(s) => {
40-
print!("{}", s);
41-
0
42-
}
43-
Err(e) => {
44-
eprintln!("Failed to parse libbpf output: {}", e);
45-
1
46-
}
47-
}
48-
}
49-
5036
if dbg {
51-
unsafe { libbpf_sys::libbpf_set_print(Some(cb)) };
37+
set_print(Some((PrintLevel::Debug, |_, s| print!("{}", s))));
5238
} else {
53-
unsafe { libbpf_sys::libbpf_set_print(None) };
39+
set_print(None);
5440
}
55-
5641
self
5742
}
5843

libbpf-rs/src/print.rs

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use crate::*;
2+
use lazy_static::lazy_static;
3+
use std::io::{self, Write};
4+
use std::os::raw::c_char;
5+
use std::sync::Mutex;
6+
7+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
8+
#[repr(u32)]
9+
pub enum PrintLevel {
10+
Warn = libbpf_sys::LIBBPF_WARN,
11+
Info = libbpf_sys::LIBBPF_INFO,
12+
Debug = libbpf_sys::LIBBPF_DEBUG,
13+
}
14+
15+
impl From<libbpf_sys::libbpf_print_level> for PrintLevel {
16+
fn from(level: libbpf_sys::libbpf_print_level) -> Self {
17+
match level {
18+
libbpf_sys::LIBBPF_WARN => Self::Warn,
19+
libbpf_sys::LIBBPF_INFO => Self::Info,
20+
libbpf_sys::LIBBPF_DEBUG => Self::Debug,
21+
// shouldn't happen, but anything unknown becomes the highest level
22+
_ => Self::Warn,
23+
}
24+
}
25+
}
26+
27+
pub type PrintCallback = fn(PrintLevel, String);
28+
29+
/// Mimic the default print functionality of libbpf. This way if the user calls `get_print` when no
30+
/// previous callback had been set, with the intention of restoring it, everything will behave as
31+
/// expected.
32+
fn default_callback(_lvl: PrintLevel, msg: String) {
33+
let _ = io::stderr().write(msg.as_bytes());
34+
}
35+
36+
// While we can't say that set_print is thread-safe, because we shouldn't assume that of
37+
// libbpf_set_print, we should still make sure that things are sane on the rust side of things.
38+
// Therefore we are using a lock to keep the log level and the callback in sync.
39+
//
40+
// We don't do anything that can panic with the lock held, so we'll unconditionally unwrap() when
41+
// locking the mutex.
42+
//
43+
// Note that default print behavior ignores debug messages.
44+
lazy_static! {
45+
static ref PRINT_CB: Mutex<Option<(PrintLevel, PrintCallback)>> =
46+
Mutex::new(Some((PrintLevel::Info, default_callback)));
47+
}
48+
49+
extern "C" fn outer_print_cb(
50+
level: libbpf_sys::libbpf_print_level,
51+
fmtstr: *const c_char,
52+
va_list: *mut libbpf_sys::__va_list_tag,
53+
) -> i32 {
54+
let level = level.into();
55+
if let Some((min_level, func)) = { *PRINT_CB.lock().unwrap() } {
56+
if level <= min_level {
57+
let msg = match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
58+
Ok(s) => s,
59+
Err(e) => format!("Failed to parse libbpf output: {}", e),
60+
};
61+
func(level, msg);
62+
}
63+
}
64+
1 // return value is ignored by libbpf
65+
}
66+
67+
/// Set a callback to receive log messages from libbpf, instead of printing them to stderr.
68+
///
69+
/// # Arguments
70+
///
71+
/// * `callback` - Either a tuple `(min_level, function)` where `min_level` is the lowest priority
72+
/// log message to handle, or `None` to disable all printing.
73+
///
74+
/// This overrides (and is overridden by) [`ObjectBuilder::debug`]
75+
///
76+
/// # Examples
77+
///
78+
/// To pass all messages to the `log` crate:
79+
///
80+
/// ```
81+
/// use log;
82+
/// use libbpf_rs::{PrintLevel, set_print};
83+
///
84+
/// fn print_to_log(level: PrintLevel, msg: String) {
85+
/// match level {
86+
/// PrintLevel::Debug => log::debug!("{}", msg),
87+
/// PrintLevel::Info => log::info!("{}", msg),
88+
/// PrintLevel::Warn => log::warn!("{}", msg),
89+
/// }
90+
/// }
91+
///
92+
/// set_print(Some((PrintLevel::Debug, print_to_log)));
93+
/// ```
94+
///
95+
/// To disable printing completely:
96+
///
97+
/// ```
98+
/// use libbpf_rs::set_print;
99+
/// set_print(None);
100+
/// ```
101+
///
102+
/// To temporarliy suppress output:
103+
///
104+
/// ```
105+
/// use libbpf_rs::set_print;
106+
///
107+
/// let prev = set_print(None);
108+
/// // do things quietly
109+
/// set_print(prev);
110+
/// ```
111+
pub fn set_print(
112+
mut callback: Option<(PrintLevel, PrintCallback)>,
113+
) -> Option<(PrintLevel, PrintCallback)> {
114+
let real_cb: libbpf_sys::libbpf_print_fn_t;
115+
real_cb = callback.as_ref().and(Some(outer_print_cb));
116+
std::mem::swap(&mut callback, &mut *PRINT_CB.lock().unwrap());
117+
unsafe { libbpf_sys::libbpf_set_print(real_cb) };
118+
callback
119+
}
120+
121+
/// Return the current print callback and level.
122+
///
123+
/// # Examples
124+
///
125+
/// To temporarliy suppress output:
126+
///
127+
/// ```
128+
/// use libbpf_rs::{get_print, set_print};
129+
///
130+
/// let prev = get_print();
131+
/// set_print(None);
132+
/// // do things quietly
133+
/// set_print(prev);
134+
/// ```
135+
pub fn get_print() -> Option<(PrintLevel, PrintCallback)> {
136+
*PRINT_CB.lock().unwrap()
137+
}

libbpf-rs/tests/test_print.rs

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! This test is in its own file because the underlying libbpf_set_print function used by
2+
//! set_print() and ObjectBuilder::debug() sets global state. The default is to run multiple tests
3+
//! in different threads, so this test will always race with the others unless its isolated to a
4+
//! different process.
5+
//!
6+
//! For the same reason, all tests here must run serially.
7+
8+
use libbpf_rs::{get_print, set_print, ObjectBuilder, PrintCallback, PrintLevel};
9+
use serial_test::serial;
10+
use std::sync::atomic::{AtomicBool, Ordering};
11+
12+
#[test]
13+
#[serial]
14+
fn test_set_print() {
15+
static CORRECT_LEVEL: AtomicBool = AtomicBool::new(false);
16+
static CORRECT_MESSAGE: AtomicBool = AtomicBool::new(false);
17+
18+
fn callback(level: PrintLevel, msg: String) {
19+
if level == PrintLevel::Warn {
20+
CORRECT_LEVEL.store(true, Ordering::Relaxed);
21+
}
22+
23+
if msg.starts_with("libbpf: ") {
24+
CORRECT_MESSAGE.store(true, Ordering::Relaxed);
25+
}
26+
}
27+
28+
set_print(Some((PrintLevel::Debug, callback)));
29+
// expect_err requires that OpenObject implement Debug, which it does not.
30+
let obj = ObjectBuilder::default().open_file("/dev/null");
31+
assert!(obj.is_err(), "Successfully loaded /dev/null?");
32+
33+
let correct_level = CORRECT_LEVEL.load(Ordering::Relaxed);
34+
let correct_message = CORRECT_MESSAGE.load(Ordering::Relaxed);
35+
assert!(correct_level, "Did not capture a warning");
36+
assert!(correct_message, "Did not capture the correct message");
37+
}
38+
39+
#[test]
40+
#[serial]
41+
fn test_set_restore_print() {
42+
fn callback1(_: PrintLevel, _: String) {
43+
println!("one");
44+
}
45+
fn callback2(_: PrintLevel, _: String) {
46+
println!("two");
47+
}
48+
49+
set_print(Some((PrintLevel::Warn, callback1)));
50+
let prev = get_print();
51+
assert_eq!(prev, Some((PrintLevel::Warn, callback1 as PrintCallback)));
52+
53+
set_print(Some((PrintLevel::Debug, callback2)));
54+
let prev = get_print();
55+
assert_eq!(prev, Some((PrintLevel::Debug, callback2 as PrintCallback)));
56+
}
57+
58+
#[test]
59+
#[serial]
60+
fn test_set_and_save_print() {
61+
fn callback1(_: PrintLevel, _: String) {
62+
println!("one");
63+
}
64+
fn callback2(_: PrintLevel, _: String) {
65+
println!("two");
66+
}
67+
68+
set_print(Some((PrintLevel::Warn, callback1)));
69+
let prev = set_print(Some((PrintLevel::Debug, callback2)));
70+
assert_eq!(prev, Some((PrintLevel::Warn, callback1 as PrintCallback)));
71+
72+
let prev = set_print(None);
73+
assert_eq!(prev, Some((PrintLevel::Debug, callback2 as PrintCallback)));
74+
}

0 commit comments

Comments
 (0)