diff --git a/Cargo.lock b/Cargo.lock index 11cc3ce0..db19ea9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,7 @@ name = "bpf_query" version = "0.1.0" dependencies = [ "clap", + "iced-x86", "libbpf-rs", "nix", ] @@ -213,6 +214,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "iced-x86" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd366a53278429c028367e0ba22a46cab6d565a57afb959f06e92c7a69e7828" +dependencies = [ + "lazy_static", +] + [[package]] name = "instant" version = "0.1.12" diff --git a/examples/bpf_query/Cargo.toml b/examples/bpf_query/Cargo.toml index 019c5b58..5a8aae50 100644 --- a/examples/bpf_query/Cargo.toml +++ b/examples/bpf_query/Cargo.toml @@ -9,3 +9,6 @@ edition = "2021" libbpf-rs = { path = "../../libbpf-rs" } nix = { version = "0.26", default-features = false, features = ["net", "user"] } clap = { version = "4.0.32", default-features = false, features = ["std", "derive", "help", "usage"] } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +iced-x86 = "1.20.0" diff --git a/examples/bpf_query/src/main.rs b/examples/bpf_query/src/main.rs index cc720be3..79b3982f 100644 --- a/examples/bpf_query/src/main.rs +++ b/examples/bpf_query/src/main.rs @@ -4,11 +4,17 @@ use clap::Parser; use libbpf_rs::query; use nix::unistd::Uid; +#[derive(Debug, Parser)] +struct ProgArgs { + #[arg(short, long)] + disassemble: bool, +} + /// Query the system about BPF-related information #[derive(Debug, Parser)] enum Command { /// Display information about progs - Prog, + Prog(ProgArgs), /// Display information about maps Map, /// Display information about BTF @@ -17,12 +23,34 @@ enum Command { Link, } -fn prog() { - for prog in query::ProgInfoIter::default() { +fn prog(args: ProgArgs) { + let opts = query::ProgInfoQueryOptions::default().include_all(); + for prog in query::ProgInfoIter::with_query_opts(opts) { println!( "name={:<16} type={:<15} run_count={:<2} runtime_ns={}", prog.name, prog.ty, prog.run_cnt, prog.run_time_ns ); + if args.disassemble { + #[cfg(target_arch = "x86_64")] + { + use iced_x86::Formatter; + + let mut d = iced_x86::Decoder::new(32, &prog.jited_prog_insns, 0); + let mut f = iced_x86::GasFormatter::new(); + while d.can_decode() { + let ip = d.ip(); + let insn = d.decode(); + let mut f_insn = String::new(); + f.format(&insn, &mut f_insn); + println!(" {}: {}", ip, f_insn); + } + } + + #[cfg(not(target_arch = "x86_64"))] + { + println!(" Unable to disassemble on non-x86_64"); + } + } } } @@ -34,7 +62,7 @@ fn map() { fn btf() { for btf in query::BtfInfoIter::default() { - println!("id={:4} size={}", btf.id, btf.btf_size); + println!("id={:4} name={} size={}", btf.id, btf.name, btf.btf.len()); } } @@ -65,7 +93,7 @@ fn main() { let opts = Command::parse(); match opts { - Command::Prog => prog(), + Command::Prog(args) => prog(args), Command::Map => map(), Command::Btf => btf(), Command::Link => link(), diff --git a/libbpf-rs/src/query.rs b/libbpf-rs/src/query.rs index 1a27796b..573d35f4 100644 --- a/libbpf-rs/src/query.rs +++ b/libbpf-rs/src/query.rs @@ -12,7 +12,9 @@ use core::ffi::c_void; use std::convert::TryFrom; +// TODO: convert remaining instances of size_of to size_of_val for clarity use std::mem::size_of; +use std::mem::size_of_val; use std::os::raw::c_char; use std::string::String; use std::time::Duration; @@ -25,6 +27,7 @@ use crate::util; use crate::MapType; use crate::ProgramAttachType; use crate::ProgramType; +use crate::Result; macro_rules! gen_info_impl { // This magic here allows us to embed doc comments into macro expansions @@ -103,6 +106,38 @@ fn name_arr_to_string(a: &[c_char], default: &str) -> String { } } +/// BTF Line information +#[derive(Clone, Debug)] +pub struct LineInfo { + /// Offset of instruction in vector + pub insn_off: u32, + /// File name offset + pub file_name_off: u32, + /// Line offset in debug info + pub line_off: u32, + /// Line number + pub line_num: u32, + /// Line column number + pub line_col: u32, +} + +impl From<&libbpf_sys::bpf_line_info> for LineInfo { + fn from(item: &libbpf_sys::bpf_line_info) -> Self { + LineInfo { + insn_off: item.insn_off, + file_name_off: item.file_name_off, + line_off: item.line_off, + line_num: item.line_col >> 10, + line_col: item.line_col & 0x3ff, + } + } +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +/// Bpf identifier tag +pub struct Tag([u8; 8]); + /// Information about a BPF program #[derive(Debug, Clone)] // TODO: Document members. @@ -110,96 +145,298 @@ fn name_arr_to_string(a: &[c_char], default: &str) -> String { pub struct ProgramInfo { pub name: String, pub ty: ProgramType, - pub tag: [u8; 8], + pub tag: Tag, pub id: u32, - pub jited_prog_len: u32, - pub xlated_prog_len: u32, - pub jited_prog_insns: u64, - pub xlated_prog_insns: u64, + pub jited_prog_insns: Vec, + pub xlated_prog_insns: Vec, /// Duration since system boot pub load_time: Duration, pub created_by_uid: u32, - pub nr_map_ids: u32, - pub map_ids: u64, + pub map_ids: Vec, pub ifindex: u32, pub gpl_compatible: bool, pub netns_dev: u64, pub netns_ino: u64, - pub nr_jited_ksyms: u32, - pub nr_jited_func_lens: u32, - pub jited_ksyms: u64, - pub jited_func_lens: u64, + pub jited_ksyms: Vec<*const c_void>, + pub jited_func_lens: Vec, pub btf_id: u32, pub func_info_rec_size: u32, - pub func_info: u64, - pub nr_func_info: u32, - pub nr_line_info: u32, - pub line_info: u64, - pub jited_line_info: u64, - pub nr_jited_line_info: u32, + pub func_info: Vec, + pub line_info: Vec, + pub jited_line_info: Vec<*const c_void>, pub line_info_rec_size: u32, pub jited_line_info_rec_size: u32, - pub nr_prog_tags: u32, - pub prog_tags: u64, + pub prog_tags: Vec, pub run_time_ns: u64, pub run_cnt: u64, } +#[derive(Default, Debug)] +/// An iterator for the information of loaded bpf programs +pub struct ProgInfoIter { + cur_id: u32, + opts: ProgInfoQueryOptions, +} + +#[derive(Clone, Default, Debug)] +/// Options to query the program info currently loaded +pub struct ProgInfoQueryOptions { + /// Include the vector of bpf instructions in the result + include_xlated_prog_insns: bool, + /// Include the vector of jited instructions in the result + include_jited_prog_insns: bool, + /// Include the ids of maps associated with the program + include_map_ids: bool, + /// Include source line information corresponding to xlated code + include_line_info: bool, + /// Include function type information corresponding to xlated code + include_func_info: bool, + /// Include source line information corresponding to jited code + include_jited_line_info: bool, + /// Include function type information corresponding to jited code + include_jited_func_lens: bool, + /// Include program tags + include_prog_tags: bool, + /// Include the jited kernel symbols + include_jited_ksyms: bool, +} + +impl ProgInfoIter { + /// Generate an iter from more specific query options + pub fn with_query_opts(opts: ProgInfoQueryOptions) -> Self { + Self { + opts, + ..Self::default() + } + } +} + +impl ProgInfoQueryOptions { + /// Include the vector of jited bpf instructions in the result + pub fn include_xlated_prog_insns(mut self, v: bool) -> Self { + self.include_xlated_prog_insns = v; + self + } + + /// Include the vector of jited instructions in the result + pub fn include_jited_prog_insns(mut self, v: bool) -> Self { + self.include_jited_prog_insns = v; + self + } + + /// Include the ids of maps associated with the program + pub fn include_map_ids(mut self, v: bool) -> Self { + self.include_map_ids = v; + self + } + + /// Include source line information corresponding to xlated code + pub fn include_line_info(mut self, v: bool) -> Self { + self.include_line_info = v; + self + } + + /// Include function type information corresponding to xlated code + pub fn include_func_info(mut self, v: bool) -> Self { + self.include_func_info = v; + self + } + + /// Include source line information corresponding to jited code + pub fn include_jited_line_info(mut self, v: bool) -> Self { + self.include_jited_line_info = v; + self + } + + /// Include function type information corresponding to jited code + pub fn include_jited_func_lens(mut self, v: bool) -> Self { + self.include_jited_func_lens = v; + self + } + + /// Include program tags + pub fn include_prog_tags(mut self, v: bool) -> Self { + self.include_prog_tags = v; + self + } + + /// Include the jited kernel symbols + pub fn include_jited_ksyms(mut self, v: bool) -> Self { + self.include_jited_ksyms = v; + self + } + + /// Include all + pub fn include_all(self) -> Self { + self.include_xlated_prog_insns(true) + .include_jited_prog_insns(true) + .include_map_ids(true) + .include_line_info(true) + .include_func_info(true) + .include_jited_line_info(true) + .include_jited_func_lens(true) + .include_prog_tags(true) + .include_jited_ksyms(true) + } +} + impl ProgramInfo { - fn from_uapi(_fd: i32, s: libbpf_sys::bpf_prog_info) -> Option { - let name = name_arr_to_string(&s.name, "(?)"); - let ty = match ProgramType::try_from(s.type_) { + fn load_from_fd(fd: i32, opts: &ProgInfoQueryOptions) -> Result { + let mut item = libbpf_sys::bpf_prog_info::default(); + + let mut xlated_prog_insns: Vec = Vec::new(); + let mut jited_prog_insns: Vec = Vec::new(); + let mut map_ids: Vec = Vec::new(); + let mut jited_line_info: Vec<*const c_void> = Vec::new(); + let mut line_info: Vec = Vec::new(); + let mut func_info: Vec = Vec::new(); + let mut jited_func_lens: Vec = Vec::new(); + let mut prog_tags: Vec = Vec::new(); + let mut jited_ksyms: Vec<*const c_void> = Vec::new(); + + let item_ptr: *mut libbpf_sys::bpf_prog_info = &mut item; + let mut len = size_of_val(&item) as u32; + + let ret = + unsafe { libbpf_sys::bpf_obj_get_info_by_fd(fd, item_ptr as *mut c_void, &mut len) }; + util::parse_ret(ret)?; + + let name = name_arr_to_string(&item.name, "(?)"); + let ty = match ProgramType::try_from(item.type_) { Ok(ty) => ty, Err(_) => ProgramType::Unknown, }; - Some(ProgramInfo { + if opts.include_xlated_prog_insns { + xlated_prog_insns.resize(item.xlated_prog_len as usize, 0u8); + item.xlated_prog_insns = xlated_prog_insns.as_mut_ptr() as *mut c_void as u64; + } else { + item.xlated_prog_len = 0; + } + + if opts.include_jited_prog_insns { + jited_prog_insns.resize(item.jited_prog_len as usize, 0u8); + item.jited_prog_insns = jited_prog_insns.as_mut_ptr() as *mut c_void as u64; + } else { + item.jited_prog_len = 0; + } + + if opts.include_map_ids { + map_ids.resize(item.nr_map_ids as usize, 0u32); + item.map_ids = map_ids.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_map_ids = 0; + } + + if opts.include_line_info { + line_info.resize( + item.nr_line_info as usize, + libbpf_sys::bpf_line_info::default(), + ); + item.line_info = line_info.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_line_info = 0; + } + + if opts.include_func_info { + func_info.resize( + item.nr_func_info as usize, + libbpf_sys::bpf_func_info::default(), + ); + item.func_info = func_info.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_func_info = 0; + } + + if opts.include_jited_line_info { + jited_line_info.resize(item.nr_jited_line_info as usize, std::ptr::null()); + item.jited_line_info = jited_line_info.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_jited_line_info = 0; + } + + if opts.include_jited_func_lens { + jited_func_lens.resize(item.nr_jited_func_lens as usize, 0); + item.jited_func_lens = jited_func_lens.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_jited_func_lens = 0; + } + + if opts.include_prog_tags { + prog_tags.resize(item.nr_prog_tags as usize, Tag::default()); + item.prog_tags = prog_tags.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_prog_tags = 0; + } + + if opts.include_jited_ksyms { + jited_ksyms.resize(item.nr_jited_ksyms as usize, std::ptr::null()); + item.jited_ksyms = jited_ksyms.as_mut_ptr() as *mut c_void as u64; + } else { + item.nr_jited_ksyms = 0; + } + + let ret = + unsafe { libbpf_sys::bpf_obj_get_info_by_fd(fd, item_ptr as *mut c_void, &mut len) }; + util::parse_ret(ret)?; + + return Ok(ProgramInfo { name, ty, - tag: s.tag, - id: s.id, - jited_prog_len: s.jited_prog_len, - xlated_prog_len: s.xlated_prog_len, - jited_prog_insns: s.jited_prog_insns, - xlated_prog_insns: s.xlated_prog_insns, - load_time: Duration::from_nanos(s.load_time), - created_by_uid: s.created_by_uid, - nr_map_ids: s.nr_map_ids, - map_ids: s.map_ids, - ifindex: s.ifindex, - gpl_compatible: s._bitfield_1.get_bit(0), - netns_dev: s.netns_dev, - netns_ino: s.netns_ino, - nr_jited_ksyms: s.nr_jited_ksyms, - nr_jited_func_lens: s.nr_jited_func_lens, - jited_ksyms: s.jited_ksyms, - jited_func_lens: s.jited_func_lens, - btf_id: s.btf_id, - func_info_rec_size: s.func_info_rec_size, - func_info: s.func_info, - nr_func_info: s.nr_func_info, - nr_line_info: s.nr_line_info, - line_info: s.line_info, - jited_line_info: s.jited_line_info, - nr_jited_line_info: s.nr_jited_line_info, - line_info_rec_size: s.line_info_rec_size, - jited_line_info_rec_size: s.jited_line_info_rec_size, - nr_prog_tags: s.nr_prog_tags, - prog_tags: s.prog_tags, - run_time_ns: s.run_time_ns, - run_cnt: s.run_cnt, - }) + tag: Tag(item.tag), + id: item.id, + jited_prog_insns, + xlated_prog_insns, + load_time: Duration::from_nanos(item.load_time), + created_by_uid: item.created_by_uid, + map_ids, + ifindex: item.ifindex, + gpl_compatible: item._bitfield_1.get_bit(0), + netns_dev: item.netns_dev, + netns_ino: item.netns_ino, + jited_ksyms, + jited_func_lens, + btf_id: item.btf_id, + func_info_rec_size: item.func_info_rec_size, + func_info, + line_info: line_info.iter().map(|li| li.into()).collect(), + jited_line_info, + line_info_rec_size: item.line_info_rec_size, + jited_line_info_rec_size: item.jited_line_info_rec_size, + prog_tags, + run_time_ns: item.run_time_ns, + run_cnt: item.run_cnt, + }); } } -gen_info_impl!( - /// Iterator that returns [`ProgramInfo`]s. - ProgInfoIter, - ProgramInfo, - libbpf_sys::bpf_prog_info, - libbpf_sys::bpf_prog_get_next_id, - libbpf_sys::bpf_prog_get_fd_by_id -); +impl Iterator for ProgInfoIter { + type Item = ProgramInfo; + + fn next(&mut self) -> Option { + loop { + if unsafe { libbpf_sys::bpf_prog_get_next_id(self.cur_id, &mut self.cur_id) } != 0 { + return None; + } + + let fd = unsafe { libbpf_sys::bpf_prog_get_fd_by_id(self.cur_id) }; + if fd < 0 { + if errno::errno() == errno::Errno::ENOENT as i32 { + continue; + } + return None; + } + + let prog = ProgramInfo::load_from_fd(fd, &self.opts); + let _ = close(fd); + + match prog { + Ok(p) => return Some(p), + Err(e) => eprintln!("Failed to load program: {}", e), + } + } + } +} /// Information about a BPF map #[derive(Debug, Clone)] @@ -260,32 +497,84 @@ gen_info_impl!( /// Information about BPF type format #[derive(Debug, Clone)] -// TODO: Document members. #[allow(missing_docs)] pub struct BtfInfo { - pub btf: u64, - pub btf_size: u32, + /// The name associated with this btf information in the kernel + pub name: String, + /// The raw btf bytes from the kernel + pub btf: Vec, + /// The btf id associated with this btf information in the kernel pub id: u32, } impl BtfInfo { - fn from_uapi(_fd: i32, s: libbpf_sys::bpf_btf_info) -> Option { - Some(Self { - btf: s.btf, - btf_size: s.btf_size, - id: s.id, + fn load_from_fd(fd: i32) -> Result { + let mut item = libbpf_sys::bpf_btf_info::default(); + let mut btf: Vec = Vec::new(); + let mut name: Vec = Vec::new(); + + let item_ptr: *mut libbpf_sys::bpf_btf_info = &mut item; + let mut len = size_of::() as u32; + + let ret = + unsafe { libbpf_sys::bpf_obj_get_info_by_fd(fd, item_ptr as *mut c_void, &mut len) }; + util::parse_ret(ret)?; + + // The API gives you the ascii string length while expecting + // you to give it back space for a nul-terminator + item.name_len += 1; + name.resize(item.name_len as usize, 0u8); + item.name = name.as_mut_ptr() as *mut c_void as u64; + + btf.resize(item.btf_size as usize, 0u8); + item.btf = btf.as_mut_ptr() as *mut c_void as u64; + + let ret = + unsafe { libbpf_sys::bpf_obj_get_info_by_fd(fd, item_ptr as *mut c_void, &mut len) }; + util::parse_ret(ret)?; + + Ok(BtfInfo { + name: String::from_utf8(name).unwrap_or_else(|_| "(?)".to_string()), + btf, + id: item.id, }) } } -gen_info_impl!( - /// Iterator that returns [`BtfInfo`]s. - BtfInfoIter, - BtfInfo, - libbpf_sys::bpf_btf_info, - libbpf_sys::bpf_btf_get_next_id, - libbpf_sys::bpf_btf_get_fd_by_id -); +#[derive(Debug, Default)] +/// An iterator for the btf type information of modules and programs +/// in the kernel +pub struct BtfInfoIter { + cur_id: u32, +} + +impl Iterator for BtfInfoIter { + type Item = BtfInfo; + + fn next(&mut self) -> Option { + loop { + if unsafe { libbpf_sys::bpf_btf_get_next_id(self.cur_id, &mut self.cur_id) } != 0 { + return None; + } + + let fd = unsafe { libbpf_sys::bpf_btf_get_fd_by_id(self.cur_id) }; + if fd < 0 { + if errno::errno() == errno::Errno::ENOENT as i32 { + continue; + } + return None; + } + + let info = BtfInfo::load_from_fd(fd); + let _ = close(fd); + + match info { + Ok(i) => return Some(i), + Err(e) => eprintln!("Failed to load btf information: {}", e), + } + } + } +} #[derive(Debug, Clone)] // TODO: Document members.