summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Bertschinger <tahbertschinger@gmail.com>2024-05-02 17:21:04 -0600
committerThomas Bertschinger <tahbertschinger@gmail.com>2024-05-07 21:29:32 -0400
commitb560d96cdf0c3a38c30148ab597a483b323e9d84 (patch)
tree17a86d87bfdb2e8400a96f9ec48b7ec8008d4dee
parent2f3f9ff7d1f871668cf9b0fad18e42266789ef28 (diff)
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 <tahbertschinger@gmail.com>
-rw-r--r--c_src/bcachefs.c4
-rw-r--r--src/bcachefs.rs3
-rw-r--r--src/commands/debug/bkey_types.rs162
-rw-r--r--src/commands/debug/mod.rs6
-rw-r--r--src/commands/debug/parser.rs10
-rw-r--r--src/commands/mod.rs1
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<u64> {
- Ok(self.0.relocate(offset as u64, value))
- }
-
- fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result<usize> {
- <usize as gimli::ReaderOffset>::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<gimli::EndianSlice<'data, gimli::RunTimeEndian>, &'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(&section.data), endian);
- gimli::RelocateReader::new(slice, &section.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(&section.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<Reader>,
+ unit: &gimli::Unit<Reader>,
+ entry: &gimli::DebuggingInformationEntry<Reader>,
+) -> Option<String> {
+ 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<Reader>,
unit: &gimli::Unit<Reader>,
@@ -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<BchMember> = 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<BchMember> = 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<Reader>,
node: gimli::EntriesTreeNode<Reader>,
members: &mut Vec<BchMember>,
- 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<Reader>,
node: gimli::EntriesTreeNode<Reader>,
members: &mut Vec<BchMember>,
- 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<String>) -> 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")]