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"
"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,6 +95,7 @@ void bcachefs_usage(void)
" fusemount Mount a filesystem via FUSE\n"
"\n"
"Miscellaneous:\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");
}

View File

@ -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),
};

View File

@ -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,16 +185,19 @@ 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 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();
process_compound_type(dwarf, unit, node, &mut members, 0, CompType::Struct)?;
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();
while let Some(child) = children.next()? {
@ -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,7 +309,7 @@ fn get_size(
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() {
let mut type_entry = unit.entries_at_offset(offset).ok()?;
type_entry.next_entry().ok()?;
@ -293,7 +317,6 @@ fn get_size(
return get_size(unit, t);
}
}
}
None
}
@ -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
}

View File

@ -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
}

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)
}
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)?;

View File

@ -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")]