def __init__(self, debugger_store, app): Thread.__init__(self) self.daemon = True self._debugger_store = debugger_store self._app = app self._listener = debugger_store.debugger.GetListener() lldb = get_lldb() self.breakpoint_event_type_to_name_map = { lldb.eBreakpointEventTypeAdded: 'Added', lldb.eBreakpointEventTypeCommandChanged: 'Command Changed', lldb.eBreakpointEventTypeConditionChanged: 'Condition Changed', lldb.eBreakpointEventTypeDisabled: 'Disabled', lldb.eBreakpointEventTypeEnabled: 'Enabled', lldb.eBreakpointEventTypeIgnoreChanged: 'Ignore Changed', lldb.eBreakpointEventTypeInvalidType: 'Invalid Type', lldb.eBreakpointEventTypeLocationsAdded: 'Location Added', lldb.eBreakpointEventTypeLocationsRemoved: 'Location Removed', lldb.eBreakpointEventTypeLocationsResolved: 'Location Resolved', lldb.eBreakpointEventTypeRemoved: 'Removed', lldb.eBreakpointEventTypeThreadChanged: 'Thread Changed', } process = debugger_store.debugger.GetSelectedTarget().process self._add_listener_to_process(process) # LLDB will not emit any stopping event during attach. # Linux lldb has a bug of not emitting stopping event during launch. if self._debugger_store.is_attach or sys.platform.startswith('linux'): if process.state != lldb.eStateStopped: # Instead of using assert() which will crash debugger log an error message # and tolerate this non-fatal situation. log_error( 'Inferior should be stopped after attach or linux launch') self._send_paused_notification(process) self._add_listener_to_target(process.target)
def _handle_process_event(self, event): lldb = get_lldb() # Ignore non-stopping events. if lldb.SBProcess.GetRestartedFromEvent(event): log_debug('Non stopping event: %s' % str(event)) return # Reset the object group so old frame variable objects don't linger # forever. self._debugger_store.thread_manager.release() process = lldb.SBProcess.GetProcessFromEvent(event) if process.state == lldb.eStateStopped: self._send_paused_notification(process) elif process.state == lldb.eStateExited: exit_message = 'Process(%d) exited with: %u' % ( process.GetProcessID(), process.GetExitStatus()) if process.GetExitDescription(): exit_message += (', ' + process.GetExitDescription()) self._send_user_output('log', exit_message) self.should_quit = True else: self._send_notification('Debugger.resumed', None) event_type = event.GetType() if event_type == lldb.SBProcess.eBroadcastBitSTDOUT: # Read stdout from inferior. process_output = '' while True: output_part = process.GetSTDOUT(1024) if not output_part or len(output_part) == 0: break process_output += output_part self._send_user_output('log', process_output)
def run(self): lldb = get_lldb() while not self.should_quit: event = lldb.SBEvent() if self._listener.WaitForEvent(1, event): if lldb.SBTarget.EventIsTargetEvent(event): self._handle_target_event(event) elif lldb.SBProcess.EventIsProcessEvent(event): self._handle_process_event(event) # Even though Breakpoints are registered on SBTarget # lldb.SBTarget.EventIsTargetEvent() # will return false for breakpoint events so handle them here. elif lldb.SBBreakpoint.EventIsBreakpointEvent(event): self._handle_breakpoint_event(event) elif lldb.SBWatchpoint.EventIsWatchpointEvent(event): self._handle_watchpoint_event(event) else: self._handle_unknown_event(event) # Event loop terminates, shutdown chrome server app. self._app.shutdown() # Detach/Kill inferior. if self._debugger_store.is_attach: self._debugger_store.debugger.GetSelectedTarget().process.Detach() else: self._debugger_store.debugger.GetSelectedTarget().process.Kill()
def update(self, process): """Update threads status for input process.""" threads_array = [] lldb = get_lldb() for thread in process.threads: description_stream = lldb.SBStream() thread.GetDescription(description_stream) frame = thread.GetSelectedFrame() location = self._debugger_store.location_serializer \ .get_frame_location(frame) threads_array.append({ 'id': thread.GetThreadID(), 'name': thread.GetName(), 'address': self._get_frame_name(frame), 'location': location, 'hasSource': self._debugger_store.location_serializer.has_source(frame), 'stopReason': self.get_thread_stop_description(thread), 'description': description_stream.GetData(), }) stopThreadId = process.GetSelectedThread().GetThreadID() if self._previousStopThreadId is not None and self._previousStopThreadId != stopThreadId: self._threadSwitchMessage = "Active thread switched from thread {0} to thread {1}" \ .format(self._previousStopThreadId, stopThreadId) else: self._threadSwitchMessage = None self._previousStopThreadId = stopThreadId params = { 'owningProcessId': process.id, 'stopThreadId': stopThreadId, 'threads': threads_array, } self._debugger_store.chrome_channel.send_notification('Debugger.threadsUpdated', params)
def send_threads_updated(self, process): """Update threads status for input process.""" threads_array = [] lldb = get_lldb() stopThreadId = process.GetSelectedThread().GetThreadID() for thread in process.threads: description_stream = lldb.SBStream() thread.GetDescription(description_stream) frame = thread.GetSelectedFrame() location = self._debugger_store.location_serializer \ .get_frame_location(frame) threads_array.append({ 'id': thread.GetThreadID(), 'name': thread.GetName(), 'address': self._get_frame_name(frame), 'location': location, 'hasSource': self._debugger_store.location_serializer.has_source(frame), 'stopReason': self.get_thread_stop_description(thread), 'description': description_stream.GetData(), }) params = { 'owningProcessId': process.id, 'stopThreadId': stopThreadId, 'threads': threads_array, } self._debugger_store.chrome_channel.send_notification( 'Debugger.threadsUpdated', params)
def folly_fbstring_core_formatter(valobj, internal_dict): '''Type summary formatter for folly fbstring_core. Please refer to https://github.com/facebook/folly/blob/master/folly/FBString.h for implementation details. ''' lldb = get_lldb() target_byte_order = lldb.target.GetByteOrder() capacity_sbvalue = valobj.GetValueForExpressionPath('.ml_.capacity_') category_extract_mask = 0x3 if target_byte_order == lldb.eByteOrderBig else \ (0xC0000000 if capacity_sbvalue.size == 4 else 0xC000000000000000) class Category: Small = 0 Medium = (0x2 if target_byte_order == lldb.eByteOrderBig else (0x80000000 if capacity_sbvalue.size == 4 else 0x8000000000000000)) Large = (0x2 if target_byte_order == lldb.eByteOrderBig else (0x40000000 if capacity_sbvalue.size == 4 else 0x4000000000000000)) category = get_category_value(capacity_sbvalue, category_extract_mask) if category == Category.Small: return valobj.GetValueForExpressionPath('.small_').GetSummary() else: assert category == Category.Medium or category == Category.Large, \ 'Unknown category: %d' % category return valobj.GetValueForExpressionPath('.ml_.data_').GetSummary()
def send_threads_updated(self, process): """Update threads status for input process.""" threads_array = [] lldb = get_lldb() stopThreadId = process.GetSelectedThread().GetThreadID() for thread in process.threads: description_stream = lldb.SBStream() thread.GetDescription(description_stream) frame = thread.GetSelectedFrame() location = self._debugger_store.location_serializer \ .get_frame_location(frame) threads_array.append({ 'id': thread.GetThreadID(), 'name': thread.GetName(), 'address': self._get_frame_name(frame), 'location': location, 'hasSource': self._debugger_store.location_serializer.has_source(frame), 'stopReason': self.get_thread_stop_description(thread), 'description': description_stream.GetData(), }) params = { 'owningProcessId': process.id, 'stopThreadId': stopThreadId, 'threads': threads_array, } self._debugger_store.chrome_channel.send_notification('Debugger.threadsUpdated', params)
def _send_module_event_notification(self, event, is_load): lldb = get_lldb() module_count = lldb.SBTarget.GetNumModulesFromEvent(event) for i in range(module_count): module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event) module_file_name = module.GetPlatformFileSpec().GetFilename() output = 'Module(%s) %s' % (module_file_name, 'load' if is_load else 'unload') self._send_user_output('log', output)
def handle_stop_debugging_signal(signum, frame): log_debug('handle_stop_debugging_signal called') if lldb_debugger.GetSelectedTarget() is not None and \ lldb_debugger.GetSelectedTarget().process is not None and \ lldb_debugger.GetSelectedTarget().process.state == \ get_lldb().eStateStopped: lldb_debugger.GetSelectedTarget().process.Detach() os._exit(0)
def handle(cls, value, add_object_func): lldb = get_lldb() if (value.type.type == lldb.eTypeClassStruct or ((value.type.type == lldb.eTypeClassTypedef) and (value.type.GetCanonicalType().type == lldb.eTypeClassStruct))): obj = add_object_func(CppStructSBValueRemoteObject(value, add_object_func)) return obj.serialized_value return None
def evaluate(self, params): # Cast to string from possible unicode. expression = str(params['expression']) # `objectGroups` are used by the client to designate remote objects on # the server that should stick around (for potential future queries), # and eventually released with a `releaseObjectGroup` call. # # Incidentally, they have names denoting which part of the client made # the request. We use these names to disambiguate LLDB commands from # C-style expressions. if params['objectGroup'] == 'console': result = get_lldb().SBCommandReturnObject() self.debugger_store.debugger.GetCommandInterpreter().HandleCommand( expression, result) return { 'result': { 'value': result.GetOutput() + result.GetError(), 'type': 'text', }, 'wasThrown': False, } elif params['objectGroup'] == 'watch-group': frame = self.debugger_store.debugger.GetSelectedTarget(). \ process.GetSelectedThread().GetSelectedFrame() # TODO: investigate why "EvaluateExpression" # is not working for some scenarios on Linux. if sys.platform.startswith('linux'): value = frame.GetValueForVariablePath(expression) else: value = frame.EvaluateExpression(expression) # `value.error` is an `SBError` instance which captures whether the # result had an error. `SBError.success` denotes no error. if value.error.success: return { 'result': value_serializer.serialize_value( value, self.debugger_store.remote_object_manager. get_add_object_func(params['objectGroup'])), 'wasThrown': False, } else: return { 'result': { 'type': 'text', }, 'wasThrown': True, 'exceptionDetails': { 'text': value.error.description, } } return { 'result': {}, 'wasThrown': False, }
def start_debugging(debugger, arguments, ipc_channel, is_attach): lldb = get_lldb() listener = lldb.SBListener('Chrome Dev Tools Listener') error = lldb.SBError() if getattr(arguments, 'executable_path', None): argument_list = map(os.path.expanduser, map(str, arguments.launch_arguments)) \ if arguments.launch_arguments else None environment_variables = [six.binary_type(arg) for arg in arguments.launch_environment_variables] \ if arguments.launch_environment_variables else None # TODO: should we resolve symbol link? executable_path = os.path.expanduser(str(arguments.executable_path)) \ if arguments.executable_path else None working_directory = os.path.expanduser(str(arguments.working_directory)) \ if arguments.working_directory else None stdin_filepath = os.path.expanduser(str(arguments.stdin_filepath)) \ if arguments.stdin_filepath else None target = debugger.CreateTarget( executable_path, # filename None, # target_triple None, # platform_name True, # add_dependent_modules error) # error if error.Fail(): sys.exit(error.description) target.Launch( listener, argument_list, environment_variables, stdin_filepath, None, # stdout_path None, # stderr_path working_directory, 0, # launch flags True, # Stop at entry error) # error elif getattr(arguments, 'pname', None): target = debugger.CreateTarget(None) target.AttachToProcessWithName( listener, str(arguments.pname), False, # does not wait for process to launch. error) elif getattr(arguments, 'pid', None): target = debugger.CreateTarget(None) target.AttachToProcessWithID(listener, int(arguments.pid), error) else: sys.exit('Unknown arguments: %s' % arguments) if error.Fail(): sys.exit(error.description) else: if is_attach: output = 'Successfully attached process.' else: output = 'Successfully launched process.' ipc_channel.send_output_message_async('log', output)
def _add_listener_to_process(self, process): # Listen for process events (Start/Stop/Interrupt/etc). broadcaster = process.GetBroadcaster() lldb = get_lldb() mask = lldb.SBProcess.eBroadcastBitStateChanged | \ lldb.SBProcess.eBroadcastBitSTDOUT | \ lldb.SBProcess.eBroadcastBitSTDERR | \ lldb.SBProcess.eBroadcastBitInterrupt broadcaster.AddListener(self._listener, mask)
def handle(cls, value, add_object_func): lldb = get_lldb() if (value.type.type == lldb.eTypeClassStruct or ((value.type.type == lldb.eTypeClassTypedef) and (value.type.GetCanonicalType().type == lldb.eTypeClassStruct))): obj = add_object_func( CppStructSBValueRemoteObject(value, add_object_func)) return obj.serialized_value return None
def _handle_target_event(self, event): lldb = get_lldb() if event.GetType() == lldb.SBTarget.eBroadcastBitModulesLoaded: self._handle_module_load_event(event) elif event.GetType() == lldb.SBTarget.eBroadcastBitModulesUnloaded: self._handle_module_unload_event(event) elif event.GetType() == lldb.SBTarget.eBroadcastBitSymbolsLoaded: self._send_user_output('log', 'Symbol loaded') else: self.self._handle_unknown_event(event)
def _add_listener_to_target(self, target): # Listen for breakpoint/watchpoint events (Added/Removed/Disabled/etc). broadcaster = target.GetBroadcaster() lldb = get_lldb() mask = lldb.SBTarget.eBroadcastBitBreakpointChanged | \ lldb.SBTarget.eBroadcastBitWatchpointChanged | \ lldb.SBTarget.eBroadcastBitModulesLoaded | \ lldb.SBTarget.eBroadcastBitModulesUnloaded | \ lldb.SBTarget.eBroadcastBitSymbolsLoaded broadcaster.AddListener(self._listener, mask)
def evaluate(self, params): # Cast to string from possible unicode. expression = str(params['expression']) # `objectGroups` are used by the client to designate remote objects on # the server that should stick around (for potential future queries), # and eventually released with a `releaseObjectGroup` call. # # Incidentally, they have names denoting which part of the client made # the request. We use these names to disambiguate LLDB commands from # C-style expressions. if params['objectGroup'] == 'console': result = get_lldb().SBCommandReturnObject() self.debugger_store.debugger.GetCommandInterpreter().HandleCommand(expression, result) return { 'result': { 'value': result.GetOutput() + result.GetError(), 'type': 'text', }, 'wasThrown': False, } elif params['objectGroup'] == 'watch-group': frame = self.debugger_store.debugger.GetSelectedTarget(). \ process.GetSelectedThread().GetSelectedFrame() # TODO: investigate why "EvaluateExpression" # is not working for some scenarios on Linux. if sys.platform.startswith('linux'): value = frame.GetValueForVariablePath(expression) else: value = frame.EvaluateExpression(expression) # `value.error` is an `SBError` instance which captures whether the # result had an error. `SBError.success` denotes no error. if value.error.success: return { 'result': value_serializer.serialize_value( value, self.debugger_store.remote_object_manager. get_add_object_func(params['objectGroup'])), 'wasThrown': False, } else: return { 'result': { 'type': 'text', }, 'wasThrown': True, 'exceptionDetails': { 'text': value.error.description, } } return { 'result': {}, 'wasThrown': False, }
def _should_stop(self, process, thread): '''Determine if the thread should be considered stopped. Arguably signals that have been masked as non-stop by `process handle` should not stop the thread. TODO: Remove this if/when lldb fixes this behavior ''' lldb = get_lldb() if thread.GetStopReason() == lldb.eStopReasonSignal: signum = thread.GetStopReasonDataAtIndex(0) return process.GetUnixSignals().GetShouldStop(signum) return thread.GetStopReason() != lldb.eStopReasonNone
def StopReason_to_string(reason): lldb = get_lldb() """Converts lldb.StopReason to a string""" return { lldb.eStopReasonInvalid: 'invalid', lldb.eStopReasonNone: 'none', lldb.eStopReasonTrace: 'trace', lldb.eStopReasonBreakpoint: 'breakpoint', lldb.eStopReasonWatchpoint: 'watchpoint', lldb.eStopReasonSignal: 'signal', lldb.eStopReasonException: 'exception', lldb.eStopReasonPlanComplete: 'plan-complete', }[reason]
def _update_stop_thread(self, process): '''lldb on Linux has a bug of not setting stop thread correctly. This method fixes this issue. TODO: remove this when lldb fixes this on Linux. ''' thread = process.GetSelectedThread() lldb = get_lldb() if thread.GetStopReason() != lldb.eStopReasonNone: return for thread in process.threads: if thread.GetStopReason() != lldb.eStopReasonNone: process.SetSelectedThread(thread) return
def continueToLocation(self, params): filelike = self.debugger_store.file_manager.get_by_client_url(params['location']['scriptId']) if not filelike or not isinstance(filelike, file_manager.File): # Only support setting breakpoints in real files. return {} lldb = get_lldb() path = str(params['location']['scriptId']) thread = self.debugger_store.debugger.GetSelectedTarget().GetProcess().GetSelectedThread() frame = thread.GetSelectedFrame() # atom line numbers a 0-based, while lldb is 1-based line = int(params['location']['lineNumber']) + 1 thread.StepOverUntil(frame, filelike.server_obj, line) return {}
def handle(cls, value, add_object_func): lldb = get_lldb() """ValueHandler for dereferenceable types """ DEREF_TYPE_CLASSES = [ lldb.eTypeClassPointer, # TODO[jeffreytan]: I do not think ObjectC/C++ support reference yet # so disable for now(enable it when we support classic C++). # lldb.eTypeClassReference, ] if value.type.type in DEREF_TYPE_CLASSES: obj = add_object_func(DereferenceableSBValueRemoteObject(value, add_object_func)) return obj.serialized_value return None
def _WatchpointEventType_to_string(event): lldb = get_lldb() """Converts lldb.BreakpointEventType to a string""" return { lldb.eWatchpointEventTypeAdded: 'added', lldb.eWatchpointEventTypeCommandChanged: 'command-changed', lldb.eWatchpointEventTypeConditionChanged: 'condition-changed', lldb.eWatchpointEventTypeDisabled: 'disabled', lldb.eWatchpointEventTypeEnabled: 'enabled', lldb.eWatchpointEventTypeIgnoreChanged: 'ignore-changed', lldb.eWatchpointEventTypeInvalidType: 'invalid-type', lldb.eWatchpointEventTypeRemoved: 'removed', lldb.eWatchpointEventTypeThreadChanged: 'thread-changed', lldb.eWatchpointEventTypeTypeChanged: 'type-changed', }[event]
def handle(cls, value, add_object_func): lldb = get_lldb() """ValueHandler for dereferenceable types """ DEREF_TYPE_CLASSES = [ lldb.eTypeClassPointer, # TODO[jeffreytan]: I do not think ObjectC/C++ support reference yet # so disable for now(enable it when we support classic C++). # lldb.eTypeClassReference, ] if value.type.type in DEREF_TYPE_CLASSES: obj = add_object_func( DereferenceableSBValueRemoteObject(value, add_object_func)) return obj.serialized_value return None
def setPauseOnExceptions(self, params): # First, unhook the old breakpoint exceptions. if self.exceptionBreakpointId is not None: self.debugger_store.debugger.GetSelectedTarget().BreakpointDelete( self.exceptionBreakpointId) self.exceptionBreakpointId = None # Next, we've been asked to do one of 'none' or 'uncaught' or 'all'. # But we'll treat none+uncaught as no-op since that's all LLDB can do. if params['state'] == 'all': breakpoint = self.debugger_store.debugger.GetSelectedTarget( ).BreakpointCreateForException(get_lldb().eLanguageTypeC_plus_plus, False, # don't pause on catch True # do pause on throw ) self.exceptionBreakpointId = breakpoint.id return {}
def main(): arguments = parse_args() lldb_python_path = getattr(arguments, 'lldb_python_path', None) if lldb_python_path is not None: set_custom_lldb_path(os.path.expanduser(lldb_python_path)) lldb = get_lldb() debugger = lldb.SBDebugger.Create() is_attach = (getattr(arguments, 'executable_path', None) == None) is_interactive = getattr(arguments, 'interactive', False) ipc_channel = IpcChannel(is_interactive) start_debugging(debugger, arguments, ipc_channel, is_attach) register_signal_handler(debugger) chrome_channel = ChromeChannel() debugger_store = DebuggerStore( debugger, chrome_channel, ipc_channel, is_attach, str(getattr(arguments, 'basepath', '.'))) try: app = ChromeDevToolsDebuggerApp(debugger_store, getattr(arguments, 'port', 0)) # Tell IDE server is ready. log_debug('Port: %s' % app.debug_server.server_port) event_thread = LLDBListenerThread(debugger_store, app) event_thread.start() if is_interactive: app.start_nonblocking() interactive_loop(debugger) else: app.start_blocking() except KeyboardInterrupt: # Force app to exit on Ctrl-C. os._exit(1) event_thread.join() lldb.SBDebugger.Destroy(debugger) lldb.SBDebugger.Terminate() # TODO: investigate why we need os._exit() to terminate python process. os._exit(0)
def _handle_breakpoint_event(self, event): lldb = get_lldb() breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event) event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) log_debug('Breakpoint event: [%s] %s ' % (self.breakpoint_event_type_to_name_map[event_type], self._get_description_from_object(breakpoint))) if event_type == lldb.eBreakpointEventTypeLocationsResolved: for location in \ self._debugger_store.location_serializer.get_breakpoint_locations(breakpoint): params = { 'breakpointId': str(breakpoint.id), 'location': location, } self._send_notification('Debugger.breakpointResolved', params) else: # TODO: handle other breakpoint event types. pass
def _handle_breakpoint_event(self, event): lldb = get_lldb() breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event) event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) log_debug('Breakpoint event: [%s] %s ' % ( self.breakpoint_event_type_to_name_map[event_type], self._get_description_from_object(breakpoint))) if event_type == lldb.eBreakpointEventTypeLocationsResolved: for location in \ self._debugger_store.location_serializer.get_breakpoint_locations(breakpoint): params = { 'breakpointId': str(breakpoint.id), 'location': location, } self._send_notification('Debugger.breakpointResolved', params) else: # TODO: handle other breakpoint event types. pass
def __init__(self, debugger_store, app): Thread.__init__(self) self.daemon = True self._debugger_store = debugger_store self._app = app self._listener = debugger_store.debugger.GetListener() lldb = get_lldb() self.breakpoint_event_type_to_name_map = { lldb.eBreakpointEventTypeAdded: 'Added', lldb.eBreakpointEventTypeCommandChanged: 'Command Changed', lldb.eBreakpointEventTypeConditionChanged: 'Condition Changed', lldb.eBreakpointEventTypeDisabled: 'Disabled', lldb.eBreakpointEventTypeEnabled: 'Enabled', lldb.eBreakpointEventTypeIgnoreChanged: 'Ignore Changed', lldb.eBreakpointEventTypeInvalidType: 'Invalid Type', lldb.eBreakpointEventTypeLocationsAdded: 'Location Added', lldb.eBreakpointEventTypeLocationsRemoved: 'Location Removed', lldb.eBreakpointEventTypeLocationsResolved: 'Location Resolved', lldb.eBreakpointEventTypeRemoved: 'Removed', lldb.eBreakpointEventTypeThreadChanged: 'Thread Changed', } process = debugger_store.debugger.GetSelectedTarget().process self._add_listener_to_process(process) # LLDB will not emit any stopping event during attach. # Linux lldb has a bug of not emitting stopping event during launch. if self._debugger_store.is_attach or sys.platform.startswith('linux'): if process.state != lldb.eStateStopped: # Instead of using assert() which will crash debugger log an error message # and tolerate this non-fatal situation. log_error('Inferior should be stopped after attach or linux launch') self._send_paused_notification(process) self._add_listener_to_target(process.target)
def _handle_unknown_event(self, event): log_error('Unknown event: %d %s %s' % ( event.GetType(), get_lldb().SBEvent.GetCStringFromEvent(event), self._get_description_from_object(event)))
def _get_description_from_object(self, lldb_object): description_stream = get_lldb().SBStream() lldb_object.GetDescription(description_stream) return description_stream.GetData()
def _getSteppingFlag(self): lldb = get_lldb() if self.debugger_store.getDebuggerSettings()['singleThreadStepping']: return lldb.eOnlyThisThread return lldb.eOnlyDuringStepping
def _handle_unknown_event(self, event): log_error( 'Unknown event: %d %s %s' % (event.GetType(), get_lldb().SBEvent.GetCStringFromEvent(event), self._get_description_from_object(event)))