mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-02-23 00:00:02 +03:00
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:
parent
2f3f9ff7d1
commit
b560d96cdf
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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(§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()))?;
|
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(§ion.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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)?;
|
||||||
|
@ -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")]
|
||||||
|
Loading…
Reference in New Issue
Block a user