From 0b804c092b2402c6186027d3325e36c3d9479525 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 4 Jul 2025 12:35:49 -0500 Subject: [PATCH] scripts: gdb: Added some useful GDB scripts to test.py --gdb These just invoke the existing dbg*.py python scripts, but allow quick references to variables in the debugginged process: (gdb) dbgflags o file->b.o.flags LFS3_O_RDWR 0x00000002 Open a file as read and write LFS3_o_REG 0x10000000 Type = regular-file LFS3_o_UNSYNC 0x01000000 File's metadata does not match disk Quite neat and useful! This works by injecting dbg.gdb.py via gdb -x, which includes the necessary python hooks to add these commands to gdb. This can be overridden/extended with test.py/bench.py's --gdb-script flag. Currently limited to scripts that seem the most useful for process internals: - dbgerr - Decode littlefs error codes - dbgflags - Decode littlefs flags - dbgtag - Decode littlefs tags --- scripts/bench.py | 20 ++++++++-- scripts/dbg.gdb.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++ scripts/test.py | 32 ++++++++++++--- 3 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 scripts/dbg.gdb.py diff --git a/scripts/bench.py b/scripts/bench.py index 113f986d..b0f3dfe9 100755 --- a/scripts/bench.py +++ b/scripts/bench.py @@ -40,6 +40,7 @@ RUNNER_PATH = ['./runners/bench_runner'] HEADER_PATH = 'runners/bench_runner.h' GDB_PATH = ['gdb'] +GDB_SCRIPTS = ['./scripts/dbg.gdb.py'] VALGRIND_PATH = ['valgrind'] PERF_SCRIPT = ['./scripts/perf.py'] @@ -1469,12 +1470,16 @@ def run(runner, bench_ids=[], **args): or args.get('gdb_main')): failure = failures[0] cmd = find_runner(runner, failure.id, **args) + gdb_path = args['gdb_path'] + gdb_scripts = (args.get('gdb_script') or GDB_SCRIPTS) if args.get('gdb_main'): # we don't really need the case breakpoint here, but it # can be helpful path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break main', '-ex', 'break %s:%d' % (path, lineno), @@ -1482,13 +1487,17 @@ def run(runner, bench_ids=[], **args): '--args'] elif args.get('gdb_perm'): path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break %s:%d' % (path, lineno), '-ex', 'run', '--args'] else: - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'run', '--args'] @@ -1693,6 +1702,11 @@ if __name__ == "__main__": default=GDB_PATH, help="Path to the gdb executable, may include flags. " "Defaults to %r." % GDB_PATH) + bench_parser.add_argument( + '--gdb-script', + action='append', + help="Paths to scripts to execute when dropping into gdb. " + "Defaults to %r." % GDB_SCRIPTS) bench_parser.add_argument( '-e', '--exec', type=lambda e: e.split(), diff --git a/scripts/dbg.gdb.py b/scripts/dbg.gdb.py new file mode 100644 index 00000000..6cbda5fc --- /dev/null +++ b/scripts/dbg.gdb.py @@ -0,0 +1,97 @@ +# +# hooks for gdb: +# (gdb) source ./scripts/dbg.gdb.py +# +# + + +# dbgerr +class DbgErr(gdb.Command): + """Decode littlefs error codes. See -h/--help for more info.""" + + def __init__(self): + super().__init__("dbgerr", + 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/dbgerr.py'] + args)) + +DbgErr() + + +# dbgflags +class DbgFlags(gdb.Command): + """Decode littlefs flags. See -h/--help for more info.""" + + def __init__(self): + super().__init__("dbgflags", + gdb.COMMAND_DATA, + gdb.COMPLETE_EXPRESSION) + + def invoke(self, args, *_): + args = args.split() + # hack, but don't eval if -l or --list specified + if '-l' not in args and '--list' not in args: + # 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): + # dbgflags is special in that first arg may be prefix + if i > 0 or len(nonflags) <= 1: + try: + args[n] = '%d' % gdb.parse_and_eval(args[n]) + except gdb.error as e: + raise gdb.GdbError(e) + + # execute + gdb.execute(' '.join(['!./scripts/dbgflags.py'] + args)) + +DbgFlags() + + +# 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() + diff --git a/scripts/test.py b/scripts/test.py index f48d739b..f7f757b2 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -41,6 +41,7 @@ RUNNER_PATH = ['./runners/test_runner'] HEADER_PATH = 'runners/test_runner.h' GDB_PATH = ['gdb'] +GDB_SCRIPTS = ['./scripts/dbg.gdb.py'] VALGRIND_PATH = ['valgrind'] PERF_SCRIPT = ['./scripts/perf.py'] @@ -1444,12 +1445,16 @@ def run(runner, test_ids=[], **args): or args.get('gdb_pl_after')): failure = failures[0] cmd = find_runner(runner, failure.id, **args) + gdb_path = args['gdb_path'] + gdb_scripts = (args.get('gdb_script') or GDB_SCRIPTS) if args.get('gdb_main'): # we don't really need the case breakpoint here, but it # can be helpful path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break main', '-ex', 'break %s:%d' % (path, lineno), @@ -1457,14 +1462,18 @@ def run(runner, test_ids=[], **args): '--args'] elif args.get('gdb_perm'): path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break %s:%d' % (path, lineno), '-ex', 'run', '--args'] elif args.get('gdb_pl') is not None: path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break %s:%d' % (path, lineno), '-ex', 'ignore 1 %d' % args['gdb_pl'], @@ -1477,7 +1486,9 @@ def run(runner, test_ids=[], **args): failure.id.split(':', 2)[-1])) if failure.id.count(':') >= 2 else 0) path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break %s:%d' % (path, lineno), '-ex', 'ignore 1 %d' % max(powerlosses-1, 0), @@ -1490,14 +1501,18 @@ def run(runner, test_ids=[], **args): failure.id.split(':', 2)[-1])) if failure.id.count(':') >= 2 else 0) path, lineno = find_path(runner, failure.id, **args) - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'break %s:%d' % (path, lineno), '-ex', 'ignore 1 %d' % powerlosses, '-ex', 'run', '--args'] else: - cmd[:0] = args['gdb_path'] + [ + cmd[:0] = [ + *gdb_path, + *it.chain.from_iterable(['-x', s] for s in gdb_scripts), '-q', '-ex', 'run', '--args'] @@ -1722,6 +1737,11 @@ if __name__ == "__main__": default=GDB_PATH, help="Path to the gdb executable, may include flags. " "Defaults to %r." % GDB_PATH) + test_parser.add_argument( + '--gdb-script', + action='append', + help="Paths to scripts to execute when dropping into gdb. " + "Defaults to %r." % GDB_SCRIPTS) test_parser.add_argument( '-e', '--exec', type=lambda e: e.split(),