summaryrefslogtreecommitdiff
path: root/buildscripts/gdb
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2016-12-08 15:15:01 -0500
committerMark Benvenuto <mark.benvenuto@mongodb.com>2016-12-08 15:15:01 -0500
commitaad88dd01b39f1c689280289e8fe00f87dcb1db3 (patch)
tree6a664574e8c54f0038af561a2c067b0ad156ec22 /buildscripts/gdb
parent76dc485d830aaaad77c7d0302b62e4d3fb8d2ad8 (diff)
downloadmongo-aad88dd01b39f1c689280289e8fe00f87dcb1db3.tar.gz
SERVER-26634 GDB Pretty-Printers and Commands
Diffstat (limited to 'buildscripts/gdb')
-rw-r--r--buildscripts/gdb/mongo.py384
1 files changed, 384 insertions, 0 deletions
diff --git a/buildscripts/gdb/mongo.py b/buildscripts/gdb/mongo.py
new file mode 100644
index 00000000000..9f34c77c451
--- /dev/null
+++ b/buildscripts/gdb/mongo.py
@@ -0,0 +1,384 @@
+"""GDB Pretty-printers and utility commands for MongoDB
+"""
+import gdb.printing
+import glob
+import os
+import sys
+
+try:
+ import bson
+ import bson.json_util
+ import collections
+ from bson.codec_options import CodecOptions
+except ImportError as e:
+ print("Warning: Could not load bson library for Python '" + str(sys.version) + "'.")
+ print("Check with the pip command if pymongo 3.x is installed.")
+ bson = None
+
+
+def get_unique_ptr(obj):
+ """Read the value of a libstdc++ std::unique_ptr"""
+ return obj["_M_t"]['_M_head_impl']
+
+###################################################################################################
+#
+# Pretty-Printers
+#
+###################################################################################################
+class StatusPrinter(object):
+ """Pretty-printer for mongo::Status"""
+ OK = 0 # ErrorCodes::OK
+
+ def __init__(self, val):
+ self.val = val
+
+ def to_string(self):
+ code = self.code()
+ if code == StatusPrinter.OK:
+ return 'Status::OK()'
+
+ # Remove the mongo::ErrorCodes:: prefix. Does nothing if not a real ErrorCode.
+ code = str(code).split('::')[-1]
+
+ info = self.val['_error'].dereference()
+ location = info['location']
+ reason = info['reason']
+ if location:
+ return 'Status(%s, %s, %s)'%(code, reason, location)
+ else:
+ return 'Status(%s, %s)'%(code, reason)
+
+ def code(self):
+ if not self.val['_error']:
+ return StatusPrinter.OK
+ return self.val['_error'].dereference()['code']
+
+
+class StatusWithPrinter:
+ """Pretty-printer for mongo::StatusWith<>"""
+ OK = 0 # ErrorCodes::OK
+
+ def __init__(self, val):
+ self.val = val
+
+ def to_string(self):
+ code = self.code()
+ if code == StatusPrinter.OK:
+ return 'StatusWith(%s, %s)'%(code, self.val['_t'])
+
+ # Remove the mongo::ErrorCodes:: prefix. Does nothing if not a real ErrorCode.
+ code = str(code).split('::')[-1]
+
+ info = self.val['_status']['_error'].dereference()
+ location = info['_status']['location']
+ reason = info['_status']['reason']
+ if location:
+ return 'StatusWith(%s, %s, %s)'%(code, reason, location)
+ else:
+ return 'StatusWith(%s, %s)'%(code, reason)
+
+ def code(self):
+ if not self.val['_status']['_error']:
+ return StatusWithPrinter.OK
+ return self.val['_status'].dereference()['code']
+
+
+class StringDataPrinter:
+ """Pretty-printer for mongo::StringData"""
+ def __init__(self, val):
+ self.val = val
+
+ def display_hint(self):
+ return 'string'
+
+ def to_string(self):
+ size = self.val["_size"]
+ if size == -1:
+ return self.val['_data'].lazy_string()
+ else:
+ return self.val['_data'].lazy_string(length = size)
+
+
+class BSONObjPrinter:
+ """Pretty-printer for mongo::BSONObj"""
+ def __init__(self, val):
+ self.val = val
+ self.ptr = self.val['_objdata'].cast(gdb.lookup_type('void').pointer())
+ self.size = self.ptr.cast(gdb.lookup_type('int').pointer()).dereference()
+
+ def display_hint(self):
+ return 'map'
+
+ def children(self):
+ inferior = gdb.selected_inferior()
+ buf = bytes(inferior.read_memory(self.ptr, self.size))
+ options = CodecOptions(document_class=collections.OrderedDict)
+ bsondoc = bson.BSON.decode(buf, codec_options=options)
+
+ for k, v in bsondoc.items():
+ yield 'key', k
+ yield 'value', bson.json_util.dumps(v)
+
+ def to_string(self):
+ ownership = "owned" if self.val['_ownedBuffer']['_buffer']['_holder']['px'] else "unowned"
+
+ size = self.size
+ if size < 5 or size > 17*1024*1024:
+ # TODO: print invalid sizes in hex as they may be sentinel bytes.
+ size = hex(size)
+
+ if size == 5:
+ return "%s empty BSONObj @ %s"%(ownership, self.ptr)
+ else:
+ return "%s BSONObj %s bytes @ %s"%(ownership, size, self.ptr)
+
+
+class UnorderedFastKeyTablePrinter:
+ """Pretty-printer for mongo::UnorderedFastKeyTable<>"""
+
+ def __init__(self, val):
+ self.val = val
+
+ # Get the value_type by doing a type lookup
+ valueTypeName = val.type.strip_typedefs().name + "::value_type"
+ valueType = gdb.lookup_type(valueTypeName).target()
+ self.valueTypePtr = valueType.pointer()
+
+ def display_hint (self):
+ return 'map'
+
+ def to_string(self):
+ return "UnorderedFastKeyTablePrinter<%s> with %s elems " % (
+ self.val.type.template_argument(0), self.val["_size"])
+
+ def children(self):
+ cap = self.val["_area"]["_hashMask"] + 1
+ it = get_unique_ptr(self.val["_area"]["_entries"])
+ end = it + cap
+
+ if it == 0:
+ return
+
+ while it != end:
+ elt = it.dereference()
+ it += 1
+ if not elt['_used']:
+ continue
+
+ value = elt['_data']["__data"].cast(self.valueTypePtr).dereference()
+
+ yield ('key', value['first'])
+ yield ('value', value['second'])
+
+
+class DecorablePrinter:
+ """Pretty-printer for mongo::Decorable<>"""
+
+ def __init__(self, val):
+ self.val = val
+
+ decl_vector = val["_decorations"]["_registry"]["_decorationInfo"]
+ # TODO: abstract out navigating a std::vector
+ self.start = decl_vector["_M_impl"]["_M_start"]
+ finish = decl_vector["_M_impl"]["_M_finish"]
+ decinfo_t = gdb.lookup_type('mongo::DecorationRegistry::DecorationInfo')
+ self.count = int( (int(finish) - int(self.start)) / decinfo_t.sizeof )
+
+ def display_hint (self):
+ return 'map'
+
+ def to_string(self):
+ return "Decorable<%s> with %s elems " % (self.val.type.template_argument(0),
+ self.count)
+
+ def children(self):
+
+ decorationData = get_unique_ptr(self.val["_decorations"]["_decorationData"])
+
+ for index in range(self.count):
+ descriptor = self.start[index]
+ dindex = int(descriptor["descriptor"]["_index"])
+
+ # In order to get the type stored in the decorable, we examine the type of its
+ # constructor, and do some string manipulations.
+ # TODO: abstract out navigating a std::function
+ type_name = str(descriptor["constructor"]["_M_functor"]["_M_unused"]["_M_object"])
+ type_name = type_name[0:len(type_name) - 1]
+ type_name = type_name[0: type_name.rindex(">")]
+ type_name = type_name[type_name.index("constructAt<"):].replace("constructAt<", "")
+
+ # If the type is a pointer type, strip the * at the end.
+ if type_name.endswith('*'):
+ type_name = type_name[0:len(type_name) - 1]
+ type_name = type_name.rstrip()
+
+ # Cast the raw char[] into the actual object that is stored there.
+ type_t = gdb.lookup_type(type_name)
+ obj = decorationData[dindex].cast(type_t)
+
+ yield ('key', type_name)
+ yield ('value', obj.address)
+
+
+def find_match_brackets(search, opening='<', closing='>'):
+ """Returns the index of the closing bracket that matches the first opening bracket.
+ Returns -1 if no last matching bracket is found, i.e. not a a template.
+
+ Example:
+ 'Foo<T>::iterator<U>''
+ returns 5
+ """
+ index = search.find(opening)
+ if index == -1:
+ return -1
+
+ index = index + 1
+ count = 1
+ str_len = len(search)
+ while index < str_len and count > 0:
+ c = search[index]
+
+ index = index + 1
+ if c == opening:
+ count = count + 1
+ elif c == closing:
+ count = count - 1
+
+ if count == 0:
+ return index - 1
+
+ return -1
+
+
+class MongoSubPrettyPrinter(gdb.printing.SubPrettyPrinter):
+ """Sub pretty printer managed by the pretty-printer collection"""
+ def __init__(self, name, prefix, printer):
+ super(MongoSubPrettyPrinter, self).__init__(name)
+ self.prefix = prefix
+ self.printer = printer
+
+
+class MongoPrettyPrinterCollection(gdb.printing.PrettyPrinter):
+ """MongoDB-specific printer printer collection that ignores subtypes.
+ It will match 'HashTable<T> but not 'HashTable<T>::iterator' when asked for 'HashTable'
+ """
+ def __init__(self):
+ super(MongoPrettyPrinterCollection, self).__init__("mongo", [])
+
+ def add(self, name, prefix, printer):
+ self.subprinters.append(MongoSubPrettyPrinter(name, prefix, printer))
+
+ def __call__(self, val):
+
+ # Get the type name.
+ lookup_tag = gdb.types.get_basic_type(val.type).tag
+ if not lookup_tag:
+ lookup_tag = val.type.name
+ if not lookup_tag:
+ return None
+
+ index = find_match_brackets(lookup_tag)
+
+ # Ignore subtypes of classes
+ # We do not want HashTable<T>::iterator as an example, just HashTable<T>
+ if index == -1 or index + 1 == len(lookup_tag):
+ for printer in self.subprinters:
+ if printer.enabled and lookup_tag.find(printer.prefix) == 0:
+ return printer.printer(val)
+
+ return None
+
+
+def build_pretty_printer():
+ pp = MongoPrettyPrinterCollection()
+ if bson is not None:
+ pp.add('BSONObj', 'mongo::BSONObj', BSONObjPrinter)
+ pp.add('Decorable', 'mongo::Decorable', DecorablePrinter)
+ pp.add('Status', 'mongo::Status', StatusPrinter)
+ pp.add('StatusWith', 'mongo::StatusWith', StatusWithPrinter)
+ pp.add('StringData', 'mongo::StringData', StringDataPrinter)
+ pp.add('UnorderedFastKeyTable', 'mongo::UnorderedFastKeyTable', UnorderedFastKeyTablePrinter)
+ return pp
+
+###################################################################################################
+#
+# Commands
+#
+###################################################################################################
+# Dictionary of commands so we can write a help function that describes the MongoDB commands.
+mongo_commands = {}
+
+def register_mongo_command(obj, name, command_class):
+ """Register a command with no completer as a mongo command"""
+ global mongo_commands
+ gdb.Command.__init__(obj, name, command_class)
+
+ mongo_commands[name] = obj.__doc__
+
+class DumpGlobalServiceContext(gdb.Command):
+ """Dump the Global Service Context"""
+
+ def __init__(self):
+ register_mongo_command(self, "mongodb-services", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, _from_tty):
+ gdb.execute("print *('mongo::(anonymous namespace)::globalServiceContext')")
+
+
+class MongoDBAnalyze(gdb.Command):
+ """Analyze MongoDB process"""
+
+ def __init__(self):
+ register_mongo_command(self, "mongodb-analyze", gdb.COMMAND_STATUS)
+
+ def invoke(self, arg, _from_tty):
+ print("Running Hang Analyzer Supplement")
+ if len(gdb.objfiles()) == 0:
+ print("Skipping, not attached to an inferior")
+ return
+
+ main_binary_name = gdb.objfiles()[0].filename
+
+ if main_binary_name.endswith('mongod'):
+ self.analyze_mongod()
+ else:
+ print("No process specific analysis done for: %s" % (main_binary_name))
+
+ def analyze_mongod(self):
+ """GDB in-process python supplement"""
+
+ # Call into mongod, and dump the state of lock manager
+ # Note that output will go to mongod's standard output, not the debugger output window
+ gdb.execute("call ('mongo::(anonymous namespace)::globalLockManager').dump()",
+ from_tty=False, to_string=False)
+
+
+class MongoDBHelp(gdb.Command):
+ """Dump list of mongodb commands"""
+
+ def __init__(self):
+ gdb.Command.__init__(self, "mongodb-help", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, _from_tty):
+ print("Command - Description" % (key, mongo_commands[key]))
+ for key in mongo_commands:
+ print("%s - %s" % (key, mongo_commands[key]))
+
+# Initialize Commands
+DumpGlobalServiceContext()
+MongoDBAnalyze()
+MongoDBHelp()
+
+###################################################################################################
+#
+# Setup
+#
+###################################################################################################
+
+# Register pretty-printers, replace existing mongo printers
+gdb.printing.register_pretty_printer(
+ gdb.current_objfile(),
+ build_pretty_printer(),
+ True)
+
+print("MongoDB GDB pretty-printers and commands loaded, run 'mongodb-help' for list of commands")