First commit with working display

This commit is contained in:
Ricardo LAP
2026-09-13 20:50:51 +00:00
parent a83af259b3
commit c54c68c899
6690 changed files with 1093387 additions and 2 deletions

View File

@@ -0,0 +1 @@
3.10

View File

@@ -0,0 +1,37 @@
# lvglgdb
lvglgdb is a GDB script for LVGL.
# Installation
```bash
pip install lvglgdb
```
# Simple Usage
In your GDB session, run:
```bash
py import lvglgdb
dump obj
dump cache image
dump cache image_header
```
# 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
```

View File

@@ -0,0 +1,13 @@
import sys
from os import path
here = path.dirname(path.abspath(__file__))
if __name__ == "__main__":
if here not in sys.path:
sys.path.insert(0, here)
for key in tuple(filter(lambda m: m.startswith("lvglgdb"), sys.modules.keys())):
del sys.modules[key]
import lvglgdb # noqa: F401

View File

@@ -0,0 +1,38 @@
from .value import Value
from .lvgl import (
curr_inst,
LVDisplay,
LVDrawBuf,
LVList,
LVObject,
dump_style_info,
LVCache,
LVCacheEntry,
LVCacheLRURB,
LVCacheLRURBIterator,
LVCacheIteratorBase,
LVImageCache,
LVImageHeaderCache,
create_cache_iterator,
LVRedBlackTree,
)
from . import cmds as cmds
__all__ = [
"curr_inst",
"LVDisplay",
"LVDrawBuf",
"LVList",
"LVCache",
"LVRedBlackTree",
"LVObject",
"dump_style_info",
"Value",
"LVCacheEntry",
"LVCacheLRURB",
"LVCacheLRURBIterator",
"LVCacheIteratorBase",
"LVImageCache",
"LVImageHeaderCache",
"create_cache_iterator",
]

View File

@@ -0,0 +1,32 @@
import gdb
from .core import DumpObj
from .display import DumpDisplayBuf
from .draw import InfoDrawUnit
from .misc import InfoStyle, DumpCache
from .debugger import Debugger
from .drivers import Lvglobal
__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()
DumpCache()
# Infos
InfoStyle()
InfoDrawUnit()
# Drivers
Lvglobal()

View File

@@ -0,0 +1,5 @@
from .lv_obj import DumpObj
__all__ = [
"DumpObj",
]

View File

@@ -0,0 +1,64 @@
import argparse
import gdb
from lvglgdb.lvgl import curr_inst
from lvglgdb.lvgl import LVObject, dump_obj_info
from lvglgdb.value import Value
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(Value(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)

View File

@@ -0,0 +1,75 @@
import argparse
import gdb
class Debugger(gdb.Command):
"""Start debugpy server or connect to a debug server, so we can debug python code from IDE like PyCharm/VSCode"""
def __init__(self, host="localhost", port=11451):
self.__host = host
self.__port = port
super().__init__("debugger", gdb.COMMAND_USER)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description=Debugger.__doc__)
parser.add_argument(
"--host",
type=str,
help="Server listening host",
)
parser.add_argument(
"-p",
"--port",
type=int,
help="Server listening port",
)
parser.add_argument(
"-t",
"--type",
choices=["pycharm", "vscode", "eclipse"],
help="Debugger type",
required=True,
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
if args.host:
self.__host = args.host
if args.port:
self.__port = args.port
if args.type == "pycharm":
self.connect_to_pycharm()
elif args.type == "vscode":
self.connect_to_vscode()
elif args.type == "eclipse":
self.connect_to_eclipse()
def connect_to_pycharm(self):
try:
import pydevd_pycharm
except ImportError:
print("pydevd_pycharm module not found. Please install it using pip.")
return
pydevd_pycharm.settrace(
self.__host, port=self.__port, stdoutToServer=True, stderrToServer=True
)
def connect_to_vscode(self):
try:
import debugpy
except ImportError:
print("debugpy module not found. Please install it using pip.")
return
debugpy.listen((self.__host, self.__port))
debugpy.wait_for_client()
def connect_to_eclipse(self):
print("Eclipse is not implemented yet")

View File

@@ -0,0 +1,5 @@
from .lv_display import DumpDisplayBuf
__all__ = [
"DumpDisplayBuf",
]

View File

@@ -0,0 +1,54 @@
import argparse
import gdb
from lvglgdb.lvgl import curr_inst
from lvglgdb.lvgl import LVDrawBuf
class DumpDisplayBuf(gdb.Command):
"""dump display buf to image"""
def __init__(self):
super(DumpDisplayBuf, self).__init__(
"dump display", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump display draw buffer.")
parser.add_argument(
"-p",
"--prefix",
type=str,
default="",
help="prefix of dump file path",
)
parser.add_argument(
"-f",
"--format",
type=str,
choices=["bmp", "png"],
default=None,
help="dump file format (bmp or png)",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
display = curr_inst().disp_default()
if not display:
print("Error: Invalid display pointer")
return
buffers = {
"buf_1": display.buf_1,
"buf_2": display.buf_2,
}
for buf_name, buf_ptr in buffers.items():
if buf_ptr is not None:
draw_buf = LVDrawBuf(buf_ptr)
filename = f"{args.prefix}{buf_name}.{args.format.lower() if args.format else 'bmp'}"
draw_buf.data_dump(filename, args.format)
else:
print(f"Warning: {buf_name} buffer is None, skipping.")

View File

@@ -0,0 +1,5 @@
from .lv_draw import InfoDrawUnit
__all__ = [
"InfoDrawUnit",
]

View File

@@ -0,0 +1,50 @@
import gdb
from lvglgdb.value import Value
from lvglgdb.lvgl import curr_inst
class InfoDrawUnit(gdb.Command):
"""dump draw unit info"""
def __init__(self):
super(InfoDrawUnit, self).__init__(
"info draw_unit", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def dump_draw_unit(self, draw_unit: Value):
# Dereference to get the string content of the name from draw_unit
name = draw_unit.name.string()
# Print draw_unit information and the name
print(f"Draw Unit: {draw_unit}, Name: {name}")
# Handle different draw_units based on the name
def lookup_type(name):
try:
return gdb.lookup_type(name)
except gdb.error:
return None
types = {
"DMA2D": lookup_type("lv_draw_dma2d_unit_t"),
"NEMA_GFX": lookup_type("lv_draw_nema_gfx_unit_t"),
"NXP_PXP": lookup_type("lv_draw_pxp_unit_t"),
"NXP_VGLITE": lookup_type("lv_draw_vglite_unit_t"),
"OPENGLES": lookup_type("lv_draw_opengles_unit_t"),
"DAVE2D": lookup_type("lv_draw_dave2d_unit_t"),
"SDL": lookup_type("lv_draw_sdl_unit_t"),
"SW": lookup_type("lv_draw_sw_unit_t"),
"VG_LITE": lookup_type("lv_draw_vg_lite_unit_t"),
}
type = types.get(name, lookup_type("lv_draw_unit_t"))
print(
draw_unit.cast(type, ptr=True)
.dereference()
.format_string(pretty_structs=True, symbols=True)
)
def invoke(self, args, from_tty):
for unit in curr_inst().draw_units():
self.dump_draw_unit(unit)

View File

@@ -0,0 +1,5 @@
from .nuttx import Lvglobal
__all__ = [
"Lvglobal",
]

View File

@@ -0,0 +1,5 @@
from .nuttx import Lvglobal
__all__ = [
"Lvglobal",
]

View File

@@ -0,0 +1,55 @@
# GDB script to get lvgl global pointer in NuttX.
import argparse
import sys
import gdb
class Lvglobal(gdb.Command):
"""Set which lvgl instance to inspect by finding lv_global pointer in task TLS data."""
def __init__(self):
super(Lvglobal, self).__init__("lvglobal", gdb.COMMAND_USER)
def set_lvgl_instance(self, inst):
from lvglgdb.lvgl import curr_inst
curr_inst().reset()
curr_inst().ensure_init(inst)
def invoke(self, arg, from_tty):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument("-p", "--pid", type=int, help="Optional process ID")
try:
args = parser.parse_args(gdb.string_to_argv(arg))
except SystemExit:
return
try:
from nxgdb import utils
except ImportError:
print("nxgdb is not installed, can't find lvgl global pointer.")
return
lv_global = utils.gdb_eval_or_none("lv_global")
if lv_global:
print(f"Found single instance lv_global@{hex(lv_global.address)}")
self.set_lvgl_instance(lv_global)
return
# find the lvgl global pointer in tls
if not args.pid:
print("LVGL is in multi-process mode; please provide --pid.")
return
lv_key = utils.gdb_eval_or_none("lv_nuttx_tlskey")
if lv_key is None:
lv_key = utils.gdb_eval_or_none("lv_global_default::index")
if lv_key is None:
print("Can't find lvgl tls key in multi-process mode.")
return
lv_global = utils.get_task_tls(args.pid, lv_key)
if lv_global:
print(f"Found lv_global@{hex(lv_global)}")
self.set_lvgl_instance(lv_global)
else:
print(f"\nCan't find lv_global with tlskey@{hex(lv_key)}.")

View File

@@ -0,0 +1,4 @@
from .lv_style import InfoStyle
from .lv_cache import DumpCache
__all__ = ["InfoStyle", "DumpCache"]

View File

@@ -0,0 +1,40 @@
import argparse
import gdb
class DumpCache(gdb.Command):
"""dump cache info for specified cache"""
def __init__(self):
super(DumpCache, self).__init__(
"dump cache", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump lvgl cache info.")
parser.add_argument(
"cache",
type=str,
choices=["image", "image_header"],
default="image",
help="cache to dump.",
)
from lvglgdb import curr_inst
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
cache = None
if args.cache == "image":
cache = curr_inst().image_cache()
elif args.cache == "image_header":
cache = curr_inst().image_header_cache()
if not cache:
print("Invalid cache: ", args.cache)
return
cache.print_entries()

View File

@@ -0,0 +1,40 @@
import argparse
import gdb
from lvglgdb.value import Value
from lvglgdb.lvgl import LVObject
from lvglgdb.lvgl import dump_style_info
class InfoStyle(gdb.Command):
"""dump obj style value for specified obj"""
def __init__(self):
super(InfoStyle, self).__init__(
"info style", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump lvgl obj local style.")
parser.add_argument(
"obj",
type=str,
help="obj to show style.",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
obj = gdb.parse_and_eval(args.obj)
if not obj:
print("Invalid obj: ", args.obj)
return
obj = Value(obj)
# show all styles applied to this obj
for style in LVObject(obj).styles:
print(" ", end="")
dump_style_info(style)

View File

@@ -0,0 +1,43 @@
from .core import LVObject, curr_inst, dump_obj_info
from .display import LVDisplay
from .draw import LVDrawBuf
from .misc import (
LVList,
dump_style_info,
LVRedBlackTree,
dump_rb_info,
LVCache,
dump_cache_info,
LVCacheEntry,
dump_cache_entry_info,
LVCacheLRURB,
dump_lru_rb_cache_info,
LVCacheLRURBIterator,
LVCacheIteratorBase,
LVImageCache,
LVImageHeaderCache,
create_cache_iterator,
)
__all__ = [
"LVObject",
"LVDisplay",
"LVDrawBuf",
"curr_inst",
"LVList",
"dump_style_info",
"dump_obj_info",
"LVRedBlackTree",
"dump_rb_info",
"LVCache",
"dump_cache_info",
"LVCacheEntry",
"dump_cache_entry_info",
"LVCacheLRURB",
"dump_lru_rb_cache_info",
"LVCacheLRURBIterator",
"LVCacheIteratorBase",
"LVImageCache",
"LVImageHeaderCache",
"create_cache_iterator",
]

View File

@@ -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",
]

View File

@@ -0,0 +1,117 @@
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
def image_cache(self):
from ..misc.lv_image_cache import LVImageCache
return LVImageCache(self.lv_global.img_cache)
def image_header_cache(self):
from ..misc.lv_image_header_cache import LVImageHeaderCache
return LVImageHeaderCache(self.lv_global.img_header_cache)
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).cast("lv_global_t", ptr=True)
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

View File

@@ -0,0 +1,74 @@
from lvglgdb.value import Value
class LVObject(Value):
"""LVGL object"""
def __init__(self, obj: Value):
super().__init__(obj.cast("lv_obj_t", ptr=True))
@property
def class_name(self):
name = self.class_p.name
if name:
return name.string()
return self.class_p.format_string(symbols=True, address=True, styling=True)
@property
def x1(self):
return int(self.coords.x1)
@property
def y1(self):
return int(self.coords.y1)
@property
def x2(self):
return int(self.coords.x2)
@property
def y2(self):
return int(self.coords.y2)
@property
def child_count(self):
return self.spec_attr.child_cnt if self.spec_attr else 0
@property
def children(self):
if not self.spec_attr:
return
for i in range(self.child_count):
child = self.spec_attr.children[i]
yield LVObject(child)
@property
def styles(self):
LV_STYLE_PROP_INV = 0
LV_STYLE_PROP_ANY = 0xFF
count = self.style_cnt
if count == 0:
return
styles = self.super_value("styles")
for i in range(count):
style = styles[i].style
prop_cnt = style.prop_cnt
values_and_props = style.values_and_props.cast(
"lv_style_const_prop_t", ptr=True
)
for j in range(prop_cnt):
prop = values_and_props[j].prop
if prop == LV_STYLE_PROP_INV or prop == LV_STYLE_PROP_ANY:
continue
yield values_and_props[j]
def get_child(self, index: int):
return self.spec_attr.children[index] if self.spec_attr else None
def dump_obj_info(obj: LVObject):
clzname = obj.class_name
coords = f"{obj.x1},{obj.y1},{obj.x2},{obj.y2}"
print(f"{clzname}@{hex(obj)} {coords}")

View File

@@ -0,0 +1,5 @@
from .lv_display import LVDisplay
__all__ = [
"LVDisplay",
]

View File

@@ -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

View File

@@ -0,0 +1,5 @@
from .lv_draw_buf import LVDrawBuf
__all__ = [
"LVDrawBuf",
]

View File

@@ -0,0 +1,205 @@
from pathlib import Path
from typing import Optional
import gdb
import numpy as np
from PIL import Image
from lvglgdb.value import Value
class LVDrawBuf(Value):
"""LVGL draw buffer"""
_color_formats = {
"RGB565": None,
"RGB888": None,
"ARGB8888": None,
"XRGB8888": None,
}
_bpp = {
"RGB565": 16,
"RGB888": 24,
"ARGB8888": 32,
"XRGB8888": 32,
}
def __init__(self, draw_buf: Value):
super().__init__(draw_buf)
self._init_color_formats()
def _init_color_formats(self):
"""init color formats from enum"""
for fmt in self._color_formats.keys():
if self._color_formats[fmt] is None:
try:
self._color_formats[fmt] = int(
gdb.parse_and_eval(f"LV_COLOR_FORMAT_{fmt}")
)
except gdb.error:
print(f"Warning: Failed to get LV_COLOR_FORMAT_{fmt}")
self._color_formats[fmt] = -1
def color_format_info(self) -> dict:
"""get color format info"""
header = self.super_value("header")
cf = int(header["cf"])
fmt_name = "UNKNOWN"
for name, val in self._color_formats.items():
if val == cf:
fmt_name = name
break
return {"value": cf, "name": fmt_name, "bpp": self._bpp.get(fmt_name, 0)}
@property
def header(self) -> dict:
"""Get the image header information as a dictionary"""
header = self.super_value("header")
return {
"w": header["w"],
"h": header["h"],
"stride": header["stride"],
"cf": header["cf"], # Color format
}
@property
def data_size(self) -> int:
"""Get the total buffer size in bytes"""
return self.super_value("data_size")
@property
def data(self) -> bytes:
"""Get the buffer data ptr"""
return self.super_value("data")
def data_dump(self, filename: str, format: str = None) -> bool:
"""
Dump the buffer data to an image file.
Args:
filename: Output file path
format: Image format (None for auto-detection from filename)
Returns:
bool: True if successful, False otherwise
"""
try:
# Validate input parameters
if not filename:
raise ValueError("Output filename cannot be empty")
# Get buffer metadata
header = self.super_value("header")
stride = int(header["stride"])
height = int(header["h"])
cf_info = self.color_format_info()
data_ptr = self.super_value("data")
data_size = int(self.super_value("data_size"))
width = (stride * 8) // cf_info["bpp"] if cf_info["bpp"] else 0
expected_data_size = stride * height
# Validate buffer data
if not data_ptr:
raise ValueError("Data pointer is NULL")
if width <= 0 or height <= 0:
raise ValueError(f"Invalid dimensions: {width}x{height}")
if data_size <= 0:
raise ValueError(f"Invalid data size: {data_size}")
if data_size < expected_data_size:
raise ValueError(
f"Data size mismatch: expected {expected_data_size}, got {data_size}"
)
elif data_size > expected_data_size:
gdb.write(
f"\033[93mData size mismatch: expected {expected_data_size}, got {data_size}\033[0m\n"
)
# Read pixel data
pixel_data = (
gdb.selected_inferior()
.read_memory(int(data_ptr), expected_data_size)
.tobytes()
)
if not pixel_data:
raise ValueError("Failed to read pixel data")
# Process based on color format
img = self._convert_to_image(pixel_data, width, height, cf_info["value"])
if img is None:
return False
# Determine output format
output_format = (
format.upper() if format else Path(filename).suffix[1:].upper() or "BMP"
)
# Save image
img.save(filename, format=output_format)
print(
f"Successfully saved {cf_info['name']} buffer as {output_format} to {filename}"
)
return True
except gdb.MemoryError:
print("Error: Failed to access memory")
return False
except ValueError as e:
print(f"Validation error: {str(e)}")
return False
except Exception as e:
print(f"Unexpected error: {str(e)}")
return False
def _convert_to_image(
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.
Args:
pixel_data: Raw pixel bytes
width: Image width
height: Image height
color_format: LVGL color format value
Returns:
PIL.Image or None if conversion fails
"""
try:
if color_format == self._color_formats["RGB565"]:
# 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)
)
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
)
rgb_arr = arr[:, :, [2, 1, 0]] # BGR -> RGB
return Image.fromarray(rgb_arr, "RGB")
elif color_format == self._color_formats["ARGB8888"]:
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(-1, 4)
rgba = np.column_stack((arr[:, 1:4], arr[:, 0])) # ARGB -> RGBA
return Image.frombytes("RGBA", (width, height), rgba.tobytes())
elif color_format == self._color_formats["XRGB8888"]:
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(-1, 4)
rgb = arr[:, [2, 1, 0]] # BGR -> RGB
return Image.fromarray(rgb.reshape(height, width, 3), "RGB")
raise ValueError(f"Unsupported color format: 0x{color_format:x}")
except Exception as e:
print(f"Conversion error: {str(e)}")
return None

View File

@@ -0,0 +1,194 @@
# LVGL GDB Scripts - Misc Module Extensions
This directory contains GDB debugging scripts for LVGL's miscellaneous data structures, including red-black trees and
cache systems.
## Objects
### 1. lv_rb.py - Red-Black Tree Support
Provides debugging support for LVGL's red-black tree (`lv_rb_t`) implementation.
**Classes:**
- `LVRedBlackTree` - Iterator for red-black tree nodes
**Functions:**
- `dump_rb_info(rb, datatype)` - Dump basic red-black tree information
**Usage:**
```gdbscript
# In GDB:
python tree = LVRedBlackTree(some_rb_tree)
python for data in tree: print(data)
python dump_rb_info(some_rb_tree, datatype)
```
### 2. lv_cache.py - Cache System Support
Provides debugging support for LVGL's cache system (`lv_cache_t`).
**Classes:**
- `LVCache` - Wrapper for cache objects
**Functions:**
- `dump_cache_info(cache, datatype)` - Dump cache information
**Usage:**
```gdbscript
# In GDB:
python cache = LVCache(some_cache)
python print(f"Cache usage: {cache.get_usage_percentage()}%")
python dump_cache_info(some_cache, datatype)
```
### 3. lv_cache_entry.py - Cache Entry Support
Provides debugging support for cache entries (`lv_cache_entry_t`).
**Classes:**
- `LVCacheEntry` - Wrapper for cache entries
**Functions:**
- `dump_cache_entry_info(entry, datatype)` - Dump cache entry information
**Usage:**
```gdbscript
# In GDB:
python entry = LVCacheEntry(some_entry)
python print(f"Ref count: {entry.get_ref_count()}")
python dump_cache_entry_info(some_entry, datatype)
```
### 4. lv_cache_lru_rb.py - LRU Cache with RB Tree Support
Specialized support for LRU-based caches using red-black trees.
**Classes:**
- `LVCacheLRURB` - Specialized cache wrapper for LRU RB implementations
**Functions:**
- `dump_lru_rb_cache_info(cache)` - Dump LRU RB cache information
### 5. lv_image_cache.py and lv_image_header_cache.py - Image Cache Support
Provides debugging support for LVGL's image cache (`lv_image_cache_t`).
**Classes:**
- `LVImageCache` - Wrapper for image cache objects
**Usage:**
```gdbscript
# In GDB:
python curr_inst().image_cache().print_info()
python curr_inst().image_cache().print_entries()
```
## Integration
All these modules are automatically imported and available when using the LVGL GDB scripts. You can access them through
the main `lvgl` module:
```gdbscript
# In GDB:
python from lvglgdb import LVRedBlackTree, LVCache, dump_rb_info, dump_cache_info
```
## Type Conversion Support
All wrapper classes support multiple input types:
- `gdb.Value` objects (from GDB expressions)
- `Value` objects (from lvglgdb.value module)
- Raw pointers as integers (e.g., `0x12345678`)
This allows flexible usage patterns:
```gdbscript
# From GDB variable
entry = LVCacheEntry(my_entry)
# From raw memory address
entry = LVCacheEntry(0x7fff12345678)
# From GDB expression result
entry = LVCacheEntry(gdb.parse_and_eval("*(lv_cache_entry_t*)0x7fff12345678"))
```
## Examples
### Debugging a Red-Black Tree
```gdbscript
# Create tree iterator from variable
tree = LVRedBlackTree(some_rb_variable)
# Create from raw pointer (0x12345678)
tree = LVRedBlackTree(0x12345678)
# Iterate through all nodes
for data_ptr in tree:
print(f"Data pointer: {data_ptr}")
# Get tree info
print(f"Tree has {len(tree)} nodes")
tree.print_info()
```
### Debugging a Cache
```gdbscript
# Create cache wrapper from variable
cache = LVCache(some_cache_variable)
# Create from raw pointer (0x12345678)
cache = LVCache(0x12345678)
# Check cache status
print(f"Cache usage: {cache.get_usage_percentage():.1f}%")
print(f"Cache enabled: {cache.is_enabled()}")
# Dump full info
cache.print_info()
```
### Debugging Cache Entries
```gdbscript
# Create entry wrapper from variable
entry = LVCacheEntry(some_entry_variable)
# Create from raw pointer (0x12345678)
entry = LVCacheEntry(0x12345678)
# Check entry status
print(f"Ref count: {entry.get_ref_count()}")
print(f"Invalid: {entry.is_invalid()}")
# Dump full info
entry.print_info()
```
## File Structure
- `lv_rb.py` - Red-black tree debugging support
- `lv_cache.py` - General cache debugging support
- `lv_cache_entry.py` - Cache entry debugging support
- `lv_cache_lru_rb.py` - LRU RB cache specialized support
- `lv_image_cache.py` - Image cache debugging support
- `lv_image_header_cache.py` - Image header cache debugging support
- `lv_cache_iter_base.py` - Cache iterator base class
- `lv_cache_iter_factory.py` - Cache iterator factory functions
- `__init__.py` - Module exports and initialization

View File

@@ -0,0 +1,28 @@
from .lv_ll import LVList
from .lv_style import dump_style_info
from .lv_rb import LVRedBlackTree, dump_rb_info
from .lv_cache import LVCache, dump_cache_info
from .lv_cache_entry import LVCacheEntry, dump_cache_entry_info
from .lv_cache_lru_rb import LVCacheLRURB, dump_lru_rb_cache_info, LVCacheLRURBIterator
from .lv_cache_iter_base import LVCacheIteratorBase
from .lv_cache_iter_factory import create_cache_iterator
from .lv_image_cache import LVImageCache
from .lv_image_header_cache import LVImageHeaderCache
__all__ = [
"LVList",
"dump_style_info",
"LVRedBlackTree",
"dump_rb_info",
"LVCache",
"dump_cache_info",
"LVCacheEntry",
"dump_cache_entry_info",
"LVCacheLRURB",
"dump_lru_rb_cache_info",
"LVCacheIteratorBase",
"LVCacheLRURBIterator",
"LVImageCache",
"LVImageHeaderCache",
"create_cache_iterator",
]

View File

@@ -0,0 +1,99 @@
from typing import Union, List, Optional, Dict
import gdb
from lvglgdb.value import Value
from .lv_cache_iter_factory import create_cache_iterator
class LVCache(Value):
"""LVGL cache wrapper - focuses on cache-level operations"""
def __init__(
self, cache: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str]
):
# Convert to Value first if needed
if isinstance(cache, int):
cache = Value(cache).cast("lv_cache_t", ptr=True)
if cache is None:
raise ValueError("Failed to cast pointer to lv_cache_t")
elif isinstance(cache, gdb.Value) and not isinstance(cache, Value):
cache = Value(cache)
elif not cache:
raise ValueError("Invalid cache")
self.datatype = (
gdb.lookup_type(datatype).pointer()
if isinstance(datatype, str)
else datatype
)
super().__init__(cache)
def print_info(self):
"""Dump cache information"""
print(f"Cache Info:")
print(f" Name: {self.name.as_string()}")
print(f" Node Size: {int(self.node_size)}")
print(f" Max Size: {int(self.max_size)}")
print(f" Current Size: {int(self.size)}")
print(f" Free Size: {int(self.max_size) - int(self.size)}")
print(f" Enabled: {bool(int(self.max_size) > 0)}")
# Try to identify cache type
try:
iterator = create_cache_iterator(self)
print(f" Iterator Type: {iterator.__class__.__name__}")
iterator.cache.print_info()
except gdb.error:
pass
def is_enabled(self):
"""Check if cache is enabled"""
return int(self.max_size) > 0
def get_usage_percentage(self):
"""Get cache usage percentage"""
if int(self.max_size) == 0:
return 0.0
return int(self.size) / int(self.max_size) * 100.0
def __iter__(self):
"""Create appropriate iterator based on cache class"""
return create_cache_iterator(self)
def items(self):
"""Get all cache entries as a list"""
entries = []
for entry in self:
entries.append(entry)
return entries
def print_entries(self, max_entries=10):
"""Print cache entries in readable format"""
cache_entries = self.items()
cache_entries_cnt = len(cache_entries)
print(f"Cache Entries ({cache_entries_cnt} total):")
count = 0
for i, entry in enumerate(cache_entries):
if count >= max_entries:
print(
f" ... showing first {max_entries} of {cache_entries_cnt} entries"
)
break
print(f" [{i}] {entry}")
count += 1
if count == 0:
print(" (empty)")
elif count < int(cache_entries_cnt):
print(f" ... {cache_entries_cnt - count} more entries not shown")
def dump_cache_info(
cache: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str]
):
"""Dump cache information"""
cache_obj = LVCache(cache, datatype)
cache_obj.print_info()

View File

@@ -0,0 +1,103 @@
from typing import Union
import gdb
from lvglgdb.value import Value
class LVCacheEntry(Value):
"""LVGL cache entry wrapper - focuses on entry-level operations"""
def __init__(
self, entry: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str]
):
# Convert to Value first if needed
if isinstance(entry, int):
entry = Value(entry).cast("lv_cache_entry_t", ptr=True)
if entry is None:
raise ValueError("Failed to cast pointer to lv_cache_entry_t")
elif isinstance(entry, gdb.Value) and not isinstance(entry, Value):
entry = Value(entry)
elif not entry:
raise ValueError("Invalid cache entry")
self.datatype = (
gdb.lookup_type(datatype).pointer()
if isinstance(datatype, str)
else datatype
)
super().__init__(entry)
@classmethod
def from_data_ptr(
cls, data_ptr: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str]
):
"""Create LVCacheEntry from data pointer"""
if data_ptr.type == gdb.lookup_type("void").pointer() and datatype is None:
raise ValueError("Data pointer is void*, datatype must be provided")
data_ptr = data_ptr.cast(datatype)
entry_ptr = int(data_ptr) + data_ptr.type.target().sizeof
return cls(entry_ptr, datatype)
def print_info(self):
"""Dump cache entry information"""
print(f"Cache Entry Info:")
print(f" Reference Count: {int(self.ref_cnt)}")
print(f" Node Size: {int(self.node_size)}")
print(f" Flags: {int(self.flags)}")
print(f" Invalid: {self.is_invalid()}")
print(f" Disable Delete: {self.is_disabled_delete()}")
# Try to get cache info if available
try:
cache = self.cache
if cache:
print(f" Cache: {cache}")
except:
pass
def get_data(self):
"""Get entry data pointer"""
data_ptr = Value(int(self) - self.get_node_size()).cast(self.datatype)
return data_ptr
def is_invalid(self):
"""Check if entry is invalid"""
return bool(int(self.flags) & 1) # LV_CACHE_ENTRY_FLAG_INVALID
def is_disabled_delete(self):
"""Check if entry has disable delete flag"""
return bool(int(self.flags) & 2) # LV_CACHE_ENTRY_FLAG_DISABLE_DELETE
def get_ref_count(self):
"""Get reference count"""
return int(self.ref_cnt)
def get_node_size(self):
"""Get node size"""
return int(self.node_size)
def get_flags(self):
"""Get flags"""
return int(self.flags)
def __str__(self):
"""Provide better string representation for debugging"""
try:
data = self.get_data()
return f"CacheEntry(ref_cnt={self.get_ref_count()}, valid={not self.is_invalid()}, data={data.dereference()})"
except gdb.error:
pass
return super().__str__()
def dump_cache_entry_info(
entry: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str]
):
"""Dump cache entry information"""
entry_obj = LVCacheEntry(entry, datatype)
entry_obj.print_info()

View File

@@ -0,0 +1,36 @@
from typing import List, Optional
import gdb
from lvglgdb.value import Value
class LVCacheIteratorBase:
"""Base class for cache iterators"""
def __init__(self, cache):
self.cache = cache
self._entries: List[Value] = []
self._current_index = 0
self._collect_entries()
def __iter__(self):
return self
def __next__(self):
"""Get next cache entry"""
if self._current_index < len(self._entries):
entry = self._entries[self._current_index]
self._current_index += 1
return entry
else:
raise StopIteration
def __len__(self):
return len(self._entries)
def __str__(self):
return f"{self.__class__.__name__}(cache={self.cache.name}, entries={len(self._entries)})"
def _collect_entries(self):
"""To be implemented by subclasses"""
raise NotImplementedError("Subclasses must implement _collect_entries")

View File

@@ -0,0 +1,32 @@
import gdb
import re
from .lv_cache_iter_base import LVCacheIteratorBase
def create_cache_iterator(cache) -> LVCacheIteratorBase | None:
"""Factory function to create appropriate iterator for cache class"""
from .lv_cache_lru_rb import LVCacheLRURB
def get_class(s):
if s == "lv_cache_class_lru_rb_size":
return LVCacheLRURB
elif s == "lv_cache_class_lru_rb_count":
return LVCacheLRURB
return None
try:
if cache.clz:
symbol = cache.clz.format_string(symbols=True, address=False)
m = re.match("<(.*)>", symbol)
if m:
symbol = m.group(1)
cache_class = get_class(symbol)
if cache_class:
return iter(cache_class(cache))
except Exception:
raise TypeError(f"Unsupported cache type {cache}")

View File

@@ -0,0 +1,104 @@
from typing import Union, List, Optional
import gdb
from lvglgdb.value import Value
from .lv_cache_iter_base import LVCacheIteratorBase
from .lv_rb import LVRedBlackTree
from .lv_cache_entry import LVCacheEntry
from .lv_cache import LVCache
class LVCacheLRURBIterator(LVCacheIteratorBase):
"""Iterator for LRU RB cache implementation - traverses linked list and red-black tree"""
def __init__(self, cache):
super().__init__(cache)
def _collect_entries(self):
"""Collect entries from LRU RB cache by traversing the linked list"""
try:
# Cast cache to lv_lru_rb_t_ to access internal structures
lru_cache = self.cache.cast("lv_lru_rb_t_", ptr=True)
if not lru_cache:
return
# Access the linked list
rb = lru_cache.rb
if not rb or not rb.root:
return
rb = LVRedBlackTree(rb)
for node in rb:
self._entries.append(
LVCacheEntry.from_data_ptr(node, self.cache.datatype)
)
except Exception as e:
print(f"Error in _collect_lru_entries: {e}")
import traceback
traceback.print_exc()
class LVCacheLRURB(LVCache):
"""LVGL LRU-based cache using red-black tree iterator"""
def __init__(self, cache: Union[Value, gdb.Value, int]):
# Convert to Value first if needed
if isinstance(cache, int):
cache = Value(cache).cast("lv_cache_lru_rb_t", ptr=True)
if cache is None:
raise ValueError("Failed to cast pointer to lv_cache_lru_rb_t")
elif isinstance(cache, gdb.Value) and not isinstance(cache, Value):
cache = Value(cache)
elif not cache:
raise ValueError("Invalid cache")
self.cache_base = cache
super().__init__(cache, cache.datatype)
def print_info(self):
"""Dump LRU RB cache information"""
print(f"LRU RB Cache Info:")
# Try to get cache class info
try:
clz = self.clz
if clz:
print(f" Cache Class: {clz}")
# Check if it's LRU RB based
if "lru_rb" in str(clz).lower():
print(f" Type: LRU with Red-Black Tree")
except:
pass
def is_count_based(self):
"""Check if this is count-based LRU cache"""
try:
name = str(self.name)
return "count" in name.lower() or "lru_rb_count" in str(self.clz).lower()
except:
return False
def is_size_based(self):
"""Check if this is size-based LRU cache"""
try:
name = str(self.name)
return "size" in name.lower() or "lru_rb_size" in str(self.clz).lower()
except:
return False
def __iter__(self):
"""Create iterator for this LRU RB cache"""
return LVCacheLRURBIterator(self)
def items(self):
"""Get all cache entries as a list"""
entries = []
for entry in self:
entries.append(entry)
return entries
def dump_lru_rb_cache_info(cache: Union[Value, gdb.Value, int]):
"""Dump LRU RB cache information"""
cache_obj = LVCacheLRURB(cache)
cache_obj.print_info()

View File

@@ -0,0 +1,88 @@
from typing import Union
import gdb
from prettytable import PrettyTable
from lvglgdb.value import Value
from .lv_cache import LVCache
from .lv_cache_entry import LVCacheEntry
class LVImageCache(object):
def __init__(self, cache: Value):
self._cache = LVCache(cache, "lv_image_cache_data_t")
def print_info(self):
self._cache.print_info()
def print_entries(self):
"""Print image cache entries using prettytable format"""
table = PrettyTable()
table.field_names = [
"size",
"data_size",
"cf",
"rc",
"type",
"decoder",
"decoded",
"src",
]
table.align = "r" # Right align all columns by default
table.align["src"] = "l" # Left align source column
table.align["type"] = "c" # Center align type column
for entry in self._cache:
entry: LVCacheEntry
data_ptr = entry.get_data()
if not data_ptr:
continue
decoded = data_ptr.decoded
try:
header = decoded.header
w = int(header.w)
h = int(header.h)
cf = int(header.cf)
data_size = int(decoded.data_size) if decoded else 0
decoded_ptr = decoded.data if decoded else 0
decoder_name = data_ptr.decoder.name.as_string()
src_type = int(data_ptr.src_type)
src = data_ptr.src
ref_cnt = entry.get_ref_count()
size_str = f"{w}x{h}"
if src_type == 0: # LV_IMAGE_SRC_VARIABLE
src_str = src.format_string(
symbols=True, address=True, styling=True
)
type_str = "var"
elif src_type == 1: # LV_IMAGE_SRC_FILE
src_str = (
src.cast("char", ptr=True).as_string() if src else "(null)"
)
type_str = "file"
else: # Unknown type
src_str = f"{int(src):#x}" if src else "0x0"
type_str = "unkn"
table.add_row(
[
size_str,
f"{data_size}",
f"{cf}",
f"{ref_cnt}",
type_str,
decoder_name,
f"{int(decoded_ptr):#x}",
src_str,
]
)
except gdb.error as e:
table.add_row(["ERROR", "", "", "", "", "", "", str(e)])
continue
print(table)

View File

@@ -0,0 +1,74 @@
from typing import Union
import gdb
from prettytable import PrettyTable
from lvglgdb.value import Value
from .lv_cache import LVCache
from .lv_cache_entry import LVCacheEntry
class LVImageHeaderCache(object):
def __init__(self, cache: Value):
self._cache = LVCache(cache, "lv_image_header_cache_data_t")
def print_info(self):
self._cache.print_info()
def print_entries(self):
"""Print image cache entries using prettytable format"""
table = PrettyTable()
table.field_names = ["size", "cf", "rc", "type", "decoder", "src"]
table.align = "r" # Right align all columns by default
table.align["src"] = "l" # Left align source column
table.align["type"] = "c" # Center align type column
for entry in self._cache:
entry: LVCacheEntry
data_ptr = entry.get_data()
if not data_ptr:
continue
try:
header = data_ptr.header
w = int(header.w)
h = int(header.h)
cf = int(header.cf)
src_type = int(data_ptr.src_type)
src = data_ptr.src
decoder_name = data_ptr.decoder.name.as_string()
ref_cnt = entry.get_ref_count()
size_str = f"{w}x{h}"
if src_type == 0: # LV_IMAGE_SRC_VARIABLE
src_str = src.format_string(
symbols=True, address=True, styling=True
)
type_str = "var"
elif src_type == 1: # LV_IMAGE_SRC_FILE
src_str = (
src.cast("char", ptr=True).as_string() if src else "(null)"
)
type_str = "file"
else: # Unknown type
src_str = f"{int(src):#x}" if src else "0x0"
type_str = "unkn"
table.add_row(
[
size_str,
f"{cf}",
f"{ref_cnt}",
type_str,
decoder_name,
src_str,
]
)
except gdb.error as e:
table.add_row(["ERROR", "", "", "", "", str(e)])
continue
print(table)

View File

@@ -0,0 +1,53 @@
from typing import Union
import gdb
from lvglgdb.value import Value
class LVList(Value):
"""LVGL linked list iterator"""
def __init__(self, ll: Value, nodetype: Union[gdb.Type, str] = None):
if not ll:
raise ValueError("Invalid linked list")
super().__init__(ll)
self.nodetype = (
gdb.lookup_type(nodetype).pointer()
if isinstance(nodetype, str)
else nodetype
)
self.lv_ll_node_t = gdb.lookup_type("lv_ll_node_t").pointer()
self.current = self.head
self._next_offset = self.n_size + self.lv_ll_node_t.sizeof
self._prev_offset = self.n_size
def _next(self, node):
next_value = Value(int(node) + self._next_offset)
return next_value.cast(self.lv_ll_node_t, ptr=True).dereference()
def _prev(self, node):
prev_value = Value(int(node) + self._prev_offset)
return prev_value.cast(self.lv_ll_node_t, ptr=True).dereference()
def __iter__(self):
return self
def __next__(self):
if not self.current:
raise StopIteration
nodetype = self.nodetype if self.nodetype else self.lv_ll_node_t
node = self.current.cast(nodetype)
self.current = self._next(self.current)
return node
@property
def len(self):
len = 0
node = self.head
while node:
len += 1
node = self._next(node)
return len

View File

@@ -0,0 +1,183 @@
from typing import Union
import gdb
from lvglgdb.value import Value
class LVRedBlackTree(Value):
"""LVGL red-black tree iterator"""
def __init__(
self, rb: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str] = None
):
# Convert to Value first if needed
if isinstance(rb, int):
rb = Value(rb).cast("lv_rb_t", ptr=True)
if rb is None:
raise ValueError("Failed to cast pointer to lv_rb_t")
elif isinstance(rb, gdb.Value) and not isinstance(rb, Value):
rb = Value(rb)
elif not rb:
raise ValueError("Invalid red-black tree")
super().__init__(rb)
self.lv_rb_node_t = gdb.lookup_type("lv_rb_node_t").pointer()
self.datatype = (
gdb.lookup_type(datatype).pointer()
if isinstance(datatype, str)
else datatype
)
def minimum_from(self, node):
"""Find minimum node from given node"""
if not node:
return None
current = node
while current.left:
current = current.left
return current
def maximum_from(self, node):
"""Find maximum node from given node"""
if not node:
return None
current = node
while current.right:
current = current.right
return current
def minimum(self):
"""Find minimum node in the tree"""
root = self.root
return self.minimum_from(root)
def maximum(self):
"""Find maximum node in the tree"""
root = self.root
return self.maximum_from(root)
def __iter__(self):
"""Create a new iterator for this tree"""
return LVRedBlackTreeIterator(self)
def __len__(self):
"""Get the number of nodes in the tree"""
count = 0
for _ in self:
count += 1
return count
def get_data(self, node):
"""Get typed data from node, with optional type conversion"""
if not node or not node.data:
return None
data = node.data
if self.datatype:
return data.cast(self.datatype)
return data
def format_data(self, data):
"""Format data for display - simple GDB style"""
if data is None:
return "None"
try:
ptr_addr = f"0x{int(data):x}"
except:
return str(data)
if self.datatype and data:
try:
struct_data = data.dereference()
return f"{ptr_addr} -> {struct_data}"
except:
pass
return ptr_addr
def print_info(self):
"""Dump basic tree information"""
print(f"Red-Black Tree Info:")
print(f" Size: {int(self.size)}")
print(f" Node Count: {len(self)}")
print(f" Root: {self.root}")
if self.root:
root_color = "Red" if int(self.root.color) == 0 else "Black"
print(f" Root Color: {root_color}")
if self.datatype:
print(f" Data Type: {self.datatype}")
def print_tree(self, max_items=10):
"""Print tree data in a readable format"""
print(f"Red-Black Tree Contents ({len(self)} total items):")
count = 0
for i, data in enumerate(self):
if count >= max_items:
print(f" ... showing first {max_items} of {len(self)} items")
break
formatted = self.format_data(data)
print(f" [{i}] {formatted}")
count += 1
if count == 0:
print(" (empty)")
elif count < len(self):
print(f" ... {len(self) - count} more items not shown")
class LVRedBlackTreeIterator:
"""Iterator for LVRedBlackTree that supports multiple traversals"""
def __init__(self, tree: LVRedBlackTree):
self.tree = tree
self.current = tree.minimum()
def __iter__(self):
return self
def __next__(self):
if not self.current:
raise StopIteration
data = self.tree.get_data(self.current)
# Move to next node (in-order traversal)
if self.current.right:
self.current = self.tree.minimum_from(self.current.right)
else:
parent = self.current.parent
while parent and self.current == parent.right:
self.current = parent
parent = parent.parent
self.current = parent
return data
def __str__(self):
"""Better string representation for iterator"""
current = self.current
if not current:
return "LVRedBlackTreeIterator(ended)"
try:
data = self.tree.get_data(current)
if data:
return f"LVRedBlackTreeIterator(current={data})"
except:
pass
return f"LVRedBlackTreeIterator(current=0x{int(current):x})"
def dump_rb_info(
rb: Union[Value, gdb.Value, int], datatype: Union[gdb.Type, str] = None
):
"""Dump red-black tree information"""
tree = LVRedBlackTree(rb, datatype=datatype)
tree.print_info()

View File

@@ -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}")

View File

@@ -0,0 +1,63 @@
import gdb
from typing import Optional, Union
class Value(gdb.Value):
def __init__(self, value: Union[gdb.Value, "Value"]):
super().__init__(value)
def __getitem__(self, key):
try:
value = super().__getitem__(key)
except gdb.error:
value = super().__getattr__(key)
return Value(value)
def __getattr__(self, key):
if hasattr(super(), key):
return Value(super().__getattribute__(key))
return Value(super().__getitem__(key))
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
)
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":
return self[attr]
def as_string(self):
"""Convert to string if possible"""
try:
return self.string()
except gdb.error:
return str(self)
def __str__(self):
"""Provide better string representation for debugging"""
try:
ptr_val = int(self)
return f"({self.type})0x{ptr_val:x}"
except gdb.error:
pass
return super().__str__()
def __repr__(self):
"""Provide detailed representation"""
try:
content = self.dereference()
return f"Value({self.__str__()}: {content})"
except gdb.error:
pass
return f"Value({self.__str__()})"

View File

@@ -0,0 +1,17 @@
[project]
name = "lvglgdb"
version = "0.1.1"
description = "LVGL GDB scripts"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"numpy~=2.2.6",
"Pillow~=11.3.0",
"prettytable~=3.16.0",
]
[project.urls]
Homepage = "https://lvgl.io"
Documentation = "https://docs.lvgl.io/master/debugging/gdb_plugin.html"
Repository = "https://github.com/lvgl/lvgl"

View File

@@ -0,0 +1,3 @@
numpy~=2.2.6
Pillow~=11.3.0
prettytable~=3.16.0