From da24713e9ee6c673a6caf00f76d15049d6372ad8 Mon Sep 17 00:00:00 2001 From: Benign X <1341398182@qq.com> Date: Thu, 12 Feb 2026 21:08:27 +0800 Subject: [PATCH] feat(gdb): add style inspection with LVStyle wrapper and info style command --- scripts/gdb/lvglgdb/__init__.py | 16 ++ scripts/gdb/lvglgdb/cmds/misc/lv_style.py | 43 +-- scripts/gdb/lvglgdb/lvgl/__init__.py | 14 +- scripts/gdb/lvglgdb/lvgl/core/__init__.py | 4 +- scripts/gdb/lvglgdb/lvgl/core/lv_obj.py | 84 ++++-- scripts/gdb/lvglgdb/lvgl/misc/__init__.py | 10 +- scripts/gdb/lvglgdb/lvgl/misc/lv_style.py | 310 +++++++++++++++++++++- 7 files changed, 436 insertions(+), 45 deletions(-) diff --git a/scripts/gdb/lvglgdb/__init__.py b/scripts/gdb/lvglgdb/__init__.py index 5a19e905d2..91f89a0127 100644 --- a/scripts/gdb/lvglgdb/__init__.py +++ b/scripts/gdb/lvglgdb/__init__.py @@ -5,7 +5,15 @@ from .lvgl import ( LVDrawBuf, LVList, LVObject, + ObjStyle, + LVStyle, + StyleEntry, dump_style_info, + dump_obj_styles, + dump_obj_info, + style_prop_name, + decode_selector, + format_style_value, LVCache, LVCacheEntry, LVCacheLRURB, @@ -26,7 +34,15 @@ __all__ = [ "LVCache", "LVRedBlackTree", "LVObject", + "ObjStyle", + "LVStyle", + "StyleEntry", "dump_style_info", + "dump_obj_styles", + "dump_obj_info", + "style_prop_name", + "decode_selector", + "format_style_value", "Value", "LVCacheEntry", "LVCacheLRURB", diff --git a/scripts/gdb/lvglgdb/cmds/misc/lv_style.py b/scripts/gdb/lvglgdb/cmds/misc/lv_style.py index 5d86772713..dc6f609c2e 100644 --- a/scripts/gdb/lvglgdb/cmds/misc/lv_style.py +++ b/scripts/gdb/lvglgdb/cmds/misc/lv_style.py @@ -2,12 +2,11 @@ import argparse import gdb from lvglgdb.value import Value -from lvglgdb.lvgl import LVObject -from lvglgdb.lvgl import dump_style_info +from lvglgdb.lvgl import LVStyle, dump_obj_styles class InfoStyle(gdb.Command): - """dump obj style value for specified obj""" + """Dump style properties. Default: single lv_style_t. Use --obj for object styles.""" def __init__(self): super(InfoStyle, self).__init__( @@ -15,11 +14,18 @@ class InfoStyle(gdb.Command): ) def invoke(self, args, from_tty): - parser = argparse.ArgumentParser(description="Dump lvgl obj local style.") + parser = argparse.ArgumentParser(description="Dump lvgl style properties.") parser.add_argument( - "obj", + "style", type=str, - help="obj to show style.", + nargs="?", + help="lv_style_t variable to inspect.", + ) + parser.add_argument( + "--obj", + type=str, + default=None, + help="lv_obj_t variable to inspect all styles.", ) try: @@ -27,14 +33,17 @@ class InfoStyle(gdb.Command): 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) + if args.obj: + obj = gdb.parse_and_eval(args.obj) + if not obj: + print("Invalid obj:", args.obj) + return + dump_obj_styles(Value(obj)) + elif args.style: + style = gdb.parse_and_eval(args.style) + if not style: + print("Invalid style:", args.style) + return + LVStyle(Value(style)).print_entries() + else: + print("Usage: info style or info style --obj ") diff --git a/scripts/gdb/lvglgdb/lvgl/__init__.py b/scripts/gdb/lvglgdb/lvgl/__init__.py index 90e04d4dcc..f005aebbe7 100644 --- a/scripts/gdb/lvglgdb/lvgl/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/__init__.py @@ -1,9 +1,14 @@ -from .core import LVObject, curr_inst, dump_obj_info +from .core import LVObject, ObjStyle, curr_inst, dump_obj_info, dump_obj_styles from .display import LVDisplay from .draw import LVDrawBuf from .misc import ( LVList, + LVStyle, + StyleEntry, dump_style_info, + style_prop_name, + decode_selector, + format_style_value, LVRedBlackTree, dump_rb_info, LVCache, @@ -21,12 +26,19 @@ from .misc import ( __all__ = [ "LVObject", + "ObjStyle", "LVDisplay", "LVDrawBuf", "curr_inst", "LVList", + "LVStyle", + "StyleEntry", "dump_style_info", + "dump_obj_styles", "dump_obj_info", + "style_prop_name", + "decode_selector", + "format_style_value", "LVRedBlackTree", "dump_rb_info", "LVCache", diff --git a/scripts/gdb/lvglgdb/lvgl/core/__init__.py b/scripts/gdb/lvglgdb/lvgl/core/__init__.py index fa320a7fad..f57ae299bb 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/core/__init__.py @@ -1,8 +1,10 @@ -from .lv_obj import LVObject, dump_obj_info +from .lv_obj import LVObject, ObjStyle, dump_obj_info, dump_obj_styles from .lv_global import curr_inst __all__ = [ "LVObject", + "ObjStyle", "curr_inst", "dump_obj_info", + "dump_obj_styles", ] diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py b/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py index 6998c06a8c..25fc61bad6 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_obj.py @@ -1,4 +1,29 @@ from lvglgdb.value import Value +from lvglgdb.lvgl.misc.lv_style import LVStyle, StyleEntry, decode_selector + + +class ObjStyle: + """A single style slot from obj->styles[], wrapping LVStyle.""" + + def __init__(self, index: int, selector: int, flags: list, style: LVStyle): + self.index = index + self.selector = selector + self.flags = flags + self.style = style + + @property + def selector_str(self) -> str: + return decode_selector(self.selector) + + @property + def flags_str(self) -> str: + return ",".join(self.flags) if self.flags else "-" + + def __iter__(self): + return iter(self.style) + + def __len__(self): + return len(list(self.style)) class LVObject(Value): @@ -38,31 +63,34 @@ class LVObject(Value): 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) + yield LVObject(self.spec_attr.children[i]) + + @property + def obj_styles(self): + """Yield ObjStyle for each entry in obj->styles[].""" + count = int(self.style_cnt) + if count == 0: + return + styles_arr = self.super_value("styles") + for i in range(count): + raw = styles_arr[i] + flags = [] + if int(raw.is_local): + flags.append("local") + if int(raw.is_trans): + flags.append("trans") + if int(raw.is_theme): + flags.append("theme") + if int(raw.is_disabled): + flags.append("disabled") + yield ObjStyle(i, int(raw.selector), flags, LVStyle(raw.style)) @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] + """Yield StyleEntry for all style properties across all slots.""" + for obj_style in self.obj_styles: + yield from obj_style def get_child(self, index: int): return self.spec_attr.children[index] if self.spec_attr else None @@ -72,3 +100,17 @@ 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}") + + +def dump_obj_styles(obj: Value): + """Print all styles of an object, reusing LVStyle.print_entries().""" + lv_obj = LVObject(Value(obj)) + + has_any = False + for obj_style in lv_obj.obj_styles: + has_any = True + print(f"[{obj_style.index}] {obj_style.selector_str} {obj_style.flags_str}") + obj_style.style.print_entries() + + if not has_any: + print("No styles applied.") diff --git a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py index 01b15439fa..673d614ae4 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py @@ -1,5 +1,8 @@ from .lv_ll import LVList -from .lv_style import dump_style_info +from .lv_style import ( + LVStyle, StyleEntry, + dump_style_info, style_prop_name, decode_selector, format_style_value, +) 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 @@ -11,7 +14,12 @@ from .lv_image_header_cache import LVImageHeaderCache __all__ = [ "LVList", + "LVStyle", + "StyleEntry", "dump_style_info", + "style_prop_name", + "decode_selector", + "format_style_value", "LVRedBlackTree", "dump_rb_info", "LVCache", diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py index eb621c3a37..756cab72b0 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py @@ -1,7 +1,309 @@ +from dataclasses import dataclass +from typing import Iterator + +import gdb +from prettytable import PrettyTable from lvglgdb.value import Value +# Style property ID to name mapping (from lv_style.h enum) +_STYLE_PROP_NAMES = { + 1: "WIDTH", + 2: "HEIGHT", + 3: "LENGTH", + 4: "TRANSFORM_WIDTH", + 5: "TRANSFORM_HEIGHT", + 8: "MIN_WIDTH", + 9: "MAX_WIDTH", + 10: "MIN_HEIGHT", + 11: "MAX_HEIGHT", + 12: "TRANSLATE_X", + 13: "TRANSLATE_Y", + 14: "RADIAL_OFFSET", + 16: "X", + 17: "Y", + 18: "ALIGN", + 24: "PAD_TOP", + 25: "PAD_BOTTOM", + 26: "PAD_LEFT", + 27: "PAD_RIGHT", + 28: "PAD_RADIAL", + 29: "PAD_ROW", + 30: "PAD_COLUMN", + 32: "MARGIN_TOP", + 33: "MARGIN_BOTTOM", + 34: "MARGIN_LEFT", + 35: "MARGIN_RIGHT", + 40: "BG_GRAD", + 41: "BG_GRAD_DIR", + 42: "BG_MAIN_OPA", + 43: "BG_GRAD_OPA", + 44: "BG_GRAD_COLOR", + 45: "BG_MAIN_STOP", + 46: "BG_GRAD_STOP", + 48: "BG_IMAGE_SRC", + 49: "BG_IMAGE_OPA", + 50: "BG_IMAGE_RECOLOR_OPA", + 51: "BG_IMAGE_TILED", + 52: "BG_IMAGE_RECOLOR", + 56: "BORDER_WIDTH", + 57: "BORDER_COLOR", + 58: "BORDER_OPA", + 59: "BORDER_POST", + 60: "BORDER_SIDE", + 64: "OUTLINE_WIDTH", + 65: "OUTLINE_COLOR", + 66: "OUTLINE_OPA", + 67: "OUTLINE_PAD", + 72: "BG_OPA", + 73: "BG_COLOR", + 74: "SHADOW_WIDTH", + 75: "LINE_WIDTH", + 76: "ARC_WIDTH", + 77: "TEXT_FONT", + 78: "IMAGE_RECOLOR_OPA", + 80: "IMAGE_OPA", + 81: "SHADOW_OPA", + 82: "LINE_OPA", + 83: "ARC_OPA", + 84: "TEXT_OPA", + 88: "SHADOW_COLOR", + 89: "IMAGE_RECOLOR", + 90: "LINE_COLOR", + 91: "ARC_COLOR", + 92: "TEXT_COLOR", + 96: "ARC_IMAGE_SRC", + 97: "SHADOW_OFFSET_X", + 98: "SHADOW_OFFSET_Y", + 99: "SHADOW_SPREAD", + 100: "LINE_DASH_WIDTH", + 101: "TEXT_ALIGN", + 102: "TEXT_LETTER_SPACE", + 103: "TEXT_LINE_SPACE", + 104: "LINE_DASH_GAP", + 105: "LINE_ROUNDED", + 106: "IMAGE_COLORKEY", + 107: "TEXT_OUTLINE_STROKE_WIDTH", + 108: "TEXT_OUTLINE_STROKE_OPA", + 109: "TEXT_OUTLINE_STROKE_COLOR", + 110: "TEXT_DECOR", + 111: "ARC_ROUNDED", + 112: "OPA", + 113: "OPA_LAYERED", + 114: "COLOR_FILTER_DSC", + 115: "COLOR_FILTER_OPA", + 116: "ANIM", + 117: "ANIM_DURATION", + 118: "TRANSITION", + 120: "RADIUS", + 121: "BITMAP_MASK_SRC", + 122: "BLEND_MODE", + 123: "ROTARY_SENSITIVITY", + 124: "TRANSLATE_RADIAL", + 128: "CLIP_CORNER", + 129: "BASE_DIR", + 130: "RECOLOR", + 131: "RECOLOR_OPA", + 132: "LAYOUT", + 136: "BLUR_RADIUS", + 137: "BLUR_BACKDROP", + 138: "BLUR_QUALITY", + 144: "DROP_SHADOW_RADIUS", + 145: "DROP_SHADOW_OFFSET_X", + 146: "DROP_SHADOW_OFFSET_Y", + 147: "DROP_SHADOW_COLOR", + 148: "DROP_SHADOW_OPA", + 149: "DROP_SHADOW_QUALITY", + 152: "TRANSFORM_SCALE_X", + 153: "TRANSFORM_SCALE_Y", + 154: "TRANSFORM_PIVOT_X", + 155: "TRANSFORM_PIVOT_Y", + 156: "TRANSFORM_ROTATION", + 157: "TRANSFORM_SKEW_X", + 158: "TRANSFORM_SKEW_Y", + 160: "FLEX_FLOW", + 161: "FLEX_MAIN_PLACE", + 162: "FLEX_CROSS_PLACE", + 163: "FLEX_TRACK_PLACE", + 164: "FLEX_GROW", + 165: "GRID_COLUMN_DSC_ARRAY", + 166: "GRID_ROW_DSC_ARRAY", + 168: "GRID_COLUMN_ALIGN", + 169: "GRID_ROW_ALIGN", + 170: "GRID_CELL_COLUMN_POS", + 171: "GRID_CELL_COLUMN_SPAN", + 172: "GRID_CELL_X_ALIGN", + 173: "GRID_CELL_ROW_POS", + 174: "GRID_CELL_ROW_SPAN", + 175: "GRID_CELL_Y_ALIGN", +} -def dump_style_info(style: Value): - prop = int(style.prop) - value = style.value - print(f"{prop} = {value}") +_PART_NAMES = { + 0x00: "MAIN", + 0x01: "SCROLLBAR", + 0x02: "INDICATOR", + 0x03: "KNOB", + 0x04: "SELECTED", + 0x05: "ITEMS", + 0x06: "CURSOR", + 0x08: "CUSTOM_FIRST", + 0x0F: "ANY", +} + +_STATE_FLAGS = { + 0x0001: "ALT", + 0x0004: "CHECKED", + 0x0008: "FOCUSED", + 0x0010: "FOCUS_KEY", + 0x0020: "EDITED", + 0x0040: "HOVERED", + 0x0080: "PRESSED", + 0x0100: "SCROLLED", + 0x0200: "DISABLED", + 0x1000: "USER_1", + 0x2000: "USER_2", + 0x4000: "USER_3", + 0x8000: "USER_4", +} + +_COLOR_PROPS = { + 44, + 52, + 57, + 65, + 73, + 88, + 89, + 90, + 91, + 92, + 109, + 130, + 147, +} + +_POINTER_PROPS = { + 40, + 48, + 77, + 96, + 114, + 116, + 118, + 121, + 165, + 166, +} + + +def style_prop_name(prop_id: int) -> str: + """Resolve style property ID to human-readable name.""" + return _STYLE_PROP_NAMES.get(prop_id, f"UNKNOWN({prop_id})") + + +def decode_selector(selector: int) -> str: + """Decode selector into part + state string.""" + part_val = (selector >> 16) & 0xFF + state_val = selector & 0xFFFF + + part_str = _PART_NAMES.get(part_val, f"PART({part_val:#x})") + + if state_val == 0: + state_str = "DEFAULT" + elif state_val == 0xFFFF: + state_str = "ANY" + else: + flags = [name for bit, name in _STATE_FLAGS.items() if state_val & bit] + state_str = "|".join(flags) if flags else f"STATE({state_val:#x})" + + return f"{part_str}|{state_str}" + + +def format_style_value(prop_id: int, value: Value) -> str: + """Format a style value based on property type.""" + try: + if prop_id in _COLOR_PROPS: + color = value.color + r = int(color.red) & 0xFF + g = int(color.green) & 0xFF + b = int(color.blue) & 0xFF + block = f"\033[48;2;{r};{g};{b}m \033[0m" + return f"#{r:02x}{g:02x}{b:02x} {block}" + elif prop_id in _POINTER_PROPS: + ptr = int(value.ptr) + return f"{ptr:#x}" if ptr else "NULL" + else: + return str(int(value.num)) + except gdb.error: + return str(value) + + +@dataclass +class StyleEntry: + """A single resolved style property.""" + + prop_id: int + value: Value + + @property + def prop_name(self) -> str: + return style_prop_name(self.prop_id) + + @property + def value_str(self) -> str: + return format_style_value(self.prop_id, self.value) + + +class LVStyle(Value): + """LVGL style wrapper for lv_style_t.""" + + def __init__(self, style: Value): + # Ensure we always hold a lv_style_t* pointer, like LVObject does + typ = style.type.strip_typedefs() + if typ.code != gdb.TYPE_CODE_PTR: + style = Value(style.address) + super().__init__(style.cast("lv_style_t", ptr=True)) + + def __iter__(self) -> Iterator[StyleEntry]: + prop_cnt = int(self.prop_cnt) + if prop_cnt == 0xFF: + # Constant style: lv_style_const_prop_t array terminated by prop==0 + const_props = self.values_and_props.cast("lv_style_const_prop_t", ptr=True) + j = 0 + while True: + prop_id = int(const_props[j].prop) + if prop_id == 0 or prop_id == 0xFF: + break + yield StyleEntry(prop_id, const_props[j].value) + j += 1 + elif prop_cnt > 0: + # Normal style: values[prop_cnt] then props[prop_cnt] (uint8_t) + base = self.values_and_props + value_t = gdb.lookup_type("lv_style_value_t") + values_ptr = base.cast(value_t, ptr=True) + props_offset = prop_cnt * value_t.sizeof + props_ptr = Value(int(base) + props_offset).cast("uint8_t", ptr=True) + + for j in range(prop_cnt): + prop_id = int(props_ptr[j]) + if prop_id == 0: + continue + yield StyleEntry(prop_id, values_ptr[j]) + + def print_entries(self): + """Print style properties as a table.""" + entries = list(self.__iter__()) + if not entries: + print("Empty style.") + return + + table = PrettyTable() + table.field_names = ["prop", "value"] + table.align = "l" + for e in entries: + table.add_row([e.prop_name, e.value_str]) + print(table) + + +def dump_style_info(entry: StyleEntry): + """Print a single style property.""" + print(f"{entry.prop_name}({entry.prop_id}) = {entry.value_str}")