mirror of https://github.com/aya-rs/aya
Merge dfd26f9071
into 49404367d8
commit
0d6cb90621
@ -1,4 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.checkOnSave.command": "clippy"
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.checkOnSave.command": "clippy"
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "aya-common"
|
||||
version = "0.1.0"
|
||||
description = "Code shared between aya and aya-bpf crates."
|
||||
keywords = ["ebpf", "bpf", "linux", "kernel"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["The Aya Contributors"]
|
||||
repository = "https://github.com/aya-rs/aya"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/aya"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
regex = {version = "1", optional = true}
|
||||
thiserror = {version = "1", optional = true}
|
||||
memoffset = {version = "0.6", optional = true}
|
||||
lazy_static = {version = "1", optional = true}
|
||||
|
||||
[features]
|
||||
default = []
|
||||
user = [ "regex", "thiserror", "memoffset", "lazy_static" ]
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
@ -0,0 +1 @@
|
||||
#include <linux/ptrace.h>
|
@ -0,0 +1,11 @@
|
||||
/* automatically generated by rust-bindgen 0.60.1 */
|
||||
|
||||
pub type __u64 = ::std::os::raw::c_ulonglong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct user_pt_regs {
|
||||
pub regs: [__u64; 31usize],
|
||||
pub sp: __u64,
|
||||
pub pc: __u64,
|
||||
pub pstate: __u64,
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/* automatically generated by rust-bindgen 0.60.1 */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct pt_regs {
|
||||
pub uregs: [::std::os::raw::c_long; 18usize],
|
||||
}
|
@ -0,0 +1 @@
|
||||
/* automatically generated by rust-bindgen 0.60.1 */
|
@ -0,0 +1,27 @@
|
||||
/* automatically generated by rust-bindgen 0.60.1 */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct pt_regs {
|
||||
pub r15: ::std::os::raw::c_ulong,
|
||||
pub r14: ::std::os::raw::c_ulong,
|
||||
pub r13: ::std::os::raw::c_ulong,
|
||||
pub r12: ::std::os::raw::c_ulong,
|
||||
pub rbp: ::std::os::raw::c_ulong,
|
||||
pub rbx: ::std::os::raw::c_ulong,
|
||||
pub r11: ::std::os::raw::c_ulong,
|
||||
pub r10: ::std::os::raw::c_ulong,
|
||||
pub r9: ::std::os::raw::c_ulong,
|
||||
pub r8: ::std::os::raw::c_ulong,
|
||||
pub rax: ::std::os::raw::c_ulong,
|
||||
pub rcx: ::std::os::raw::c_ulong,
|
||||
pub rdx: ::std::os::raw::c_ulong,
|
||||
pub rsi: ::std::os::raw::c_ulong,
|
||||
pub rdi: ::std::os::raw::c_ulong,
|
||||
pub orig_rax: ::std::os::raw::c_ulong,
|
||||
pub rip: ::std::os::raw::c_ulong,
|
||||
pub cs: ::std::os::raw::c_ulong,
|
||||
pub eflags: ::std::os::raw::c_ulong,
|
||||
pub rsp: ::std::os::raw::c_ulong,
|
||||
pub ss: ::std::os::raw::c_ulong,
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
#![allow(
|
||||
dead_code,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
clippy::all,
|
||||
missing_docs
|
||||
)]
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod linux_bindings_aarch64;
|
||||
#[cfg(target_arch = "arm")]
|
||||
mod linux_bindings_armv7;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
mod linux_bindings_riscv64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod linux_bindings_x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use linux_bindings_x86_64::*;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use linux_bindings_armv7::*;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use linux_bindings_aarch64::*;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use linux_bindings_riscv64::*;
|
@ -0,0 +1,306 @@
|
||||
#![no_std]
|
||||
|
||||
pub const USDT_MAX_SPEC_COUNT: u32 = 256;
|
||||
pub const USDT_MAX_IP_COUNT: u32 = 4 * USDT_MAX_SPEC_COUNT;
|
||||
pub const USDT_MAX_ARG_COUNT: usize = 12;
|
||||
|
||||
/// The type of argument in a USDT program.
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "user", derive(Debug))]
|
||||
pub enum UsdtArgType {
|
||||
/// Value is Constant.
|
||||
Const,
|
||||
/// Value is stored in a Register.
|
||||
Reg,
|
||||
/// Value is stored in a Register and requires dereferencing.
|
||||
RegDeref,
|
||||
}
|
||||
|
||||
impl Default for UsdtArgType {
|
||||
fn default() -> Self {
|
||||
UsdtArgType::Const
|
||||
}
|
||||
}
|
||||
|
||||
/// The specifcation of an argument in a USDT program.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "user", derive(Debug))]
|
||||
pub struct UsdtArgSpec {
|
||||
/// Meaning of val_off differs based on `arg_type`.
|
||||
/// If Constant, this holds the scalar value of unknow type, up to u64 in size.
|
||||
/// If RegDeref, this contains an offset which is an i64.
|
||||
pub val_off: u64,
|
||||
/// Type of Argument.
|
||||
pub arg_type: UsdtArgType,
|
||||
/// Offset of the register within the BPF context
|
||||
pub reg_off: i16,
|
||||
/// Whether the value should be interpreted as signed
|
||||
pub arg_signed: bool,
|
||||
/// Number of bits that need to be cleared and, optionally,
|
||||
/// sign-extended to cast arguments that are 1, 2, or 4 bytes
|
||||
/// long into final 8-byte u64/s64 value returned to user.
|
||||
pub arg_bitshift: i8,
|
||||
}
|
||||
|
||||
/// The specification of a USDT
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "user", derive(Debug))]
|
||||
pub struct UsdtSpec {
|
||||
/// Specification used to access arguments.
|
||||
pub args: [UsdtArgSpec; USDT_MAX_ARG_COUNT],
|
||||
/// User supplied cookie since the BPF Attach Cookie is used internally.
|
||||
pub cookie: u64,
|
||||
/// Number of args in this tracepoint
|
||||
pub arg_count: i16,
|
||||
}
|
||||
|
||||
#[cfg(feature = "user")]
|
||||
mod generated;
|
||||
|
||||
#[cfg(feature = "user")]
|
||||
extern crate std;
|
||||
#[cfg(feature = "user")]
|
||||
pub mod with_std {
|
||||
use crate::{UsdtArgSpec, UsdtArgType, UsdtSpec, USDT_MAX_ARG_COUNT};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::{format, string::String};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ParseError {
|
||||
#[error("error parsing usdt arg spec: {0}")]
|
||||
UsdtArgSpecError(String),
|
||||
#[error("error parsing usdt spec: {0}")]
|
||||
UsdtSpecError(String),
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref USDT_REGEX: Regex =
|
||||
Regex::new(r"^(-?[0-9]+)@((-?[0-9]+)\(%(.*)\)|%(.*)|\$([0-9]+))$").unwrap();
|
||||
}
|
||||
|
||||
impl std::str::FromStr for UsdtArgSpec {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut spec = UsdtArgSpec::default();
|
||||
let caps = USDT_REGEX.captures(s).unwrap();
|
||||
|
||||
if caps.len() != 7 {
|
||||
return Err(ParseError::UsdtArgSpecError(format!(
|
||||
"could not parse {}",
|
||||
s
|
||||
)));
|
||||
}
|
||||
let mut arg_size: isize = caps.get(1).unwrap().as_str().parse().unwrap();
|
||||
if caps.get(3).is_some() && caps.get(4).is_some() {
|
||||
spec.arg_type = UsdtArgType::RegDeref;
|
||||
spec.val_off = caps.get(3).unwrap().as_str().parse::<i64>().map_err(|e| {
|
||||
ParseError::UsdtArgSpecError(format!("could not parse {}: {}", s, e))
|
||||
})? as u64;
|
||||
spec.reg_off = calc_pt_regs_offset(caps.get(4).unwrap().as_str())?;
|
||||
} else if caps.get(5).is_some() {
|
||||
spec.arg_type = UsdtArgType::Reg;
|
||||
spec.reg_off = calc_pt_regs_offset(caps.get(5).unwrap().as_str())?;
|
||||
} else if caps.get(6).is_some() {
|
||||
spec.arg_type = UsdtArgType::Const;
|
||||
spec.val_off = caps.get(6).unwrap().as_str().parse::<i64>().map_err(|e| {
|
||||
ParseError::UsdtArgSpecError(format!("could not parse {}: {}", s, e))
|
||||
})? as u64;
|
||||
}
|
||||
if arg_size < 0 {
|
||||
spec.arg_signed = true;
|
||||
arg_size = -arg_size;
|
||||
}
|
||||
match arg_size {
|
||||
1 | 2 | 4 | 8 => spec.arg_bitshift = (arg_size * 8) as i8,
|
||||
_ => {
|
||||
return Err(ParseError::UsdtArgSpecError(format!(
|
||||
"arg size was not 1,2,4,8: {}",
|
||||
s
|
||||
)))
|
||||
}
|
||||
}
|
||||
Ok(spec)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn calc_pt_regs_offset(reg: &str) -> Result<i16, ParseError> {
|
||||
use crate::generated::pt_regs;
|
||||
use memoffset::offset_of;
|
||||
match reg {
|
||||
"rip" | "eip" => Ok(offset_of!(pt_regs, rip) as i16),
|
||||
"rax" | "eax" | "ax" | "al" => Ok(offset_of!(pt_regs, rax) as i16),
|
||||
"rbx" | "ebx" | "bx" | "bl" => Ok(offset_of!(pt_regs, rbx) as i16),
|
||||
"rcx" | "ecx" | "cx" | "cl" => Ok(offset_of!(pt_regs, rcx) as i16),
|
||||
"rdx" | "edx" | "dx" | "dl" => Ok(offset_of!(pt_regs, rdx) as i16),
|
||||
"rsi" | "esi" | "si" | "sil" => Ok(offset_of!(pt_regs, rsi) as i16),
|
||||
"rdi" | "edi" | "di" | "dil" => Ok(offset_of!(pt_regs, rdi) as i16),
|
||||
"rbp" | "ebp" | "bp" | "bpl" => Ok(offset_of!(pt_regs, rbp) as i16),
|
||||
"rsp" | "esp" | "sp" | "bsl" => Ok(offset_of!(pt_regs, rsp) as i16),
|
||||
"r8" | "r8d" | "r8w" | "r8b" => Ok(offset_of!(pt_regs, r8) as i16),
|
||||
"r9" | "r9d" | "r9w" | "r9b" => Ok(offset_of!(pt_regs, r9) as i16),
|
||||
"r10" | "r10d" | "r10w" | "r10b" => Ok(offset_of!(pt_regs, r10) as i16),
|
||||
"r11" | "r11d" | "r11w" | "r11b" => Ok(offset_of!(pt_regs, r11) as i16),
|
||||
"r12" | "r12d" | "r12w" | "r12b" => Ok(offset_of!(pt_regs, r12) as i16),
|
||||
"r13" | "r13d" | "r13w" | "r13b" => Ok(offset_of!(pt_regs, r13) as i16),
|
||||
"r14" | "r14d" | "r14w" | "r14b" => Ok(offset_of!(pt_regs, r14) as i16),
|
||||
"r15" | "r15d" | "r15w" | "r15b" => Ok(offset_of!(pt_regs, r15) as i16),
|
||||
_ => Err(ParseError::UsdtArgSpecError(format!(
|
||||
"unknown register: {}",
|
||||
reg
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn calc_pt_regs_offset(reg: &str) -> Result<i16, ParseError> {
|
||||
use crate::generated::user_pt_regs;
|
||||
use memoffset::offset_of;
|
||||
use std::mem;
|
||||
match reg {
|
||||
r if r.starts_with('x') => {
|
||||
let n: usize = r.strip_prefix('x').unwrap().parse().map_err(|_| {
|
||||
ParseError::UsdtArgSpecError(format!(
|
||||
"invalid register format. expected: xN. got: {}",
|
||||
reg
|
||||
))
|
||||
})?;
|
||||
Ok((offset_of!(user_pt_regs, regs) + (n * mem::size_of::<u64>())) as i16)
|
||||
}
|
||||
"sp" => Ok(offset_of!(user_pt_regs, sp) as i16),
|
||||
_ => Err(ParseError::UsdtArgSpecError(format!(
|
||||
"unknown register: {}",
|
||||
reg
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
fn calc_pt_regs_offset(reg: &str) -> Result<i16, ParseError> {
|
||||
use crate::generated::pt_regs;
|
||||
use memoffset::offset_of;
|
||||
use std::mem;
|
||||
match reg {
|
||||
r if r.starts_with('r') => {
|
||||
let n: usize = r.strip_prefix('r').unwrap().parse().map_err(|_| {
|
||||
ParseError::UsdtArgSpecError(format!(
|
||||
"invalid register format. expected: rN. got: {}",
|
||||
reg
|
||||
))
|
||||
})?;
|
||||
Ok((offset_of!(pt_regs, uregs) + (n * mem::size_of::<u32>())) as i16)
|
||||
}
|
||||
_ => Err(ParseError::UsdtArgSpecError(format!(
|
||||
"unknown register: {}",
|
||||
reg
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
fn calc_pt_regs_offset(reg: &str) -> Result<i16, ParseError> {
|
||||
unimplemented!("riscv support for usdt probes not implemented")
|
||||
}
|
||||
|
||||
impl std::str::FromStr for UsdtSpec {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use std::vec::Vec;
|
||||
let parts: Vec<&str> = s.split_whitespace().collect();
|
||||
if parts.len() > USDT_MAX_ARG_COUNT {
|
||||
return Err(ParseError::UsdtSpecError(format!("too many args: {}", s)));
|
||||
}
|
||||
let mut args = parts
|
||||
.iter()
|
||||
.map(|s| s.parse::<UsdtArgSpec>().unwrap())
|
||||
.collect::<Vec<UsdtArgSpec>>();
|
||||
let arg_count = args.len() as i16;
|
||||
args.resize(USDT_MAX_ARG_COUNT, UsdtArgSpec::default());
|
||||
Ok(UsdtSpec {
|
||||
args: args.try_into().unwrap(),
|
||||
cookie: 0,
|
||||
arg_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use memoffset::offset_of;
|
||||
|
||||
#[cfg_attr(target_arch = "x86_64", test)]
|
||||
fn test_parse_specs() {
|
||||
use crate::generated::pt_regs;
|
||||
|
||||
let s = "-8@%rax -8@%rcx";
|
||||
let res: UsdtSpec = s.parse().unwrap();
|
||||
assert_eq!(res.arg_count, 2);
|
||||
assert_eq!(
|
||||
res.args[0],
|
||||
UsdtArgSpec {
|
||||
val_off: 0,
|
||||
arg_type: UsdtArgType::Reg,
|
||||
reg_off: offset_of!(pt_regs, rax) as i16,
|
||||
arg_signed: true,
|
||||
arg_bitshift: 64
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
res.args[1],
|
||||
UsdtArgSpec {
|
||||
val_off: 0,
|
||||
arg_type: UsdtArgType::Reg,
|
||||
reg_off: offset_of!(pt_regs, rcx) as i16,
|
||||
arg_signed: true,
|
||||
arg_bitshift: 64
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "x86_64", test)]
|
||||
fn test_parse_args() {
|
||||
use crate::generated::pt_regs;
|
||||
|
||||
assert_eq!(
|
||||
"-4@-1204(%rbp)".parse::<UsdtArgSpec>().unwrap(),
|
||||
UsdtArgSpec {
|
||||
val_off: -1204i64 as u64,
|
||||
arg_type: UsdtArgType::RegDeref,
|
||||
reg_off: offset_of!(pt_regs, rbp) as i16,
|
||||
arg_signed: true,
|
||||
arg_bitshift: 32
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"-4@%edi".parse::<UsdtArgSpec>().unwrap(),
|
||||
UsdtArgSpec {
|
||||
val_off: 0,
|
||||
arg_type: UsdtArgType::Reg,
|
||||
reg_off: offset_of!(pt_regs, rdi) as i16,
|
||||
arg_signed: true,
|
||||
arg_bitshift: 32
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"-4@$5".parse::<UsdtArgSpec>().unwrap(),
|
||||
UsdtArgSpec {
|
||||
val_off: 5,
|
||||
arg_type: UsdtArgType::Const,
|
||||
reg_off: 0,
|
||||
arg_signed: true,
|
||||
arg_bitshift: 32
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,525 @@
|
||||
//! User statically-defined tracepoints.
|
||||
use aya_common::{UsdtSpec, USDT_MAX_SPEC_COUNT};
|
||||
use libc::pid_t;
|
||||
use object::{elf::*, read::elf::*, Endianness};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
convert::TryInto,
|
||||
ffi::CStr,
|
||||
fs,
|
||||
io::{self, BufRead, Cursor, Read},
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
generated::{bpf_prog_type::BPF_PROG_TYPE_KPROBE, BPF_NOEXIST},
|
||||
maps::{MapError, MapRefMut},
|
||||
programs::{
|
||||
define_link_wrapper, load_program,
|
||||
perf_attach::{perf_attach, PerfLinkIdInner, PerfLinkInner},
|
||||
probe::create_as_probe,
|
||||
utils::{LD_SO_CACHE, LD_SO_CACHE_FILE},
|
||||
Link, ProbeKind, ProgramData, ProgramError,
|
||||
},
|
||||
Pod, FEATURES,
|
||||
};
|
||||
|
||||
use crate::programs::utils::{ProcMap, ProcMapError};
|
||||
|
||||
unsafe impl Pod for UsdtSpec {}
|
||||
|
||||
/// Name of the map used for USDT specs.
|
||||
pub const USDT_SPEC_MAP: &str = "__bpf_usdt_specs";
|
||||
/// Name of the map used for USDT to IP mappings.
|
||||
pub const USDT_IP_TO_SPEC_MAP: &str = "__bpf_usdt_ip_to_spec_id";
|
||||
|
||||
/// A user statically-defined tracepoint program.
|
||||
///
|
||||
/// USDT programs are the fastest of the userspace tracing programs and can be
|
||||
/// used to trace events instrumented libraries or binaries. Unlike uprobes and
|
||||
/// uretprobes that have access to all CPU registers, USDTs provide a structured
|
||||
/// specification for accessing the arguments for each tracepoint. When compliled
|
||||
/// a single tracepoint may have mutliple different entrypoints in the same program.
|
||||
/// In order to simply access to arguments from eBPF, Aya keeps state in 2 maps:
|
||||
///
|
||||
/// - [`USDT_SPEC_MAP`] which keeps track of USDT specifications
|
||||
/// - [`USDT_IP_TO_SPEC_MAP`] which keeps track of Instructio Pointers to USDT specs.
|
||||
///
|
||||
/// The last map is not used on kernels which support the BPF Attach Cookie feature.
|
||||
///
|
||||
/// # Minimum kernel version
|
||||
///
|
||||
/// While support was added to the kenel in 4.19, Aya depends on a feature that
|
||||
/// allows the kernel to manage semaphore reference counting which was added in
|
||||
/// 4.20.
|
||||
///
|
||||
/// The minimum supported kernel version is 4.20.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?;
|
||||
/// use aya::{Bpf, programs::{Usdt, usdt::{USDT_SPEC_MAP, USDT_IP_TO_SPEC_MAP}}};
|
||||
/// use aya::maps::{Array, HashMap};
|
||||
/// use std::convert::TryInto;
|
||||
///
|
||||
/// let spec_map = Array::try_from(bpf.map_mut(USDT_SPEC_MAP).unwrap())?;
|
||||
/// let ip_to_spec_map = HashMap::try_from(bpf.map_mut(USDT_IP_TO_SPEC_MAP).unwrap())?;
|
||||
/// let program: &mut Usdt = bpf.program_mut("usdt").unwrap().try_into()?;
|
||||
/// program.load()?;
|
||||
/// program.attach(
|
||||
/// spec_map,
|
||||
/// ip_to_spec_map,
|
||||
/// "clock",
|
||||
/// "loop",
|
||||
/// "/path/to/target/debug/clock",
|
||||
/// Some(12345),
|
||||
/// )?;
|
||||
/// # Ok::<(), aya::BpfError>(())
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "BPF_PROG_TYPE_KPROBE")]
|
||||
pub struct Usdt {
|
||||
pub(crate) data: ProgramData<UsdtLink>,
|
||||
}
|
||||
|
||||
impl Usdt {
|
||||
/// Loads the program inside the kernel.
|
||||
pub fn load(&mut self) -> Result<(), ProgramError> {
|
||||
load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
|
||||
}
|
||||
|
||||
/// Attaches the program.
|
||||
///
|
||||
/// Attaches the USDT to the tracepoint with a matching `tp_provider` and `tp_name`
|
||||
/// in the `target`.
|
||||
///
|
||||
/// If `pid` is not `None`, the program executes only when the target
|
||||
/// function is executed by the given `pid`. This is only supported in kernels which
|
||||
/// provide the BPF Attach Cookie feature.
|
||||
///
|
||||
/// The `target` argument can be an absolute path to a binary or library, or
|
||||
/// a library name (eg: `"libc"`).
|
||||
///
|
||||
/// Since there a single tracepoint can have multiple entrypoints, a single `UsdtLinkId`
|
||||
/// may be comprised of multiple links.
|
||||
///
|
||||
/// The returned value can be used to detach, see [Usdt::detach].
|
||||
pub fn attach<T: AsRef<Path>>(
|
||||
&mut self,
|
||||
mut spec_map: crate::maps::Array<MapRefMut, UsdtSpec>,
|
||||
mut ip_to_spec_map: crate::maps::HashMap<MapRefMut, i64, u32>,
|
||||
tp_provider: &str,
|
||||
tp_name: &str,
|
||||
target: T,
|
||||
pid: Option<pid_t>,
|
||||
) -> Result<UsdtLinkId, ProgramError> {
|
||||
let target = target.as_ref();
|
||||
let target_str = &*target.as_os_str().to_string_lossy();
|
||||
|
||||
let mut path = if let Some(pid) = pid {
|
||||
let proc_map_libs =
|
||||
ProcMap::new(pid).map_err(|e| UsdtError::ProcMapError { pid, source: e })?;
|
||||
proc_map_libs
|
||||
.find_by_name(target_str)
|
||||
.map_err(|io_error| UsdtError::FileError {
|
||||
filename: format!("/proc/{}/maps", pid),
|
||||
io_error,
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if path.is_none() {
|
||||
path = if target.is_absolute() {
|
||||
Some(target_str)
|
||||
} else {
|
||||
let cache = LD_SO_CACHE
|
||||
.as_ref()
|
||||
.map_err(|error| UsdtError::InvalidLdSoCache {
|
||||
io_error: error.clone(),
|
||||
})?;
|
||||
cache.resolve(target_str)
|
||||
}
|
||||
.map(String::from)
|
||||
};
|
||||
|
||||
let path = path.ok_or(UsdtError::InvalidTarget {
|
||||
path: target.to_owned(),
|
||||
})?;
|
||||
|
||||
let tracepoints = collect_usdts(&path, tp_provider, tp_name, pid)?;
|
||||
let mut perf_links = vec![];
|
||||
let mut spec_ids = VecDeque::with_capacity(USDT_MAX_SPEC_COUNT as usize);
|
||||
for i in 0..USDT_MAX_SPEC_COUNT {
|
||||
spec_ids.push_back(i)
|
||||
}
|
||||
let mut spec_id_map = HashMap::new();
|
||||
for t in tracepoints {
|
||||
let id = if spec_id_map.contains_key(&t.args) {
|
||||
*(spec_id_map.get(&t.args).unwrap())
|
||||
} else {
|
||||
let id = spec_ids.pop_front().unwrap();
|
||||
spec_id_map.insert(t.args.clone(), id);
|
||||
spec_map.set(id, t.spec, 0)?;
|
||||
id
|
||||
};
|
||||
let mut cookie = Some(id as u64);
|
||||
if !FEATURES.bpf_cookie {
|
||||
cookie.take();
|
||||
if let Err(MapError::SyscallError { call, io_error }) =
|
||||
ip_to_spec_map.insert(t.abs_ip.try_into().unwrap(), id, BPF_NOEXIST.into())
|
||||
{
|
||||
if io_error.raw_os_error().unwrap() != (-libc::EEXIST) {
|
||||
return Err(ProgramError::MapError(MapError::SyscallError {
|
||||
call,
|
||||
io_error,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
let fd = create_as_probe(ProbeKind::UProbe, &path, t.rel_ip, pid, Some(t.sem_off))?;
|
||||
let link = perf_attach(self.data.fd_or_err()?, fd, cookie)?;
|
||||
perf_links.push(link);
|
||||
}
|
||||
let link = UsdtLink(MultiPerfLink { perf_links });
|
||||
self.data.links.insert(link)
|
||||
}
|
||||
|
||||
/// Detaches the program.
|
||||
///
|
||||
/// See [UProbe::attach].
|
||||
pub fn detach(&mut self, link_id: UsdtLinkId) -> Result<(), ProgramError> {
|
||||
self.data.links.remove(link_id)
|
||||
}
|
||||
|
||||
/// Takes ownership of the link referenced by the provided link_id.
|
||||
///
|
||||
/// The link will be detached on `Drop` and the caller is now responsible
|
||||
/// for managing its lifetime.
|
||||
pub fn take_link(&mut self, link_id: UsdtLinkId) -> Result<UsdtLink, ProgramError> {
|
||||
self.data.take_link(link_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// The identifer of a MultiPerfLink.
|
||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||
pub(crate) struct MultiPerfLinkId(Vec<PerfLinkIdInner>);
|
||||
|
||||
// A wrapper around multiple PerfLinkInner
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MultiPerfLink {
|
||||
perf_links: Vec<PerfLinkInner>,
|
||||
}
|
||||
|
||||
impl Link for MultiPerfLink {
|
||||
type Id = MultiPerfLinkId;
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
let ids = self.perf_links.iter().map(|p| p.id()).collect();
|
||||
MultiPerfLinkId(ids)
|
||||
}
|
||||
|
||||
fn detach(self) -> Result<(), ProgramError> {
|
||||
for l in self.perf_links {
|
||||
l.detach()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
define_link_wrapper!(
|
||||
/// The link used by [Usdt] programs.
|
||||
UsdtLink,
|
||||
/// The type returned by [Usdt::attach]. Can be passed to [Usdt::detach].
|
||||
UsdtLinkId,
|
||||
MultiPerfLink,
|
||||
MultiPerfLinkId
|
||||
);
|
||||
|
||||
/// The type returned when attaching a [`Usdt`] fails.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UsdtError {
|
||||
/// There was an error parsing `/etc/ld.so.cache`.
|
||||
#[error("error reading `{}` file", LD_SO_CACHE_FILE)]
|
||||
InvalidLdSoCache {
|
||||
/// the original [`io::Error`]
|
||||
#[source]
|
||||
io_error: Arc<io::Error>,
|
||||
},
|
||||
|
||||
/// The target program could not be found.
|
||||
#[error("could not resolve uprobe target `{path}`")]
|
||||
InvalidTarget {
|
||||
/// path to target
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
/// There was an error resolving the target symbol.
|
||||
#[error("error resolving symbol")]
|
||||
SymbolError {
|
||||
/// symbol name
|
||||
symbol: String,
|
||||
/// the original error
|
||||
#[source]
|
||||
error: Box<dyn std::error::Error + Send + Sync>,
|
||||
},
|
||||
|
||||
/// There was an error accessing `filename`.
|
||||
#[error("`{filename}`")]
|
||||
FileError {
|
||||
/// The file name
|
||||
filename: String,
|
||||
/// The [`io::Error`] returned from the file operation
|
||||
#[source]
|
||||
io_error: io::Error,
|
||||
},
|
||||
|
||||
/// There was en error resolving a path
|
||||
#[error("error fetching libs for {pid}")]
|
||||
ProcMapError {
|
||||
/// The pid
|
||||
pid: i32,
|
||||
/// The [`ProcMapError`] that caused the error
|
||||
#[source]
|
||||
source: ProcMapError,
|
||||
},
|
||||
|
||||
/// Unsupported file type
|
||||
#[error("unsupported file type")]
|
||||
Unsupported,
|
||||
|
||||
/// An [`io::Error`]
|
||||
#[error("io error")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
/// An [`object::Error`]
|
||||
#[error("error parsing ELF")]
|
||||
Object(#[from] object::Error),
|
||||
|
||||
/// Can't find matching offset in shard libs
|
||||
#[error("can't find matching offset in shared libs")]
|
||||
OffsetError,
|
||||
|
||||
/// Section is not executable
|
||||
#[error("section is not executable")]
|
||||
NoExec,
|
||||
|
||||
/// Segment is not found
|
||||
#[error("segment not found")]
|
||||
SegmentNotFound,
|
||||
|
||||
/// BPF Cookies are not supported
|
||||
#[error("bpf cookies are required to support attachment without a pid")]
|
||||
NoCookie,
|
||||
}
|
||||
|
||||
fn collect_usdts(
|
||||
path: &str,
|
||||
provider: &str,
|
||||
name: &str,
|
||||
pid: Option<pid_t>,
|
||||
) -> Result<Vec<UsdtTarget>, UsdtError> {
|
||||
let file = fs::read(path)?;
|
||||
let data = &*file;
|
||||
if let Ok(elf) = object::elf::FileHeader32::parse(data) {
|
||||
if mem::size_of::<usize>() != 4 {
|
||||
return Err(UsdtError::Unsupported);
|
||||
}
|
||||
return collect_usdts_from_elf(elf, data, provider, name, pid);
|
||||
} else if let Ok(elf) = object::elf::FileHeader64::parse(data) {
|
||||
if mem::size_of::<usize>() != 8 {
|
||||
return Err(UsdtError::Unsupported);
|
||||
}
|
||||
return collect_usdts_from_elf(elf, data, provider, name, pid);
|
||||
}
|
||||
Err(UsdtError::Unsupported)
|
||||
}
|
||||
|
||||
fn collect_usdts_from_elf<Elf: FileHeader<Endian = Endianness>>(
|
||||
elf: &Elf,
|
||||
data: &[u8],
|
||||
provider: &str,
|
||||
name: &str,
|
||||
pid: Option<pid_t>,
|
||||
) -> Result<Vec<UsdtTarget>, UsdtError> {
|
||||
let endian = elf.endian()?;
|
||||
let sections = elf.sections(endian, data)?;
|
||||
let program_headers = elf.program_headers(endian, data)?;
|
||||
let mut results = vec![];
|
||||
let mut base_addr: Option<u64> = None;
|
||||
if let Some((_, base_section)) = sections.section_by_name(endian, b".stapsdt.base") {
|
||||
base_addr = Some(base_section.sh_addr(endian).into())
|
||||
};
|
||||
if let Some((_, notes_section)) = sections.section_by_name(endian, b".note.stapsdt") {
|
||||
if let Some(mut notes) = notes_section.notes(endian, data)? {
|
||||
while let Ok(Some(note)) = notes.next() {
|
||||
if note.name() != b"stapsdt" {
|
||||
continue;
|
||||
}
|
||||
if note.n_type(endian) != 3 {
|
||||
continue;
|
||||
}
|
||||
let note_data = note.desc();
|
||||
let n = UsdtNote::parse(endian, note_data)?;
|
||||
if n.provider != provider || n.name != name {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut abs_ip = n.loc_addr;
|
||||
if let Some(addr) = base_addr {
|
||||
abs_ip += addr - n.base_addr;
|
||||
}
|
||||
|
||||
let seg = find_segment_by_address::<Elf>(program_headers, endian, abs_ip)
|
||||
.ok_or(UsdtError::SegmentNotFound)?;
|
||||
if seg.p_flags(endian) & PF_X == 0 {
|
||||
return Err(UsdtError::NoExec);
|
||||
}
|
||||
let rel_ip = abs_ip - seg.p_vaddr(endian).into() + seg.p_offset(endian).into();
|
||||
|
||||
// If attaching to a sharef library and bpf cookies are not supported.
|
||||
// Abs address of attach points are required
|
||||
if elf.e_type(endian) == ET_DYN && !FEATURES.bpf_cookie {
|
||||
if pid.is_none() {
|
||||
return Err(UsdtError::NoCookie);
|
||||
}
|
||||
let proc_map_libs =
|
||||
ProcMap::new(pid.unwrap()).map_err(|e| UsdtError::ProcMapError {
|
||||
pid: pid.unwrap(),
|
||||
source: e,
|
||||
})?;
|
||||
let res = proc_map_libs
|
||||
.find_by_offset(rel_ip)
|
||||
.ok_or(UsdtError::OffsetError)?;
|
||||
abs_ip = res.address - res.offset + rel_ip;
|
||||
}
|
||||
|
||||
let mut sem_off = 0;
|
||||
if n.sem_addr != 0x0 {
|
||||
// semaphore refcnt support was in 4.20, which is min supported version so we assume its supported
|
||||
let seg = find_segment_by_address::<Elf>(program_headers, endian, n.sem_addr)
|
||||
.ok_or(UsdtError::SegmentNotFound)?;
|
||||
if seg.p_flags(endian) & PF_X == 0 {
|
||||
return Err(UsdtError::NoExec);
|
||||
}
|
||||
sem_off = n.sem_addr - seg.p_vaddr(endian).into() + seg.p_offset(endian).into();
|
||||
}
|
||||
let spec = n.args.parse().unwrap();
|
||||
results.push(UsdtTarget {
|
||||
abs_ip,
|
||||
rel_ip,
|
||||
sem_off,
|
||||
args: n.args,
|
||||
spec,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn find_segment_by_address<Elf: FileHeader<Endian = Endianness>>(
|
||||
program_headers: &[Elf::ProgramHeader],
|
||||
endian: Endianness,
|
||||
addr: u64,
|
||||
) -> Option<&Elf::ProgramHeader> {
|
||||
program_headers.iter().find(|&header| {
|
||||
header.p_vaddr(endian).into() < addr
|
||||
&& addr < (header.p_vaddr(endian).into() + header.p_memsz(endian).into())
|
||||
})
|
||||
}
|
||||
|
||||
// A resolved Usdt target.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UsdtTarget {
|
||||
abs_ip: u64,
|
||||
rel_ip: u64,
|
||||
sem_off: u64,
|
||||
args: String,
|
||||
spec: UsdtSpec,
|
||||
}
|
||||
|
||||
// A parsed note from an ELF stapsdt note.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UsdtNote {
|
||||
loc_addr: u64,
|
||||
base_addr: u64,
|
||||
sem_addr: u64,
|
||||
provider: String,
|
||||
name: String,
|
||||
args: String,
|
||||
}
|
||||
|
||||
impl UsdtNote {
|
||||
pub(crate) fn parse(endianness: Endianness, data: &[u8]) -> Result<UsdtNote, UsdtError> {
|
||||
let mut cursor = Cursor::new(data);
|
||||
let read_u64 = |cursor: &mut Cursor<_>| -> Result<u64, io::Error> {
|
||||
let mut buf = [0u8; mem::size_of::<u64>()];
|
||||
cursor.read_exact(&mut buf)?;
|
||||
match endianness {
|
||||
Endianness::Big => Ok(u64::from_be_bytes(buf)),
|
||||
Endianness::Little => Ok(u64::from_le_bytes(buf)),
|
||||
}
|
||||
};
|
||||
let read_string = |cursor: &mut Cursor<_>| -> Result<String, io::Error> {
|
||||
let mut buf = vec![];
|
||||
cursor.read_until(b'\0', &mut buf)?;
|
||||
Ok(CStr::from_bytes_with_nul(&buf)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string())
|
||||
};
|
||||
let loc_addr = read_u64(&mut cursor)?;
|
||||
let base_addr = read_u64(&mut cursor)?;
|
||||
let sem_addr = read_u64(&mut cursor)?;
|
||||
let provider = read_string(&mut cursor)?;
|
||||
let name = read_string(&mut cursor)?;
|
||||
let args = read_string(&mut cursor)?;
|
||||
|
||||
let res = UsdtNote {
|
||||
loc_addr,
|
||||
base_addr,
|
||||
sem_addr,
|
||||
provider,
|
||||
name,
|
||||
args,
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_stapsdt() {
|
||||
/*
|
||||
/usr/bin/mariadb: file format elf64-x86-64
|
||||
|
||||
Contents of section .note.stapsdt:
|
||||
0000 08000000 34000000 03000000 73746170 ....4.......stap
|
||||
0010 73647400 34a10d00 00000000 382e3600 sdt.4.......8.6.
|
||||
0020 00000000 00000000 00000000 6c696267 ............libg
|
||||
0030 63630075 6e77696e 64003840 25726469 cc.unwind.8@%rdi
|
||||
0040 20384025 72736900 8@%rsi
|
||||
*/
|
||||
let data: &[u8] = &[
|
||||
0x34, 0xa1, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x2e, 0x36, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x69, 0x62, 0x67,
|
||||
0x63, 0x63, 0x00, 0x75, 0x6e, 0x77, 0x69, 0x6e, 0x64, 0x00, 0x38, 0x40, 0x25, 0x72,
|
||||
0x64, 0x69, 0x20, 0x38, 0x40, 0x25, 0x72, 0x73, 0x69, 0x00,
|
||||
];
|
||||
let n = UsdtNote::parse(Endianness::Little, data).unwrap();
|
||||
assert_eq!(n.loc_addr, 0xda134);
|
||||
assert_eq!(n.base_addr, 0x362e38);
|
||||
assert_eq!(n.sem_addr, 0x0);
|
||||
assert_eq!(n.provider, "libgcc");
|
||||
assert_eq!(n.name, "unwind");
|
||||
assert_eq!(n.args, "8@%rdi 8@%rsi");
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use aya_common::{
|
||||
UsdtArgType, UsdtSpec, USDT_MAX_ARG_COUNT, USDT_MAX_IP_COUNT, USDT_MAX_SPEC_COUNT,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
helpers::{bpf_probe_read_kernel, bpf_probe_read_user},
|
||||
macros::map,
|
||||
maps::{Array, HashMap},
|
||||
BpfContext,
|
||||
};
|
||||
|
||||
// aarch64 uses user_pt_regs instead of pt_regs
|
||||
#[cfg(not(bpf_target_arch = "aarch64"))]
|
||||
use crate::bindings::pt_regs;
|
||||
#[cfg(bpf_target_arch = "aarch64")]
|
||||
use crate::bindings::user_pt_regs as pt_regs;
|
||||
|
||||
#[cfg(not(feature = "cookie"))]
|
||||
use crate::args::FromPtRegs;
|
||||
|
||||
#[map(name = "__bpf_usdt_specs")]
|
||||
static USDT_SPECS: Array<UsdtSpec> = Array::with_max_entries(USDT_MAX_SPEC_COUNT, 0);
|
||||
|
||||
#[map(name = "__bpf_usdt_ip_to_spec_id")]
|
||||
static USDT_IP_TO_SPEC_ID: HashMap<i64, u32> = HashMap::with_max_entries(USDT_MAX_IP_COUNT, 0);
|
||||
|
||||
pub struct UsdtContext {
|
||||
pub regs: *mut pt_regs,
|
||||
}
|
||||
|
||||
/// Errors from Usdt map operations
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UsdtError {
|
||||
MaxArgCount,
|
||||
SpecIdNotFound,
|
||||
ValueError,
|
||||
IpNotFound,
|
||||
}
|
||||
|
||||
impl UsdtContext {
|
||||
/// Creates a new Usdtcontext.
|
||||
pub fn new(ctx: *mut c_void) -> UsdtContext {
|
||||
UsdtContext {
|
||||
regs: ctx as *mut pt_regs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the register that holds the next instruction pointer.
|
||||
#[cfg(not(feature = "cookie"))]
|
||||
#[inline(always)]
|
||||
fn ip<T: FromPtRegs>(&self) -> Option<T> {
|
||||
T::from_ip(unsafe { &*self.regs })
|
||||
}
|
||||
|
||||
/// Access the spec_id from the BPF Attach Cookie.
|
||||
#[cfg(feature = "cookie")]
|
||||
#[inline(always)]
|
||||
fn spec_id(&self) -> Result<u32, ()> {
|
||||
unsafe { Ok(aya_bpf_bindings::helpers::bpf_get_attach_cookie(self.as_ptr()) as u32) }
|
||||
}
|
||||
|
||||
/// Access the spec_id using the `USDT_IP_TO_SPEC_ID` map
|
||||
#[cfg(not(feature = "cookie"))]
|
||||
#[inline(always)]
|
||||
fn spec_id(&self) -> Result<u32, UsdtError> {
|
||||
let ip: i64 = self.ip().ok_or(UsdtError::IpNotFound)?;
|
||||
let spec = unsafe {
|
||||
USDT_IP_TO_SPEC_ID
|
||||
.get(&ip)
|
||||
.ok_or(UsdtError::SpecIdNotFound)?
|
||||
};
|
||||
Ok(*spec)
|
||||
}
|
||||
|
||||
/// Returns the value of the USDT argument `n` as a u64.
|
||||
///
|
||||
/// This uses the USDT_SPEC_MAP to determine the correct specification to use in order
|
||||
/// to read the value of argument `n` from the eBPF Context.
|
||||
#[inline(always)]
|
||||
pub fn arg(&self, n: usize) -> Result<u64, UsdtError> {
|
||||
if n > USDT_MAX_ARG_COUNT {
|
||||
return Err(UsdtError::MaxArgCount);
|
||||
}
|
||||
let spec_id = self.spec_id().map_err(|_| UsdtError::SpecIdNotFound)?;
|
||||
let spec = USDT_SPECS.get(spec_id).ok_or(UsdtError::SpecIdNotFound)?;
|
||||
|
||||
if n > (spec.arg_count as usize) {
|
||||
return Err(UsdtError::MaxArgCount);
|
||||
}
|
||||
|
||||
let arg_spec = &spec.args[n];
|
||||
let mut val = match arg_spec.arg_type {
|
||||
UsdtArgType::Const => arg_spec.val_off,
|
||||
UsdtArgType::Reg => unsafe {
|
||||
bpf_probe_read_kernel(self.as_ptr().offset(arg_spec.reg_off as isize) as *const _)
|
||||
.map_err(|_| UsdtError::ValueError)?
|
||||
},
|
||||
UsdtArgType::RegDeref => unsafe {
|
||||
let ptr: u64 = bpf_probe_read_kernel(
|
||||
self.as_ptr().offset(arg_spec.reg_off as isize) as *const _,
|
||||
)
|
||||
.map_err(|_| UsdtError::ValueError)?;
|
||||
let ptr = ptr as *const u64;
|
||||
bpf_probe_read_user::<u64>(ptr.offset(arg_spec.val_off as isize))
|
||||
.map_err(|_| UsdtError::ValueError)?
|
||||
// TODO: libbpf applies a bitshift here if the arch is big endian
|
||||
},
|
||||
};
|
||||
|
||||
// cast arg from 1, 2, or 4 bytes to final 8 byte size clearing
|
||||
// necessary upper arg_bitshift bits, with sign extension if argument
|
||||
// is signed
|
||||
val <<= arg_spec.arg_bitshift;
|
||||
if arg_spec.arg_signed {
|
||||
val = ((val as i64) >> arg_spec.arg_bitshift) as u64
|
||||
} else {
|
||||
val >>= arg_spec.arg_bitshift;
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BpfContext for UsdtContext {
|
||||
fn as_ptr(&self) -> *mut c_void {
|
||||
self.regs as *mut c_void
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
clang \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
ssh \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
libc6-dev \
|
||||
libc6-dev-arm64-cross \
|
||||
libc6-dev-armel-cross \
|
||||
libc6-dev-riscv64-cross \
|
||||
&& \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV RUSTUP_HOME=/rust
|
||||
ENV CARGO_HOME=/cargo
|
||||
ENV PATH=/cargo/bin:/rust/bin:$PATH
|
||||
RUN echo "(curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal -c rustfmt --default-toolchain nightly --no-modify-path) && rustup default nightly" > /install-rust.sh && chmod 755 /install-rust.sh
|
||||
RUN ./install-rust.sh
|
@ -0,0 +1,72 @@
|
||||
use anyhow::anyhow;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aya_tool::{bindgen, write_to_file};
|
||||
|
||||
use crate::codegen::{Architecture, Options};
|
||||
|
||||
pub fn codegen(opts: &Options) -> Result<(), anyhow::Error> {
|
||||
codegen_bindings(opts)
|
||||
}
|
||||
|
||||
fn codegen_bindings(opts: &Options) -> Result<(), anyhow::Error> {
|
||||
let types = [
|
||||
// Registers
|
||||
"pt_regs",
|
||||
"user_pt_regs",
|
||||
];
|
||||
|
||||
let dir = PathBuf::from("aya-common");
|
||||
let generated = dir.join("src/generated");
|
||||
|
||||
let builder = || {
|
||||
bindgen::user_builder()
|
||||
.header(dir.join("include/linux_wrapper.h").to_string_lossy())
|
||||
.clang_args(&[
|
||||
"-I",
|
||||
&*opts.libbpf_dir.join("include/uapi").to_string_lossy(),
|
||||
])
|
||||
.clang_args(&["-I", &*opts.libbpf_dir.join("include").to_string_lossy()])
|
||||
};
|
||||
|
||||
for arch in Architecture::supported() {
|
||||
let mut bindgen = builder();
|
||||
|
||||
// Set target triple. This will set the right flags (which you can see
|
||||
// running clang -target=X -E - -dM </dev/null)
|
||||
let target = match arch {
|
||||
Architecture::X86_64 => "x86_64-unknown-linux-gnu",
|
||||
Architecture::ARMv7 => "armv7-unknown-linux-gnu",
|
||||
Architecture::AArch64 => "aarch64-unknown-linux-gnu",
|
||||
Architecture::RISCV64 => "riscv64-unknown-linux-gnu",
|
||||
};
|
||||
bindgen = bindgen.clang_args(&["-target", target]);
|
||||
|
||||
// Set the sysroot. This is needed to ensure that the correct arch
|
||||
// specific headers are imported.
|
||||
let sysroot = match arch {
|
||||
Architecture::X86_64 => &opts.x86_64_sysroot,
|
||||
Architecture::ARMv7 => &opts.armv7_sysroot,
|
||||
Architecture::AArch64 => &opts.aarch64_sysroot,
|
||||
Architecture::RISCV64 => &opts.riscv64_sysroot,
|
||||
};
|
||||
bindgen = bindgen.clang_args(&["-I", &*sysroot.to_string_lossy()]);
|
||||
|
||||
for x in &types {
|
||||
bindgen = bindgen.allowlist_type(x);
|
||||
}
|
||||
|
||||
let bindings = bindgen
|
||||
.generate()
|
||||
.map_err(|_| anyhow!("bindgen failed"))?
|
||||
.to_string();
|
||||
|
||||
// write the bindings, with the original helpers removed
|
||||
write_to_file(
|
||||
&generated.join(format!("linux_bindings_{}.rs", arch)),
|
||||
&bindings.to_string(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue