# Debug Pretty Printers

> 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.

- Skill: `ezengine/debug-pretty-printers` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ezengine/debug-pretty-printers`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ezengine/debug-pretty-printers/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ezengine (https://skillmd.com/u/ezengine)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ezengine/debug-pretty-printers

---


# 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:

```bash
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:

```bash
# 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:

```bash
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):

```bash
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:
   ```bash
   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):
   ```bash
   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:

```bash
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)

```python
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

```python
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:
```python
# 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:
```python
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:
```bash
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:
```python
# 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`):

```python
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):

```python
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.

