use anyhow::{bail, Context, Result};
use bpf_api::collections::Queue;
use bpf_api::probes::{AttachInfo, AttachType, Probe};
use bpf_api::prog::{Program, ProgramAttr, ProgramType};
use bpf_script::compiler::Compiler;
use bpf_script::types::{AddToTypeDatabase, TypeDatabase};
use bpf_script_derive::AddToTypeDatabase;
use btf::Btf;
use clap::Parser;
fn get_function_address(image_path: &str, function: Option<&String>, dynamic: bool) -> Result<u64> {
let file = std::fs::read(image_path).context("Could not read file.")?;
let file_data = file.as_slice();
let mut file = elf::File::open_stream(file_data).expect("Could not parse ELF Header");
let (symtab, strtab) = if dynamic {
file.dynamic_symbol_table()
.context("Failed to read symbol table")?
.context("File contained no symbol table")?
} else {
file.symbol_table()
.context("Failed to read symbol table")?
.context("File contained no symbol table")?
};
let function = if let Some(function) = function {
function
} else {
return Ok(file.ehdr.e_entry);
};
Ok(symtab
.iter()
.find(|s| {
let name = strtab
.get(s.st_name as usize)
.expect("Malformed symbol table");
name == function
})
.context("Failed to find function address")?
.st_value)
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
image_path: String,
function: Option<String>,
#[arg(short, long)]
arg: Vec<String>,
}
fn main() -> Result<()> {
let args = Args::parse();
let address = if let Ok(address) =
get_function_address(&args.image_path, args.function.as_ref(), false)
{
address
} else if let Ok(address) = get_function_address(&args.image_path, args.function.as_ref(), true)
{
address
} else {
bail!("Failed to find function address")
};
let mut database = TypeDatabase::default();
#[repr(C, align(1))]
#[derive(Copy, Clone, Debug, Default, AddToTypeDatabase)]
struct ExecEntry {
pub uid_gid: u64,
pub args: [u64; 4],
pub strs: [[u8; 32]; 4],
pub comm: [u8; 16],
}
ExecEntry::add_to_database(&mut database)
.context("Failed to add ExecEntry to type database.")?;
let btf = Btf::from_file("/sys/kernel/btf/vmlinux").context("Failed to parse BTF")?;
database
.add_btf_types(&btf)
.context("Failed to add BTF types to database.")?;
let mut compiler = Compiler::create(&database);
let queue = Queue::<ExecEntry>::with_capacity(10).expect("Failed to create BPF queue");
compiler.capture("queue", queue.get_identifier().into());
compiler
.compile(
r#"
fn(regs: &bpf_user_pt_regs_t)
entry: ExecEntry = 0
entry.uid_gid = get_current_uid_gid()
entry.args[0] = regs.di
entry.args[1] = regs.si
entry.args[2] = regs.dx
entry.args[3] = regs.cx
probe_read_str(&entry.strs[0], 32, regs.di)
probe_read_str(&entry.strs[1], 32, regs.si)
probe_read_str(&entry.strs[2], 32, regs.dx)
probe_read_str(&entry.strs[3], 32, regs.cx)
get_current_comm(&entry.comm, 16)
map_push_elem(queue, &entry, 0)
"#,
)
.context("Failed to compile script")?;
let attr = ProgramAttr {
prog_name: None,
prog_type: ProgramType::KProbe,
expected_attach_type: Some(AttachType::PerfEvent),
attach_btf_id: None,
};
let bytecode = compiler.get_bytecode();
let program = Program::create(&attr, &bytecode, None).expect("Failed to create program");
let attach_info = AttachInfo::UProbe((args.image_path, address));
let mut probe = Probe::create(attach_info);
probe.attach(&program).expect("Failed to attach program");
fn from_cstr(buf: &[u8]) -> String {
String::from_utf8_lossy(match buf.iter().position(|c| *c == 0) {
Some(p) => &buf[0..p],
None => buf,
})
.to_string()
}
fn format_arguments(args: &[String], entry: &ExecEntry) -> String {
let mut formatted = vec![];
for (i, arg_type) in args.iter().enumerate() {
match arg_type.as_str() {
"cstr" => formatted.push(format!("\"{}\"", from_cstr(&entry.strs[i]))),
"num" => formatted.push(entry.args[i].to_string()),
"hex" => formatted.push(format!("{:#0x}", entry.args[i])),
"ptr" => formatted.push(format!("{:#016x}", entry.args[i])),
fmt => panic!("Unknown formatter: {}", fmt),
}
}
formatted.join(", ")
}
println!("Reading from queue...");
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
while let Ok(entry) = queue.pop() {
let comm = from_cstr(&entry.comm);
if comm == "user-tracer" {
continue;
}
let args = format_arguments(&args.arg, &entry);
println!(
"comm={}, gid/uid={}/{}, args=[{}]",
comm,
entry.uid_gid >> 32,
entry.uid_gid as u32,
args,
);
}
}
}