Files
littlefs/scripts/dbgtag.py
Christopher Haster 7da44f12ae Added redund hints to more tags
Well, kinda. At the moment we don't have any reund support (it's a
TODO), so arguably redund=0 and this is just a comment tweak.

Though our mdirs _are_ already redund=1... so maybe these should
actually set redund=1?

It's unclear, so for now I've just tweaked the comment, and we should
probably revisit when _actually_ implementing meta/data redundancy.

---

Note this only really affects struct tags:

  LFS3_TAG_STRUCT         0x04tt  v--- -1-- +ttt tttt
  LFS3_TAG_BRANCH         0x040r  v--- -1-- +--- --rr
  LFS3_TAG_DATA           0x0404  v--- -1-- +--- -1rr
  LFS3_TAG_BLOCK          0x0408  v--- -1-- +--- 1err
  LFS3_TAG_DDKEY*         0x0410  v--- -1-- +--1 --rr
  LFS3_TAG_DID            0x0420  v--- -1-- +-1- ----
  LFS3_TAG_BSHRUB         0x0428  v--- -1-- +-1- 1-rr
  LFS3_TAG_BTREE          0x042c  v--- -1-- +-1- 11rr
  LFS3_TAG_MROOT          0x0431  v--- -1-- +-11 --rr
  LFS3_TAG_MDIR           0x0435  v--- -1-- +-11 -1rr
  LFS3_TAG_MSHRUB+        0x0438  v--- -1-- +-11 1-rr
  LFS3_TAG_MTREE          0x043c  v--- -1-- +-11 11rr
  LFS3_TAG_BMRANGE        0x044u  v--- -1-- +1-- ++uu
  LFS3_TAG_BMFREE         0x0440  v--- -1-- +1-- ----
  LFS3_TAG_BMINUSE        0x0441  v--- -1-- +1-- ---1
  LFS3_TAG_BMERASED       0x0442  v--- -1-- +1-- --1-
  LFS3_TAG_BMBAD          0x0443  v--- -1-- +1-- --11
  LFS3_TAG_DDRC*          0x0450  v--- -1-- +1-1 ----
  LFS3_TAG_DDPCOEFF*      0x0451  v--- -1-- +1-1 ---1
  LFs3_TAG_PCOEFFMAP*     0x0460  v--- -1-- +11- ----

This redund hint may be useful for debugging and the theoretical
CKMETAREDUND feature.
2025-11-18 00:58:18 -06:00

394 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
# prevent local imports
if __name__ == "__main__":
__import__('sys').path.pop(0)
import functools as ft
import io
import math as mt
import os
import struct
import sys
TAG_NULL = 0x0000 ## v--- ---- +--- ----
TAG_INTERNAL = 0x0000 ## v--- ---- +ttt tttt
TAG_CONFIG = 0x0100 ## v--- ---1 +ttt tttt
TAG_MAGIC = 0x0131 # v--- ---1 +-11 --rr
TAG_VERSION = 0x0134 # v--- ---1 +-11 -1--
TAG_RCOMPAT = 0x0135 # v--- ---1 +-11 -1-1
TAG_WCOMPAT = 0x0136 # v--- ---1 +-11 -11-
TAG_OCOMPAT = 0x0137 # v--- ---1 +-11 -111
TAG_GEOMETRY = 0x0138 # v--- ---1 +-11 1---
TAG_NAMELIMIT = 0x0139 # v--- ---1 +-11 1--1
TAG_FILELIMIT = 0x013a # v--- ---1 +-11 1-1-
TAG_GDELTA = 0x0200 ## v--- --1- +ttt tttt
TAG_GRMDELTA = 0x0230 # v--- --1- +-11 --++
TAG_GBMAPDELTA = 0x0234 # v--- --1- +-11 -1rr
TAG_NAME = 0x0300 ## v--- --11 +ttt tttt
TAG_BNAME = 0x0300 # v--- --11 +--- ----
TAG_REG = 0x0301 # v--- --11 +--- ---1
TAG_DIR = 0x0302 # v--- --11 +--- --1-
TAG_STICKYNOTE = 0x0303 # v--- --11 +--- --11
TAG_BOOKMARK = 0x0304 # v--- --11 +--- -1--
TAG_MNAME = 0x0330 # v--- --11 +-11 ----
TAG_STRUCT = 0x0400 ## v--- -1-- +ttt tttt
TAG_BRANCH = 0x0400 # v--- -1-- +--- --rr
TAG_DATA = 0x0404 # v--- -1-- +--- -1rr
TAG_BLOCK = 0x0408 # v--- -1-- +--- 1err
TAG_DID = 0x0420 # v--- -1-- +-1- ----
TAG_BSHRUB = 0x0428 # v--- -1-- +-1- 1-rr
TAG_BTREE = 0x042c # v--- -1-- +-1- 11rr
TAG_MROOT = 0x0431 # v--- -1-- +-11 --rr
TAG_MDIR = 0x0435 # v--- -1-- +-11 -1rr
TAG_MTREE = 0x043c # v--- -1-- +-11 11rr
TAG_BMRANGE = 0x0440 # v--- -1-- +1-- ++uu
TAG_BMFREE = 0x0440 # v--- -1-- +1-- ----
TAG_BMINUSE = 0x0441 # v--- -1-- +1-- ---1
TAG_BMERASED = 0x0442 # v--- -1-- +1-- --1-
TAG_BMBAD = 0x0443 # v--- -1-- +1-- --11
TAG_ATTR = 0x0600 ## v--- -11a +aaa aaaa
TAG_UATTR = 0x0600 # v--- -11- +aaa aaaa
TAG_SATTR = 0x0700 # v--- -111 +aaa aaaa
TAG_SHRUB = 0x1000 ## v--1 kkkk +kkk kkkk
TAG_ALT = 0x4000 ## v1cd kkkk +kkk kkkk
TAG_B = 0x0000
TAG_R = 0x2000
TAG_LE = 0x0000
TAG_GT = 0x1000
TAG_CKSUM = 0x3000 ## v-11 ---- ++++ +pqq
TAG_PHASE = 0x0003
TAG_PERTURB = 0x0004
TAG_NOTE = 0x3100 ## v-11 ---1 ++++ ++++
TAG_ECKSUM = 0x3200 ## v-11 --1- ++++ ++++
TAG_GCKSUMDELTA = 0x3300 ## v-11 --11 ++++ ++++
# self-parsing tag repr
class Tag:
def __init__(self, name, tag, encoding, help):
self.name = name
self.tag = tag
self.encoding = encoding
self.help = help
# derive mask from encoding
self.mask = sum(
(1 if x in 'v-01' else 0) << len(self.encoding)-1-i
for i, x in enumerate(self.encoding))
def __repr__(self):
return 'Tag(%r, %r, %r)' % (
self.name,
self.tag,
self.encoding)
def __eq__(self, other):
return self.name == other.name
def __ne__(self, other):
return self.name != other.name
def __hash__(self):
return hash(self.name)
def line(self):
# substitute mask chars when zero
tag = '0x%s' % ''.join(
n if n != '0' else next(
(x for x in self.encoding[i*4:i*4+4]
if x not in 'v-01+'),
'0')
for i, n in enumerate('%04x' % self.tag))
# group into nibbles
encoding = ' '.join(self.encoding[i*4:i*4+4]
for i in range(len(self.encoding)//4))
return ('LFS3_%s' % self.name, tag, encoding)
def specificity(self):
return sum(1 for x in self.encoding if x in 'v-01')
def matches(self, tag):
return (tag & self.mask) == (self.tag & self.mask)
def get(self, chars, tag):
return sum(
tag & ((1 if x in chars else 0) << len(self.encoding)-1-i)
for i, x in enumerate(self.encoding))
def max(self, chars):
return max(len(self.encoding)-1-i
for i, x in enumerate(self.encoding) if x in chars)
def min(self, chars):
return min(len(self.encoding)-1-i
for i, x in enumerate(self.encoding) if x in chars)
def width(self, chars):
return self.max(chars) - self.min(chars)
def __contains__(self, chars):
return any(x in self.encoding for x in chars)
@staticmethod
@ft.cache
def tags():
# parse our script's source to figure out tags
import inspect
import re
tags = []
tag_pattern = re.compile(
'^(?P<name>TAG_[^ ]*) *= *(?P<tag>[^#]*?) *'
'#+ *(?P<encoding>(?:[^ ] *?){16}) *(?P<help>.*)$')
for line in (inspect.getsource(
inspect.getmodule(inspect.currentframe()))
.replace('\\\n', '')
.splitlines()):
m = tag_pattern.match(line)
if m:
tags.append(Tag(
m.group('name'),
globals()[m.group('name')],
m.group('encoding').replace(' ', ''),
m.group('help')))
return tags
# find best matching tag
@staticmethod
def find(tag):
# find tags, note this is cached
tags__ = Tag.tags()
# find the most specific matching tag, ignoring valid bits
return max((t for t in tags__ if t.matches(tag & 0x7fff)),
key=lambda t: t.specificity(),
default=None)
# human readable tag repr
@staticmethod
def repr(tag, weight=None, size=None, *,
global_=False,
toff=None):
# find the most specific matching tag, ignoring the shrub bit
t = Tag.find(tag & ~(TAG_SHRUB if tag & 0x7000 == TAG_SHRUB else 0))
# build repr
r = []
# normal tag?
if not tag & TAG_ALT:
if t is not None:
# prefix shrub tags with shrub
if tag & 0x7000 == TAG_SHRUB:
r.append('shrub')
# lowercase name
r.append(t.name.split('_', 1)[1].lower())
# gstate tag?
if global_:
if r[-1] == 'gdelta':
r[-1] = 'gstate'
elif r[-1].endswith('delta'):
r[-1] = r[-1][:-len('delta')]
# include perturb/phase bits
if 'q' in t:
r.append('q%d' % t.get('q', tag))
if 'p' in t and tag & TAG_PERTURB:
r.append('p')
# include unmatched fields, but not just redund, and
# only reserved bits if non-zero
if 'tua' in t or ('+' in t and t.get('+', tag) != 0):
r.append(' 0x%0*x' % (
(t.width('tuar+')+4-1)//4,
t.get('tuar+', tag)))
# unknown tag?
else:
r.append('0x%04x' % tag)
# weight?
if weight:
r.append(' w%d' % weight)
# size? don't include if null
if size is not None and (size or tag & 0x7fff):
r.append(' %d' % size)
# alt pointer?
else:
r.append('alt')
r.append('r' if tag & TAG_R else 'b')
r.append('gt' if tag & TAG_GT else 'le')
r.append(' 0x%0*x' % (
(t.width('k')+4-1)//4,
t.get('k', tag)))
# weight?
if weight is not None:
r.append(' w%d' % weight)
# jump?
if size and toff is not None:
r.append(' 0x%x' % (0xffffffff & (toff-size)))
elif size:
r.append(' -%d' % size)
return ''.join(r)
# open with '-' for stdin/stdout
def openio(path, mode='r', buffering=-1):
import os
if path == '-':
if 'r' in mode:
return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
else:
return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
else:
return open(path, mode, buffering)
def fromleb128(data, j=0):
word = 0
d = 0
while j+d < len(data):
b = data[j+d]
word |= (b & 0x7f) << 7*d
word &= 0xffffffff
if not b & 0x80:
return word, d+1
d += 1
return word, d
def fromtag(data, j=0):
d = 0
tag = struct.unpack('>H', data[j:j+2].ljust(2, b'\0'))[0]; d += 2
weight, d_ = fromleb128(data, j+d); d += d_
size, d_ = fromleb128(data, j+d); d += d_
return tag>>15, tag&0x7fff, weight, size, d
def list_tags():
# find tags
tags__ = Tag.tags()
# list
lines = []
for t in tags__:
lines.append(t.line())
# figure out widths
w = [0, 0]
for l in lines:
w[0] = max(w[0], len(l[0]))
w[1] = max(w[1], len(l[1]))
# then print results
for l in lines:
print('%-*s %-*s %s' % (
w[0], l[0],
w[1], l[1],
l[2]))
def dbg_tags(data, *,
word_bits=32):
# figure out tag size in bytes
if word_bits != 0:
n = 2 + 2*mt.ceil(word_bits / 7)
lines = []
# interpret as ints?
if not isinstance(data, bytes):
for tag in data:
lines.append((
' '.join('%02x' % b for b in struct.pack('>H', tag)),
Tag.repr(tag)))
# interpret as bytes?
else:
j = 0
while j < len(data):
# bounded tags?
if word_bits != 0:
v, tag, w, size, d = fromtag(data[j:j+n])
# unbounded?
else:
v, tag, w, size, d = fromtag(data, j)
lines.append((
' '.join('%02x' % b for b in data[j:j+d]),
Tag.repr(tag, w, size)))
j += d
# skip attached data if there is any
if not tag & TAG_ALT:
j += size
# figure out widths
w = [0]
for l in lines:
w[0] = max(w[0], len(l[0]))
# then print results
for l in lines:
print('%-*s %s' % (
w[0], l[0],
l[1]))
def main(tags, *,
list=False,
hex=False,
input=None,
word_bits=32):
import builtins
list_, list = list, builtins.list
hex_, hex = hex, builtins.hex
# list all known tags
if list_:
list_tags()
# interpret as a sequence of hex bytes
elif hex_:
bytes_ = [b for tag in tags for b in tag.split()]
dbg_tags(bytes(int(b, 16) for b in bytes_),
word_bits=word_bits)
# parse tags in a file
elif input:
with openio(input, 'rb') as f:
dbg_tags(f.read(),
word_bits=word_bits)
# default to interpreting as ints
else:
dbg_tags((int(tag, 0) for tag in tags),
word_bits=word_bits)
if __name__ == "__main__":
import argparse
import sys
parser = argparse.ArgumentParser(
description="Decode littlefs tags.",
allow_abbrev=False)
parser.add_argument(
'tags',
nargs='*',
help="Tags to decode.")
parser.add_argument(
'-l', '--list',
action='store_true',
help="List all known tags.")
parser.add_argument(
'-x', '--hex',
action='store_true',
help="Interpret as a sequence of hex bytes.")
parser.add_argument(
'-i', '--input',
help="Read tags from this file. Can use - for stdin.")
parser.add_argument(
'-w', '--word-bits',
nargs='?',
type=lambda x: int(x, 0),
const=0,
help="Word size in bits. 0 is unbounded. Defaults to 32.")
sys.exit(main(**{k: v
for k, v in vars(parser.parse_intermixed_args()).items()
if v is not None}))