Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 74 additions & 22 deletions src/etc/lldb_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from rust_types import is_tuple_fields

if TYPE_CHECKING:
from lldb import SBValue, SBType, SBTypeStaticField, SBTarget
from lldb import SBValue, SBType, SBTypeStaticField, SBTarget, SBProcess

# from lldb.formatters import Logger

Expand Down Expand Up @@ -289,6 +289,28 @@ def vec_to_string(vec: SBValue) -> str:
)


def read_string(
process: SBProcess, address: int, length: int, error: Optional[SBError] = None
) -> str:
"""Reads a string from running process's memory. If `error` is passed in, it will be passed
to the `SBProcess.ReadMemory` call, and will reflect any errors after the function is called.

If any error or exception occurs, a placeholder byte array of the form "<error: [reason]>" will
be returned instead."""

if error is None:
error = SBError()
try:
data = process.ReadMemory(address, length, error)
if error.Success():
return '"' + data.decode("utf-8", "replace") + '"'
else:
return f"<error: {error.GetCString()}>"
except Exception as e:
print(f"Unable to generate String summary: {e.__cause__}")
return "<error: Unable to read memory>"


def StdStringSummaryProvider(valobj: SBValue, dict: LLDBOpaque):
inner_vec = (
valobj.GetNonSyntheticValue()
Expand All @@ -305,16 +327,25 @@ def StdStringSummaryProvider(valobj: SBValue, dict: LLDBOpaque):
)

length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned()
capacity = (
inner_vec.GetChildMemberWithName("buf")
.GetChildMemberWithName("cap")
.GetValueAsUnsigned()
)

if length <= 0:
return '""'
error = SBError()

no_hi_bit_max: int = 1 << ((pointer.GetByteSize() * 8) - 1)
# technically length isn't a NoHighBit<usize>, but length should always be <= capacity
if length >= no_hi_bit_max or capacity >= no_hi_bit_max:
return "<error: invalid len/capacity>"
if pointer.GetValueAsUnsigned() == 0:
return "<error: String pointer is null>"

process = pointer.GetProcess()
data = process.ReadMemory(pointer.GetValueAsUnsigned(), length, error)
if error.Success():
return '"' + data.decode("utf8", "replace") + '"'
else:
raise Exception("ReadMemory error: %s", error.GetCString())

return read_string(process, pointer.GetValueAsAddress(), length)


def StdOsStringSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
Expand Down Expand Up @@ -363,15 +394,9 @@ def StdPathSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
data_ptr = valobj.GetChildMemberWithName("data_ptr")

start = data_ptr.GetValueAsUnsigned()
error = SBError()
process = data_ptr.GetProcess()
data = process.ReadMemory(start, length, error)
if PY3:
try:
data = data.decode(encoding="UTF-8")
except UnicodeDecodeError:
return "%r" % data
return '"%s"' % data

return read_string(process, start, length)


def sequence_formatter(output: str, valobj: SBValue, _dict: LLDBOpaque):
Expand Down Expand Up @@ -443,6 +468,9 @@ def has_children(self) -> bool:
class StdStringSyntheticProvider:
def __init__(self, valobj: SBValue, _dict: LLDBOpaque):
self.valobj = valobj
ptr_size = valobj.GetTarget().GetAddressByteSize() * 8
self.no_hi_bit_max = 1 << (ptr_size - 1)

self.update()

def update(self):
Expand All @@ -454,7 +482,25 @@ def update(self):
.GetChildMemberWithName("pointer")
.GetChildMemberWithName("pointer")
)
self.length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned()

self.capacity = (
inner_vec.GetChildMemberWithName("buf")
.GetChildMemberWithName("cap")
.GetValueAsUnsigned()
)

# As of 4/18/2026, LLDB cannot accurately determine the difference between Some("") and None
# this just makes sure we're not trying to access data when the string is clearly in an
# invalid state.
if (
self.capacity >= self.no_hi_bit_max
or self.data_ptr.GetValueAsUnsigned() == 0
):
self.capacity = 0
self.length = 0
else:
self.length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned()

self.element_type = self.data_ptr.GetType().GetPointeeType()

def has_children(self) -> bool:
Expand Down Expand Up @@ -928,6 +974,8 @@ def __init__(self, valobj: SBValue, _dict: LLDBOpaque):
# logger >> "[StdVecSyntheticProvider] for " + str(valobj.GetName())
self.valobj = valobj
self.element_type = None
ptr_size = valobj.GetTarget().GetAddressByteSize() * 8
self.no_hi_bit_max = 1 << (ptr_size - 1)
self.update()

def num_children(self) -> int:
Expand All @@ -949,15 +997,19 @@ def get_child_at_index(self, index: int) -> Optional[SBValue]:
return element

def update(self):
self.length = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned()
self.buf = self.valobj.GetChildMemberWithName("buf").GetChildMemberWithName(
"inner"
)

buf: SBValue = self.valobj.GetChildMemberWithName("buf")
self.data_ptr = unwrap_unique_or_non_null(
self.buf.GetChildMemberWithName("ptr")
buf.GetChildMemberWithName("inner").GetChildMemberWithName("ptr")
)

capacity: int = buf.GetChildMemberWithName("cap").GetValueAsUnsigned()

if capacity >= self.no_hi_bit_max or self.data_ptr.GetValueAsUnsigned() == 0:
self.capacity = 0
self.length = 0
else:
self.length = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned()

self.element_type = self.valobj.GetType().GetTemplateArgumentType(0)

if not self.element_type.IsValid():
Expand Down
Loading