# Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """GDB support for Chrome types. Add this to your gdb by amending your ~/.gdbinit as follows: python import sys sys.path.insert(1, "/path/to/tools/gdb/") import gdb_chrome end Use (gdb) p /r any_variable to print |any_variable| without using any printers. To interactively type Python for development of the printers: (gdb) python foo = gdb.parse_and_eval('bar') to put the C++ value 'bar' in the current scope into a Python variable 'foo'. Then you can interact with that variable: (gdb) python print foo['impl_'] """ import datetime import gdb import gdb.printing import os import re import sys sys.path.insert( 1, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'util')) import class_methods sys.path.insert( 1, os.path.join( os.path.dirname(os.path.abspath(__file__)), '..', '..', 'third_party', 'blink', 'tools', 'gdb')) try: import blink finally: sys.path.pop(1) # When debugging this module, set the below variable to True, and then use # (gdb) python del sys.modules['gdb_chrome'] # (gdb) python import gdb_chrome # to reload. _DEBUGGING = False pp_set = gdb.printing.RegexpCollectionPrettyPrinter("chromium") def typed_ptr(ptr): """Prints a pointer along with its exact type. By default, gdb would print just the address, which takes more steps to interpret. """ # Returning this as a cast expression surrounded by parentheses # makes it easier to cut+paste inside of gdb. return '((%s)%s)' % (ptr.dynamic_type, ptr) def yield_fields(val): """Use this in a printer's children() method to print an object's fields. e.g. def children(): for result in yield_fields(self.val): yield result """ try: fields = val.type.target().fields() except: fields = val.type.fields() for field in fields: if field.is_base_class: yield (field.name, val.cast(gdb.lookup_type(field.name))) else: yield (field.name, val[field.name]) class Printer(object): def __init__(self, val): self.val = val class StringPrinter(Printer): def display_hint(self): return 'string' class String16Printer(StringPrinter): def to_string(self): return blink.ustring_to_string(self.val['_M_dataplus']['_M_p']) pp_set.add_printer('string16', '^string16|std::basic_string<(unsigned short|char16_t).*>$', String16Printer) class GURLPrinter(StringPrinter): def to_string(self): return self.val['spec_'] pp_set.add_printer('GURL', '^GURL$', GURLPrinter) class FilePathPrinter(StringPrinter): def to_string(self): return self.val['path_']['_M_dataplus']['_M_p'] pp_set.add_printer('FilePath', '^FilePath$', FilePathPrinter) class SmartPtrPrinter(Printer): def to_string(self): return '%s%s' % (self.typename, typed_ptr(self.ptr())) class ScopedPtrPrinter(SmartPtrPrinter): typename = 'scoped_ptr' def ptr(self): return self.val['impl_']['data_']['ptr'] pp_set.add_printer('scoped_ptr', '^scoped_ptr<.*>$', ScopedPtrPrinter) class ScopedRefPtrPrinter(SmartPtrPrinter): typename = 'scoped_refptr' def ptr(self): return self.val['ptr_'] pp_set.add_printer('scoped_refptr', '^scoped_refptr<.*>$', ScopedRefPtrPrinter) class LinkedPtrPrinter(SmartPtrPrinter): typename = 'linked_ptr' def ptr(self): return self.val['value_'] pp_set.add_printer('linked_ptr', '^linked_ptr<.*>$', LinkedPtrPrinter) class WeakPtrPrinter(SmartPtrPrinter): typename = 'base::WeakPtr' def ptr(self): # Check that the pointer is valid. The invalidated flag is stored at # val.ref_.flag_.ptr_->invalidated_.flag_.__a_.__a_value. This is a gdb # implementation of base::WeakReference::IsValid(). This is necessary # because calling gdb.parse_and_eval('(*(%s*)(%s)).ref_.IsValid()' % # (self.val.type, self.val.address))) does not work in all cases. ptr = self.val['ref_']['flag_']['ptr_'] if (ptr and not ptr.dereference()['invalidated_']['flag_']['__a_']['__a_value']): return self.val['ptr_'] return gdb.Value(0).cast(self.val['ptr_'].type) pp_set.add_printer('base::WeakPtr', '^base::WeakPtr<.*>$', WeakPtrPrinter) class CallbackPrinter(Printer): """Callbacks provide no usable information so reduce the space they take.""" def to_string(self): return '...' pp_set.add_printer('base::OnceCallback', '^base::OnceCallback<.*>$', CallbackPrinter) pp_set.add_printer('base::RepeatingCallback', '^base::RepeatingCallback<.*>$', CallbackPrinter) class LocationPrinter(Printer): def to_string(self): return '%s()@%s:%s' % (self.val['function_name_'].string(), self.val['file_name_'].string(), self.val['line_number_']) pp_set.add_printer('base::Location', '^base::Location$', LocationPrinter) class PendingTaskPrinter(Printer): def to_string(self): return 'From %s' % (self.val['posted_from'],) def children(self): for result in yield_fields(self.val): if result[0] not in ('task', 'posted_from'): yield result pp_set.add_printer('base::PendingTask', '^base::PendingTask$', PendingTaskPrinter) class LockPrinter(Printer): def to_string(self): try: if self.val['owned_by_thread_']: return 'Locked by thread %s' % self.val['owning_thread_id_'] else: return 'Unlocked' except gdb.error: return 'Unknown state' pp_set.add_printer('base::Lock', '^base::Lock$', LockPrinter) class AbslOptionalPrinter(Printer): def to_string(self): if self.val['engaged_']: return "%s: %s" % (str(self.val.type.tag), self.val['data_']) else: return "%s: is empty" % str(self.val.type.tag) pp_set.add_printer('absl::optional', '^absl::optional<.*>$', AbslOptionalPrinter) class TimeDeltaPrinter(object): def __init__(self, val): self._timedelta = datetime.timedelta(microseconds=int(val['delta_'])) def timedelta(self): return self._timedelta def to_string(self): return str(self._timedelta) pp_set.add_printer('base::TimeDelta', '^base::TimeDelta$', TimeDeltaPrinter) class TimeTicksPrinter(TimeDeltaPrinter): def __init__(self, val): self._timedelta = datetime.timedelta(microseconds=int(val['us_'])) pp_set.add_printer('base::TimeTicks', '^base::TimeTicks$', TimeTicksPrinter) class TimePrinter(object): def __init__(self, val): timet_offset = gdb.parse_and_eval('base::Time::kTimeTToMicrosecondsOffset') self._datetime = ( datetime.datetime.fromtimestamp(0) + datetime.timedelta(microseconds=int(val['us_'] - timet_offset))) def datetime(self): return self._datetime def to_string(self): return str(self._datetime) pp_set.add_printer('base::Time', '^base::Time$', TimePrinter) class FlatTreePrinter(object): def __init__(self, val): self.val = val def to_string(self): # It would be nice to match the output of std::map which is a little # nicer than printing the vector of pairs. But iterating over it in # Python is much more complicated and this output is reasonable. # (Without this printer, a flat_map will output 7 lines of internal # template goop before the vector contents.) return 'base::flat_tree with ' + str(self.val['impl_']['body_']) pp_set.add_printer('base::flat_map', '^base::flat_map<.*>$', FlatTreePrinter) pp_set.add_printer('base::flat_set', '^base::flat_set<.*>$', FlatTreePrinter) pp_set.add_printer('base::flat_tree', '^base::internal::flat_tree<.*>$', FlatTreePrinter) class ValuePrinter(object): def __init__(self, val): self.val = val def get_type(self): return self.val['type_'] def to_string(self): typestr = str(self.get_type()) # Trim prefix to just get the emum short name. typestr = typestr[typestr.rfind(':') + 1:] if typestr == 'NONE': return 'base::Value of type NONE' if typestr == 'BOOLEAN': valuestr = self.val['bool_value_'] if typestr == 'INTEGER': valuestr = self.val['int_value_'] if typestr == 'DOUBLE': valuestr = self.val['double_value_'] if typestr == 'STRING': valuestr = self.val['string_value_'] if typestr == 'BINARY': valuestr = self.val['binary_value_'] if typestr == 'DICTIONARY': valuestr = self.val['dict_'] if typestr == 'LIST': valuestr = self.val['list_'] return "base::Value of type %s = %s" % (typestr, str(valuestr)) pp_set.add_printer('base::Value', '^base::Value$', ValuePrinter) pp_set.add_printer('base::ListValue', '^base::ListValue$', ValuePrinter) pp_set.add_printer('base::DictionaryValue', '^base::DictionaryValue$', ValuePrinter) class IpcMessagePrinter(Printer): def header(self): return self.val['header_'].cast( gdb.lookup_type('IPC::Message::Header').pointer()) def to_string(self): message_type = self.header()['type'] return '%s of kind %s line %s' % (self.val.dynamic_type, (message_type >> 16).cast( gdb.lookup_type('IPCMessageStart')), message_type & 0xffff) def children(self): yield ('header_', self.header().dereference()) yield ('capacity_after_header_', self.val['capacity_after_header_']) for field in self.val.type.fields(): if field.is_base_class: continue yield (field.name, self.val[field.name]) pp_set.add_printer('IPC::Message', '^IPC::Message$', IpcMessagePrinter) class NotificationRegistrarPrinter(Printer): def to_string(self): try: registrations = self.val['registered_'] vector_finish = registrations['_M_impl']['_M_finish'] vector_start = registrations['_M_impl']['_M_start'] if vector_start == vector_finish: return 'Not watching notifications' if vector_start.dereference().type.sizeof == 0: # Incomplete type: b/8242773 return 'Watching some notifications' return ('Watching %s notifications; ' 'print %s->registered_ for details') % (int( vector_finish - vector_start), typed_ptr(self.val.address)) except gdb.error: return 'NotificationRegistrar' pp_set.add_printer('content::NotificationRegistrar', '^content::NotificationRegistrar$', NotificationRegistrarPrinter) class SiteInstanceImplPrinter(object): def __init__(self, val): self.val = val.cast(val.dynamic_type) def to_string(self): return 'SiteInstanceImpl@%s for %s' % (self.val.address, self.val['site_']) def children(self): yield ('id_', self.val['id_']) yield ('has_site_', self.val['has_site_']) if self.val['browsing_instance_']['ptr_']: yield ('browsing_instance_', self.val['browsing_instance_']['ptr_']) if self.val['process_']: yield ('process_', typed_ptr(self.val['process_'])) pp_set.add_printer('content::SiteInstanceImpl', '^content::SiteInstanceImpl$', SiteInstanceImplPrinter) class RenderProcessHostImplPrinter(object): def __init__(self, val): self.val = val.cast(val.dynamic_type) def to_string(self): pid = '' try: child_process_launcher_ptr = ( self.val['child_process_launcher_']['impl_']['data_']['ptr']) if child_process_launcher_ptr: context = (child_process_launcher_ptr['context_']['ptr_']) if context: pid = ' PID %s' % str(context['process_']['process_']) except gdb.error: # The definition of the Context type may not be available. # b/8242773 pass return 'RenderProcessHostImpl@%s%s' % (self.val.address, pid) def children(self): yield ('id_', self.val['id_']) yield ('listeners_', self.val['listeners_']['data_']) yield ('worker_ref_count_', self.val['worker_ref_count_']) yield ('fast_shutdown_started_', self.val['fast_shutdown_started_']) yield ('deleting_soon_', self.val['deleting_soon_']) yield ('pending_views_', self.val['pending_views_']) yield ('visible_widgets_', self.val['visible_widgets_']) yield ('backgrounded_', self.val['backgrounded_']) yield ('widget_helper_', self.val['widget_helper_']) yield ('is_initialized_', self.val['is_initialized_']) yield ('browser_context_', typed_ptr(self.val['browser_context_'])) yield ('sudden_termination_allowed_', self.val['sudden_termination_allowed_']) yield ('ignore_input_events_', self.val['ignore_input_events_']) yield ('is_guest_', self.val['is_guest_']) pp_set.add_printer('content::RenderProcessHostImpl', '^content::RenderProcessHostImpl$', RenderProcessHostImplPrinter) class AtomicPrinter(Printer): typename = 'atomic' def to_string(self): return self.val['__a_']['__a_value'] pp_set.add_printer('std::__Cr::atomic', '^std::__Cr::atomic<.*>$', AtomicPrinter) gdb.printing.register_pretty_printer(gdb, pp_set, replace=_DEBUGGING) """Implementations of inlined libc++ std container functions.""" def gdb_running_under_rr(): try: # rr defines the when command to return the current event number. gdb.execute('when') # If there was no error executing the command, we are running under rr. return True except gdb.error: return False def find_nearest_frame_matching(frame, predicate): while frame and not predicate(frame): frame = frame.older() return frame class ReverseCallback(gdb.Command): """Find when the currently running callback was created.""" def __init__(self): super(ReverseCallback, self).__init__("reverse-callback", gdb.COMMAND_USER) def invoke(self, arg, from_tty): if not gdb_running_under_rr(): raise gdb.error('reverse-callback requires debugging under rr: ' + 'https://rr-project.org/') # Find the stack frame which extracts the bind state from the task. bind_state_frame = find_nearest_frame_matching( gdb.selected_frame(), lambda frame : frame.function() and re.match('^base::internal::Invoker' + '::RunOnce\(base::internal::BindStateBase\*\)$', frame.function().name)) if bind_state_frame is None: raise Exception( 'base::internal::Invoker frame not found; are you in a callback?') bind_state_frame.select() # Disable all existing breakpoints. was_enabled = [] for breakpoint in gdb.breakpoints(): was_enabled.append(breakpoint.enabled) breakpoint.enabled = False # Break on the initialization of the BindState. storage_address = gdb.parse_and_eval('storage') watchpoint = gdb.Breakpoint('*' + str(storage_address), gdb.BP_WATCHPOINT) # Find the construction. gdb.execute('reverse-continue') # Restore breakpoints watchpoint.delete() for breakpoint, enabled in zip(gdb.breakpoints(), was_enabled): breakpoint.enabled = enabled # Find the stack frame which created the BindState. def in_bindstate(frame): return frame.function() and frame.function().name.startswith( 'base::internal::BindState<') creation_frame = find_nearest_frame_matching( find_nearest_frame_matching(gdb.selected_frame(), in_bindstate), lambda frame: not in_bindstate(frame)) # The callback creates the bindstate, step up once more to get the creator # of the callback. creation_frame.older().select() ReverseCallback() @class_methods.Class('std::__1::vector', template_types=['T']) class LibcppVector(object): @class_methods.member_function('T&', 'operator[]', ['int']) def element(obj, i): return obj['__begin_'][i] @class_methods.member_function('size_t', 'size', []) def size(obj): return obj['__end_'] - obj['__begin_'] @class_methods.Class('std::__1::unique_ptr', template_types=['T']) class LibcppUniquePtr(object): @class_methods.member_function('T*', 'get', []) def get(obj): return obj['__ptr_']['__value_'] @class_methods.member_function('T*', 'operator->', []) def arrow(obj): return obj['__ptr_']['__value_'] @class_methods.member_function('T&', 'operator*', []) def dereference(obj): return obj['__ptr_']['__value_'].dereference()