diff --git a/scripts/gdb/lvglgdb/__init__.py b/scripts/gdb/lvglgdb/__init__.py index d861c390a3..5a19e905d2 100644 --- a/scripts/gdb/lvglgdb/__init__.py +++ b/scripts/gdb/lvglgdb/__init__.py @@ -1,5 +1,21 @@ from .value import Value -from .lvgl import curr_inst, LVDisplay, LVDrawBuf, LVList, LVObject, dump_style_info +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__ = [ @@ -7,7 +23,16 @@ __all__ = [ "LVDisplay", "LVDrawBuf", "LVList", + "LVCache", + "LVRedBlackTree", "LVObject", "dump_style_info", "Value", + "LVCacheEntry", + "LVCacheLRURB", + "LVCacheLRURBIterator", + "LVCacheIteratorBase", + "LVImageCache", + "LVImageHeaderCache", + "create_cache_iterator", ] diff --git a/scripts/gdb/lvglgdb/cmds/__init__.py b/scripts/gdb/lvglgdb/cmds/__init__.py index 9df3e93b2a..51995708f4 100644 --- a/scripts/gdb/lvglgdb/cmds/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/__init__.py @@ -3,7 +3,7 @@ import gdb from .core import DumpObj from .display import DumpDisplayBuf from .draw import InfoDrawUnit -from .misc import InfoStyle +from .misc import InfoStyle, DumpCache from .debugger import Debugger from .drivers import Lvglobal @@ -22,6 +22,7 @@ Debugger() # Dumps DumpObj() DumpDisplayBuf() +DumpCache() # Infos InfoStyle() diff --git a/scripts/gdb/lvglgdb/cmds/misc/__init__.py b/scripts/gdb/lvglgdb/cmds/misc/__init__.py index d8370d743a..67bd220396 100644 --- a/scripts/gdb/lvglgdb/cmds/misc/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/misc/__init__.py @@ -1,5 +1,4 @@ from .lv_style import InfoStyle +from .lv_cache import DumpCache -__all__ = [ - "InfoStyle", -] +__all__ = ["InfoStyle", "DumpCache"] diff --git a/scripts/gdb/lvglgdb/cmds/misc/lv_cache.py b/scripts/gdb/lvglgdb/cmds/misc/lv_cache.py new file mode 100644 index 0000000000..571c19f421 --- /dev/null +++ b/scripts/gdb/lvglgdb/cmds/misc/lv_cache.py @@ -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() diff --git a/scripts/gdb/lvglgdb/lvgl/__init__.py b/scripts/gdb/lvglgdb/lvgl/__init__.py index ac043bfbd5..90e04d4dcc 100644 --- a/scripts/gdb/lvglgdb/lvgl/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/__init__.py @@ -1,7 +1,23 @@ from .core import LVObject, curr_inst, dump_obj_info from .display import LVDisplay from .draw import LVDrawBuf -from .misc import LVList, dump_style_info +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", @@ -11,4 +27,17 @@ __all__ = [ "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", ] diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py index 9aa6fb2bc5..39c27b788f 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py @@ -57,6 +57,16 @@ class LVGL: 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") diff --git a/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_buf.py b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_buf.py index a1c6840878..96e52874d7 100644 --- a/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_buf.py +++ b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_buf.py @@ -109,13 +109,19 @@ class LVDrawBuf(Value): 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}") + 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") + 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() + gdb.selected_inferior() + .read_memory(int(data_ptr), expected_data_size) + .tobytes() ) if not pixel_data: raise ValueError("Failed to read pixel data") diff --git a/scripts/gdb/lvglgdb/lvgl/misc/README.md b/scripts/gdb/lvglgdb/lvgl/misc/README.md new file mode 100644 index 0000000000..da42f19c4c --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/README.md @@ -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 diff --git a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py index bce06ebf31..01b15439fa 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py @@ -1,7 +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", ] diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_cache.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache.py new file mode 100644 index 0000000000..beeea4cc75 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache.py @@ -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() diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_entry.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_entry.py new file mode 100644 index 0000000000..1120bb058c --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_entry.py @@ -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() diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_iter_base.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_iter_base.py new file mode 100644 index 0000000000..80057ce07c --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_iter_base.py @@ -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") diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_iter_factory.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_iter_factory.py new file mode 100644 index 0000000000..17112db2f5 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_iter_factory.py @@ -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}") diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_lru_rb.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_lru_rb.py new file mode 100644 index 0000000000..5f784d3014 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_cache_lru_rb.py @@ -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() diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_image_cache.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_image_cache.py new file mode 100644 index 0000000000..324059b8e7 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_image_cache.py @@ -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) diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_image_header_cache.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_image_header_cache.py new file mode 100644 index 0000000000..89259e8d5e --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_image_header_cache.py @@ -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) diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_rb.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_rb.py new file mode 100644 index 0000000000..9148986e44 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_rb.py @@ -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() diff --git a/scripts/gdb/lvglgdb/value.py b/scripts/gdb/lvglgdb/value.py index e215feab31..0ea7705406 100644 --- a/scripts/gdb/lvglgdb/value.py +++ b/scripts/gdb/lvglgdb/value.py @@ -34,3 +34,30 @@ class Value(gdb.Value): 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__()})" diff --git a/scripts/gdb/requirements.txt b/scripts/gdb/requirements.txt new file mode 100644 index 0000000000..6708af84bc --- /dev/null +++ b/scripts/gdb/requirements.txt @@ -0,0 +1,3 @@ +numpy~=2.2.6 +Pillow~=11.3.0 +prettytable~=3.16.0