|
7 | 7 |
|
8 | 8 | import gdb |
9 | 9 |
|
| 10 | +# pylint: disable=invalid-name,wildcard-import |
| 11 | +try: |
| 12 | + # Try to find and load the C++ pretty-printer library. |
| 13 | + import glob |
| 14 | + pp = glob.glob("/opt/mongodbtoolchain/v2/share/gcc-*/python/libstdcxx/v6/printers.py") |
| 15 | + printers = pp[0] |
| 16 | + path = os.path.dirname(os.path.dirname(os.path.dirname(printers))) |
| 17 | + sys.path.insert(0, path) |
| 18 | + from libstdcxx.v6.printers import * |
| 19 | + print("Loaded libstdc++ pretty printers from '%s'" % printers) |
| 20 | +except ImportError as e: |
| 21 | + print("Failed to load the libstdc++ pretty printers: " + str(e)) |
| 22 | +# pylint: enable=invalid-name,wildcard-import |
| 23 | + |
| 24 | +if sys.version_info[0] >= 3: |
| 25 | + # GDB only permits converting a gdb.Value instance to its numerical address when using the |
| 26 | + # long() constructor in Python 2 and not when using the int() constructor. We define the |
| 27 | + # 'long' class as an alias for the 'int' class in Python 3 for compatibility. |
| 28 | + long = int # pylint: disable=redefined-builtin,invalid-name |
| 29 | + |
10 | 30 |
|
11 | 31 | def get_process_name(): |
12 | 32 | """Return the main binary we are attached to.""" |
@@ -49,6 +69,88 @@ def get_current_thread_name(): |
49 | 69 | return fallback_name |
50 | 70 |
|
51 | 71 |
|
| 72 | +def get_global_service_context(): |
| 73 | + """Return the global ServiceContext object.""" |
| 74 | + return gdb.parse_and_eval("'mongo::(anonymous namespace)::globalServiceContext'").dereference() |
| 75 | + |
| 76 | + |
| 77 | +def get_session_catalog(): |
| 78 | + """Return the global SessionCatalog object. |
| 79 | +
|
| 80 | + Returns None if no SessionCatalog could be found. |
| 81 | + """ |
| 82 | + # The SessionCatalog is a decoration on the ServiceContext. |
| 83 | + session_catalog_dec = get_decoration(get_global_service_context(), "mongo::SessionCatalog") |
| 84 | + if session_catalog_dec is None: |
| 85 | + return None |
| 86 | + return session_catalog_dec[1] |
| 87 | + |
| 88 | + |
| 89 | +def get_decorations(obj): |
| 90 | + """Return an iterator to all decorations on a given object. |
| 91 | +
|
| 92 | + Each object returned by the iterator is a tuple whose first element is the type name of the |
| 93 | + decoration and whose second element is the decoration object itself. |
| 94 | +
|
| 95 | + TODO: De-duplicate the logic between here and DecorablePrinter. This code was copied from there. |
| 96 | + """ |
| 97 | + type_name = str(obj.type).replace(" ", "") |
| 98 | + decorable = obj.cast(gdb.lookup_type("mongo::Decorable<{}>".format(type_name))) |
| 99 | + decl_vector = decorable["_decorations"]["_registry"]["_decorationInfo"] |
| 100 | + start = decl_vector["_M_impl"]["_M_start"] |
| 101 | + finish = decl_vector["_M_impl"]["_M_finish"] |
| 102 | + |
| 103 | + decorable_t = decorable.type.template_argument(0) |
| 104 | + decinfo_t = gdb.lookup_type('mongo::DecorationRegistry<{}>::DecorationInfo'.format(decorable_t)) |
| 105 | + count = long((long(finish) - long(start)) / decinfo_t.sizeof) |
| 106 | + |
| 107 | + for i in range(count): |
| 108 | + descriptor = start[i] |
| 109 | + dindex = int(descriptor["descriptor"]["_index"]) |
| 110 | + |
| 111 | + type_name = str(descriptor["constructor"]) |
| 112 | + type_name = type_name[0:len(type_name) - 1] |
| 113 | + type_name = type_name[0:type_name.rindex(">")] |
| 114 | + type_name = type_name[type_name.index("constructAt<"):].replace("constructAt<", "") |
| 115 | + # get_unique_ptr should be loaded from 'mongo_printers.py'. |
| 116 | + decoration_data = get_unique_ptr(decorable["_decorations"]["_decorationData"]) # pylint: disable=undefined-variable |
| 117 | + |
| 118 | + if type_name.endswith('*'): |
| 119 | + type_name = type_name[0:len(type_name) - 1] |
| 120 | + type_name = type_name.rstrip() |
| 121 | + type_t = gdb.lookup_type(type_name) |
| 122 | + obj = decoration_data[dindex].cast(type_t) |
| 123 | + yield (type_name, obj) |
| 124 | + |
| 125 | + |
| 126 | +def get_decoration(obj, type_name): |
| 127 | + """Find a decoration on 'obj' where the string 'type_name' is in the decoration's type name. |
| 128 | +
|
| 129 | + Returns a tuple whose first element is the type name of the decoration and whose |
| 130 | + second is the decoration itself. If there are multiple such decorations, returns the first one |
| 131 | + that matches. Returns None if no matching decorations were found. |
| 132 | + """ |
| 133 | + for dec_type_name, dec in get_decorations(obj): |
| 134 | + if type_name in dec_type_name: |
| 135 | + return (dec_type_name, dec) |
| 136 | + return None |
| 137 | + |
| 138 | + |
| 139 | +def get_boost_optional(optional): |
| 140 | + """ |
| 141 | + Retrieve the value stored in a boost::optional type, if it is non-empty. |
| 142 | +
|
| 143 | + Returns None if the optional is empty. |
| 144 | +
|
| 145 | + TODO: Import the boost pretty printers instead of using this custom function. |
| 146 | + """ |
| 147 | + if not optional['m_initialized']: |
| 148 | + return None |
| 149 | + value_ref_type = optional.type.template_argument(0).pointer() |
| 150 | + storage = optional['m_storage']['dummy_']['data'] |
| 151 | + return storage.cast(value_ref_type).dereference() |
| 152 | + |
| 153 | + |
52 | 154 | ################################################################################################### |
53 | 155 | # |
54 | 156 | # Commands |
@@ -91,6 +193,140 @@ def invoke(self, arg, _from_tty): # pylint: disable=no-self-use,unused-argument |
91 | 193 | DumpGlobalServiceContext() |
92 | 194 |
|
93 | 195 |
|
| 196 | +class GetMongoDecoration(gdb.Command): |
| 197 | + """ |
| 198 | + Search for a decoration on an object by typename and print it e.g. |
| 199 | +
|
| 200 | + (gdb) mongo-decoration opCtx ReadConcernArgs |
| 201 | +
|
| 202 | + would print out a decoration on opCtx whose type name contains the string "ReadConcernArgs". |
| 203 | + """ |
| 204 | + |
| 205 | + def __init__(self): |
| 206 | + """Initialize GetMongoDecoration.""" |
| 207 | + RegisterMongoCommand.register(self, "mongo-decoration", gdb.COMMAND_DATA) |
| 208 | + |
| 209 | + def invoke(self, args, _from_tty): # pylint: disable=unused-argument,no-self-use |
| 210 | + """Invoke GetMongoDecoration.""" |
| 211 | + argarr = args.split(" ") |
| 212 | + if len(argarr) < 2: |
| 213 | + raise ValueError("Must provide both an object and type_name argument.") |
| 214 | + |
| 215 | + # The object that is decorated. |
| 216 | + expr = argarr[0] |
| 217 | + # The substring of the decoration type that is to be printed. |
| 218 | + type_name_substr = argarr[1] |
| 219 | + dec = get_decoration(gdb.parse_and_eval(expr), type_name_substr) |
| 220 | + if dec: |
| 221 | + (type_name, obj) = dec |
| 222 | + print(type_name, obj) |
| 223 | + else: |
| 224 | + print("No decoration found whose type name contains '" + type_name_substr + "'.") |
| 225 | + |
| 226 | + |
| 227 | +# Register command |
| 228 | +GetMongoDecoration() |
| 229 | + |
| 230 | + |
| 231 | +class DumpMongoDSessionCatalog(gdb.Command): |
| 232 | + """Print out the mongod SessionCatalog, which maintains a table of all Sessions. |
| 233 | +
|
| 234 | + Prints out interesting information from TransactionParticipants too, which are decorations on |
| 235 | + the Session. If no arguments are provided, dumps out all sessions. Can optionally provide a |
| 236 | + session id argument. In that case, will only print the session for the specified id, if it is |
| 237 | + found. e.g. |
| 238 | +
|
| 239 | + (gdb) dump-sessions "32cb9e84-98ad-4322-acf0-e055cad3ef73" |
| 240 | +
|
| 241 | + """ |
| 242 | + |
| 243 | + def __init__(self): |
| 244 | + """Initialize DumpMongoDSessionCatalog.""" |
| 245 | + RegisterMongoCommand.register(self, "mongod-dump-sessions", gdb.COMMAND_DATA) |
| 246 | + |
| 247 | + def invoke(self, args, _from_tty): # pylint: disable=unused-argument,no-self-use,too-many-locals |
| 248 | + """Invoke DumpMongoDSessionCatalog.""" |
| 249 | + # See if a particular session id was specified. |
| 250 | + argarr = args.split(" ") |
| 251 | + lsid_to_find = None |
| 252 | + if argarr: |
| 253 | + lsid_to_find = argarr[0] |
| 254 | + |
| 255 | + # Get the SessionCatalog and the table of sessions. |
| 256 | + session_catalog = get_session_catalog() |
| 257 | + if session_catalog is None: |
| 258 | + print( |
| 259 | + "No SessionCatalog object was found on the ServiceContext. Not dumping any sessions." |
| 260 | + ) |
| 261 | + return |
| 262 | + lsid_map = session_catalog["_sessions"] |
| 263 | + session_kv_pairs = list(StdHashtableIterator(lsid_map['_M_h'])) # pylint: disable=undefined-variable |
| 264 | + print("Dumping %d Session objects from the SessionCatalog" % len(session_kv_pairs)) |
| 265 | + |
| 266 | + # Optionally search for a specified session, based on its id. |
| 267 | + if lsid_to_find: |
| 268 | + print("Only printing information for session " + lsid_to_find + ", if found.") |
| 269 | + lsids_to_print = [lsid_to_find] |
| 270 | + else: |
| 271 | + lsids_to_print = [str(s['first']['_id']) for s in session_kv_pairs] |
| 272 | + |
| 273 | + for sess_kv in session_kv_pairs: |
| 274 | + # The Session is stored inside the SessionRuntimeInfo object. |
| 275 | + session_runtime_info = sess_kv['second']['_M_ptr'].dereference() |
| 276 | + session = session_runtime_info['session'] |
| 277 | + # TODO: Add a custom pretty printer for LogicalSessionId. |
| 278 | + lsid_str = str(session['_sessionId']['_id']) |
| 279 | + |
| 280 | + # If we are only interested in a specific session, then we print out the entire Session |
| 281 | + # object, to aid more detailed debugging. |
| 282 | + if lsid_str == lsid_to_find: |
| 283 | + print("SessionId", "=", lsid_str) |
| 284 | + print(session) |
| 285 | + # Terminate if this is the only session we care about. |
| 286 | + break |
| 287 | + |
| 288 | + # Only print info for the necessary sessions. |
| 289 | + if lsid_str not in lsids_to_print: |
| 290 | + continue |
| 291 | + |
| 292 | + # If we are printing multiple sessions, we only print the most interesting fields from |
| 293 | + # the Session object for the sake of efficiency. We print the session id string first so |
| 294 | + # the session is easily identifiable. |
| 295 | + print("Session (" + str(session.address) + "):") |
| 296 | + print("SessionId", "=", lsid_str) |
| 297 | + session_fields_to_print = ['_sessionId', '_checkoutOpCtx', '_killRequested'] |
| 298 | + for field in session_fields_to_print: |
| 299 | + print(field, "=", session[field]) |
| 300 | + |
| 301 | + # Print the information from a TransactionParticipant if a session has one. Otherwise |
| 302 | + # we just print the session's id and nothing else. |
| 303 | + txn_part_dec = get_decoration(session, "TransactionParticipant") |
| 304 | + if txn_part_dec: |
| 305 | + # Only print the most interesting fields for debugging transactions issues. |
| 306 | + txn_part = txn_part_dec[1] |
| 307 | + fields_to_print = ['_txnState', '_activeTxnNumber'] |
| 308 | + print("TransactionParticipant (" + str(txn_part.address) + "):") |
| 309 | + for field in fields_to_print: |
| 310 | + print(field, "=", txn_part[field]) |
| 311 | + |
| 312 | + # The '_txnResourceStash' field is a boost::optional so we unpack it |
| 313 | + # manually if it is non-empty. We are only interested in its Locker object for now. |
| 314 | + # TODO: Load the boost pretty printers so the object will be printed clearly |
| 315 | + # by default, without the need for special unpacking. |
| 316 | + val = get_boost_optional(txn_part['_txnResourceStash']) |
| 317 | + if val: |
| 318 | + locker_addr = val["_locker"]["_M_t"]['_M_head_impl'] |
| 319 | + print('_txnResourceStash._locker', "@", locker_addr) |
| 320 | + else: |
| 321 | + print('_txnResourceStash', "=", None) |
| 322 | + # Separate sessions by a newline. |
| 323 | + print("") |
| 324 | + |
| 325 | + |
| 326 | +# Register command |
| 327 | +DumpMongoDSessionCatalog() |
| 328 | + |
| 329 | + |
94 | 330 | class MongoDBDumpLocks(gdb.Command): |
95 | 331 | """Dump locks in mongod process.""" |
96 | 332 |
|
|
0 commit comments