chore(gdb): add LVFsDrv wrapper with driver name resolution and dump fs_drv command

This commit is contained in:
Benign X
2026-03-06 02:28:13 +08:00
committed by VIFEX
parent 9c77d49793
commit 64c46e8c1b
9 changed files with 223 additions and 0 deletions
+2
View File
@@ -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",
]
+2
View File
@@ -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
@@ -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",
]
+16
View File
@@ -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())
+2
View File
@@ -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",
]
@@ -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
@@ -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",
]
+124
View File
@@ -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", ""))
+67
View File
@@ -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 {}