From 64c46e8c1ba8bfb4e87c59d38d98802011e552f6 Mon Sep 17 00:00:00 2001 From: Benign X <1341398182@qq.com> Date: Fri, 6 Mar 2026 02:28:13 +0800 Subject: [PATCH] chore(gdb): add LVFsDrv wrapper with driver name resolution and dump fs_drv command --- scripts/gdb/lvglgdb/__init__.py | 2 + scripts/gdb/lvglgdb/cmds/__init__.py | 2 + scripts/gdb/lvglgdb/cmds/misc/__init__.py | 2 + scripts/gdb/lvglgdb/cmds/misc/lv_fs.py | 16 +++ scripts/gdb/lvglgdb/lvgl/__init__.py | 2 + scripts/gdb/lvglgdb/lvgl/core/lv_global.py | 6 + scripts/gdb/lvglgdb/lvgl/misc/__init__.py | 2 + scripts/gdb/lvglgdb/lvgl/misc/lv_fs.py | 124 +++++++++++++++++++++ scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py | 67 +++++++++++ 9 files changed, 223 insertions(+) create mode 100644 scripts/gdb/lvglgdb/cmds/misc/lv_fs.py create mode 100644 scripts/gdb/lvglgdb/lvgl/misc/lv_fs.py create mode 100644 scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py diff --git a/scripts/gdb/lvglgdb/__init__.py b/scripts/gdb/lvglgdb/__init__.py index c961be86b7..ed9d79a025 100644 --- a/scripts/gdb/lvglgdb/__init__.py +++ b/scripts/gdb/lvglgdb/__init__.py @@ -36,6 +36,7 @@ from .lvgl import ( EVENT_CODE_NAMES, LVAnim, LVTimer, + LVFsDrv, ) from . import cmds as cmds @@ -75,4 +76,5 @@ __all__ = [ "EVENT_CODE_NAMES", "LVAnim", "LVTimer", + "LVFsDrv", ] diff --git a/scripts/gdb/lvglgdb/cmds/__init__.py b/scripts/gdb/lvglgdb/cmds/__init__.py index c569257ec7..3c16e420f8 100644 --- a/scripts/gdb/lvglgdb/cmds/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/__init__.py @@ -10,6 +10,7 @@ from .misc import ( CheckCache, DumpAnim, DumpTimer, + DumpFsDrv, ) from .debugger import Debugger from .drivers import Lvglobal @@ -34,6 +35,7 @@ CheckPrefix() CheckCache() DumpAnim() DumpTimer() +DumpFsDrv() DumpDrawTask() # Infos diff --git a/scripts/gdb/lvglgdb/cmds/misc/__init__.py b/scripts/gdb/lvglgdb/cmds/misc/__init__.py index 1ba9fb86c8..7c3de9b818 100644 --- a/scripts/gdb/lvglgdb/cmds/misc/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/misc/__init__.py @@ -2,6 +2,7 @@ from .lv_style import InfoStyle from .lv_cache import DumpCache, CheckPrefix, CheckCache from .lv_anim import DumpAnim from .lv_timer import DumpTimer +from .lv_fs import DumpFsDrv __all__ = [ "InfoStyle", @@ -10,4 +11,5 @@ __all__ = [ "CheckCache", "DumpAnim", "DumpTimer", + "DumpFsDrv", ] diff --git a/scripts/gdb/lvglgdb/cmds/misc/lv_fs.py b/scripts/gdb/lvglgdb/cmds/misc/lv_fs.py new file mode 100644 index 0000000000..8369ef15bb --- /dev/null +++ b/scripts/gdb/lvglgdb/cmds/misc/lv_fs.py @@ -0,0 +1,16 @@ +import gdb + +from lvglgdb.lvgl import curr_inst +from lvglgdb.lvgl.misc.lv_fs import LVFsDrv + + +class DumpFsDrv(gdb.Command): + """dump all registered filesystem drivers""" + + def __init__(self): + super(DumpFsDrv, self).__init__( + "dump fs_drv", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION + ) + + def invoke(self, args, from_tty): + LVFsDrv.print_entries(curr_inst().fs_drivers()) diff --git a/scripts/gdb/lvglgdb/lvgl/__init__.py b/scripts/gdb/lvglgdb/lvgl/__init__.py index 5c07ec2f07..91690bdb01 100644 --- a/scripts/gdb/lvglgdb/lvgl/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/__init__.py @@ -37,6 +37,7 @@ from .misc import ( EVENT_CODE_NAMES, LVAnim, LVTimer, + LVFsDrv, ) __all__ = [ @@ -80,4 +81,5 @@ __all__ = [ "EVENT_CODE_NAMES", "LVAnim", "LVTimer", + "LVFsDrv", ] diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py index edf22885f7..92501a6ec4 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py @@ -73,7 +73,13 @@ class LVGL: for timer in LVList(self.lv_global.timer_state.timer_ll, "lv_timer_t"): yield LVTimer(timer) + def fs_drivers(self): + from ..misc.lv_fs import LVFsDrv + # fsdrv_ll stores lv_fs_drv_t* pointers (not inline structs) + pp_type = gdb.lookup_type("lv_fs_drv_t").pointer().pointer() + for drv_pp in LVList(self.lv_global.fsdrv_ll, pp_type): + yield LVFsDrv(drv_pp.dereference()) def image_header_cache(self): from ..misc.lv_image_header_cache import LVImageHeaderCache diff --git a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py index 10e2f8fd4e..9233105dc0 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py @@ -25,6 +25,7 @@ from .lv_event import ( ) from .lv_anim import LVAnim from .lv_timer import LVTimer +from .lv_fs import LVFsDrv __all__ = [ "LVList", @@ -55,4 +56,5 @@ __all__ = [ "EVENT_CODE_NAMES", "LVAnim", "LVTimer", + "LVFsDrv", ] diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_fs.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_fs.py new file mode 100644 index 0000000000..5d27f257b7 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_fs.py @@ -0,0 +1,124 @@ +from prettytable import PrettyTable + +from lvglgdb.value import Value, ValueInput +from .lv_utils import resolve_source_name, build_global_field_map + + +class LVFsDrv(Value): + """LVGL filesystem driver wrapper""" + + def __init__(self, drv: ValueInput): + super().__init__(Value.normalize(drv, "lv_fs_drv_t")) + + @property + def letter(self) -> str: + return chr(int(self.super_value("letter"))) + + # Lazily built address -> name map shared across all instances + _addr_map = None + + @property + def driver_name(self) -> str: + """Infer driver name from lv_global_t field or open_cb source file.""" + if LVFsDrv._addr_map is None: + m = build_global_field_map("lv_fs_drv_t") + LVFsDrv._addr_map = { + addr: name.replace("_fs_drv", "") for addr, name in m.items() + } + # Try lv_global_t field match first (no debug info needed) + name = LVFsDrv._addr_map.get(int(self), None) + if name: + return name + # Fallback: resolve from open_cb source file (needs debug info) + return resolve_source_name(int(self.open_cb), prefix="lv_fs_") or "unknown" + + @property + def cache_size(self) -> int: + return int(self.super_value("cache_size")) + + @property + def ready_cb(self) -> Value: + return self.super_value("ready_cb") + + @property + def open_cb(self) -> Value: + return self.super_value("open_cb") + + @property + def close_cb(self) -> Value: + return self.super_value("close_cb") + + @property + def read_cb(self) -> Value: + return self.super_value("read_cb") + + @property + def write_cb(self) -> Value: + return self.super_value("write_cb") + + @property + def seek_cb(self) -> Value: + return self.super_value("seek_cb") + + @property + def tell_cb(self) -> Value: + return self.super_value("tell_cb") + + @property + def dir_open_cb(self) -> Value: + return self.super_value("dir_open_cb") + + @property + def dir_read_cb(self) -> Value: + return self.super_value("dir_read_cb") + + @property + def dir_close_cb(self) -> Value: + return self.super_value("dir_close_cb") + + @property + def user_data(self) -> Value: + return self.super_value("user_data") + + @staticmethod + def _fmt_cb(cb: Value) -> str: + addr = int(cb) + if not addr: + return "-" + return cb.format_string(symbols=True).replace("\x00", "") + + @staticmethod + def print_entries(drivers): + """Print filesystem drivers as a PrettyTable.""" + table = PrettyTable() + table.field_names = [ + "#", + "letter", + "type", + "cache_size", + "open_cb", + "read_cb", + "write_cb", + "close_cb", + ] + table.align = "l" + + fmt = LVFsDrv._fmt_cb + for i, drv in enumerate(drivers): + table.add_row( + [ + i, + f"{drv.letter}:", + drv.driver_name, + drv.cache_size, + fmt(drv.open_cb), + fmt(drv.read_cb), + fmt(drv.write_cb), + fmt(drv.close_cb), + ] + ) + + if not table.rows: + print("No registered filesystem drivers.") + else: + print(str(table).replace("\x00", "")) diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py new file mode 100644 index 0000000000..20b75fddab --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_utils.py @@ -0,0 +1,67 @@ +import os + +import gdb + + +def resolve_source_name(addr, prefix="lv_"): + """Resolve a code address to a short name derived from its source file. + + Tries block_for_pc first, then find_pc_line as fallback. + Strips common prefixes (e.g. "lv_fs_stdio.c" -> "stdio"). + + Args: + addr: Integer address of a function/callback. + prefix: Prefix to strip from filename (default "lv_"). + + Returns: + Short name string, or None if resolution fails. + """ + if not addr: + return None + fname = None + try: + block = gdb.block_for_pc(addr) + if block and block.function and block.function.symtab: + fname = block.function.symtab.filename + except (gdb.error, RuntimeError): + pass + if not fname: + try: + sal = gdb.find_pc_line(addr) + if sal and sal.symtab: + fname = sal.symtab.filename + except gdb.error: + pass + if not fname: + return None + base = os.path.basename(fname).replace(".c", "") + if prefix and base.startswith(prefix): + base = base[len(prefix) :] + return base + + +def build_global_field_map(field_type_name): + """Build address -> field name map for fields of a given type in lv_global_t. + + Scans lv_global_t struct fields, finds those matching the given type, + and returns a dict mapping their runtime addresses to field names. + + Args: + field_type_name: C type name string (e.g. "lv_fs_drv_t"). + + Returns: + Dict mapping int address -> str field name. + """ + try: + lv_global_val = gdb.parse_and_eval("lv_global") + lv_global_addr = int(lv_global_val.address) + lv_global_type = gdb.lookup_type("lv_global_t") + target_type = gdb.lookup_type(field_type_name) + result = {} + for field in lv_global_type.fields(): + if field.type.strip_typedefs() == target_type: + addr = lv_global_addr + field.bitpos // 8 + result[addr] = field.name + return result + except gdb.error: + return {}