refactor(gdb): lv_global supports lazy loading and refactors some structures and formats. (#8939)

This commit is contained in:
Benign X
2025-09-24 23:27:25 +08:00
committed by GitHub
parent 8b6e87021f
commit 13dd902133
30 changed files with 370 additions and 226 deletions
+20
View File
@@ -0,0 +1,20 @@
# lvglgdb
lvglgdb is a GDB script for LVGL.
# Structure
```mermaid
graph TD
lvgl["lvgl<br/>(mem→python object)"]
gdb_cmds["gdb_cmds<br/>(gdb commands)"]
lvglgdb["lvglgdb"]
lvglgdb --> lvgl
lvglgdb --> gdb_cmds
gdb_cmds --> lvgl
classDef pkg fill:white,stroke:gray
classDef core fill:white,stroke:gray
class lvglgdb,lvgl,gdb_cmds pkg
```
+12 -21
View File
@@ -1,22 +1,13 @@
from .value import *
from .lvgl import *
from .lv_global import set_lvgl_instance
from .core.lv_obj import DumpObj
from .display.lv_display import DumpDisplayBuf
from .draw.lv_draw import InfoDrawUnit
from .misc.lv_style import InfoStyle
from .debugger import *
from .value import Value
from .lvgl import curr_inst, LVDisplay, LVDrawBuf, LVList, LVObject, dump_style_info
from . import cmds as cmds
# Debugger
Debugger()
# Dumps
DumpObj()
DumpDisplayBuf()
# Infos
InfoStyle()
InfoDrawUnit()
# Set instance
set_lvgl_instance(None)
__all__ = [
"curr_inst",
"LVDisplay",
"LVDrawBuf",
"LVList",
"LVObject",
"dump_style_info",
"Value",
]
+27
View File
@@ -0,0 +1,27 @@
import gdb
from .core import DumpObj
from .display import DumpDisplayBuf
from .draw import InfoDrawUnit
from .misc import InfoStyle
from .debugger import Debugger
__all__ = []
# Set pagination off and python print-stack full
gdb.execute("set pagination off")
gdb.write("set pagination off\n")
gdb.execute("set python print-stack full")
gdb.write("set python print-stack full\n")
# Debugger
Debugger()
# Dumps
DumpObj()
DumpDisplayBuf()
# Infos
InfoStyle()
InfoDrawUnit()
@@ -0,0 +1,5 @@
from .lv_obj import DumpObj
__all__ = [
"DumpObj",
]
+63
View File
@@ -0,0 +1,63 @@
import argparse
import gdb
from lvglgdb.lvgl import curr_inst
from lvglgdb.lvgl import LVObject, dump_obj_info
class DumpObj(gdb.Command):
"""dump obj tree from specified obj"""
def __init__(self):
super(DumpObj, self).__init__(
"dump obj", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def dump_obj(self, obj: LVObject, depth=0, limit=None):
if not obj:
return
# dump self
print(" " * depth, end="")
dump_obj_info(obj)
if limit is not None and depth >= limit:
return
# dump children
for child in obj.children:
self.dump_obj(child, depth + 1, limit=limit)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump lvgl obj tree.")
parser.add_argument(
"-L",
"--level",
type=int,
default=None,
help="Limit the depth of the tree.",
)
parser.add_argument(
"root",
type=str,
nargs="?",
default=None,
help="Optional root obj to dump.",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
if args.root:
root = gdb.parse_and_eval(args.root)
root = LVObject(root)
self.dump_obj(root, limit=args.level)
else:
# dump all displays
depth = 0
for disp in curr_inst().displays():
print(f"Display {hex(disp)}")
for screen in disp.screens:
print(f'{" " * (depth + 1)}Screen@{hex(screen)}')
self.dump_obj(screen, depth=depth + 1, limit=args.level)
@@ -57,7 +57,9 @@ class Debugger(gdb.Command):
print("pydevd_pycharm module not found. Please install it using pip.")
return
pydevd_pycharm.settrace(self.__host, port=self.__port, stdoutToServer=True, stderrToServer=True)
pydevd_pycharm.settrace(
self.__host, port=self.__port, stdoutToServer=True, stderrToServer=True
)
def connect_to_vscode(self):
try:
@@ -0,0 +1,5 @@
from .lv_display import DumpDisplayBuf
__all__ = [
"DumpDisplayBuf",
]
@@ -1,52 +1,8 @@
import argparse
import gdb
from ..core.lv_obj import LVObject
from ..draw.lv_draw_buf import LVDrawBuf
from ..value import Value
from .. import lv_global
class LVDisplay(Value):
"""LVGL display"""
def __init__(self, disp: Value):
super().__init__(disp)
@property
def hor_res(self) -> int:
"""Get horizontal resolution in pixels"""
return int(self.super_value("hor_res"))
@property
def ver_res(self) -> int:
"""Get vertical resolution in pixels"""
return int(self.super_value("ver_res"))
@property
def screens(self):
screens = self.super_value("screens")
for i in range(self.screen_cnt):
yield LVObject(screens[i])
# Buffer-related properties
@property
def buf_1(self):
"""Get first draw buffer (may be None)"""
buf_ptr = self.super_value("buf_1")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@property
def buf_2(self):
"""Get second draw buffer (may be None)"""
buf_ptr = self.super_value("buf_2")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@property
def buf_act(self):
"""Get currently active draw buffer (may be None)"""
buf_ptr = self.super_value("buf_act")
return LVDrawBuf(buf_ptr) if buf_ptr else None
from lvglgdb.lvgl import curr_inst
from lvglgdb.lvgl import LVDrawBuf
class DumpDisplayBuf(gdb.Command):
@@ -80,7 +36,7 @@ class DumpDisplayBuf(gdb.Command):
except SystemExit:
return
display = lv_global.g_lvgl_instance.disp_default()
display = curr_inst().disp_default()
if not display:
print("Error: Invalid display pointer")
return
@@ -0,0 +1,5 @@
from .lv_draw import InfoDrawUnit
__all__ = [
"InfoDrawUnit",
]
@@ -1,7 +1,7 @@
import gdb
from .. import lv_global
from ..value import Value
from lvglgdb.value import Value
from lvglgdb.lvgl import curr_inst
class InfoDrawUnit(gdb.Command):
@@ -46,5 +46,5 @@ class InfoDrawUnit(gdb.Command):
)
def invoke(self, args, from_tty):
for unit in lv_global.g_lvgl_instance.draw_units():
for unit in curr_inst().draw_units():
self.dump_draw_unit(unit)
@@ -0,0 +1,5 @@
from .lv_style import InfoStyle
__all__ = [
"InfoStyle",
]
@@ -1,8 +1,9 @@
import argparse
import gdb
from ..value import Value
from ..core.lv_obj import LVObject
from lvglgdb.value import Value
from lvglgdb.lvgl import LVObject
from lvglgdb.lvgl import dump_style_info
class InfoStyle(gdb.Command):
@@ -37,9 +38,3 @@ class InfoStyle(gdb.Command):
for style in LVObject(obj).styles:
print(" ", end="")
dump_style_info(style)
def dump_style_info(style: Value):
prop = int(style.prop)
value = style.value
print(f"{prop} = {value}")
-30
View File
@@ -1,30 +0,0 @@
from typing import Union
import gdb
from .value import Value
g_lvgl_instance = None
def set_lvgl_instance(lv_global: Union[gdb.Value, Value, None]):
global g_lvgl_instance
if not lv_global:
try:
lv_global = Value(gdb.parse_and_eval("lv_global").address)
except gdb.error as e:
print(f"Failed to get lv_global: {e}")
return
if not isinstance(lv_global, Value):
lv_global = Value(lv_global)
inited = lv_global.inited
if not inited:
print(
"\x1b[31mlvgl is not initialized yet. Please call `set_lvgl_instance(None)` later.\x1b[0m"
)
return
from .lvgl import LVGL
g_lvgl_instance = LVGL(lv_global)
-44
View File
@@ -1,44 +0,0 @@
from typing import Iterator
import gdb
from .display.lv_display import LVDisplay
from .misc.lv_ll import LVList
from .value import Value
gdb.execute("set pagination off")
gdb.write("set pagination off\n")
gdb.execute("set python print-stack full")
gdb.write("set python print-stack full\n")
class LVGL:
"""LVGL instance"""
def __init__(self, lv_global: Value):
self.lv_global = lv_global.cast("lv_global_t", ptr=True)
def displays(self) -> Iterator[LVDisplay]:
ll = self.lv_global.disp_ll
if not ll:
return
for disp in LVList(ll, "lv_display_t"):
yield LVDisplay(disp)
def disp_default(self):
disp_default = self.lv_global.disp_default
return LVDisplay(disp_default) if disp_default else None
def screen_active(self):
disp = self.lv_global.disp_default
return disp.act_scr if disp else None
def draw_units(self):
unit = self.lv_global.draw_info.unit_head
# Iterate through all draw units
while unit:
yield unit
unit = unit.next
+14
View File
@@ -0,0 +1,14 @@
from .core import LVObject, curr_inst, dump_obj_info
from .display import LVDisplay
from .draw import LVDrawBuf
from .misc import LVList, dump_style_info
__all__ = [
"LVObject",
"LVDisplay",
"LVDrawBuf",
"curr_inst",
"LVList",
"dump_style_info",
"dump_obj_info",
]
@@ -0,0 +1,8 @@
from .lv_obj import LVObject, dump_obj_info
from .lv_global import curr_inst
__all__ = [
"LVObject",
"curr_inst",
"dump_obj_info",
]
+107
View File
@@ -0,0 +1,107 @@
import gdb
from typing import Optional, Union, Iterator, TYPE_CHECKING
from lvglgdb.value import Value
from ..misc.lv_ll import LVList
"""
LVGL global instance
Note:
lv_global is the global instance of LVGL.
It will contain other components like display, draw unit, etc.
So if you want add a new component, please add it in this class.
And just import your component when you need it. Not expose to the whole file.
"""
__all__ = ["curr_inst"]
"""
Type hint for IDE, just imported at typechecking phase not in runtime.
"""
if TYPE_CHECKING:
from ..display.lv_display import LVDisplay
class LVGL:
"""LVGL instance"""
def __init__(self, lv_global: Value):
self.lv_global = lv_global.cast("lv_global_t", ptr=True)
def displays(self) -> "Iterator[LVDisplay]":
ll = self.lv_global.disp_ll
if not ll:
return
from ..display.lv_display import LVDisplay
for disp in LVList(ll, "lv_display_t"):
yield LVDisplay(disp)
def disp_default(self):
from ..display.lv_display import LVDisplay
disp_default = self.lv_global.disp_default
return LVDisplay(disp_default) if disp_default else None
def screen_active(self):
disp = self.lv_global.disp_default
return disp.act_scr if disp else None
def draw_units(self):
unit = self.lv_global.draw_info.unit_head
# Iterate through all draw units
while unit:
yield unit
unit = unit.next
class _LVGLSingleton:
__slots__ = ("_lvgl", "_ready")
def __init__(self) -> None:
self._lvgl: Optional[object] = None
self._ready = False
def ensure_init(self, lv_global: Union[gdb.Value, Value, None] = None) -> bool:
if self._ready:
return True
if lv_global is None:
try:
lv_global = Value(gdb.parse_and_eval("lv_global").address)
except gdb.error as e:
print(f"Failed to get lv_global: {e}")
return False
elif not isinstance(lv_global, Value):
lv_global = Value(lv_global)
if not lv_global.inited:
print(
"\x1b[31mlvgl is not initialized yet. "
"Please call `ensure_init()` later.\x1b[0m"
)
return False
self._lvgl = LVGL(lv_global)
self._ready = True
return True
def __getattr__(self, name: str):
if not self._ready and not self.ensure_init():
raise RuntimeError("LVGL singleton not ready")
return getattr(self._lvgl, name)
def reset(self) -> None:
self._lvgl = None
self._ready = False
__curr_inst = _LVGLSingleton()
def curr_inst() -> _LVGLSingleton:
"""Get the global instance of LVGL"""
return __curr_inst
@@ -1,8 +1,4 @@
import argparse
import gdb
from ..value import Value
from .. import lv_global
from lvglgdb.value import Value
class LVObject(Value):
@@ -70,64 +66,6 @@ class LVObject(Value):
return self.spec_attr.children[index] if self.spec_attr else None
class DumpObj(gdb.Command):
"""dump obj tree from specified obj"""
def __init__(self):
super(DumpObj, self).__init__(
"dump obj", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def dump_obj(self, obj: LVObject, depth=0, limit=None):
if not obj:
return
# dump self
print(" " * depth, end="")
dump_obj_info(obj)
if limit is not None and depth >= limit:
return
# dump children
for child in obj.children:
self.dump_obj(child, depth + 1, limit=limit)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump lvgl obj tree.")
parser.add_argument(
"-L",
"--level",
type=int,
default=None,
help="Limit the depth of the tree.",
)
parser.add_argument(
"root",
type=str,
nargs="?",
default=None,
help="Optional root obj to dump.",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
if args.root:
root = gdb.parse_and_eval(args.root)
root = LVObject(root)
self.dump_obj(root, limit=args.level)
else:
# dump all displays
depth = 0
for disp in lv_global.g_lvgl_instance.displays():
print(f"Display {hex(disp)}")
for screen in disp.screens:
print(f'{" " * (depth + 1)}Screen@{hex(screen)}')
self.dump_obj(screen, depth=depth + 1, limit=args.level)
def dump_obj_info(obj: LVObject):
clzname = obj.class_name
coords = f"{obj.x1},{obj.y1},{obj.x2},{obj.y2}"
@@ -0,0 +1,5 @@
from .lv_display import LVDisplay
__all__ = [
"LVDisplay",
]
@@ -0,0 +1,45 @@
from ..core.lv_obj import LVObject
from ..draw.lv_draw_buf import LVDrawBuf
from lvglgdb.value import Value
class LVDisplay(Value):
"""LVGL display"""
def __init__(self, disp: Value):
super().__init__(disp)
@property
def hor_res(self) -> int:
"""Get horizontal resolution in pixels"""
return int(self.super_value("hor_res"))
@property
def ver_res(self) -> int:
"""Get vertical resolution in pixels"""
return int(self.super_value("ver_res"))
@property
def screens(self):
screens = self.super_value("screens")
for i in range(self.screen_cnt):
yield LVObject(screens[i])
# Buffer-related properties
@property
def buf_1(self):
"""Get first draw buffer (may be None)"""
buf_ptr = self.super_value("buf_1")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@property
def buf_2(self):
"""Get second draw buffer (may be None)"""
buf_ptr = self.super_value("buf_2")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@property
def buf_act(self):
"""Get currently active draw buffer (may be None)"""
buf_ptr = self.super_value("buf_act")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@@ -0,0 +1,5 @@
from .lv_draw_buf import LVDrawBuf
__all__ = [
"LVDrawBuf",
]
@@ -5,7 +5,7 @@ import gdb
import numpy as np
from PIL import Image
from ..value import Value
from lvglgdb.value import Value
class LVDrawBuf(Value):
@@ -143,7 +143,7 @@ class LVDrawBuf(Value):
return False
def _convert_to_image(
self, pixel_data: bytes, width: int, height: int, color_format: int
self, pixel_data: bytes, width: int, height: int, color_format: int
) -> Optional[Image.Image]:
"""
Convert raw pixel data to PIL Image based on color format.
@@ -162,14 +162,18 @@ class LVDrawBuf(Value):
# Convert RGB565 to RGB888
arr = np.frombuffer(pixel_data, dtype=np.uint8)
arr = arr.reshape((height, width, 2))
rgb565 = np.frombuffer(pixel_data, dtype=np.uint16).reshape((height, width))
rgb565 = np.frombuffer(pixel_data, dtype=np.uint16).reshape(
(height, width)
)
r = (((rgb565 & 0xF800) >> 11) << 3).astype(np.uint8)
g = ((rgb565 & 0x07E0) >> 3).astype(np.uint8)
b = ((rgb565 & 0x001F) << 3).astype(np.uint8)
return Image.fromarray(np.dstack((r, g, b)), "RGB")
elif color_format == self._color_formats["RGB888"]:
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(height, width, 3)
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(
height, width, 3
)
rgb_arr = arr[:, :, [2, 1, 0]] # BGR -> RGB
return Image.fromarray(rgb_arr, "RGB")
@@ -0,0 +1,7 @@
from .lv_ll import LVList
from .lv_style import dump_style_info
__all__ = [
"LVList",
"dump_style_info",
]
@@ -1,7 +1,7 @@
from typing import Union
import gdb
from ..value import Value
from lvglgdb.value import Value
class LVList(Value):
@@ -0,0 +1,7 @@
from lvglgdb.value import Value
def dump_style_info(style: Value):
prop = int(style.prop)
value = style.value
print(f"{prop} = {value}")
+8 -4
View File
@@ -4,7 +4,7 @@ from typing import Optional, Union
class Value(gdb.Value):
def __init__(self, value: Union[gdb.Value, 'Value']):
def __init__(self, value: Union[gdb.Value, "Value"]):
super().__init__(value)
def __getitem__(self, key):
@@ -19,14 +19,18 @@ class Value(gdb.Value):
return Value(super().__getattribute__(key))
return Value(super().__getitem__(key))
def cast(self, type_name: Union[str, gdb.Type], ptr: bool = False) -> Optional['Value']:
def cast(
self, type_name: Union[str, gdb.Type], ptr: bool = False
) -> Optional["Value"]:
try:
gdb_type = gdb.lookup_type(type_name) if isinstance(type_name, str) else type_name
gdb_type = (
gdb.lookup_type(type_name) if isinstance(type_name, str) else type_name
)
if ptr:
gdb_type = gdb_type.pointer()
return Value(super().cast(gdb_type))
except gdb.error:
return None
def super_value(self, attr: str) -> 'Value':
def super_value(self, attr: str) -> "Value":
return self[attr]