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>
This commit is contained in:
Thomas Bertschinger 2024-05-02 17:21:04 -06:00
parent 2f3f9ff7d1
commit b560d96cdf
6 changed files with 107 additions and 79 deletions

View File

@ -86,6 +86,7 @@ void bcachefs_usage(void)
"\n" "\n"
"Debug:\n" "Debug:\n"
"These commands work on offline, unmounted filesystems\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" " dump Dump filesystem metadata to a qcow2 image\n"
" list List filesystem metadata in textual form\n" " list List filesystem metadata in textual form\n"
" list_journal List contents of journal\n" " list_journal List contents of journal\n"
@ -94,7 +95,8 @@ void bcachefs_usage(void)
" fusemount Mount a filesystem via FUSE\n" " fusemount Mount a filesystem via FUSE\n"
"\n" "\n"
"Miscellaneous:\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"); " version Display the version of the invoked bcachefs tool\n");
} }

View File

@ -103,11 +103,12 @@ fn main() {
}; };
let ret = match cmd { let ret = match cmd {
"debug" => commands::debug(args[1..].to_vec()),
"completions" => commands::completions(args[1..].to_vec()), "completions" => commands::completions(args[1..].to_vec()),
"list" => commands::list(args[1..].to_vec()), "list" => commands::list(args[1..].to_vec()),
"list_bkeys" => commands::list_bkeys(),
"mount" => commands::mount(args, symlink_cmd), "mount" => commands::mount(args, symlink_cmd),
"subvolume" => commands::subvolume(args[1..].to_vec()), "subvolume" => commands::subvolume(args[1..].to_vec()),
"debug" => commands::debug(args[1..].to_vec()),
_ => handle_c_command(args, symlink_cmd), _ => handle_c_command(args, symlink_cmd),
}; };

View File

@ -2,7 +2,6 @@
//! //!
//! This is adapted from `gimli/crates/examples/src/bin/simple.rs`. //! This is adapted from `gimli/crates/examples/src/bin/simple.rs`.
use gimli::Reader as _;
use object::{Object, ObjectSection}; use object::{Object, ObjectSection};
use std::collections::HashSet; use std::collections::HashSet;
use std::{borrow, error, fs}; 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)] #[derive(Debug)]
pub struct BchStruct { pub struct BchStruct {
name: String, name: String,
@ -53,33 +68,13 @@ pub struct BchMember {
offset: u64, 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`. // The section data that will be stored in `DwarfSections` and `DwarfPackageSections`.
#[derive(Default)] #[derive(Default)]
struct Section<'data> { struct Section<'data> {
data: borrow::Cow<'data, [u8]>, data: borrow::Cow<'data, [u8]>,
relocations: RelocationMap,
} }
// The reader type that will be stored in `Dwarf` and `DwarfPackage`. type Reader<'data> = gimli::EndianSlice<'data, gimli::RunTimeEndian>;
// If you don't need relocations, you can use `gimli::EndianSlice` directly.
type Reader<'data> =
gimli::RelocateReader<gimli::EndianSlice<'data, gimli::RunTimeEndian>, &'data RelocationMap>;
fn process_file( fn process_file(
object: &object::File, object: &object::File,
@ -99,27 +94,17 @@ fn process_file(
Ok(match object.section_by_name(name) { Ok(match object.section_by_name(name) {
Some(section) => Section { Some(section) => Section {
data: section.uncompressed_data()?, data: section.uncompressed_data()?,
relocations: section.relocation_map().map(RelocationMap)?,
}, },
None => Default::default(), 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()))?; let dwarf_sections = gimli::DwarfSections::load(|id| load_section(object, id.name()))?;
// Create `Reader`s for all of the sections and do preliminary parsing. // 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`. // 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(); let mut bkey_types = HashSet::new();
load_bkey_types(&mut bkey_types); load_bkey_types(&mut bkey_types);
@ -167,6 +152,30 @@ enum CompType {
Struct, 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( fn process_tree(
dwarf: &gimli::Dwarf<Reader>, dwarf: &gimli::Dwarf<Reader>,
unit: &gimli::Unit<Reader>, unit: &gimli::Unit<Reader>,
@ -176,15 +185,18 @@ fn process_tree(
) -> gimli::Result<()> { ) -> gimli::Result<()> {
let entry = node.entry(); let entry = node.entry();
if entry.tag() == gimli::DW_TAG_structure_type { if entry.tag() == gimli::DW_TAG_structure_type {
if let Some(name) = entry.attr(gimli::DW_AT_name)? { let name = entry_name(dwarf, unit, entry);
if let Ok(name) = dwarf.attr_string(unit, name.value()) { let Some(name) = name else { return Ok(()); };
let name = name.to_string_lossy()?.into_owned();
if bkey_types.remove(&name.clone()) { if bkey_types.remove(&name) {
let mut members: Vec<BchMember> = Vec::new(); let mut members: Vec<BchMember> = Vec::new();
process_compound_type(dwarf, unit, node, &mut members, 0, CompType::Struct)?; let parent_info = ParentInfo {
struct_list.0.push(BchStruct { name, members }); 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 { } else {
let mut children = node.children(); let mut children = node.children();
@ -200,12 +212,11 @@ fn process_compound_type(
unit: &gimli::Unit<Reader>, unit: &gimli::Unit<Reader>,
node: gimli::EntriesTreeNode<Reader>, node: gimli::EntriesTreeNode<Reader>,
members: &mut Vec<BchMember>, members: &mut Vec<BchMember>,
starting_offset: u64, parent: &ParentInfo,
comp: CompType,
) -> gimli::Result<()> { ) -> gimli::Result<()> {
let mut children = node.children(); let mut children = node.children();
while let Some(child) = children.next()? { 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(()) Ok(())
@ -239,38 +250,51 @@ fn process_comp_member(
unit: &gimli::Unit<Reader>, unit: &gimli::Unit<Reader>,
node: gimli::EntriesTreeNode<Reader>, node: gimli::EntriesTreeNode<Reader>,
members: &mut Vec<BchMember>, members: &mut Vec<BchMember>,
starting_offset: u64, parent: &ParentInfo,
comp: CompType,
) -> gimli::Result<()> { ) -> gimli::Result<()> {
let entry = node.entry().clone(); let entry = node.entry().clone();
let offset = match comp { let Some(offset) = (match parent.ty {
CompType::Union => Some(0), CompType::Union => Some(0),
CompType::Struct => entry CompType::Struct => entry
.attr(gimli::DW_AT_data_member_location)? .attr(gimli::DW_AT_data_member_location)?
.and_then(|offset| offset.value().udata_value()), .and_then(|offset| offset.value().udata_value()),
}; }) else {
let Some(offset) = offset else {
return Ok(()); return Ok(());
}; };
let name = entry_name(dwarf, unit, &entry);
if let Some((ref_type, comp)) = get_comp_ref(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))?; 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 { let Some(size) = get_size(unit, &entry) else {
return Ok(()); return Ok(());
}; };
let name = entry.attr(gimli::DW_AT_name)?;
let Some(name) = name else { return Ok(()) }; let Some(name) = name else { return Ok(()) };
let name = dwarf.attr_string(unit, name.value())?; let mut name_with_prefix = String::from(parent.member_prefix);
let name = name.to_string_lossy()?.into_owned(); name_with_prefix.push_str(&name);
members.push(BchMember { members.push(BchMember {
name, name: name_with_prefix,
offset: offset + starting_offset, offset: offset + parent.starting_offset,
size, size,
}); });
@ -285,13 +309,12 @@ fn get_size(
return size.udata_value(); return size.udata_value();
} }
if let Some(ref_type) = entry.attr(gimli::DW_AT_type).ok()? { let ref_type = entry.attr(gimli::DW_AT_type).ok()??;
if let gimli::AttributeValue::UnitRef(offset) = ref_type.value() { if let gimli::AttributeValue::UnitRef(offset) = ref_type.value() {
let mut type_entry = unit.entries_at_offset(offset).ok()?; let mut type_entry = unit.entries_at_offset(offset).ok()?;
type_entry.next_entry().ok()?; type_entry.next_entry().ok()?;
if let Some(t) = type_entry.current() { if let Some(t) = type_entry.current() {
return get_size(unit, t); return get_size(unit, t);
}
} }
} }
@ -301,21 +324,12 @@ fn get_size(
/// Return a list of the known bkey types. /// Return a list of the known bkey types.
pub fn get_bkey_type_info() -> BkeyTypes { pub fn get_bkey_type_info() -> BkeyTypes {
let path = fs::read_link("/proc/self/exe").unwrap(); let path = fs::read_link("/proc/self/exe").unwrap();
let file = fs::File::open(path).unwrap(); let file = fs::File::open(path).unwrap();
let mmap = unsafe { memmap2::Mmap::map(&file).unwrap() }; let mmap = unsafe { memmap2::Mmap::map(&file).unwrap() };
let object = object::File::parse(&*mmap).unwrap(); let object = object::File::parse(&*mmap).unwrap();
let mut struct_list = BkeyTypes::new(); let mut struct_list = BkeyTypes::new();
process_file(&object, &mut struct_list).unwrap(); 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 struct_list
} }

View File

@ -148,3 +148,9 @@ pub fn debug(argv: Vec<String>) -> i32 {
0 0
} }
pub fn list_bkeys() -> i32 {
print!("{}", bkey_types::get_bkey_type_info());
0
}

View File

@ -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) 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> { fn parse_update_cmd(input: &str) -> IResult<&str, DebugCommand> {
let (input, (_, btree, _, bpos, _, bkey, _, field, _, value)) = all_consuming(tuple(( let (input, (_, btree, _, bpos, _, bkey, _, field, _, value)) = all_consuming(tuple((
space1, space1,
@ -52,9 +56,9 @@ fn parse_update_cmd(input: &str) -> IResult<&str, DebugCommand> {
space1, space1,
parse_bpos, parse_bpos,
space1, space1,
symbol_name, bkey_name,
char('.'), char('.'),
symbol_name, field_name,
char('='), char('='),
u64, u64,
)))(input)?; )))(input)?;

View File

@ -12,6 +12,7 @@ pub use list::list;
pub use completions::completions; pub use completions::completions;
pub use subvolume::subvolume; pub use subvolume::subvolume;
pub use debug::debug; pub use debug::debug;
pub use debug::list_bkeys;
#[derive(clap::Parser, Debug)] #[derive(clap::Parser, Debug)]
#[command(name = "bcachefs")] #[command(name = "bcachefs")]