mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-18 06:55:48 +03:00
Migrated from robin_hood to unordered_dense (for UnorderedMap and UnorederedSet).
Added support for SmallVector using svector (and we are already using it in a couple of places). Added debug symbol inspector script for the new SmallVector in project/scripts/debug. Added tilde support for files and folders passed as parameters from the CLI in ecode (and added FileSystem::expandTilde). Fixed a bug in "light-dark" parsing.
This commit is contained in:
64
projects/scripts/debug/README.md
Normal file
64
projects/scripts/debug/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# eepp Debugger Scripts
|
||||
|
||||
This directory contains Python scripts for GDB and LLDB to make debugging the `eepp` framework easier.
|
||||
|
||||
Specifically, these scripts provide "pretty-printers" for custom, highly-optimized data structures like `EE::SmallVector`. Because `EE::SmallVector` uses advanced bit-packing and tagged pointers to minimize stack overhead, it can look like unreadable memory in a raw debugger. These scripts unmask the data so it reads exactly like a standard `std::vector`.
|
||||
|
||||
## GDB (GNU Debugger)
|
||||
|
||||
To make GDB automatically format `EE::SmallVector` objects, you need to load the `eepp_gdb.py` script.
|
||||
|
||||
### Option 1: Auto-Load for all eepp projects (Recommended)
|
||||
Add the following line to your `~/.gdbinit` file to load the pretty-printers automatically every time you start GDB. Make sure to replace `/path/to/eepp` with your actual local path:
|
||||
|
||||
```
|
||||
python exec(open("/path/to/eepp/projects/scripts/debug/eepp_gdb.py").read())
|
||||
```
|
||||
|
||||
### Option 2: Manual Load (Per Session)
|
||||
|
||||
If you only want to load it for a specific debugging session, run this command inside the GDB prompt:
|
||||
|
||||
```
|
||||
(gdb) source /path/to/eepp/projects/scripts/debug/eepp_gdb.py
|
||||
```
|
||||
|
||||
## LLDB (Clang)
|
||||
|
||||
LLDB uses a different Python API for formatting variables. Use the eepp_lldb.py script to get clean summaries and expandable array elements.
|
||||
|
||||
### Option 1: Auto-Load for all eepp projects (Recommended)
|
||||
|
||||
Add this line to your `~/.lldbinit` file so LLDB automatically imports the formatting rules:
|
||||
|
||||
```
|
||||
command script import /path/to/eepp/projects/scripts/debug/eepp_lldb.py
|
||||
```
|
||||
|
||||
### Option 2: Manual Load (Per Session)
|
||||
|
||||
To load the script manually while LLDB is running, use this command in the LLDB prompt:
|
||||
|
||||
```
|
||||
(lldb) command script import /path/to/eepp/projects/scripts/debug/eepp_lldb.py
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Once loaded, an `EE::SmallVector` containing elements will display cleanly in your watch window or terminal output:
|
||||
|
||||
### Without the script:
|
||||
|
||||
```
|
||||
clientsCopy = { m_data = { _M_elems = "\x07\x00\x00..." } }
|
||||
```
|
||||
|
||||
### With the script:
|
||||
|
||||
`clientsCopy = [Direct] size=3`
|
||||
|
||||
`[0] = 0x00007fffffff1230`
|
||||
|
||||
`[1] = 0x00007fffffff1280`
|
||||
|
||||
`[2] = 0x00007fffffff12a0`
|
||||
72
projects/scripts/debug/eepp_gdb.py
Normal file
72
projects/scripts/debug/eepp_gdb.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import gdb
|
||||
|
||||
class EESmallVectorPrinter:
|
||||
"""Pretty Printer for EE::SmallVector (ankerl::svector)"""
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
# Get the underlying uint8_t array
|
||||
self.m_data = self.val["m_data"]["_M_elems"]
|
||||
self.type_t = self.val.type.template_argument(0)
|
||||
|
||||
def is_direct(self):
|
||||
# bit 0 of the first byte is the discriminator
|
||||
return (int(self.m_data[0]) & 1) != 0
|
||||
|
||||
def to_string(self):
|
||||
if self.is_direct():
|
||||
size = int(self.m_data[0]) >> 1
|
||||
return f"EE::SmallVector<{self.type_t}> [Direct] (size={size})"
|
||||
else:
|
||||
# For indirect, we have to fetch the pointer from m_data
|
||||
void_ptr = self.m_data.address.cast(
|
||||
gdb.lookup_type("void").pointer().pointer()
|
||||
).dereference()
|
||||
storage_ptr = void_ptr.cast(
|
||||
gdb.lookup_type(f"ankerl::v1_0_3::detail::storage<{self.type_t}>").pointer()
|
||||
)
|
||||
size = int(storage_ptr["m_size"])
|
||||
cap = int(storage_ptr["m_capacity"])
|
||||
return f"EE::SmallVector<{self.type_t}> [Indirect] (size={size}, capacity={cap})"
|
||||
|
||||
def children(self):
|
||||
if self.is_direct():
|
||||
size = int(self.m_data[0]) >> 1
|
||||
# Data starts at the alignment of T
|
||||
align_t = self.type_t.alignof
|
||||
data_ptr = (
|
||||
self.m_data.address.cast(self.type_t.pointer()) + 1
|
||||
) # simplistic alignment check
|
||||
# Real logic from svector.h: m_data.data() + std::alignment_of_v<T>
|
||||
# We'll use the address offset directly
|
||||
base_addr = self.m_data.address
|
||||
data_ptr = (base_addr + align_t).cast(self.type_t.pointer())
|
||||
else:
|
||||
void_ptr = self.m_data.address.cast(
|
||||
gdb.lookup_type("void").pointer().pointer()
|
||||
).dereference()
|
||||
storage_ptr = void_ptr.cast(
|
||||
gdb.lookup_type(f"ankerl::v1_0_3::detail::storage<{self.type_t}>").pointer()
|
||||
)
|
||||
size = int(storage_ptr["m_size"])
|
||||
# In indirect mode, the storage object has a data() method,
|
||||
# but we'll calculate the offset manually for GDB:
|
||||
# offset_to_data = round_up(sizeof(header), alignment_of_t)
|
||||
data_ptr = (
|
||||
storage_ptr.cast(gdb.lookup_type("char").pointer()) + 16
|
||||
) # Approx header size
|
||||
data_ptr = data_ptr.cast(self.type_t.pointer())
|
||||
|
||||
for i in range(size):
|
||||
yield f"[{i}]", (data_ptr + i).dereference()
|
||||
|
||||
|
||||
def register_ee_printers(obj):
|
||||
if obj is None:
|
||||
obj = gdb
|
||||
obj.pretty_printers.append(
|
||||
lambda val: EESmallVectorPrinter(val) if "svector" in str(val.type) else None
|
||||
)
|
||||
|
||||
|
||||
register_ee_printers(gdb.current_objfile())
|
||||
105
projects/scripts/debug/eepp_lldb.py
Normal file
105
projects/scripts/debug/eepp_lldb.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import lldb
|
||||
|
||||
|
||||
class EESmallVectorSyntheticProvider:
|
||||
def __init__(self, valobj, internal_dict):
|
||||
self.valobj = valobj
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
self.size = 0
|
||||
self.capacity = 0
|
||||
self.is_direct = True
|
||||
self.data_addr = lldb.LLDB_INVALID_ADDRESS
|
||||
|
||||
# 1. Get the type of T (e.g., Client*)
|
||||
self.type_t = self.valobj.GetType().GetTemplateArgumentType(0)
|
||||
|
||||
# 2. Find the m_data array
|
||||
self.m_data = self.valobj.GetChildMemberWithName("m_data")
|
||||
if not self.m_data.IsValid():
|
||||
return
|
||||
|
||||
process = self.valobj.GetProcess()
|
||||
addr = self.m_data.GetLoadAddress()
|
||||
error = lldb.SBError()
|
||||
|
||||
if addr == lldb.LLDB_INVALID_ADDRESS or not process.IsValid():
|
||||
return
|
||||
|
||||
# 3. Read the first byte (the discriminator & size)
|
||||
first_byte = process.ReadUnsignedIntegerFromMemory(addr, 1, error)
|
||||
self.is_direct = (first_byte & 1) != 0
|
||||
|
||||
ptr_size = process.GetAddressByteSize()
|
||||
|
||||
if self.is_direct:
|
||||
self.size = first_byte >> 1
|
||||
self.capacity = 0 # Implied by N, but not explicitly stored
|
||||
|
||||
# Data starts at offset equal to the alignment of T
|
||||
align_t = self.type_t.GetByteAlign()
|
||||
if align_t == 0:
|
||||
align_t = self.type_t.GetByteSize() # Fallback
|
||||
if align_t == 0:
|
||||
align_t = ptr_size
|
||||
|
||||
self.data_addr = addr + align_t
|
||||
else:
|
||||
# 4. Indirect Mode: read the pointer to the heap storage
|
||||
void_ptr = process.ReadPointerFromMemory(addr, error)
|
||||
|
||||
# The storage header contains: size_t m_size, size_t m_capacity
|
||||
self.size = process.ReadUnsignedIntegerFromMemory(void_ptr, ptr_size, error)
|
||||
self.capacity = process.ReadUnsignedIntegerFromMemory(
|
||||
void_ptr + ptr_size, ptr_size, error
|
||||
)
|
||||
|
||||
# Calculate offset_to_data: round_up(sizeof(header), alignment_of_t)
|
||||
header_size = 2 * ptr_size
|
||||
align_t = self.type_t.GetByteAlign()
|
||||
if align_t == 0:
|
||||
align_t = self.type_t.GetByteSize()
|
||||
if align_t == 0:
|
||||
align_t = ptr_size
|
||||
|
||||
offset = ((header_size + (align_t - 1)) // align_t) * align_t
|
||||
self.data_addr = void_ptr + offset
|
||||
|
||||
def num_children(self):
|
||||
return self.size
|
||||
|
||||
def get_child_index(self, name):
|
||||
try:
|
||||
return int(name.lstrip("[").rstrip("]"))
|
||||
except:
|
||||
return -1
|
||||
|
||||
def get_child_at_index(self, index):
|
||||
if index < 0 or index >= self.size:
|
||||
return None
|
||||
item_addr = self.data_addr + index * self.type_t.GetByteSize()
|
||||
return self.valobj.CreateValueFromAddress(f"[{index}]", item_addr, self.type_t)
|
||||
|
||||
def has_children(self):
|
||||
return self.size > 0
|
||||
|
||||
|
||||
def EESmallVectorSummaryProvider(valobj, internal_dict):
|
||||
provider = EESmallVectorSyntheticProvider(valobj, internal_dict)
|
||||
if provider.is_direct:
|
||||
return f"[Direct] size={provider.size}"
|
||||
else:
|
||||
return f"[Indirect] size={provider.size}, capacity={provider.capacity}"
|
||||
|
||||
|
||||
def __lldb_init_module(debugger, internal_dict):
|
||||
# Register the Summary (the text next to the variable)
|
||||
debugger.HandleCommand(
|
||||
'type summary add -x "^EE::SmallVector<.+>$" -F eepp_lldb.EESmallVectorSummaryProvider'
|
||||
)
|
||||
|
||||
# Register the Synthetic Children (the expandable array elements)
|
||||
debugger.HandleCommand(
|
||||
'type synthetic add -x "^EE::SmallVector<.+>$" -l eepp_lldb.EESmallVectorSyntheticProvider'
|
||||
)
|
||||
Reference in New Issue
Block a user