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:
+103
-44
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user