From b560d96cdf0c3a38c30148ab597a483b323e9d84 Mon Sep 17 00:00:00 2001 From: Thomas Bertschinger Date: Thu, 2 May 2024 17:21:04 -0600 Subject: [PATCH] add "list_bkeys" command; also handle members of named embedded structs previously only anonymous embedded structs worked right also various refactorings Signed-off-by: Thomas Bertschinger --- c_src/bcachefs.c | 4 +- src/bcachefs.rs | 3 +- src/commands/debug/bkey_types.rs | 162 +++++++++++++++++-------------- src/commands/debug/mod.rs | 6 ++ src/commands/debug/parser.rs | 10 +- src/commands/mod.rs | 1 + 6 files changed, 107 insertions(+), 79 deletions(-) diff --git a/c_src/bcachefs.c b/c_src/bcachefs.c index c5b61097..9001d7f8 100644 --- a/c_src/bcachefs.c +++ b/c_src/bcachefs.c @@ -86,6 +86,7 @@ void bcachefs_usage(void) "\n" "Debug:\n" "These commands work on offline, unmounted filesystems\n" + " debug Operate directly on the underlying btrees of a filesystem\n" " dump Dump filesystem metadata to a qcow2 image\n" " list List filesystem metadata in textual form\n" " list_journal List contents of journal\n" @@ -94,7 +95,8 @@ void bcachefs_usage(void) " fusemount Mount a filesystem via FUSE\n" "\n" "Miscellaneous:\n" - " completions Generate shell completions\n" + " list_bkeys List all bkey types known to the current bcachefs version\n" + " completions Generate shell completions\n" " version Display the version of the invoked bcachefs tool\n"); } diff --git a/src/bcachefs.rs b/src/bcachefs.rs index dbe79578..39f69177 100644 --- a/src/bcachefs.rs +++ b/src/bcachefs.rs @@ -103,11 +103,12 @@ fn main() { }; let ret = match cmd { + "debug" => commands::debug(args[1..].to_vec()), "completions" => commands::completions(args[1..].to_vec()), "list" => commands::list(args[1..].to_vec()), + "list_bkeys" => commands::list_bkeys(), "mount" => commands::mount(args, symlink_cmd), "subvolume" => commands::subvolume(args[1..].to_vec()), - "debug" => commands::debug(args[1..].to_vec()), _ => handle_c_command(args, symlink_cmd), }; diff --git a/src/commands/debug/bkey_types.rs b/src/commands/debug/bkey_types.rs index 5178af5b..a0c6a1a3 100644 --- a/src/commands/debug/bkey_types.rs +++ b/src/commands/debug/bkey_types.rs @@ -2,7 +2,6 @@ //! //! This is adapted from `gimli/crates/examples/src/bin/simple.rs`. -use gimli::Reader as _; use object::{Object, ObjectSection}; use std::collections::HashSet; use std::{borrow, error, fs}; @@ -29,6 +28,22 @@ impl BkeyTypes { } } +impl std::fmt::Display for BkeyTypes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for bkey in self.0.iter() { + for memb in bkey.members.iter() { + writeln!( + f, + "{} {} {} {}", + bkey.name, memb.name, memb.size, memb.offset + )?; + } + writeln!(f)?; + } + Ok(()) + } +} + #[derive(Debug)] pub struct BchStruct { name: String, @@ -53,33 +68,13 @@ pub struct BchMember { offset: u64, } -// This is a simple wrapper around `object::read::RelocationMap` that implements -// `gimli::read::Relocate` for use with `gimli::RelocateReader`. -// You only need this if you are parsing relocatable object files. -#[derive(Debug, Default)] -struct RelocationMap(object::read::RelocationMap); - -impl<'a> gimli::read::Relocate for &'a RelocationMap { - fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result { - Ok(self.0.relocate(offset as u64, value)) - } - - fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result { - ::from_u64(self.0.relocate(offset as u64, value as u64)) - } -} - // The section data that will be stored in `DwarfSections` and `DwarfPackageSections`. #[derive(Default)] struct Section<'data> { data: borrow::Cow<'data, [u8]>, - relocations: RelocationMap, } -// The reader type that will be stored in `Dwarf` and `DwarfPackage`. -// If you don't need relocations, you can use `gimli::EndianSlice` directly. -type Reader<'data> = - gimli::RelocateReader, &'data RelocationMap>; +type Reader<'data> = gimli::EndianSlice<'data, gimli::RunTimeEndian>; fn process_file( object: &object::File, @@ -99,27 +94,17 @@ fn process_file( Ok(match object.section_by_name(name) { Some(section) => Section { data: section.uncompressed_data()?, - relocations: section.relocation_map().map(RelocationMap)?, }, None => Default::default(), }) } - // Borrow a `Section` to create a `Reader`. - fn borrow_section<'data>( - section: &'data Section<'data>, - endian: gimli::RunTimeEndian, - ) -> Reader<'data> { - let slice = gimli::EndianSlice::new(borrow::Cow::as_ref(§ion.data), endian); - gimli::RelocateReader::new(slice, §ion.relocations) - } - - // Load all of the sections. let dwarf_sections = gimli::DwarfSections::load(|id| load_section(object, id.name()))?; // Create `Reader`s for all of the sections and do preliminary parsing. // Alternatively, we could have used `Dwarf::load` with an owned type such as `EndianRcSlice`. - let dwarf = dwarf_sections.borrow(|section| borrow_section(section, endian)); + let dwarf = dwarf_sections + .borrow(|section| gimli::EndianSlice::new(borrow::Cow::as_ref(§ion.data), endian)); let mut bkey_types = HashSet::new(); load_bkey_types(&mut bkey_types); @@ -167,6 +152,30 @@ enum CompType { Struct, } +/// Used to keep track of info needed for structs that contain +/// other compound types. +struct ParentInfo<'a> { + ty: CompType, + starting_offset: u64, + member_prefix: &'a str, +} + +fn entry_name( + dwarf: &gimli::Dwarf, + unit: &gimli::Unit, + entry: &gimli::DebuggingInformationEntry, +) -> Option { + entry.attr(gimli::DW_AT_name).ok()?.and_then(|name| { + Some( + dwarf + .attr_string(unit, name.value()) + .ok()? + .to_string_lossy() + .into_owned(), + ) + }) +} + fn process_tree( dwarf: &gimli::Dwarf, unit: &gimli::Unit, @@ -176,15 +185,18 @@ fn process_tree( ) -> gimli::Result<()> { let entry = node.entry(); if entry.tag() == gimli::DW_TAG_structure_type { - if let Some(name) = entry.attr(gimli::DW_AT_name)? { - if let Ok(name) = dwarf.attr_string(unit, name.value()) { - let name = name.to_string_lossy()?.into_owned(); - if bkey_types.remove(&name.clone()) { - let mut members: Vec = Vec::new(); - process_compound_type(dwarf, unit, node, &mut members, 0, CompType::Struct)?; - struct_list.0.push(BchStruct { name, members }); - } - } + let name = entry_name(dwarf, unit, entry); + let Some(name) = name else { return Ok(()); }; + + if bkey_types.remove(&name) { + let mut members: Vec = Vec::new(); + let parent_info = ParentInfo { + ty: CompType::Struct, + starting_offset: 0, + member_prefix: "", + }; + process_compound_type(dwarf, unit, node, &mut members, &parent_info)?; + struct_list.0.push(BchStruct { name, members }); } } else { let mut children = node.children(); @@ -200,12 +212,11 @@ fn process_compound_type( unit: &gimli::Unit, node: gimli::EntriesTreeNode, members: &mut Vec, - starting_offset: u64, - comp: CompType, + parent: &ParentInfo, ) -> gimli::Result<()> { let mut children = node.children(); while let Some(child) = children.next()? { - process_comp_member(dwarf, unit, child, members, starting_offset, comp)?; + process_comp_member(dwarf, unit, child, members, parent)?; } Ok(()) @@ -239,38 +250,51 @@ fn process_comp_member( unit: &gimli::Unit, node: gimli::EntriesTreeNode, members: &mut Vec, - starting_offset: u64, - comp: CompType, + parent: &ParentInfo, ) -> gimli::Result<()> { let entry = node.entry().clone(); - let offset = match comp { + let Some(offset) = (match parent.ty { CompType::Union => Some(0), CompType::Struct => entry .attr(gimli::DW_AT_data_member_location)? .and_then(|offset| offset.value().udata_value()), - }; - let Some(offset) = offset else { + }) else { return Ok(()); }; + let name = entry_name(dwarf, unit, &entry); + if let Some((ref_type, comp)) = get_comp_ref(unit, &entry) { + let prefix = if let Some(ref name) = name { + let mut prefix = name.clone(); + prefix.push('.'); + prefix + } else { + String::from("") + }; + let parent = ParentInfo { + ty: comp, + starting_offset: offset, + member_prefix: &prefix, + }; let mut tree = unit.entries_tree(Some(ref_type))?; - process_compound_type(dwarf, unit, tree.root()?, members, offset, comp)?; + process_compound_type(dwarf, unit, tree.root()?, members, &parent)?; + + return Ok(()); }; let Some(size) = get_size(unit, &entry) else { return Ok(()); }; - let name = entry.attr(gimli::DW_AT_name)?; let Some(name) = name else { return Ok(()) }; - let name = dwarf.attr_string(unit, name.value())?; - let name = name.to_string_lossy()?.into_owned(); + let mut name_with_prefix = String::from(parent.member_prefix); + name_with_prefix.push_str(&name); members.push(BchMember { - name, - offset: offset + starting_offset, + name: name_with_prefix, + offset: offset + parent.starting_offset, size, }); @@ -285,13 +309,12 @@ fn get_size( return size.udata_value(); } - if let Some(ref_type) = entry.attr(gimli::DW_AT_type).ok()? { - if let gimli::AttributeValue::UnitRef(offset) = ref_type.value() { - let mut type_entry = unit.entries_at_offset(offset).ok()?; - type_entry.next_entry().ok()?; - if let Some(t) = type_entry.current() { - return get_size(unit, t); - } + let ref_type = entry.attr(gimli::DW_AT_type).ok()??; + if let gimli::AttributeValue::UnitRef(offset) = ref_type.value() { + let mut type_entry = unit.entries_at_offset(offset).ok()?; + type_entry.next_entry().ok()?; + if let Some(t) = type_entry.current() { + return get_size(unit, t); } } @@ -301,21 +324,12 @@ fn get_size( /// Return a list of the known bkey types. pub fn get_bkey_type_info() -> BkeyTypes { let path = fs::read_link("/proc/self/exe").unwrap(); - let file = fs::File::open(path).unwrap(); let mmap = unsafe { memmap2::Mmap::map(&file).unwrap() }; let object = object::File::parse(&*mmap).unwrap(); + let mut struct_list = BkeyTypes::new(); process_file(&object, &mut struct_list).unwrap(); - /* - for s in struct_list.0.iter() { - for m in s.members.iter() { - println!("{} {} {} {}", s.name, m.name, m.offset, m.size); - } - println!(""); - } - */ - struct_list } diff --git a/src/commands/debug/mod.rs b/src/commands/debug/mod.rs index 0a88cbb5..2f2ff644 100644 --- a/src/commands/debug/mod.rs +++ b/src/commands/debug/mod.rs @@ -148,3 +148,9 @@ pub fn debug(argv: Vec) -> i32 { 0 } + +pub fn list_bkeys() -> i32 { + print!("{}", bkey_types::get_bkey_type_info()); + + 0 +} diff --git a/src/commands/debug/parser.rs b/src/commands/debug/parser.rs index 79fadc61..ca23c50c 100644 --- a/src/commands/debug/parser.rs +++ b/src/commands/debug/parser.rs @@ -41,10 +41,14 @@ fn parse_dump_cmd(input: &str) -> IResult<&str, DebugCommand> { )) } -fn symbol_name(input: &str) -> IResult<&str, &str> { +fn bkey_name(input: &str) -> IResult<&str, &str> { take_while(|c: char| c.is_alphabetic() || c == '_')(input) } +fn field_name(input: &str) -> IResult<&str, &str> { + take_while(|c: char| c.is_alphabetic() || c == '_' || c == '.')(input) +} + fn parse_update_cmd(input: &str) -> IResult<&str, DebugCommand> { let (input, (_, btree, _, bpos, _, bkey, _, field, _, value)) = all_consuming(tuple(( space1, @@ -52,9 +56,9 @@ fn parse_update_cmd(input: &str) -> IResult<&str, DebugCommand> { space1, parse_bpos, space1, - symbol_name, + bkey_name, char('.'), - symbol_name, + field_name, char('='), u64, )))(input)?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 831aaf5a..31257190 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -12,6 +12,7 @@ pub use list::list; pub use completions::completions; pub use subvolume::subvolume; pub use debug::debug; +pub use debug::list_bkeys; #[derive(clap::Parser, Debug)] #[command(name = "bcachefs")]