Skip to content

query: Extract tracepoint & k(ret)probe from perf_event links #1213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion examples/bpf_query/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fn link() {
query::LinkTypeInfo::KprobeMulti(_) => "kprobemulti",
query::LinkTypeInfo::UprobeMulti(_) => "uprobemulti",
query::LinkTypeInfo::SockMap(_) => "sockmap",
query::LinkTypeInfo::PerfEvent => "perf_event",
query::LinkTypeInfo::PerfEvent(_) => "perf_event",
};

println!(
Expand All @@ -107,6 +107,52 @@ fn link() {
" attach_type={:?} target_obj_id={} target_btf_id={}",
tracing.attach_type, tracing.target_obj_id, tracing.target_btf_id
);
} else if let query::LinkTypeInfo::PerfEvent(ref perf_event) = link.info {
match &perf_event.event_type {
query::PerfEventType::Tracepoint { name, cookie } => {
let Some(name) = name else {
continue;
};

print!(" tracepoint {}", name.to_string_lossy());
if *cookie != 0 {
print!(" cookie={cookie}");
}
println!();
}
query::PerfEventType::Kprobe {
func_name,
is_retprobe,
addr,
offset,
missed,
cookie,
} => {
let probe_type = if *is_retprobe { "kretprobe" } else { "kprobe" };
let func_name = func_name.as_ref().map(|s| s.to_string_lossy());

print!(" {probe_type}");
if *addr != 0 {
print!(" addr={addr:x}");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be

Suggested change
print!(" addr={addr:x}");
print!(" addr={addr:#x}");

?

}
if let Some(func_name) = func_name {
print!(" func={func_name}");
}
if *offset != 0 {
print!(" offset={offset:#x}");
}
if *missed != 0 {
print!(" missed={missed}");
}
if *cookie != 0 {
print!(" cookie={cookie}");
}
println!();
}
query::PerfEventType::Unknown(ty) => {
println!(" unknown perf event type: {ty}");
}
}
}
}
}
Expand Down
154 changes: 152 additions & 2 deletions libbpf-rs/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//! ```

use std::ffi::c_void;
use std::ffi::CStr;
use std::ffi::CString;
use std::io;
use std::mem::size_of_val;
Expand Down Expand Up @@ -726,6 +727,43 @@ pub struct UprobeMultiLinkInfo {
pub pid: u32,
}

/// Information about a perf event link.
#[derive(Debug, Clone)]
pub struct PerfEventLinkInfo {
/// The specific type of perf event with decoded information.
pub event_type: PerfEventType,
}

/// Specific types of perf events with decoded information.
#[derive(Debug, Clone)]
pub enum PerfEventType {
/// A tracepoint event.
Tracepoint {
/// The tracepoint name.
name: Option<CString>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the other link type that has strings to return is RawTracepointLinkInfo and it returns String but I think it's saner to return a CString/OsString` and let the user decode if needed

/// Attach cookie value for this link.
cookie: u64,
},
/// A kprobe event (includes both kprobe and kretprobe).
Kprobe {
/// The function being probed.
func_name: Option<CString>,
/// Whether this is a return probe (kretprobe).
is_retprobe: bool,
/// Address of the probe.
addr: u64,
/// Offset from the function.
offset: u32,
/// Number of missed events.
missed: u64,
/// Cookie value for the kprobe.
cookie: u64,
},
/// TODO: Add support for `BPF_PERF_EVENT_EVENT`, `BPF_PERF_EVENT_UPROBE`
/// `BPF_PERF_EVENT_URETPROBE`
Comment on lines +762 to +763
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be using doc comments for TODOs; please add proper documentation instead.

Suggested change
/// TODO: Add support for `BPF_PERF_EVENT_EVENT`, `BPF_PERF_EVENT_UPROBE`
/// `BPF_PERF_EVENT_URETPROBE`
// TODO: Add support for `BPF_PERF_EVENT_EVENT`, `BPF_PERF_EVENT_UPROBE`
// `BPF_PERF_EVENT_URETPROBE`

Unknown(u32),
}

/// Information about BPF link types. Maps to the anonymous union in `struct bpf_link_info` in
/// kernel uapi.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -767,7 +805,10 @@ pub enum LinkTypeInfo {
/// Link type for sockmap programs.
SockMap(SockMapLinkInfo),
/// Link type for perf-event programs.
PerfEvent,
///
/// Contains information about the perf event configuration including type and config
/// which can be used to identify tracepoints, kprobes, uprobes, etc.
PerfEvent(PerfEventLinkInfo),
/// Unknown link type.
Unknown,
}
Expand Down Expand Up @@ -874,7 +915,116 @@ impl LinkInfo {
s.__bindgen_anon_1.sockmap.attach_type
}),
}),
libbpf_sys::BPF_LINK_TYPE_PERF_EVENT => LinkTypeInfo::PerfEvent,
libbpf_sys::BPF_LINK_TYPE_PERF_EVENT => {
// Get the BPF perf event type (BPF_PERF_EVENT_*) from the link info.
let bpf_perf_event_type = unsafe { s.__bindgen_anon_1.perf_event.type_ };

// Handle two-phase call for perf event string data if needed (this mimics the
// behavior of bpftool).
Comment on lines +922 to +923
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Comment (and naming of need_second_call) seems a bit bogus: there is no first call in scope. It could conceptually have been called from anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the first call is from the gen_info_impl macro, but I'll rename to something clearer :) )

let mut buf = [0u8; 256];
let need_second_call = match bpf_perf_event_type {
libbpf_sys::BPF_PERF_EVENT_TRACEPOINT => {
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.tracepoint
.tp_name = buf.as_mut_ptr() as u64;
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.tracepoint
.name_len = buf.len() as u32;
true
}
libbpf_sys::BPF_PERF_EVENT_KPROBE | libbpf_sys::BPF_PERF_EVENT_KRETPROBE => {
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.kprobe
.func_name = buf.as_mut_ptr() as u64;
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.kprobe
.name_len = buf.len() as u32;
true
}
_ => false,
};

if need_second_call {
let item_ptr: *mut libbpf_sys::bpf_link_info = &mut s;
let mut len = size_of_val(&s) as u32;
let ret = unsafe {
libbpf_sys::bpf_obj_get_info_by_fd(
fd.as_raw_fd(),
item_ptr as *mut c_void,
&mut len,
)
};
if ret != 0 {
return None;
}
}

let event_type = match bpf_perf_event_type {
libbpf_sys::BPF_PERF_EVENT_TRACEPOINT => {
let tp_name = unsafe {
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.tracepoint
.tp_name
};
let cookie = unsafe {
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.tracepoint
.cookie
};
let name = (tp_name != 0).then(|| unsafe {
CStr::from_ptr(tp_name as *const c_char).to_owned()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the typical contract that the kernel would set the actual name length? Or is this not the case here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually has the length - but I followed bpftool here, which just prints the string. If I have to guess - the returned length is the full one and the provided name is the null-terminated string (truncated), but i'll make sure and comment.

});

PerfEventType::Tracepoint { name, cookie }
}
libbpf_sys::BPF_PERF_EVENT_KPROBE | libbpf_sys::BPF_PERF_EVENT_KRETPROBE => {
let func_name = unsafe {
s.__bindgen_anon_1
.perf_event
.__bindgen_anon_1
.kprobe
.func_name
};
let addr =
unsafe { s.__bindgen_anon_1.perf_event.__bindgen_anon_1.kprobe.addr };
let offset =
unsafe { s.__bindgen_anon_1.perf_event.__bindgen_anon_1.kprobe.offset };
let missed =
unsafe { s.__bindgen_anon_1.perf_event.__bindgen_anon_1.kprobe.missed };
let cookie =
unsafe { s.__bindgen_anon_1.perf_event.__bindgen_anon_1.kprobe.cookie };
let func_name = (func_name != 0).then(|| unsafe {
CStr::from_ptr(func_name as *const c_char).to_owned()
});

let is_retprobe =
bpf_perf_event_type == libbpf_sys::BPF_PERF_EVENT_KRETPROBE;
PerfEventType::Kprobe {
func_name,
is_retprobe,
addr,
offset,
missed,
cookie,
}
}
ty => PerfEventType::Unknown(ty),
};

LinkTypeInfo::PerfEvent(PerfEventLinkInfo { event_type })
}
_ => LinkTypeInfo::Unknown,
};

Expand Down