debug-pretty-printers

star 2.0k

Debug and develop GDB and LLDB pretty printers for ezEngine C++ types. Use this skill when working with ezEngine debugger visualizers in ezEngine-gdb.py, ezEngine.py, or ezEngine.natvis. Covers testing with VisualizerZoo.cpp, common errors, and debugging techniques.

ezEngine By ezEngine schedule Updated 2/6/2026

name: debug-pretty-printers description: Debug and develop GDB and LLDB pretty printers for ezEngine C++ types. Use this skill when working with ezEngine debugger visualizers in ezEngine-gdb.py, ezEngine.py, or ezEngine.natvis. Covers testing with VisualizerZoo.cpp, common errors, and debugging techniques.

Debugging ezEngine Pretty Printers

This skill provides guidance for developing and debugging pretty printers (debugger visualizers) for ezEngine.

File Locations

All pretty printer files are in Code/Engine/Foundation/:

  • GDB: Code/Engine/Foundation/ezEngine-gdb.py - Python pretty printers for GDB
  • LLDB: Code/Engine/Foundation/ezEngine.py - Python pretty printers for LLDB
  • Natvis: Code/Engine/Foundation/ezEngine.natvis - Visual Studio/MSVC visualizers (XML)
  • Auto-load: .gdbinit in repository root - Automatically loads GDB printers

Testing with VisualizerZoo.cpp

The primary test file for pretty printers is Code/UnitTests/FoundationTest/CodeUtils/VisualizerZoo.cpp. This file contains test variables for all visualized types organized in EZ_TEST_BLOCK sections.

Finding test sections

Search for EZ_TEST_BLOCK macros to find each section:

grep -n "EZ_TEST_BLOCK" Code/UnitTests/FoundationTest/CodeUtils/VisualizerZoo.cpp

Eac hsection is named descriptively, e.g. EZ_TEST_BLOCK(ezTestBlock::Enabled, "Strings") contains tests for the string types.

Setting breakpoints

Each EZ_TEST_BLOCK section ends with EZ_TEST_BOOL(true); - this is where you should set your breakpoint. At this point, all variables in the block are initialized and in scope.

To find the breakpoint line for a section:

# Find the section start, then look for the EZ_TEST_BOOL(true) before the closing brace
grep -n "EZ_TEST_BLOCK.*Strings" Code/UnitTests/FoundationTest/CodeUtils/VisualizerZoo.cpp
# Then search forward from that line for EZ_TEST_BOOL(true)

Or in an editor, search for EZ_TEST_BLOCK.*SectionName, then find the EZ_TEST_BOOL(true); at the end of that block.

Running the test

First, ensure FoundationTest is built:

cmake --build Workspace/copilot --target FoundationTest

Testing GDB printers in batch mode

Use this exact command pattern to test printers (run from the repository root):

gdb -batch \
  -ex "set pagination off" \
  -ex "source Code/Engine/Foundation/ezEngine-gdb.py" \
  -ex "break VisualizerZoo.cpp:LINE_NUMBER" \
  -ex "run -run -noGui -all" \
  -ex "print variableName" \
  ./Workspace/copilot-output/Bin/LinuxNinjaGccDebug64/FoundationTest

Replace LINE_NUMBER with the line of EZ_TEST_BOOL(true); in the relevant section.

Key points:

  • Use timeout 30 prefix if the test might hang
  • The breakpoint must be on the EZ_TEST_BOOL(true); line at the END of the block (all variables initialized)
  • Add 2>&1 | tail -15 to limit output

Example: Testing string printers

  1. Find the Strings section:

    grep -n "EZ_TEST_BLOCK.*Strings" Code/UnitTests/FoundationTest/CodeUtils/VisualizerZoo.cpp
    
  2. Find the EZ_TEST_BOOL(true); at the end of that block and note the line number

  3. Run GDB with that breakpoint (from repository root):

    gdb -batch \
      -ex "set pagination off" \
      -ex "source Code/Engine/Foundation/ezEngine-gdb.py" \
      -ex "break VisualizerZoo.cpp:101" \
      -ex "run -run -noGui -all" \
      -ex "print string" \
      -ex "print stringView" \
      ./Workspace/copilot-output/Bin/LinuxNinjaGccDebug64/FoundationTest
    

Viewing expanded children

Use set print array on to see children on separate lines:

gdb -batch \
  -ex "set pagination off" \
  -ex "set print array on" \
  -ex "source Code/Engine/Foundation/ezEngine-gdb.py" \
  -ex "break VisualizerZoo.cpp:LINE_NUMBER" \
  -ex "run -run -noGui -all" \
  -ex "print variableName" \
  ./Workspace/copilot-output/Bin/LinuxNinjaGccDebug64/FoundationTest

ezEngine GDB Printer Structure

Registration pattern (from ezEngine-gdb.py)

def build_pretty_printers():
    pp = gdb.printing.RegexpCollectionPrettyPrinter("ezEngine")
    
    # Strings
    pp.add_printer('ezHybridStringBase', r'^ezHybridStringBase<.*>$', ezHybridStringPrinter)
    pp.add_printer('ezStringBuilder', r'^ezStringBuilder$', ezHybridStringPrinter)
    
    # Containers  
    pp.add_printer('ezDynamicArray', r'^ezDynamicArray<.*>$', ezDynamicArrayPrinter)
    
    return pp

gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printers())

Printer class pattern

class ezMyTypePrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        """Return the display string for the value."""
        try:
            return f"{{ count={count} }}"
        except Exception as e:
            return f"<error: {e}>"

    def children(self):
        """Yield (name, value) tuples for expandable children."""
        try:
            yield 'm_uiCount', self.val['m_uiCount']
            yield 'm_pAllocator', self.val['m_pAllocator']
            for i in range(count):
                yield f'[{i}]', (m_pElements + i).dereference()
        except Exception as e:
            yield 'error', str(e)

    def display_hint(self):
        return 'array'  # or 'string' or 'map'

Common Errors and Solutions

"Type is not a template"

Problem: Calling val.type.template_argument(0) on ezStringBuilder (not a template).

Solution: Get size from the actual member instead:

# Instead of: local_storage_size = val.type.template_argument(0)
local_storage_size = m_Data['m_StaticData'].type.sizeof

"Trying to read string with inappropriate type"

Problem: Calling .string() on array address instead of char*.

Solution: Cast to char* first:

char_ptr_type = gdb.lookup_type('char').pointer()
ptr = m_Data['m_StaticData'].address.cast(char_ptr_type)
result = ptr.string(length=count)

"There is no member named X"

Problem: Wrong member name. Check the LLDB or Natvis implementation for correct names.

Solution: Use GDB to inspect the actual type:

gdb -batch -ex "ptype ezHashedString" ./Output/Bin/LinuxNinjaGccDebug64/FoundationTest

Children showing as strings instead of expandable values

Problem: Yielding formatted strings in children() instead of GDB values.

Solution: Yield actual GDB values that have their own printers:

# Wrong - yields a string, not expandable
yield 'c0', f"{{ x={x}, y={y}, z={z} }}"

# Correct - yields the actual value, GDB will use ezVec3Printer on it
yield 'c0', elements[0].address.cast(vec3_type.pointer()).dereference()

ezHybridString inline vs heap storage

The ezHybridStringBase uses inline storage (m_StaticData) when capacity fits, otherwise heap (m_pElements):

def _get_string_data(self):
    m_Data = self.val['m_Data']
    m_uiCount = int(m_Data['m_uiCount'])
    m_uiCapacity = int(m_Data['m_uiCapacity'])
    local_storage_size = m_Data['m_StaticData'].type.sizeof

    if m_uiCapacity <= local_storage_size:
        # Using inline storage
        char_ptr_type = gdb.lookup_type('char').pointer()
        ptr = m_Data['m_StaticData'].address.cast(char_ptr_type)
    else:
        # Using heap storage
        ptr = m_Data['m_pElements']

    return ptr, m_uiCount

Template type lookup for math types

To cast array elements to vector types (e.g., for ezMat3 columns):

elements = self.val['m_fElementsCM']
elem_type = elements[0].type
vec3_type = gdb.lookup_type(f'ezVec3Template<{elem_type}>')
col0 = elements[0].address.cast(vec3_type.pointer()).dereference()

LLDB Comparison

When implementing GDB printers, reference the LLDB implementation in ezEngine.py:

  • LLDB uses set_fields(['m_uiCount', 'm_uiCapacity', 'm_pAllocator']) to add member children
  • GDB equivalent: yield each field in children() before array elements
  • LLDB GetChildMemberWithName('m_Data') → GDB self.val['m_Data']
  • LLDB GetValueAsUnsigned(0) → GDB int(self.val['m_Member'])

Debugging Workflow

  1. Identify the type: Check which printer handles it in build_pretty_printers()
  2. Find test variable: Look in VisualizerZoo.cpp for a variable of that type
  3. Set breakpoint after initialization: Use the table above for correct line numbers
  4. Test to_string() first: Get the display string working before children()
  5. Add children() incrementally: Test each child field separately
  6. Compare with LLDB/Natvis: Use existing implementations as reference for member names

Auto-loading

The .gdbinit file in the repository root automatically loads the printers when debugging in the ezEngine directory. It uses path detection relative to the current working directory.

Install via CLI
npx skills add https://github.com/ezEngine/ezEngine --skill debug-pretty-printers
Repository Details
star Stars 1,962
call_split Forks 250
navigation Branch main
article Path SKILL.md
More from Creator