mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-02-23 00:00:02 +03:00
WIP: bcachefs: new debug command
Signed-off-by: Thomas Bertschinger <tahbertschinger@gmail.com>
This commit is contained in:
parent
fbb2233089
commit
16f2849433
158
Cargo.lock
generated
158
Cargo.lock
generated
@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
@ -83,8 +89,12 @@ dependencies = [
|
||||
"colored",
|
||||
"either",
|
||||
"errno 0.2.8",
|
||||
"gimli",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2",
|
||||
"nom",
|
||||
"object",
|
||||
"rpassword",
|
||||
"udev",
|
||||
"uuid",
|
||||
@ -124,7 +134,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
"which",
|
||||
]
|
||||
|
||||
@ -228,7 +238,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -253,12 +263,38 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
@ -290,12 +326,45 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"indexmap",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@ -311,6 +380,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@ -376,6 +455,15 @@ version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.8.0"
|
||||
@ -391,6 +479,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -401,6 +498,17 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"memchr",
|
||||
"ruzstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@ -426,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -516,18 +624,52 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruzstd"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"derive_more",
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
@ -549,6 +691,16 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "udev"
|
||||
version = "0.7.0"
|
||||
|
@ -23,3 +23,7 @@ either = "1.5"
|
||||
rpassword = "7"
|
||||
bch_bindgen = { path = "bch_bindgen" }
|
||||
byteorder = "1.3"
|
||||
gimli = "0.29.0"
|
||||
object = "0.35.0"
|
||||
memmap2 = "0.9.4"
|
||||
nom = "7.1.3"
|
||||
|
115
c_src/cmd_debug.c
Normal file
115
c_src/cmd_debug.c
Normal file
@ -0,0 +1,115 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libbcachefs/bkey_types.h"
|
||||
#include "libbcachefs/btree_update.h"
|
||||
#include "libbcachefs/printbuf.h"
|
||||
#include "libbcachefs/inode.h"
|
||||
|
||||
#include "cmds.h"
|
||||
|
||||
void write_field(void *base, u64 size, u64 offset, u64 value)
|
||||
{
|
||||
u8 *field8;
|
||||
u16 *field16;
|
||||
u32 *field32;
|
||||
u64 *field64;
|
||||
|
||||
switch (size) {
|
||||
case 1:
|
||||
field8 = (u8 *) base + offset;
|
||||
*field8 = (u8) value;
|
||||
break;
|
||||
case 2:
|
||||
field16 = (u16 *) ((u8 *) base + offset);
|
||||
*field16 = (u16) value;
|
||||
break;
|
||||
case 4:
|
||||
field32 = (u32 *) ((u8 *) base + offset);
|
||||
*field32 = (u32) value;
|
||||
break;
|
||||
case 8:
|
||||
field64 = (u64 *) ((u8 *) base + offset);
|
||||
*field64 = value;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "can't handle size %llu\n", size);
|
||||
}
|
||||
}
|
||||
|
||||
int cmd_dump_bkey(struct bch_fs *c, enum btree_id id, struct bpos pos)
|
||||
{
|
||||
struct printbuf buf = PRINTBUF;
|
||||
struct btree_trans *trans = bch2_trans_get(c);
|
||||
struct btree_iter iter = { NULL };
|
||||
int ret = 0;
|
||||
|
||||
bch2_trans_iter_init(trans, &iter, id, pos, BTREE_ITER_ALL_SNAPSHOTS);
|
||||
|
||||
struct bkey_s_c k = bch2_btree_iter_peek(&iter);
|
||||
if (!bpos_eq(pos, k.k->p)) {
|
||||
printf("no such key\n");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
bch2_bkey_val_to_text(&buf, c, k);
|
||||
printf("%s\n", buf.buf);
|
||||
|
||||
out:
|
||||
bch2_trans_iter_exit(trans, &iter);
|
||||
bch2_trans_put(trans);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cmd_update_bkey(struct bch_fs *c, struct bkey_update u, struct bpos pos)
|
||||
{
|
||||
struct btree_trans *trans = bch2_trans_get(c);
|
||||
struct btree_iter iter = { NULL };
|
||||
int ret = 0;
|
||||
|
||||
set_bit(BCH_FS_no_invalid_checks, &c->flags);
|
||||
|
||||
if (!strcmp(u.bkey, "bch_inode_unpacked")) {
|
||||
bch2_trans_iter_init(trans, &iter, BTREE_ID_inodes, pos,
|
||||
BTREE_ITER_ALL_SNAPSHOTS);
|
||||
|
||||
struct bkey_s_c k = bch2_btree_iter_peek(&iter);
|
||||
if (bkey_err(k)) {
|
||||
// TODO: is this proper error handling?
|
||||
printf("error getting key: %s\n", bch2_err_str(PTR_ERR(k.k)));
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
struct bch_inode_unpacked inode;
|
||||
// TODO: check error
|
||||
bch2_inode_unpack(k, &inode);
|
||||
|
||||
write_field(&inode, u.size, u.offset, u.value);
|
||||
|
||||
ret = bch2_inode_write(trans, &iter, &inode) ?:
|
||||
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_invalid_checks);
|
||||
if (ret != 0) {
|
||||
printf("ret = %s (%d)\n", bch2_err_str(ret), ret);
|
||||
}
|
||||
} else {
|
||||
// TODO can this return an error?
|
||||
struct bkey_i *k = bch2_bkey_get_mut(trans, &iter, u.id, pos,
|
||||
BTREE_ITER_ALL_SNAPSHOTS);
|
||||
// TODO: check bkey type to confirm it matches specified type?
|
||||
bch2_trans_unlock(trans);
|
||||
|
||||
write_field(&k->v, u.size, u.offset, u.value);
|
||||
|
||||
ret = bch2_btree_insert(c, u.id, k, NULL, BCH_TRANS_COMMIT_no_invalid_checks);
|
||||
if (ret != 0) {
|
||||
printf("ret = %s (%d)\n", bch2_err_str(ret), ret);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
bch2_trans_iter_exit(trans, &iter);
|
||||
bch2_trans_put(trans);
|
||||
|
||||
return ret;
|
||||
}
|
11
c_src/cmds.h
11
c_src/cmds.h
@ -9,6 +9,14 @@
|
||||
|
||||
#include "tools-util.h"
|
||||
|
||||
struct bkey_update {
|
||||
enum btree_id id;
|
||||
const char *bkey;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
int cmd_format(int argc, char *argv[]);
|
||||
int cmd_show_super(int argc, char *argv[]);
|
||||
int cmd_reset_counters(int argc, char *argv[]);
|
||||
@ -54,6 +62,9 @@ int cmd_subvolume_snapshot(int argc, char *argv[]);
|
||||
|
||||
int cmd_fusemount(int argc, char *argv[]);
|
||||
|
||||
int cmd_dump_bkey(struct bch_fs *, enum btree_id, struct bpos);
|
||||
int cmd_update_bkey(struct bch_fs *, struct bkey_update, struct bpos);
|
||||
|
||||
void bcachefs_usage(void);
|
||||
int device_cmds(int argc, char *argv[]);
|
||||
int fs_cmds(int argc, char *argv[]);
|
||||
|
@ -1,11 +1,11 @@
|
||||
mod wrappers;
|
||||
mod commands;
|
||||
mod key;
|
||||
mod wrappers;
|
||||
|
||||
use std::ffi::{c_char, CString};
|
||||
|
||||
use commands::logger::SimpleLogger;
|
||||
use bch_bindgen::c;
|
||||
use commands::logger::SimpleLogger;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ErrnoError(pub errno::Errno);
|
||||
@ -25,10 +25,7 @@ fn handle_c_command(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
|
||||
|
||||
let argc: i32 = argv.len().try_into().unwrap();
|
||||
|
||||
let argv: Vec<_> = argv
|
||||
.into_iter()
|
||||
.map(|s| CString::new(s).unwrap())
|
||||
.collect();
|
||||
let argv: Vec<_> = argv.into_iter().map(|s| CString::new(s).unwrap()).collect();
|
||||
let mut argv = argv
|
||||
.into_iter()
|
||||
.map(|s| Box::into_raw(s.into_boxed_c_str()) as *mut c_char)
|
||||
@ -41,7 +38,7 @@ fn handle_c_command(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
|
||||
"--help" => {
|
||||
c::bcachefs_usage();
|
||||
0
|
||||
},
|
||||
}
|
||||
"data" => c::data_cmds(argc, argv),
|
||||
"device" => c::device_cmds(argc, argv),
|
||||
"dump" => c::cmd_dump(argc, argv),
|
||||
@ -110,6 +107,7 @@ fn main() {
|
||||
"list" => commands::list(args[1..].to_vec()),
|
||||
"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),
|
||||
};
|
||||
|
||||
|
289
src/commands/debug/bkey_types.rs
Normal file
289
src/commands/debug/bkey_types.rs
Normal file
@ -0,0 +1,289 @@
|
||||
//! Representation of the bcachefs bkey types, derived from DWARF debug info.
|
||||
//!
|
||||
//! This is adapted from `gimli/crates/examples/src/bin/simple.rs`.
|
||||
|
||||
use gimli::Reader as _;
|
||||
use object::{Object, ObjectSection};
|
||||
use std::{borrow, error, fs};
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// A list of the known bcachefs bkey types.
|
||||
#[derive(Debug)]
|
||||
pub struct BkeyTypes(Vec<BchStruct>);
|
||||
|
||||
impl BkeyTypes {
|
||||
pub fn new() -> Self {
|
||||
BkeyTypes(Vec::new())
|
||||
}
|
||||
|
||||
/// Given a struct name and a member name, return the size and offset of
|
||||
/// the member within the struct, or None if it does not exist.
|
||||
pub fn get_member_layout(&self, outer: &str, member: &str) -> Option<(u64, u64)> {
|
||||
for bkey_type in self.0.iter() {
|
||||
if bkey_type.name == *outer {
|
||||
return bkey_type.member_layout(member);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BchStruct {
|
||||
name: String,
|
||||
pub members: Vec<BchMember>,
|
||||
}
|
||||
|
||||
impl BchStruct {
|
||||
pub fn member_layout(&self, name: &str) -> Option<(u64, u64)> {
|
||||
for memb in self.members.iter() {
|
||||
if memb.name == *name {
|
||||
return Some((memb.size, memb.offset));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BchMember {
|
||||
name: String,
|
||||
size: 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`.
|
||||
#[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>;
|
||||
|
||||
fn process_file(
|
||||
object: &object::File,
|
||||
struct_list: &mut BkeyTypes,
|
||||
) -> Result<(), Box<dyn error::Error>> {
|
||||
let endian = if object.is_little_endian() {
|
||||
gimli::RunTimeEndian::Little
|
||||
} else {
|
||||
gimli::RunTimeEndian::Big
|
||||
};
|
||||
|
||||
// Load a `Section` that may own its data.
|
||||
fn load_section<'data>(
|
||||
object: &object::File<'data>,
|
||||
name: &str,
|
||||
) -> Result<Section<'data>, Box<dyn error::Error>> {
|
||||
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 mut bkey_types = HashSet::new();
|
||||
load_bkey_types(&mut bkey_types);
|
||||
|
||||
let mut iter = dwarf.units();
|
||||
while let Some(header) = iter.next()? {
|
||||
let unit = dwarf.unit(header)?;
|
||||
process_unit(&dwarf, &unit, struct_list, &mut bkey_types)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_bkey_types(bkey_types: &mut HashSet<String>) {
|
||||
let mut ptr: *const *const i8 = unsafe { bch_bindgen::c::bch2_bkey_types.as_ptr() };
|
||||
unsafe {
|
||||
while !(*ptr).is_null() {
|
||||
let mut bkey_name = String::from("bch_");
|
||||
bkey_name.push_str(std::ffi::CStr::from_ptr(*ptr).to_str().unwrap());
|
||||
bkey_types.insert(bkey_name);
|
||||
ptr = ptr.offset(1);
|
||||
}
|
||||
}
|
||||
|
||||
// This key type is not included in BCH2_BKEY_TYPES.
|
||||
bkey_types.insert("bch_inode_unpacked".to_string());
|
||||
}
|
||||
|
||||
fn process_unit(
|
||||
dwarf: &gimli::Dwarf<Reader>,
|
||||
unit: &gimli::Unit<Reader>,
|
||||
struct_list: &mut BkeyTypes,
|
||||
bkey_types: &mut HashSet<String>,
|
||||
) -> Result<(), gimli::Error> {
|
||||
let mut tree = unit.entries_tree(None)?;
|
||||
|
||||
process_tree(dwarf, unit, tree.root()?, struct_list, bkey_types)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_tree(
|
||||
dwarf: &gimli::Dwarf<Reader>,
|
||||
unit: &gimli::Unit<Reader>,
|
||||
node: gimli::EntriesTreeNode<Reader>,
|
||||
struct_list: &mut BkeyTypes,
|
||||
bkey_types: &mut HashSet<String>,
|
||||
) -> 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()) {
|
||||
process_struct(name, dwarf, unit, node, struct_list)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut children = node.children();
|
||||
while let Some(child) = children.next()? {
|
||||
process_tree(dwarf, unit, child, struct_list, bkey_types)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_struct(
|
||||
name: std::string::String,
|
||||
dwarf: &gimli::Dwarf<Reader>,
|
||||
unit: &gimli::Unit<Reader>,
|
||||
node: gimli::EntriesTreeNode<Reader>,
|
||||
struct_list: &mut BkeyTypes,
|
||||
) -> gimli::Result<()> {
|
||||
let mut bch_struct = BchStruct {
|
||||
name,
|
||||
members: Vec::new(),
|
||||
};
|
||||
|
||||
let mut children = node.children();
|
||||
while let Some(child) = children.next()? {
|
||||
if let Some(member) = process_struct_member(dwarf, unit, child) {
|
||||
bch_struct.members.push(member);
|
||||
}
|
||||
}
|
||||
struct_list.0.push(bch_struct);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_struct_member(
|
||||
dwarf: &gimli::Dwarf<Reader>,
|
||||
unit: &gimli::Unit<Reader>,
|
||||
node: gimli::EntriesTreeNode<Reader>,
|
||||
) -> Option<BchMember> {
|
||||
let entry = node.entry();
|
||||
|
||||
let name: Option<String> = entry.attr(gimli::DW_AT_name).ok()?.map(|name| {
|
||||
if let Ok(name) = dwarf.attr_string(unit, name.value()) {
|
||||
Some(name.to_string_lossy().ok()?.into_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
let Some(name) = name else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let offset: Option<u64> = entry
|
||||
.attr(gimli::DW_AT_data_member_location)
|
||||
.ok()?
|
||||
.map(|offset| offset.value().udata_value())?;
|
||||
let Some(offset) = offset else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let size = entry.attr(gimli::DW_AT_type).ok()?.map(|ty| {
|
||||
if let gimli::AttributeValue::UnitRef(offset) = ty.value() {
|
||||
let mut ty_entry = unit.entries_at_offset(offset).ok()?;
|
||||
ty_entry.next_entry().ok()?;
|
||||
if let Some(t) = ty_entry.current() {
|
||||
return get_size(unit, t);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})?;
|
||||
let Some(size) = size else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(BchMember { name, offset, size })
|
||||
}
|
||||
|
||||
fn get_size(
|
||||
unit: &gimli::Unit<Reader>,
|
||||
entry: &gimli::DebuggingInformationEntry<Reader>,
|
||||
) -> Option<u64> {
|
||||
if let Some(size) = entry.attr(gimli::DW_AT_byte_size).ok()? {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
struct_list
|
||||
}
|
108
src/commands/debug/mod.rs
Normal file
108
src/commands/debug/mod.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use clap::Parser;
|
||||
use std::ffi::{c_char, CString};
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use bch_bindgen::bcachefs;
|
||||
use bch_bindgen::c;
|
||||
use bch_bindgen::fs::Fs;
|
||||
|
||||
mod bkey_types;
|
||||
mod parser;
|
||||
|
||||
use bch_bindgen::c::bpos;
|
||||
|
||||
/// Debug a bcachefs filesystem.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Cli {
|
||||
#[arg(required(true))]
|
||||
devices: Vec<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DebugCommand {
|
||||
Dump(DumpCommand),
|
||||
Update(UpdateCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DumpCommand {
|
||||
btree: String,
|
||||
bpos: bpos,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UpdateCommand {
|
||||
btree: String,
|
||||
bpos: bpos,
|
||||
bkey: String,
|
||||
field: String,
|
||||
value: u64,
|
||||
}
|
||||
|
||||
fn update(fs: &Fs, type_list: &bkey_types::BkeyTypes, cmd: UpdateCommand) {
|
||||
let bkey = CString::new(cmd.bkey.clone()).unwrap();
|
||||
let bkey = bkey.as_ptr() as *const c_char;
|
||||
|
||||
let id: bch_bindgen::c::btree_id = cmd.btree.parse().expect("no such btree");
|
||||
|
||||
if let Some((size, offset)) = type_list.get_member_layout(&cmd.bkey, &cmd.field) {
|
||||
let update = c::bkey_update {
|
||||
id,
|
||||
bkey,
|
||||
offset,
|
||||
size,
|
||||
value: cmd.value,
|
||||
};
|
||||
unsafe {
|
||||
c::cmd_update_bkey(fs.raw, update, cmd.bpos);
|
||||
}
|
||||
} else {
|
||||
println!("unknown field '{}'", cmd.field);
|
||||
}
|
||||
}
|
||||
|
||||
fn dump(fs: &Fs, cmd: DumpCommand) {
|
||||
let id: bch_bindgen::c::btree_id = cmd.btree.parse().expect("no such btree");
|
||||
|
||||
unsafe {
|
||||
c::cmd_dump_bkey(fs.raw, id, cmd.bpos);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug(argv: Vec<String>) -> i32 {
|
||||
fn prompt() {
|
||||
print!("bcachefs> ");
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
|
||||
let opt = Cli::parse_from(argv);
|
||||
|
||||
let fs_opts: bcachefs::bch_opts = Default::default();
|
||||
let fs = match Fs::open(&opt.devices, fs_opts) {
|
||||
Ok(fs) => fs,
|
||||
Err(_) => {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let type_list = bkey_types::get_bkey_type_info();
|
||||
|
||||
prompt();
|
||||
let stdin = std::io::stdin();
|
||||
for line in stdin.lock().lines() {
|
||||
let line = line.unwrap();
|
||||
if let Some(cmd) = parser::parse_command(&line) {
|
||||
match cmd {
|
||||
// usage: dump <btree_type> <bpos>
|
||||
DebugCommand::Dump(cmd) => dump(&fs, cmd),
|
||||
// usage: update <btree_type> <bpos> <bkey_type>.<field>=<value>
|
||||
DebugCommand::Update(cmd) => update(&fs, &type_list, cmd),
|
||||
}
|
||||
} else {
|
||||
println!("failed to parse a command");
|
||||
};
|
||||
prompt();
|
||||
}
|
||||
|
||||
0
|
||||
}
|
89
src/commands/debug/parser.rs
Normal file
89
src/commands/debug/parser.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{tag, take_while};
|
||||
use nom::character::complete::{alpha1, char, space1, u32, u64};
|
||||
use nom::combinator::{all_consuming, value};
|
||||
use nom::sequence::tuple;
|
||||
use nom::IResult;
|
||||
|
||||
use bch_bindgen::c::bpos;
|
||||
|
||||
use crate::commands::debug::{DebugCommand, DumpCommand, UpdateCommand};
|
||||
|
||||
fn parse_bpos(input: &str) -> IResult<&str, bpos> {
|
||||
let (input, (inode, _, offset, _, snapshot)) = tuple((
|
||||
u64,
|
||||
char(':'),
|
||||
u64,
|
||||
char(':'),
|
||||
alt((u32, value(u32::MAX, tag("U32_MAX")))),
|
||||
))(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
bpos {
|
||||
inode,
|
||||
offset,
|
||||
snapshot,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_dump_cmd(input: &str) -> IResult<&str, DebugCommand> {
|
||||
let (input, (_, btree, _, bpos)) =
|
||||
all_consuming(tuple((space1, alpha1, space1, parse_bpos)))(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
DebugCommand::Dump(DumpCommand {
|
||||
btree: btree.to_string(),
|
||||
bpos,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
fn symbol_name(input: &str) -> IResult<&str, &str> {
|
||||
take_while(|c: char| c.is_alphabetic() || c == '_')(input)
|
||||
}
|
||||
|
||||
fn parse_update_cmd(input: &str) -> IResult<&str, DebugCommand> {
|
||||
let (input, (_, btree, _, bpos, _, bkey, _, field, _, value)) = all_consuming(tuple((
|
||||
space1,
|
||||
alpha1,
|
||||
space1,
|
||||
parse_bpos,
|
||||
space1,
|
||||
symbol_name,
|
||||
char('.'),
|
||||
symbol_name,
|
||||
char('='),
|
||||
u64,
|
||||
)))(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
DebugCommand::Update(UpdateCommand {
|
||||
btree: btree.to_string(),
|
||||
bpos,
|
||||
bkey: bkey.to_string(),
|
||||
field: field.to_string(),
|
||||
value,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_command_inner(input: &str) -> IResult<&str, DebugCommand> {
|
||||
let (input, cmd) = alt((tag("dump"), tag("update")))(input)?;
|
||||
|
||||
match cmd {
|
||||
"dump" => parse_dump_cmd(input),
|
||||
"update" => parse_update_cmd(input),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_command(input: &str) -> Option<DebugCommand> {
|
||||
match parse_command_inner(input) {
|
||||
Ok((_, c)) => Some(c),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
@ -5,11 +5,13 @@ pub mod mount;
|
||||
pub mod list;
|
||||
pub mod completions;
|
||||
pub mod subvolume;
|
||||
pub mod debug;
|
||||
|
||||
pub use mount::mount;
|
||||
pub use list::list;
|
||||
pub use completions::completions;
|
||||
pub use subvolume::subvolume;
|
||||
pub use debug::debug;
|
||||
|
||||
#[derive(clap::Parser, Debug)]
|
||||
#[command(name = "bcachefs")]
|
||||
|
Loading…
Reference in New Issue
Block a user