chore(gdb): add draw task/unit wrappers with gen_draw_consts and PrettyTable output

This commit is contained in:
Benign X
2026-03-05 20:49:38 +08:00
committed by VIFEX
parent ce112eba1c
commit fd843ba3a4
13 changed files with 390 additions and 37 deletions
+1
View File
@@ -17,6 +17,7 @@ py import lvglgdb
dump obj
dump cache image
dump cache image_header
dump draw_task <layer_expr>
# Inspect a single lv_style_t variable
info style my_style
+7
View File
@@ -3,6 +3,11 @@ from .lvgl import (
curr_inst,
LVDisplay,
LVDrawBuf,
LVDrawTask,
DRAW_TASK_TYPE_NAMES,
DRAW_TASK_STATE_NAMES,
DRAW_UNIT_TYPE_NAMES,
LVDrawUnit,
LVList,
LVObject,
ObjStyle,
@@ -30,6 +35,8 @@ __all__ = [
"curr_inst",
"LVDisplay",
"LVDrawBuf",
"LVDrawTask",
"LVDrawUnit",
"LVList",
"LVCache",
"LVRedBlackTree",
+2 -1
View File
@@ -2,8 +2,8 @@ import gdb
from .core import DumpObj
from .display import DumpDisplayBuf
from .draw import InfoDrawUnit
from .misc import InfoStyle, DumpCache, CheckPrefix, CheckCache
from .draw import InfoDrawUnit, DumpDrawTask
from .debugger import Debugger
from .drivers import Lvglobal
@@ -25,6 +25,7 @@ DumpDisplayBuf()
DumpCache()
CheckPrefix()
CheckCache()
DumpDrawTask()
# Infos
InfoStyle()
@@ -1,5 +1,7 @@
from .lv_draw import InfoDrawUnit
from .lv_draw_task import DumpDrawTask
__all__ = [
"InfoDrawUnit",
"DumpDrawTask",
]
+14 -30
View File
@@ -1,7 +1,8 @@
import gdb
from lvglgdb.value import Value
from lvglgdb.lvgl import curr_inst
from lvglgdb.lvgl.draw.lv_draw_unit import LVDrawUnit
from lvglgdb.lvgl.draw.lv_draw_consts import DRAW_UNIT_TYPE_NAMES
class InfoDrawUnit(gdb.Command):
@@ -12,39 +13,22 @@ class InfoDrawUnit(gdb.Command):
"info draw_unit", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def dump_draw_unit(self, draw_unit: Value):
# Dereference to get the string content of the name from draw_unit
name = draw_unit.name.string()
def invoke(self, args, from_tty):
for unit in curr_inst().draw_units():
self._dump_unit(unit)
# Print draw_unit information and the name
print(f"Draw Unit: {draw_unit}, Name: {name}")
def _dump_unit(self, unit: LVDrawUnit):
name = unit.name
print(f"Draw Unit: {unit}, Name: {name}")
# Handle different draw_units based on the name
def lookup_type(name):
try:
return gdb.lookup_type(name)
except gdb.error:
return None
type_name = DRAW_UNIT_TYPE_NAMES.get(name, "lv_draw_unit_t")
try:
target_type = gdb.lookup_type(type_name)
except gdb.error:
target_type = gdb.lookup_type("lv_draw_unit_t")
types = {
"DMA2D": lookup_type("lv_draw_dma2d_unit_t"),
"NEMA_GFX": lookup_type("lv_draw_nema_gfx_unit_t"),
"NXP_PXP": lookup_type("lv_draw_pxp_unit_t"),
"NXP_VGLITE": lookup_type("lv_draw_vglite_unit_t"),
"OPENGLES": lookup_type("lv_draw_opengles_unit_t"),
"DAVE2D": lookup_type("lv_draw_dave2d_unit_t"),
"SDL": lookup_type("lv_draw_sdl_unit_t"),
"SW": lookup_type("lv_draw_sw_unit_t"),
"VG_LITE": lookup_type("lv_draw_vg_lite_unit_t"),
}
type = types.get(name, lookup_type("lv_draw_unit_t"))
print(
draw_unit.cast(type, ptr=True)
unit.cast(target_type, ptr=True)
.dereference()
.format_string(pretty_structs=True, symbols=True)
)
def invoke(self, args, from_tty):
for unit in curr_inst().draw_units():
self.dump_draw_unit(unit)
@@ -0,0 +1,25 @@
import gdb
from lvglgdb.value import Value
from lvglgdb.lvgl.draw.lv_draw_task import LVDrawTask
class DumpDrawTask(gdb.Command):
"""dump draw tasks from a layer"""
def __init__(self):
super(DumpDrawTask, self).__init__(
"dump draw_task", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def invoke(self, args, from_tty):
if not args.strip():
print("Usage: dump draw_task <layer_expression>")
return
try:
val = gdb.parse_and_eval(args.strip())
except gdb.error as e:
print(f"Error: {e}")
return
LVDrawTask.print_entries(LVDrawTask(Value(val)))
+13 -1
View File
@@ -1,6 +1,13 @@
from .core import LVObject, ObjStyle, curr_inst, dump_obj_info, dump_obj_styles
from .display import LVDisplay
from .draw import LVDrawBuf
from .draw import (
LVDrawBuf,
LVDrawTask,
LVDrawUnit,
DRAW_TASK_TYPE_NAMES,
DRAW_TASK_STATE_NAMES,
DRAW_UNIT_TYPE_NAMES,
)
from .misc import (
LVList,
LVStyle,
@@ -29,6 +36,11 @@ __all__ = [
"ObjStyle",
"LVDisplay",
"LVDrawBuf",
"LVDrawTask",
"DRAW_TASK_TYPE_NAMES",
"DRAW_TASK_STATE_NAMES",
"DRAW_UNIT_TYPE_NAMES",
"LVDrawUnit",
"curr_inst",
"LVList",
"LVStyle",
+4 -5
View File
@@ -50,12 +50,11 @@ class LVGL:
return disp.act_scr if disp else None
def draw_units(self):
unit = self.lv_global.draw_info.unit_head
from ..draw.lv_draw_unit import LVDrawUnit
# Iterate through all draw units
while unit:
yield unit
unit = unit.next
head = self.lv_global.draw_info.unit_head
if int(head):
yield from LVDrawUnit(head)
def image_cache(self):
from ..misc.lv_image_cache import LVImageCache
+12
View File
@@ -1,5 +1,17 @@
from .lv_draw_buf import LVDrawBuf
from .lv_draw_task import LVDrawTask
from .lv_draw_unit import LVDrawUnit
from .lv_draw_consts import (
DRAW_TASK_TYPE_NAMES,
DRAW_TASK_STATE_NAMES,
DRAW_UNIT_TYPE_NAMES,
)
__all__ = [
"LVDrawBuf",
"LVDrawTask",
"DRAW_TASK_TYPE_NAMES",
"DRAW_TASK_STATE_NAMES",
"DRAW_UNIT_TYPE_NAMES",
"LVDrawUnit",
]
@@ -0,0 +1,48 @@
"""
Auto-generated draw constants from LVGL headers.
Do not edit manually. Regenerate with:
python3 scripts/gen_draw_consts.py
"""
DRAW_TASK_TYPE_NAMES = {
0: "NONE",
1: "FILL",
2: "BORDER",
3: "BOX_SHADOW",
4: "LETTER",
5: "LABEL",
6: "IMAGE",
7: "LAYER",
8: "LINE",
9: "ARC",
10: "TRIANGLE",
11: "MASK_RECTANGLE",
12: "MASK_BITMAP",
13: "BLUR",
14: "VECTOR",
15: "3D",
}
DRAW_TASK_STATE_NAMES = {
0: "BLOCKED",
1: "WAITING",
2: "QUEUED",
3: "IN_PROGRESS",
4: "FINISHED",
}
DRAW_UNIT_TYPE_NAMES = {
"DAVE2D": "lv_draw_dave2d_unit_t",
"DMA2D": "lv_draw_dma2d_unit_t",
"ESP_PPA": "lv_draw_ppa_unit_t",
"G2D": "lv_draw_g2d_unit_t",
"NANOVG": "lv_draw_nanovg_unit_t",
"NEMA_GFX": "lv_draw_nema_gfx_unit_t",
"NXP_PXP": "lv_draw_pxp_unit_t",
"OPENGLES": "lv_draw_opengles_unit_t",
"SDL": "lv_draw_sdl_unit_t",
"SW": "lv_draw_sw_unit_t",
"SW_ARM2D": "lv_draw_sw_unit_t",
"VG_LITE": "lv_draw_vg_lite_unit_t",
}
@@ -0,0 +1,77 @@
from prettytable import PrettyTable
from lvglgdb.value import Value, ValueInput
from .lv_draw_consts import DRAW_TASK_TYPE_NAMES, DRAW_TASK_STATE_NAMES
class LVDrawTask(Value):
"""LVGL draw task wrapper"""
def __init__(self, task: ValueInput):
super().__init__(Value.normalize(task, "lv_draw_task_t"))
@property
def type(self) -> int:
return int(self.super_value("type"))
@property
def type_name(self) -> str:
return DRAW_TASK_TYPE_NAMES.get(self.type, f"UNKNOWN({self.type})")
@property
def state(self) -> int:
return int(self.super_value("state"))
@property
def state_name(self) -> str:
return DRAW_TASK_STATE_NAMES.get(self.state, f"UNKNOWN({self.state})")
@property
def area(self) -> tuple:
a = self.super_value("area")
return (int(a["x1"]), int(a["y1"]), int(a["x2"]), int(a["y2"]))
@property
def opa(self) -> int:
return int(self.super_value("opa"))
@property
def next(self):
n = self.super_value("next")
return LVDrawTask(n) if int(n) else None
def __iter__(self):
node = self
while node:
yield node
node = node.next
@property
def preferred_draw_unit_id(self) -> int:
return int(self.super_value("preferred_draw_unit_id"))
@staticmethod
def print_entries(tasks):
"""Print draw tasks as a PrettyTable."""
table = PrettyTable()
table.field_names = ["#", "type", "state", "area", "opa", "unit_id"]
table.align = "l"
count = 0
for i, t in enumerate(tasks):
table.add_row(
[
i,
t.type_name,
t.state_name,
t.area,
t.opa,
t.preferred_draw_unit_id,
]
)
count += 1
if count == 0:
print("No draw tasks.")
else:
print(table)
@@ -0,0 +1,47 @@
from prettytable import PrettyTable
from lvglgdb.value import Value, ValueInput
class LVDrawUnit(Value):
"""LVGL draw unit wrapper"""
def __init__(self, unit: ValueInput):
super().__init__(Value.normalize(unit, "lv_draw_unit_t"))
@property
def name(self) -> str:
n = self.super_value("name")
return n.string() if int(n) else "(unnamed)"
@property
def idx(self) -> int:
return int(self.super_value("idx"))
@property
def next(self):
n = self.super_value("next")
return LVDrawUnit(n) if int(n) else None
def __iter__(self):
node = self
while node:
yield node
node = node.next
@staticmethod
def print_entries(units):
"""Print draw units as a PrettyTable."""
table = PrettyTable()
table.field_names = ["#", "name", "idx"]
table.align = "l"
count = 0
for i, unit in enumerate(units):
table.add_row([i, unit.name, unit.idx])
count += 1
if count == 0:
print("No draw units.")
else:
print(table)
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""
Generate draw constant tables from LVGL header and source files.
Parses lv_draw.h for task type/state enums, and scans draw unit source
files for name-to-struct-type mappings.
Usage:
python3 scripts/gen_draw_consts.py
"""
import re
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
GDB_ROOT = SCRIPT_DIR.parent
LVGL_SRC = GDB_ROOT.parent.parent / "src"
OUTPUT = GDB_ROOT / "lvglgdb" / "lvgl" / "draw" / "lv_draw_consts.py"
DRAW_H = LVGL_SRC / "draw" / "lv_draw.h"
DRAW_DIR = LVGL_SRC / "draw"
def parse_enum(path: Path, enum_type: str, prefix: str) -> dict[int, str]:
"""Parse a C enum from a header file."""
text = path.read_text()
pattern = rf"\}}\s*{re.escape(enum_type)}\s*;"
m = re.search(rf"typedef\s+enum\s*\{{(.*?){pattern}", text, re.DOTALL)
if not m:
raise RuntimeError(f"Cannot find {enum_type} enum in {path}")
entries = {}
current_val = 0
for line in m.group(1).splitlines():
line = line.strip().rstrip(",")
if (
not line
or line.startswith("/*")
or line.startswith("//")
or line.startswith("*")
or line.startswith("#")
):
continue
match = re.match(rf"({re.escape(prefix)}\w+)\s*=\s*(0x[\da-fA-F]+|\d+)", line)
if match:
name = match.group(1)
current_val = int(match.group(2), 0)
else:
match = re.match(rf"({re.escape(prefix)}\w+)", line)
if not match:
continue
name = match.group(1)
short = name.removeprefix(prefix)
entries[current_val] = short
current_val += 1
return entries
def parse_draw_unit_types(draw_dir: Path) -> dict[str, str]:
"""Scan draw unit .c files for name-to-struct-type mappings.
Looks for patterns like: unit->base_unit.name = "SW";
Then finds the corresponding struct type from the variable declaration.
"""
mappings = {}
for c_file in draw_dir.rglob("*.c"):
text = c_file.read_text()
for m in re.finditer(r'(\w+)->base_unit\.name\s*=\s*"(\w+)"', text):
var_name = m.group(1)
unit_name = m.group(2)
decl = re.search(
rf"(lv_draw_\w+_unit_t)\s*\*\s*{re.escape(var_name)}\b", text
)
if decl:
mappings[unit_name] = decl.group(1)
return mappings
def generate(
task_types: dict[int, str],
task_states: dict[int, str],
unit_types: dict[str, str],
) -> str:
"""Generate Python source for the draw constants module."""
lines = [
'"""',
"Auto-generated draw constants from LVGL headers.",
"",
"Do not edit manually. Regenerate with:",
" python3 scripts/gen_draw_consts.py",
'"""',
"",
]
lines.append("DRAW_TASK_TYPE_NAMES = {")
for k in sorted(task_types):
lines.append(f' {k}: "{task_types[k]}",')
lines.append("}")
lines.append("")
lines.append("DRAW_TASK_STATE_NAMES = {")
for k in sorted(task_states):
lines.append(f' {k}: "{task_states[k]}",')
lines.append("}")
lines.append("")
lines.append("DRAW_UNIT_TYPE_NAMES = {")
for name in sorted(unit_types):
lines.append(f' "{name}": "{unit_types[name]}",')
lines.append("}")
lines.append("")
return "\n".join(lines)
def main():
task_types = parse_enum(DRAW_H, "lv_draw_task_type_t", "LV_DRAW_TASK_TYPE_")
task_states = parse_enum(DRAW_H, "lv_draw_task_state_t", "LV_DRAW_TASK_STATE_")
unit_types = parse_draw_unit_types(DRAW_DIR)
src = generate(task_types, task_states, unit_types)
OUTPUT.write_text(src)
print(
f"Generated {OUTPUT} ({len(task_types)} task types, "
f"{len(task_states)} task states, {len(unit_types)} unit types)"
)
if __name__ == "__main__":
main()