|
|
@ -1,10 +1,12 @@
|
|
|
|
use crate::util::KernelVersion;
|
|
|
|
use crate::util::KernelVersion;
|
|
|
|
use libc::pid_t;
|
|
|
|
use libc::pid_t;
|
|
|
|
use std::{
|
|
|
|
use std::{
|
|
|
|
|
|
|
|
ffi::{OsStr, OsString},
|
|
|
|
|
|
|
|
fmt::Write as _,
|
|
|
|
fs::{self, OpenOptions},
|
|
|
|
fs::{self, OpenOptions},
|
|
|
|
io::{self, Write},
|
|
|
|
io::{self, Write},
|
|
|
|
os::fd::{AsFd as _, AsRawFd as _, OwnedFd},
|
|
|
|
os::fd::{AsFd as _, AsRawFd as _, OwnedFd},
|
|
|
|
path::Path,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
process,
|
|
|
|
process,
|
|
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -42,16 +44,64 @@ impl ProbeKind {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) fn lines(bytes: &[u8]) -> impl Iterator<Item = &OsStr> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bytes.as_ref().split(|b| b == &b'\n').map(|mut line| {
|
|
|
|
|
|
|
|
while let [stripped @ .., c] = line {
|
|
|
|
|
|
|
|
if c.is_ascii_whitespace() {
|
|
|
|
|
|
|
|
line = stripped;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
OsStr::from_bytes(line)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) trait OsStringExt {
|
|
|
|
|
|
|
|
fn starts_with(&self, needle: &OsStr) -> bool;
|
|
|
|
|
|
|
|
fn ends_with(&self, needle: &OsStr) -> bool;
|
|
|
|
|
|
|
|
fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr>;
|
|
|
|
|
|
|
|
fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr>;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl OsStringExt for OsStr {
|
|
|
|
|
|
|
|
fn starts_with(&self, needle: &OsStr) -> bool {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes().starts_with(needle.as_bytes())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn ends_with(&self, needle: &OsStr) -> bool {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes().ends_with(needle.as_bytes())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes()
|
|
|
|
|
|
|
|
.strip_prefix(prefix.as_bytes())
|
|
|
|
|
|
|
|
.map(Self::from_bytes)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes()
|
|
|
|
|
|
|
|
.strip_suffix(suffix.as_bytes())
|
|
|
|
|
|
|
|
.map(Self::from_bytes)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(crate) struct ProbeEvent {
|
|
|
|
pub(crate) struct ProbeEvent {
|
|
|
|
kind: ProbeKind,
|
|
|
|
kind: ProbeKind,
|
|
|
|
event_alias: String,
|
|
|
|
event_alias: OsString,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
|
|
|
|
pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
|
|
|
|
program_data: &mut ProgramData<T>,
|
|
|
|
program_data: &mut ProgramData<T>,
|
|
|
|
kind: ProbeKind,
|
|
|
|
kind: ProbeKind,
|
|
|
|
fn_name: &str,
|
|
|
|
fn_name: &Path,
|
|
|
|
offset: u64,
|
|
|
|
offset: u64,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
) -> Result<T::Id, ProgramError> {
|
|
|
|
) -> Result<T::Id, ProgramError> {
|
|
|
@ -90,7 +140,7 @@ pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> {
|
|
|
|
|
|
|
|
|
|
|
|
fn create_as_probe(
|
|
|
|
fn create_as_probe(
|
|
|
|
kind: ProbeKind,
|
|
|
|
kind: ProbeKind,
|
|
|
|
fn_name: &str,
|
|
|
|
fn_name: &Path,
|
|
|
|
offset: u64,
|
|
|
|
offset: u64,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
) -> Result<OwnedFd, ProgramError> {
|
|
|
|
) -> Result<OwnedFd, ProgramError> {
|
|
|
@ -126,10 +176,10 @@ fn create_as_probe(
|
|
|
|
|
|
|
|
|
|
|
|
fn create_as_trace_point(
|
|
|
|
fn create_as_trace_point(
|
|
|
|
kind: ProbeKind,
|
|
|
|
kind: ProbeKind,
|
|
|
|
name: &str,
|
|
|
|
name: &Path,
|
|
|
|
offset: u64,
|
|
|
|
offset: u64,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
) -> Result<(OwnedFd, String), ProgramError> {
|
|
|
|
) -> Result<(OwnedFd, OsString), ProgramError> {
|
|
|
|
use ProbeKind::*;
|
|
|
|
use ProbeKind::*;
|
|
|
|
|
|
|
|
|
|
|
|
let tracefs = find_tracefs_path()?;
|
|
|
|
let tracefs = find_tracefs_path()?;
|
|
|
@ -142,7 +192,7 @@ fn create_as_trace_point(
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let category = format!("{}s", kind.pmu());
|
|
|
|
let category = format!("{}s", kind.pmu());
|
|
|
|
let tpid = read_sys_fs_trace_point_id(tracefs, &category, &event_alias)?;
|
|
|
|
let tpid = read_sys_fs_trace_point_id(tracefs, &category, event_alias.as_ref())?;
|
|
|
|
let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError {
|
|
|
|
let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError {
|
|
|
|
call: "perf_event_open",
|
|
|
|
call: "perf_event_open",
|
|
|
|
io_error,
|
|
|
|
io_error,
|
|
|
@ -154,9 +204,10 @@ fn create_as_trace_point(
|
|
|
|
fn create_probe_event(
|
|
|
|
fn create_probe_event(
|
|
|
|
tracefs: &Path,
|
|
|
|
tracefs: &Path,
|
|
|
|
kind: ProbeKind,
|
|
|
|
kind: ProbeKind,
|
|
|
|
fn_name: &str,
|
|
|
|
fn_name: &Path,
|
|
|
|
offset: u64,
|
|
|
|
offset: u64,
|
|
|
|
) -> Result<String, (String, io::Error)> {
|
|
|
|
) -> Result<OsString, (PathBuf, io::Error)> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
use ProbeKind::*;
|
|
|
|
use ProbeKind::*;
|
|
|
|
|
|
|
|
|
|
|
|
let events_file_name = tracefs.join(format!("{}_events", kind.pmu()));
|
|
|
|
let events_file_name = tracefs.join(format!("{}_events", kind.pmu()));
|
|
|
@ -165,93 +216,129 @@ fn create_probe_event(
|
|
|
|
KRetProbe | URetProbe => 'r',
|
|
|
|
KRetProbe | URetProbe => 'r',
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let fixed_fn_name = fn_name.replace(['.', '/', '-'], "_");
|
|
|
|
let fn_name = fn_name.as_os_str();
|
|
|
|
|
|
|
|
|
|
|
|
let event_alias = format!(
|
|
|
|
let mut event_alias = OsString::new();
|
|
|
|
"aya_{}_{}_{}_{:#x}_{}",
|
|
|
|
write!(
|
|
|
|
|
|
|
|
&mut event_alias,
|
|
|
|
|
|
|
|
"aya_{}_{}_",
|
|
|
|
process::id(),
|
|
|
|
process::id(),
|
|
|
|
probe_type_prefix,
|
|
|
|
probe_type_prefix,
|
|
|
|
fixed_fn_name,
|
|
|
|
)
|
|
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
for b in fn_name.as_bytes() {
|
|
|
|
|
|
|
|
let b = match *b {
|
|
|
|
|
|
|
|
b'.' | b'/' | b'-' => b'_',
|
|
|
|
|
|
|
|
b => b,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
event_alias.push(OsStr::from_bytes(&[b]));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(
|
|
|
|
|
|
|
|
&mut event_alias,
|
|
|
|
|
|
|
|
"_{:#x}_{}",
|
|
|
|
offset,
|
|
|
|
offset,
|
|
|
|
PROBE_NAME_INDEX.fetch_add(1, Ordering::AcqRel)
|
|
|
|
PROBE_NAME_INDEX.fetch_add(1, Ordering::AcqRel)
|
|
|
|
);
|
|
|
|
)
|
|
|
|
let offset_suffix = match kind {
|
|
|
|
.unwrap();
|
|
|
|
KProbe => format!("+{offset}"),
|
|
|
|
|
|
|
|
UProbe | URetProbe => format!(":{offset:#x}"),
|
|
|
|
let mut probe = OsString::new();
|
|
|
|
_ => String::new(),
|
|
|
|
write!(&mut probe, "{}:{}s/", probe_type_prefix, kind.pmu(),).unwrap();
|
|
|
|
|
|
|
|
probe.push(&event_alias);
|
|
|
|
|
|
|
|
probe.push(" ");
|
|
|
|
|
|
|
|
probe.push(fn_name);
|
|
|
|
|
|
|
|
match kind {
|
|
|
|
|
|
|
|
KProbe => write!(&mut probe, "+{offset}").unwrap(),
|
|
|
|
|
|
|
|
UProbe | URetProbe => write!(&mut probe, ":{offset:#x}").unwrap(),
|
|
|
|
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let probe = format!(
|
|
|
|
probe.push("\n");
|
|
|
|
"{}:{}s/{} {}{}\n",
|
|
|
|
|
|
|
|
probe_type_prefix,
|
|
|
|
|
|
|
|
kind.pmu(),
|
|
|
|
|
|
|
|
event_alias,
|
|
|
|
|
|
|
|
fn_name,
|
|
|
|
|
|
|
|
offset_suffix
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut events_file = OpenOptions::new()
|
|
|
|
OpenOptions::new()
|
|
|
|
.append(true)
|
|
|
|
.append(true)
|
|
|
|
.open(&events_file_name)
|
|
|
|
.open(&events_file_name)
|
|
|
|
.map_err(|e| (events_file_name.display().to_string(), e))?;
|
|
|
|
.and_then(|mut events_file| events_file.write_all(probe.as_bytes()))
|
|
|
|
|
|
|
|
.map_err(|e| (events_file_name, e))?;
|
|
|
|
events_file
|
|
|
|
|
|
|
|
.write_all(probe.as_bytes())
|
|
|
|
|
|
|
|
.map_err(|e| (events_file_name.display().to_string(), e))?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ok(event_alias)
|
|
|
|
Ok(event_alias)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (String, io::Error)> {
|
|
|
|
fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (PathBuf, io::Error)> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
|
|
|
|
let ProbeEvent { kind, event_alias } = event;
|
|
|
|
let ProbeEvent { kind, event_alias } = event;
|
|
|
|
let events_file_name = tracefs.join(format!("{}_events", kind.pmu()));
|
|
|
|
let events_file_name = tracefs.join(format!("{}_events", kind.pmu()));
|
|
|
|
|
|
|
|
|
|
|
|
let events = fs::read_to_string(&events_file_name)
|
|
|
|
fs::read(&events_file_name)
|
|
|
|
.map_err(|e| (events_file_name.display().to_string(), e))?;
|
|
|
|
.and_then(|events| {
|
|
|
|
|
|
|
|
let found = lines(&events).any(|line| {
|
|
|
|
let found = events.lines().any(|line| line.contains(&event_alias));
|
|
|
|
let mut line = line.as_bytes();
|
|
|
|
|
|
|
|
// See [`create_probe_event`] and the documentation:
|
|
|
|
if found {
|
|
|
|
//
|
|
|
|
let mut events_file = OpenOptions::new()
|
|
|
|
// https://docs.kernel.org/trace/kprobetrace.html
|
|
|
|
.append(true)
|
|
|
|
//
|
|
|
|
.open(&events_file_name)
|
|
|
|
// https://docs.kernel.org/trace/uprobetracer.html
|
|
|
|
.map_err(|e| (events_file_name.display().to_string(), e))?;
|
|
|
|
loop {
|
|
|
|
|
|
|
|
match line.split_first() {
|
|
|
|
let rm = format!("-:{event_alias}\n");
|
|
|
|
None => break false,
|
|
|
|
|
|
|
|
Some((b, rest)) => {
|
|
|
|
events_file
|
|
|
|
line = rest;
|
|
|
|
.write_all(rm.as_bytes())
|
|
|
|
if *b == b'/' {
|
|
|
|
.map_err(|e| (events_file_name.display().to_string(), e))?;
|
|
|
|
break line.starts_with(event_alias.as_bytes());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if found {
|
|
|
|
|
|
|
|
OpenOptions::new()
|
|
|
|
|
|
|
|
.append(true)
|
|
|
|
|
|
|
|
.open(&events_file_name)
|
|
|
|
|
|
|
|
.and_then(|mut events_file| {
|
|
|
|
|
|
|
|
let mut rm = OsString::new();
|
|
|
|
|
|
|
|
rm.push("-:");
|
|
|
|
|
|
|
|
rm.push(event_alias);
|
|
|
|
|
|
|
|
rm.push("\n");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
events_file.write_all(rm.as_bytes())
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.map_err(|e| (events_file_name, e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (String, io::Error)> {
|
|
|
|
fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (PathBuf, io::Error)> {
|
|
|
|
let file = format!("/sys/bus/event_source/devices/{pmu}/type");
|
|
|
|
let file = Path::new("/sys/bus/event_source/devices")
|
|
|
|
|
|
|
|
.join(pmu)
|
|
|
|
let perf_ty = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?;
|
|
|
|
.join("type");
|
|
|
|
let perf_ty = perf_ty
|
|
|
|
|
|
|
|
.trim()
|
|
|
|
fs::read_to_string(&file)
|
|
|
|
.parse::<u32>()
|
|
|
|
.and_then(|perf_ty| {
|
|
|
|
.map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e)))?;
|
|
|
|
perf_ty
|
|
|
|
|
|
|
|
.trim()
|
|
|
|
Ok(perf_ty)
|
|
|
|
.parse::<u32>()
|
|
|
|
|
|
|
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.map_err(|e| (file, e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (String, io::Error)> {
|
|
|
|
fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (PathBuf, io::Error)> {
|
|
|
|
let file = format!("/sys/bus/event_source/devices/{pmu}/format/retprobe");
|
|
|
|
let file = Path::new("/sys/bus/event_source/devices")
|
|
|
|
|
|
|
|
.join(pmu)
|
|
|
|
let data = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?;
|
|
|
|
.join("format/retprobe");
|
|
|
|
|
|
|
|
|
|
|
|
let mut parts = data.trim().splitn(2, ':').skip(1);
|
|
|
|
fs::read_to_string(&file)
|
|
|
|
let config = parts.next().ok_or_else(|| {
|
|
|
|
.and_then(|data| {
|
|
|
|
(
|
|
|
|
let mut parts = data.trim().splitn(2, ':').skip(1);
|
|
|
|
file.clone(),
|
|
|
|
let config = parts
|
|
|
|
io::Error::new(io::ErrorKind::Other, "invalid format"),
|
|
|
|
.next()
|
|
|
|
)
|
|
|
|
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid format"))?;
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
config
|
|
|
|
config
|
|
|
|
.parse::<u32>()
|
|
|
|
.parse::<u32>()
|
|
|
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
|
|
|
.map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e)))
|
|
|
|
})
|
|
|
|
|
|
|
|
.map_err(|e| (file, e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|