1"""GDB Pretty-printers for MongoDB 2""" 3from __future__ import print_function 4 5import gdb.printing 6import struct 7import sys 8 9try: 10 import bson 11 import bson.json_util 12 import collections 13 from bson.codec_options import CodecOptions 14except ImportError as e: 15 print("Warning: Could not load bson library for Python '" + str(sys.version) + "'.") 16 print("Check with the pip command if pymongo 3.x is installed.") 17 bson = None 18 19 20def get_unique_ptr(obj): 21 """Read the value of a libstdc++ std::unique_ptr""" 22 return obj["_M_t"]['_M_head_impl'] 23 24 25 26################################################################################################### 27# 28# Pretty-Printers 29# 30################################################################################################### 31 32 33class StatusPrinter(object): 34 """Pretty-printer for mongo::Status""" 35 OK = 0 # ErrorCodes::OK 36 37 def __init__(self, val): 38 self.val = val 39 40 def to_string(self): 41 if not self.val['_error']: 42 return 'Status::OK()' 43 44 code = self.val['_error']['code'] 45 # Remove the mongo::ErrorCodes:: prefix. Does nothing if not a real ErrorCode. 46 code = str(code).split('::')[-1] 47 48 info = self.val['_error'].dereference() 49 location = info['location'] 50 reason = info['reason'] 51 if location: 52 return 'Status(%s, %s, %s)' % (code, reason, location) 53 else: 54 return 'Status(%s, %s)' % (code, reason) 55 56 57class StatusWithPrinter: 58 """Pretty-printer for mongo::StatusWith<>""" 59 def __init__(self, val): 60 self.val = val 61 62 def to_string(self): 63 if not self.val['_status']['_error']: 64 return 'StatusWith(OK, %s)' % (self.val['_t']) 65 66 code = self.val['_status']['_error']['code'] 67 68 # Remove the mongo::ErrorCodes:: prefix. Does nothing if not a real ErrorCode. 69 code = str(code).split('::')[-1] 70 71 info = self.val['_status']['_error'].dereference() 72 location = info['location'] 73 reason = info['reason'] 74 if location: 75 return 'StatusWith(%s, %s, %s)' % (code, reason, location) 76 else: 77 return 'StatusWith(%s, %s)' % (code, reason) 78 79 80class StringDataPrinter: 81 """Pretty-printer for mongo::StringData""" 82 83 def __init__(self, val): 84 self.val = val 85 86 def display_hint(self): 87 return 'string' 88 89 def to_string(self): 90 size = self.val["_size"] 91 if size == -1: 92 return self.val['_data'].lazy_string() 93 else: 94 return self.val['_data'].lazy_string(length=size) 95 96 97class BSONObjPrinter: 98 """Pretty-printer for mongo::BSONObj""" 99 100 def __init__(self, val): 101 self.val = val 102 self.ptr = self.val['_objdata'].cast(gdb.lookup_type('void').pointer()) 103 # Handle the endianness of the BSON object size, which is represented as a 32-bit integer 104 # in little-endian format. 105 inferior = gdb.selected_inferior() 106 if self.ptr.is_optimized_out: 107 # If the value has been optimized out, we cannot decode it. 108 self.size = -1 109 else: 110 self.size = struct.unpack('<I', inferior.read_memory(self.ptr, 4))[0] 111 112 def display_hint(self): 113 return 'map' 114 115 def children(self): 116 # Do not decode a BSONObj with an invalid size. 117 if not bson or self.size < 5 or self.size > 17 * 1024 * 1024: 118 return 119 120 inferior = gdb.selected_inferior() 121 buf = bytes(inferior.read_memory(self.ptr, self.size)) 122 options = CodecOptions(document_class=collections.OrderedDict) 123 bsondoc = bson.BSON.decode(buf, codec_options=options) 124 125 for k, v in bsondoc.items(): 126 yield 'key', k 127 yield 'value', bson.json_util.dumps(v) 128 129 def to_string(self): 130 # The value has been optimized out. 131 if self.size == -1: 132 return "BSONObj @ %s" % (self.ptr) 133 134 ownership = "owned" if self.val['_ownedBuffer']['_buffer']['_holder']['px'] else "unowned" 135 136 size = self.size 137 # Print an invalid BSONObj size in hex. 138 if size < 5 or size > 17 * 1024 * 1024: 139 size = hex(size) 140 141 if size == 5: 142 return "%s empty BSONObj @ %s" % (ownership, self.ptr) 143 else: 144 return "%s BSONObj %s bytes @ %s" % (ownership, size, self.ptr) 145 146 147class UnorderedFastKeyTablePrinter: 148 """Pretty-printer for mongo::UnorderedFastKeyTable<>""" 149 150 def __init__(self, val): 151 self.val = val 152 153 # Get the value_type by doing a type lookup 154 valueTypeName = val.type.strip_typedefs().name + "::value_type" 155 valueType = gdb.lookup_type(valueTypeName).target() 156 self.valueTypePtr = valueType.pointer() 157 158 def display_hint(self): 159 return 'map' 160 161 def to_string(self): 162 return "UnorderedFastKeyTablePrinter<%s> with %s elems " % ( 163 self.val.type.template_argument(0), self.val["_size"]) 164 165 def children(self): 166 cap = self.val["_area"]["_hashMask"] + 1 167 it = get_unique_ptr(self.val["_area"]["_entries"]) 168 end = it + cap 169 170 if it == 0: 171 return 172 173 while it != end: 174 elt = it.dereference() 175 it += 1 176 if not elt['_used']: 177 continue 178 179 value = elt['_data']["__data"].cast(self.valueTypePtr).dereference() 180 181 yield ('key', value['first']) 182 yield ('value', value['second']) 183 184 185class DecorablePrinter: 186 """Pretty-printer for mongo::Decorable<>""" 187 188 def __init__(self, val): 189 self.val = val 190 191 decl_vector = val["_decorations"]["_registry"]["_decorationInfo"] 192 # TODO: abstract out navigating a std::vector 193 self.start = decl_vector["_M_impl"]["_M_start"] 194 finish = decl_vector["_M_impl"]["_M_finish"] 195 decinfo_t = gdb.lookup_type('mongo::DecorationRegistry::DecorationInfo') 196 self.count = int((int(finish) - int(self.start)) / decinfo_t.sizeof) 197 198 def display_hint(self): 199 return 'map' 200 201 def to_string(self): 202 return "Decorable<%s> with %s elems " % (self.val.type.template_argument(0), 203 self.count) 204 205 def children(self): 206 decorationData = get_unique_ptr(self.val["_decorations"]["_decorationData"]) 207 208 for index in range(self.count): 209 descriptor = self.start[index] 210 dindex = int(descriptor["descriptor"]["_index"]) 211 212 # In order to get the type stored in the decorable, we examine the type of its 213 # constructor, and do some string manipulations. 214 # TODO: abstract out navigating a std::function 215 type_name = str(descriptor["constructor"]["_M_functor"]["_M_unused"]["_M_object"]) 216 type_name = type_name[0:len(type_name) - 1] 217 type_name = type_name[0: type_name.rindex(">")] 218 type_name = type_name[type_name.index("constructAt<"):].replace("constructAt<", "") 219 220 # If the type is a pointer type, strip the * at the end. 221 if type_name.endswith('*'): 222 type_name = type_name[0:len(type_name) - 1] 223 type_name = type_name.rstrip() 224 225 # Cast the raw char[] into the actual object that is stored there. 226 type_t = gdb.lookup_type(type_name) 227 obj = decorationData[dindex].cast(type_t) 228 229 yield ('key', "%d:%s:%s" % (index, obj.address, type_name)) 230 yield ('value', obj) 231 232 233def find_match_brackets(search, opening='<', closing='>'): 234 """Returns the index of the closing bracket that matches the first opening bracket. 235 Returns -1 if no last matching bracket is found, i.e. not a template. 236 237 Example: 238 'Foo<T>::iterator<U>'' 239 returns 5 240 """ 241 index = search.find(opening) 242 if index == -1: 243 return -1 244 245 start = index + 1 246 count = 1 247 str_len = len(search) 248 for index in range(start, str_len): 249 c = search[index] 250 251 if c == opening: 252 count += 1 253 elif c == closing: 254 count -= 1 255 256 if count == 0: 257 return index 258 259 return -1 260 261 262class MongoSubPrettyPrinter(gdb.printing.SubPrettyPrinter): 263 """Sub pretty printer managed by the pretty-printer collection""" 264 265 def __init__(self, name, prefix, is_template, printer): 266 super(MongoSubPrettyPrinter, self).__init__(name) 267 self.prefix = prefix 268 self.printer = printer 269 self.is_template = is_template 270 271 272class MongoPrettyPrinterCollection(gdb.printing.PrettyPrinter): 273 """MongoDB-specific printer printer collection that ignores subtypes. 274 It will match 'HashTable<T> but not 'HashTable<T>::iterator' when asked for 'HashTable'. 275 """ 276 277 def __init__(self): 278 super(MongoPrettyPrinterCollection, self).__init__("mongo", []) 279 280 def add(self, name, prefix, is_template, printer): 281 self.subprinters.append(MongoSubPrettyPrinter(name, prefix, is_template, printer)) 282 283 def __call__(self, val): 284 285 # Get the type name. 286 lookup_tag = gdb.types.get_basic_type(val.type).tag 287 if not lookup_tag: 288 lookup_tag = val.type.name 289 if not lookup_tag: 290 return None 291 292 index = find_match_brackets(lookup_tag) 293 294 # Ignore subtypes of classes 295 # We do not want HashTable<T>::iterator as an example, just HashTable<T> 296 if index == -1 or index + 1 == len(lookup_tag): 297 for printer in self.subprinters: 298 if printer.enabled and ( 299 (printer.is_template and lookup_tag.find(printer.prefix) == 0) or 300 (not printer.is_template and lookup_tag == printer.prefix)): 301 return printer.printer(val) 302 303 return None 304 305 306def build_pretty_printer(): 307 pp = MongoPrettyPrinterCollection() 308 pp.add('BSONObj', 'mongo::BSONObj', False, BSONObjPrinter) 309 pp.add('Decorable', 'mongo::Decorable', True, DecorablePrinter) 310 pp.add('Status', 'mongo::Status', False, StatusPrinter) 311 pp.add('StatusWith', 'mongo::StatusWith', True, StatusWithPrinter) 312 pp.add('StringData', 'mongo::StringData', False, StringDataPrinter) 313 pp.add('UnorderedFastKeyTable', 'mongo::UnorderedFastKeyTable', True, UnorderedFastKeyTablePrinter) 314 return pp 315 316################################################################################################### 317# 318# Setup 319# 320################################################################################################### 321 322# Register pretty-printers, replace existing mongo printers 323gdb.printing.register_pretty_printer( 324 gdb.current_objfile(), 325 build_pretty_printer(), 326 True) 327 328print("MongoDB GDB pretty-printers loaded") 329