First commit with working display
This commit is contained in:
@@ -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",
|
||||
]
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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}")
|
||||
Reference in New Issue
Block a user