From 29e17019645453738dbf1d093e6097a1c73b135a Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 4 Jul 2025 18:06:44 -0500 Subject: [PATCH] scripts: gdb: Globbed all dbg scripts into dbg.gdb.py This goes ahead and makes all dbg scripts available in dbg.gdb.py, via the magic of globbing __file__ relative, and dynamic python class generation. Probably one of the more evil scripts I've written, but this means we don't need to worry about dbg.gdb.py falling out-of-date when adding new dbg scripts. Not all of the dbg scripts are useful inside gdb, but most of them are. After all, what's cooler than this! (gdb) dbgrbyd -b4096 "disk" -t \ file->b.shrub.blocks[0] \ --trunk lfs3_rbyd_trunk(&file->b.shrub) rbyd 0x46.23a w2048, rev 00000000, size 629, cksum 8f5169e1 00000004: .-> 0-334 data w335 0 00000009: .-+-> 335 data w1 1 71 0000000e: | .-> 336 data w1 1 67 00000013: .-+-+-> 337 data w1 1 66 ... 00000144: | | | | .-> 350 data w1 1 74 0000019a: | | | | .-+-> 351 data w1 1 78 000001f5: | | | | | .-> 352-739 data w388 1 76 00000258: +-+-+-+-+-+-> 740-2047 data w1308 1 6c Note some tricks to help interact with bash and gdb: - Flags are passed as is (-b4096, -t, --trunk) - All non-flags are parsed as expressions (file->b.shrub.blocks[0]) - String expressions may be useful for paths and stuff ("./disk") --- scripts/dbg.gdb.py | 187 ++++++++++++++++++++++++++++----------------- 1 file changed, 115 insertions(+), 72 deletions(-) diff --git a/scripts/dbg.gdb.py b/scripts/dbg.gdb.py index 41575d7b..eaf510a3 100644 --- a/scripts/dbg.gdb.py +++ b/scripts/dbg.gdb.py @@ -1,93 +1,136 @@ # -# hooks for gdb: +# Hooks for gdb: # (gdb) source ./scripts/dbg.gdb.py # # +import shlex -# dbgerr -class DbgErr(gdb.Command): - """Decode littlefs error codes. See -h/--help for more info.""" + +# split spaces but only outside of parens and quotes +def gdbsplit(v): + parens = 0 + quote = None + escape = False + i_ = 0 + for i in range(len(v)): + if v[i].isspace() and not parens and not quote: + v_ = v[i_:i].strip() + if v_: + yield v_ + i_ = i+1 + elif quote: + if escape: + escape = False + elif v[i] == quote: + quote = None + elif v[i] == '\\': + escape = True + elif v[i] in '\'"': + quote = v[i] + elif v[i] in '([{': + parens += 1 + elif v[i] in '}])': + parens -= 1 + v_ = v[i_:].strip() + if v_: + yield v_ + +# common wrapper for dbg scripts +# +# Note some tricks to help interact with bash and gdb: +# +# - Flags are passed as is (-b4096, -t, --trunk) +# - All non-flags are parsed as expressions (file->b.shrub.blocks[0]) +# - String expressions may be useful for paths and stuff ("./disk") +# +class DbgCommand(gdb.Command): + """A littlefs debug script. See -h/--help for more info.""" + name = None + path = None def __init__(self): - super().__init__("dbgerr", + super().__init__(self.name, gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) def invoke(self, args, *_): - args = args.split() - # find nonflags - nonflags = [] - for i, a in enumerate(args): - if not a.startswith('-'): - nonflags.append(i) - # parse and eval - for i, n in enumerate(nonflags): - try: - args[n] = '%d' % gdb.parse_and_eval(args[n]) - except gdb.error as e: - raise gdb.GdbError(e) + # parse args + args = list(gdbsplit(args)) + args_ = [] + for a in args: + # pass flags as is + if a.startswith('-'): + args_.append(a) + + # parse and eval + else: + try: + v = gdb.parse_and_eval(a) + t = v.type.strip_typedefs() + if t.code in { + gdb.TYPE_CODE_ENUM, + gdb.TYPE_CODE_FLAGS, + gdb.TYPE_CODE_INT, + gdb.TYPE_CODE_RANGE, + gdb.TYPE_CODE_CHAR, + gdb.TYPE_CODE_BOOL}: + v = str(int(v)) + elif t.code in { + gdb.TYPE_CODE_FLT}: + v = str(float(v)) + else: + try: + v = v.string('utf8') + except gdb.error: + raise gdb.GdbError('Unexpected type: %s' % v.type) + except gdb.error as e: + raise gdb.GdbError(e) + + args_.append(shlex.quote(v)) + args = args_ # execute - gdb.execute(' '.join(['!./scripts/dbgerr.py'] + args)) - -DbgErr() + gdb.execute(' '.join(['!'+self.path, *args])) -# dbgflags -class DbgFlags(gdb.Command): - """Decode littlefs flags. See -h/--help for more info.""" +# at some point this was manual, then I realized I could just glob all +# scripts with this prefix +# +# # dbgerr +# class DbgErr(DbgCommand): +# name = 'dbgerr' +# path = './scripts/dbgerr.py' +# +# # dbgflags +# class DbgFlags(DbgCommand): +# name = 'dbgflags' +# path = './scripts/dbgflags.py' +# +# # dbgtag +# class DbgTag(DbgCommand): +# name = 'dbgtag' +# path = './scripts/dbgtag.py' - def __init__(self): - super().__init__("dbgflags", - gdb.COMMAND_DATA, - gdb.COMPLETE_EXPRESSION) +import os +import glob - def invoke(self, args, *_): - args = args.split() - # find nonflags - nonflags = [] - for i, a in enumerate(args): - if not a.startswith('-'): - nonflags.append(i) - # parse and eval - for i, n in enumerate(nonflags): - try: - args[n] = '%d' % gdb.parse_and_eval(args[n]) - except gdb.error as e: - raise gdb.GdbError(e) +for path in glob.glob(os.path.join( + os.path.dirname(__file__), + 'dbg*.py')): + if path == __file__: + continue - # execute - gdb.execute(' '.join(['!./scripts/dbgflags.py'] + args)) - -DbgFlags() + # create dbg class + name = os.path.splitext(os.path.basename(path))[0] + type(name, (DbgCommand,), { + 'name': name, + 'path': path + }) -# dbgtag -class DbgTag(gdb.Command): - """Decode littlefs tags. See -h/--help for more info.""" - - def __init__(self): - super().__init__("dbgtag", - gdb.COMMAND_DATA, - gdb.COMPLETE_EXPRESSION) - - def invoke(self, args, *_): - args = args.split() - # find nonflags - nonflags = [] - for i, a in enumerate(args): - if not a.startswith('-'): - nonflags.append(i) - # parse and eval - for i, n in enumerate(nonflags): - try: - args[n] = '%d' % gdb.parse_and_eval(args[n]) - except gdb.error as e: - raise gdb.GdbError(e) - - # execute - gdb.execute(' '.join(['!./scripts/dbgtag.py'] + args)) - -DbgTag() - +# initialize gdb hooks +for Dbg in DbgCommand.__subclasses__(): + if Dbg.__doc__ is None: + Dbg.__doc__ = DbgCommand.__doc__ + Dbg()