Added dbgmtree.py for debugging the littlefs metadata-tree

This builds on dbgrbyd.py and dbgbtree.py by allowing for quick
debugging of the littlefs mtree, which is a btree of rbyd pairs with a
few bells and whistles.

This also comes with a number of tweaks to dbgrbyd.py and dbgbtree.py,
mostly changing rbyd addresses to support some more mdir friendly
formats.

The syntax for rbyd addresses is starting to converge into a couple
common patterns, which is nice for quickly determining what type of
address you are looking at at a glance:

- 0x12         => An rbyd at block 0x12
- 0x12.34      => An rbyd at block 0x12 with trunk 0x34
- 0x{12,34}    => An rbyd at either block 0x12 or block 0x34 (an mdir)
- 0x{12,34}.56 => An rbyd at either block 0x12 or block 0x34 with trunk 0x56

These scripts have also been updated to support any number of blocks in
an rbyd address, for example 0x{12,34,56,78}. This is a bit of future
proofing. >2 blocks in mdirs may be explored in the future for the
increased redundancy.
This commit is contained in:
Christopher Haster
2023-05-18 21:55:02 -05:00
parent f7d4497b80
commit b67fcb0ee5
3 changed files with 993 additions and 93 deletions
+103 -44
View File
@@ -25,24 +25,41 @@ TAG_ALT = 0x0008
TAG_CRC = 0x0004
TAG_FCRC = 0x1004
# parse some rbyd addr encodings
# 0xa -> [0xa]
# 0xa.b -> ([0xa], b)
# 0x{a,b} -> [0xa, 0xb]
def rbydaddr(s):
if '.' in s:
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
s0, s1 = s.split('.', 1)
return int(s0, b), int(s1, b)
trunk = None
if '.' in s:
s, s_ = s.split('.', 1)
trunk = int(s_, b)
if s.startswith('{') and '}' in s:
ss = s[1:s.find('}')].split(',')
else:
return int(s, 0)
ss = [s]
addr = []
for s in ss:
if trunk is not None:
addr.append((int(s, b), trunk))
else:
addr.append(int(s, b))
return addr
def crc32c(data, crc=0):
crc ^= 0xffffffff
@@ -73,6 +90,13 @@ def fromtag(data):
size, d_ = fromleb128(data[2+d:])
return tag&1, tag&~1, weight, size, 2+d+d_
def frombtree(data):
w, d1 = fromleb128(data)
trunk, d2 = fromleb128(data[d1:])
block, d3 = fromleb128(data[d1+d2:])
crc = fromle32(data[d1+d2+d3:])
return w, trunk, block, crc
def popc(x):
return bin(x).count('1')
@@ -160,9 +184,47 @@ class Rbyd:
self.off = off
self.trunk = trunk
self.weight = weight
self.other_blocks = []
def addr(self):
if not self.other_blocks:
return '0x%x.%x' % (self.block, self.trunk)
else:
return '0x{%x,%s}.%x' % (
self.block,
','.join('%x' % block for block in self.other_blocks),
self.trunk)
@classmethod
def fetch(cls, f, block_size, block, trunk):
def fetch(cls, f, block_size, blocks, trunk=None):
if isinstance(blocks, int):
blocks = [blocks]
if len(blocks) > 1:
# fetch all blocks
rbyds = [cls.fetch(f, block_size, block, trunk) for block in blocks]
# determine most recent revision
i = 0
for i_, rbyd in enumerate(rbyds):
# compare with sequence arithmetic
if rbyd and (
not ((rbyd.rev - rbyds[i].rev) & 0x80000000)
or (rbyd.rev == rbyds[i].rev
and rbyd.trunk > rbyds[i].trunk)):
i = i_
# keep track of the other blocks
rbyd = rbyds[i]
rbyd.other_blocks = [rbyds[(i+1+j) % len(rbyds)].block
for j in range(len(rbyds)-1)]
return rbyd
else:
# block may encode a trunk
block = blocks[0]
if isinstance(block, tuple):
if trunk is None:
trunk = block[1]
block = block[0]
# seek to the block
f.seek(block * block_size)
data = f.read(block_size)
@@ -229,7 +291,7 @@ class Rbyd:
if not tag & 0x8:
j_ += size
return Rbyd(block, data, rev, off, trunk_, weight)
return cls(block, data, rev, off, trunk_, weight)
def lookup(self, id, tag):
if not self:
@@ -279,7 +341,7 @@ class Rbyd:
def __iter__(self):
tag = 0
id = 0
id = -1
while True:
done, id, tag, w, j, d, data = self.lookup(id, tag+0x10)
@@ -289,7 +351,7 @@ class Rbyd:
yield id, tag, w, j, d, data
def main(disk, root=0, *,
def main(disk, roots=None, *,
block_size=None,
trunk=None,
color='auto',
@@ -302,11 +364,10 @@ def main(disk, root=0, *,
else:
color = False
# root may encode a trunk
if isinstance(root, tuple):
if trunk is None:
trunk = root[1]
root = root[0]
# flatten roots, default to block 0
if not roots:
roots = [[0]]
roots = [block for roots_ in roots for block in roots_]
# we seek around a bunch, so just keep the disk open
with open(disk, 'rb') as f:
@@ -316,9 +377,9 @@ def main(disk, root=0, *,
block_size = f.tell()
# fetch the root
btree = Rbyd.fetch(f, block_size, root, trunk)
print('btree 0x%x.%x, rev %d, weight %d' % (
btree.block, btree.trunk, btree.rev, btree.weight))
btree = Rbyd.fetch(f, block_size, roots, trunk)
print('btree %s, rev %d, weight %d' % (
btree.addr(), btree.rev, btree.weight))
# look up an id, while keeping track of the search path
def lookup(id, depth=None):
@@ -366,11 +427,8 @@ def main(disk, root=0, *,
# is it another branch? continue down tree
if struct_tag == TAG_BTREE and (
depth is None or depth_ < depth):
w, d1 = fromleb128(struct_)
trunk, d2 = fromleb128(struct_[d1:])
block, d3 = fromleb128(struct_[d1+d2:])
crc = fromle32(struct_[d1+d2+d3:])
not depth or depth_ < depth):
w, trunk, block, crc = frombtree(struct_)
rbyd = Rbyd.fetch(f, block_size, block, trunk)
# corrupted? bail here so we can keep traversing the tree
@@ -461,7 +519,7 @@ def main(disk, root=0, *,
# print header
w_width = 2*m.ceil(m.log10(max(1, btree.weight)+1))+1
print('%-9s %*s%-*s %-22s %s' % (
'block',
'rbyd',
t_width, '',
w_width, 'ids',
'tag',
@@ -469,9 +527,9 @@ def main(disk, root=0, *,
if not args.get('no_truncate') else ''))
# prbyd here means the last rendered rbyd, we update
# in show_entry to always print interleaved addresses
# in dbg_entry to always print interleaved addresses
prbyd = None
def show_entry(id, w, rbyd, rid,
def dbg_entry(id, w, rbyd, rid,
name_tag, name_j, name_d, name,
struct_tag, struct_j, struct_d, struct_,
depth=None):
@@ -496,7 +554,8 @@ def main(disk, root=0, *,
'%04x.%04x:' % (rbyd.block, rbyd.trunk)
if prbyd is None or rbyd != prbyd
else '',
treerepr(id, w, not name_tag, depth) if args.get('tree') else '',
treerepr(id, w, not name_tag, depth)
if args.get('tree') else '',
w_width, '' if name_tag
else '%d-%d' % (id-(w-1), id) if w > 1
else id if w > 0
@@ -598,18 +657,18 @@ def main(disk, root=0, *,
changed = True
# show the inner entry
show_entry(id_, w_, rbyd_, rid_,
dbg_entry(id_, w_, rbyd_, rid_,
name_tag_, name_j_, name_d_, name_,
struct_tag_, struct_j_, struct_d_, struct__,
i)
# corrupted? try to keep printing the tree
if not rbyd:
print('%04x.%04x: %s%s%s%s' % (
print('%04x.%04x: %*s%s%s%s' % (
rbyd.block, rbyd.trunk,
treerepr(id, w) if args.get('tree') else '',
t_width, '',
'\x1b[31m' if color else '',
'(corrupted rbyd 0x%x.%x)' % (rbyd.block, rbyd.trunk),
'(corrupted rbyd %s)' % rbyd.addr(),
'\x1b[m' if color else ''))
prbyd = rbyd
@@ -626,7 +685,7 @@ def main(disk, root=0, *,
break
# show the entry
show_entry(id, w, rbyd, rid,
dbg_entry(id, w, rbyd, rid,
name_tag, name_j, name_d, name,
struct_tag, struct_j, struct_d, struct_)
@@ -646,10 +705,10 @@ if __name__ == "__main__":
'disk',
help="File containing the block device.")
parser.add_argument(
'root',
nargs='?',
'roots',
nargs='*',
type=rbydaddr,
help="Block address of the root of the tree.")
help="Block address of the roots of the tree.")
parser.add_argument(
'-B', '--block-size',
type=lambda x: int(x, 0),