mongo/buildscripts/lldb/lldb_printers.py

191 lines
6.7 KiB
Python

"""LLDB Pretty-printers for MongoDB.
To import script in lldb, run:
command script import buildscripts/lldb/lldb_printers.py
This file must maintain Python 2 and 3 compatibility until Apple
upgrades to Python 3 and updates their LLDB to use it.
"""
from __future__ import print_function
import string
import struct
import sys
import uuid
import lldb
try:
import bson
import collections
from bson import json_util
from bson.codec_options import CodecOptions
except ImportError:
print("Warning: Could not load bson library for Python {}.".format(sys.version))
print("Check with the pip command if pymongo 3.x is installed.")
bson = None
def __lldb_init_module(debugger, *_args):
"""Register pretty printers."""
debugger.HandleCommand(
"type summary add -s 'A${*var.__ptr_.__value_}' -x '^std::__1::unique_ptr<.+>$'")
debugger.HandleCommand("type summary add mongo::BSONObj -F lldb_printers.BSONObjPrinter")
debugger.HandleCommand("type summary add mongo::Status -F lldb_printers.StatusPrinter")
debugger.HandleCommand("type summary add mongo::StatusWith -F lldb_printers.StatusWithPrinter")
debugger.HandleCommand("type summary add mongo::StringData -F lldb_printers.StringDataPrinter")
debugger.HandleCommand("type summary add mongo::UUID -F lldb_printers.UUIDPrinter")
debugger.HandleCommand(
"type summary add --summary-string '${var.m_pathname}' 'boost::filesystem::path'")
debugger.HandleCommand(
"type synthetic add -x '^boost::optional<.+>$' --python-class lldb_printers.OptionalPrinter"
)
debugger.HandleCommand(
"type synthetic add -x '^std::unique_ptr<.+>$' --python-class lldb_printers.UniquePtrPrinter"
)
#############################
# Pretty Printer Defintions #
#############################
def StatusPrinter(valobj, *_args): # pylint: disable=invalid-name
"""Pretty-Prints MongoDB Status objects."""
err = valobj.GetChildMemberWithName("_error")
code = err.\
GetChildMemberWithName("code").\
GetValue()
if code is None:
return "Status::OK()"
reason = err.\
GetChildMemberWithName("reason").\
GetSummary()
return "Status({}, {})".format(code, reason)
def StatusWithPrinter(valobj, *_args): # pylint: disable=invalid-name
"""Extend the StatusPrinter to print the value of With for a StatusWith."""
status = valobj.GetChildMemberWithName("_status")
code = status.GetChildMemberWithName("_error").\
GetChildMemberWithName("code").\
GetValue()
if code is None:
return "StatusWith(OK, {})".format(valobj.GetChildMemberWithName("_t").GetValue())
rep = StatusPrinter(status)
return rep.replace("Status", "StatusWith", 1)
def StringDataPrinter(valobj, *_args): # pylint: disable=invalid-name
"""Print StringData value."""
ptr = valobj.GetChildMemberWithName("_data").GetValueAsUnsigned()
size1 = valobj.GetChildMemberWithName("_size").GetValueAsUnsigned(0)
return '"{}"'.format(valobj.GetProcess().ReadMemory(ptr, size1, lldb.SBError()).encode("utf-8"))
def BSONObjPrinter(valobj, *_args): # pylint: disable=invalid-name
"""Print a BSONObj in a JSON format."""
ptr = valobj.GetChildMemberWithName("_objdata").GetValueAsUnsigned()
size = struct.unpack("<I", valobj.GetProcess().ReadMemory(ptr, 4, lldb.SBError()))[0]
if size < 5 or size > 17 * 1024 * 1024:
return None
mem = bytes(memoryview(valobj.GetProcess().ReadMemory(ptr, size, lldb.SBError())))
if not bson.is_valid(mem):
return None
buf_str = bson.decode(mem)
obj = json_util.dumps(buf_str, indent=4)
# If the object is huge then just dump it as one line
if obj.count("\n") > 1000:
return json_util.dumps(buf_str)
# Otherwise try to be nice and pretty print the JSON
return obj
def UUIDPrinter(valobj, *_args): # pylint: disable=invalid-name
"""Print the UUID's hex string value."""
char_array = valobj.GetChildMemberWithName("_uuid").GetChildAtIndex(0)
raw_bytes = [x.GetValueAsUnsigned() for x in char_array]
uuid_hex_bytes = [hex(b)[2:].zfill(2) for b in raw_bytes]
return str(uuid.UUID("".join(uuid_hex_bytes)))
class UniquePtrPrinter:
"""Pretty printer for std::unique_ptr."""
def __init__(self, valobj, *_args):
"""Store valobj and retrieve object held at the unique_ptr."""
self.valobj = valobj
self.update()
def num_children(self): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
return 1
def get_child_index(self, name): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
if name == "ptr":
return 0
else:
return None
def get_child_at_index(self, index): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API.
Always prints object pointed at by the ptr.
"""
if index == 0:
return self.valobj.GetChildMemberWithName("__ptr_").GetChildMemberWithName(
"__value_").Dereference()
else:
return None
def has_children(): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
return True
def update(self): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
pass
class OptionalPrinter:
"""Pretty printer for boost::optional."""
def __init__(self, valobj, *_args):
"""Store the valobj and get the value of the optional."""
self.valobj = valobj
self.update()
def num_children(self): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
return 1
def get_child_index(self, name): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
if name == "value":
return 0
else:
return None
def get_child_at_index(self, index): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
if index == 0:
return self.value
else:
return None
def has_children(): # pylint: disable=no-self-use,no-method-argument
"""Match LLDB's expected API."""
return True
def update(self):
"""Check if the optional has changed."""
self.is_init = self.valobj.GetChildMemberWithName("m_initialized").GetValueAsUnsigned() != 0
self.value = None
if self.is_init:
temp_type = self.valobj.GetType().GetTemplateArgumentType(0)
storage = self.valobj.GetChildMemberWithName("m_storage")
self.value = storage.Cast(temp_type)