use crate::DataType;
use byteorder::{NativeEndian, ReadBytesExt};
use dof::{Probe, Provider, Section};
use std::collections::BTreeMap;
use std::mem::size_of;
use std::sync::atomic::AtomicU8;
use std::sync::atomic::Ordering;
pub(crate) const PROBE_REC_VERSION: u8 = 1;
pub fn process_section(mut data: &mut [u8], register: bool) -> Result<Section, crate::Error> {
let mut providers = BTreeMap::new();
while !data.is_empty() {
assert!(
data.len() >= size_of::<u32>(),
"Not enough bytes for length header"
);
let len = (&data[..size_of::<u32>()]).read_u32::<NativeEndian>()? as usize;
let (rec, rest) = data.split_at_mut(len);
process_probe_record(&mut providers, rec, register)?;
data = rest;
}
Ok(Section {
providers,
..Default::default()
})
}
#[cfg(all(unix, not(target_os = "freebsd")))]
pub(crate) fn addr_to_info(addr: u64) -> (Option<String>, Option<String>) {
unsafe {
let mut info = libc::Dl_info {
dli_fname: std::ptr::null(),
dli_fbase: std::ptr::null_mut(),
dli_sname: std::ptr::null(),
dli_saddr: std::ptr::null_mut(),
};
if libc::dladdr(addr as *const libc::c_void, &mut info as *mut _) == 0 {
(None, None)
} else {
(
Some(
std::ffi::CStr::from_ptr(info.dli_sname)
.to_string_lossy()
.to_string(),
),
Some(
std::ffi::CStr::from_ptr(info.dli_fname)
.to_string_lossy()
.to_string(),
),
)
}
}
}
#[cfg(target_os = "freebsd")]
pub(crate) fn addr_to_info(addr: u64) -> (Option<String>, Option<String>) {
unsafe {
#[link(name = "execinfo")]
extern "C" {
pub fn backtrace_symbols_fmt(
_: *const *mut libc::c_void,
_: libc::size_t,
_: *const libc::c_char,
) -> *mut *mut libc::c_char;
}
let addrs_arr = [addr];
let addrs = addrs_arr.as_ptr() as *const *mut libc::c_void;
let format = std::ffi::CString::new("%n\n%f").unwrap();
let symbols = backtrace_symbols_fmt(addrs, 1, format.as_ptr());
if !symbols.is_null() {
if let Some((sname, fname)) = std::ffi::CStr::from_ptr(*symbols)
.to_string_lossy()
.split_once('\n')
{
(Some(sname.to_string()), Some(fname.to_string()))
} else {
(None, None)
}
} else {
(None, None)
}
}
}
#[cfg(not(unix))]
pub(crate) fn addr_to_info(_addr: u64) -> (Option<String>, Option<String>) {
(None, None)
}
const MAX_PROVIDER_NAME_LEN: usize = 64 - 6;
const MAX_PROBE_NAME_LEN: usize = 64;
const MAX_FUNC_NAME_LEN: usize = 128;
const MAX_ARG_TYPE_LEN: usize = 128;
fn limit_string_length<S: AsRef<str>>(s: S, limit: usize) -> String {
let s = s.as_ref();
let limit = s.len().min(limit - 1);
s[..limit].to_string()
}
fn read_record_version(version: &mut u8, register: bool) -> u8 {
let ver = *version;
if !register || ver > PROBE_REC_VERSION {
return ver;
}
let ver = unsafe { std::mem::transmute::<&mut u8, &AtomicU8>(version) };
ver.swap(u8::MAX, Ordering::SeqCst)
}
fn process_probe_record(
providers: &mut BTreeMap<String, Provider>,
rec: &mut [u8],
register: bool,
) -> Result<(), crate::Error> {
let (rec, mut data) = {
let (rec, data) = rec.split_at_mut(5);
(rec, &*data)
};
let version = read_record_version(&mut rec[4], register);
if version > PROBE_REC_VERSION {
return Ok(());
}
let n_args = data.read_u8()? as usize;
let flags = data.read_u16::<NativeEndian>()?;
let address = data.read_u64::<NativeEndian>()?;
let provname = data.read_cstr();
let probename = data.read_cstr();
let args = {
let mut args = Vec::with_capacity(n_args);
for _ in 0..n_args {
args.push(limit_string_length(data.read_cstr(), MAX_ARG_TYPE_LEN));
}
args
};
let funcname = match addr_to_info(address).0 {
Some(s) => limit_string_length(s, MAX_FUNC_NAME_LEN),
None => format!("?{:#x}", address),
};
let provname = limit_string_length(provname, MAX_PROVIDER_NAME_LEN);
let provider = providers.entry(provname.clone()).or_insert(Provider {
name: provname,
probes: BTreeMap::new(),
});
let probename = limit_string_length(probename, MAX_PROBE_NAME_LEN);
let probe = provider.probes.entry(probename.clone()).or_insert(Probe {
name: probename,
function: funcname,
address,
offsets: vec![],
enabled_offsets: vec![],
arguments: vec![],
});
probe.arguments = args;
assert!(address >= probe.address);
if flags == 0 {
probe.offsets.push((address - probe.address) as u32);
} else {
probe.enabled_offsets.push((address - probe.address) as u32);
}
Ok(())
}
trait ReadCstrExt<'a> {
fn read_cstr(&mut self) -> &'a str;
}
impl<'a> ReadCstrExt<'a> for &'a [u8] {
fn read_cstr(&mut self) -> &'a str {
let index = self
.iter()
.position(|ch| *ch == 0)
.expect("ran out of bytes before we found a zero");
let ret = std::str::from_utf8(&self[..index]).unwrap();
*self = &self[index + 1..];
ret
}
}
#[allow(dead_code)]
pub(crate) fn emit_probe_record(prov: &str, probe: &str, types: Option<&[DataType]>) -> String {
#[cfg(not(target_os = "freebsd"))]
let section_ident = r#"set_dtrace_probes,"aw","progbits""#;
#[cfg(target_os = "freebsd")]
let section_ident = r#"set_dtrace_probes,"awR","progbits""#;
let is_enabled = types.is_none();
let n_args = types.map_or(0, |typ| typ.len());
let arguments = types.map_or_else(String::new, |types| {
types
.iter()
.map(|typ| format!(".asciz \"{}\"", typ.to_c_type()))
.collect::<Vec<_>>()
.join("\n")
});
format!(
r#"
.pushsection {section_ident}
.balign 8
991:
.4byte 992f-991b // length
.byte {version}
.byte {n_args}
.2byte {flags}
.8byte 990b // address
.asciz "{prov}"
.asciz "{probe}"
{arguments} // null-terminated strings for each argument
.balign 8
992: .popsection
{yeet}
"#,
section_ident = section_ident,
version = PROBE_REC_VERSION,
n_args = n_args,
flags = if is_enabled { 1 } else { 0 },
prov = prov,
probe = probe.replace("__", "-"),
arguments = arguments,
yeet = if cfg!(any(target_os = "illumos", target_os = "freebsd")) {
r#"
.pushsection yeet_dtrace_probes
.8byte 991b
.popsection
"#
} else {
""
},
)
}
#[cfg(test)]
mod test {
use std::collections::BTreeMap;
use byteorder::{NativeEndian, WriteBytesExt};
use super::emit_probe_record;
use super::process_probe_record;
use super::process_section;
use super::DataType;
use super::PROBE_REC_VERSION;
use super::{MAX_PROBE_NAME_LEN, MAX_PROVIDER_NAME_LEN};
use dtrace_parser::BitWidth;
use dtrace_parser::DataType as DType;
use dtrace_parser::Integer;
use dtrace_parser::Sign;
#[test]
fn test_process_probe_record() {
let mut rec = Vec::<u8>::new();
rec.write_u32::<NativeEndian>(0).unwrap();
rec.write_u8(PROBE_REC_VERSION).unwrap();
rec.write_u8(0).unwrap();
rec.write_u16::<NativeEndian>(0).unwrap();
rec.write_u64::<NativeEndian>(0x1234).unwrap();
rec.write_cstr("provider");
rec.write_cstr("probe");
let len = rec.len();
(&mut rec[0..])
.write_u32::<NativeEndian>(len as u32)
.unwrap();
let mut providers = BTreeMap::new();
process_probe_record(&mut providers, &mut rec, true).unwrap();
let probe = providers
.get("provider")
.unwrap()
.probes
.get("probe")
.unwrap();
assert_eq!(probe.name, "probe");
assert_eq!(probe.address, 0x1234);
}
#[test]
fn test_process_probe_record_long_names() {
let mut rec = Vec::<u8>::new();
let long_name = "p".repeat(130);
rec.write_u32::<NativeEndian>(0).unwrap();
rec.write_u8(PROBE_REC_VERSION).unwrap();
rec.write_u8(0).unwrap();
rec.write_u16::<NativeEndian>(0).unwrap();
rec.write_u64::<NativeEndian>(0x1234).unwrap();
rec.write_cstr(&long_name);
rec.write_cstr(&long_name);
let len = rec.len();
(&mut rec[0..])
.write_u32::<NativeEndian>(len as u32)
.unwrap();
let mut providers = BTreeMap::new();
process_probe_record(&mut providers, &mut rec, true).unwrap();
let expected_provider_name = &long_name[..MAX_PROVIDER_NAME_LEN - 1];
let expected_probe_name = &long_name[..MAX_PROBE_NAME_LEN - 1];
assert!(providers.get(&long_name).is_none());
let probe = providers
.get(expected_provider_name)
.unwrap()
.probes
.get(expected_probe_name)
.unwrap();
assert_eq!(probe.name, expected_probe_name);
assert_eq!(probe.address, 0x1234);
}
fn make_record(version: u8) -> Vec<u8> {
let mut data = Vec::<u8>::new();
data.write_u32::<NativeEndian>(0).unwrap();
data.write_u8(version).unwrap();
data.write_u8(0).unwrap();
data.write_u16::<NativeEndian>(0).unwrap();
data.write_u64::<NativeEndian>(0x1234).unwrap();
data.write_cstr("provider");
data.write_cstr("probe");
let len = data.len();
(&mut data[0..])
.write_u32::<NativeEndian>(len as u32)
.unwrap();
data.write_u32::<NativeEndian>(0).unwrap();
data.write_u8(version).unwrap();
data.write_u8(0).unwrap();
data.write_u16::<NativeEndian>(0).unwrap();
data.write_u64::<NativeEndian>(0x12ab).unwrap();
data.write_cstr("provider");
data.write_cstr("probe");
let len2 = data.len() - len;
(&mut data[len..])
.write_u32::<NativeEndian>(len2 as u32)
.unwrap();
data
}
#[test]
fn test_process_section() {
let mut data = make_record(PROBE_REC_VERSION);
let section = process_section(&mut data, true).unwrap();
let probe = section
.providers
.get("provider")
.unwrap()
.probes
.get("probe")
.unwrap();
assert_eq!(probe.name, "probe");
assert_eq!(probe.address, 0x1234);
assert_eq!(probe.offsets, vec![0, 0x12ab - 0x1234]);
}
#[test]
fn test_re_process_section() {
let mut data = make_record(PROBE_REC_VERSION);
let section = process_section(&mut data, true).unwrap();
assert_eq!(section.providers.len(), 1);
assert_eq!(data[4], u8::MAX);
let section = process_section(&mut data, true).unwrap();
assert_eq!(data[4], u8::MAX);
assert_eq!(section.providers.len(), 0);
}
#[test]
fn test_process_section_future_version() {
let mut data = make_record(PROBE_REC_VERSION + 1);
let section = process_section(&mut data, true).unwrap();
assert_eq!(section.providers.len(), 0);
assert_eq!(data[4], PROBE_REC_VERSION + 1);
}
trait WriteCstrExt {
fn write_cstr(&mut self, s: &str);
}
impl WriteCstrExt for Vec<u8> {
fn write_cstr(&mut self, s: &str) {
self.extend_from_slice(s.as_bytes());
self.push(0);
}
}
#[test]
fn test_emit_probe_record() {
let provider = "provider";
let probe = "probe";
let types = [
DataType::Native(DType::Pointer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit8,
})),
DataType::Native(DType::String),
];
let record = emit_probe_record(provider, probe, Some(&types));
let mut lines = record.lines();
println!("{}", record);
lines.next(); assert!(lines.next().unwrap().contains(".pushsection"));
let mut lines = lines.skip(3);
assert!(lines
.next()
.unwrap()
.contains(&format!(".byte {}", PROBE_REC_VERSION)));
assert!(lines
.next()
.unwrap()
.contains(&format!(".byte {}", types.len())));
for (typ, line) in types.iter().zip(lines.skip(4)) {
assert!(line.contains(&format!(".asciz \"{}\"", typ.to_c_type())));
}
}
#[test]
fn test_emit_probe_record_dunders() {
let provider = "provider";
let probe = "my__probe";
let types = [
DataType::Native(DType::Pointer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit8,
})),
DataType::Native(dtrace_parser::DataType::String),
];
let record = emit_probe_record(provider, probe, Some(&types));
assert!(
record.contains("my-probe"),
"Expected double-underscores to be translated to a single dash"
);
}
}