def main(): args = parseArgs() debugger = lldb.SBDebugger.Create() if args.pname: debugger.HandleCommand('process attach -n %r' % args.pname) elif args.pid: debugger.HandleCommand('process attach -p %d' % args.pid) # Run a script command in the interpreter, this seems to be necessary as # things like assembly seems to not be available from the script # environment otherwise. debugger.HandleCommand('script 1') # Turn on auto-confirm so LLDB does not block forever querying users for # command confirmations. lldb.SBDebugger.SetInternalVariable('auto-confirm', 'true', debugger.GetInstanceName()) try: app = ChromeDevToolsDebuggerApp(debugger, port=args.port, basepath=args.basepath) log_debug('Port: %s' % app.debug_server.server_port) if args.interactive: app.start_nonblocking() interactive_loop(debugger) else: app.start_blocking() except KeyboardInterrupt: # Force app to exit on Ctrl-C. os._exit(1)
def _handle_process_event(self, event): # 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 read_json_arguments_if_needed(arguments): '''If arguments_in_json is enabled we parse all the arguments from a separate input pipe. ''' if arguments.arguments_in_json: ARGUMENT_INPUT_FD = 3 buffering = 1 # 1 means line-buffered. file = os.fdopen(ARGUMENT_INPUT_FD, 'r+', buffering) init_line = file.readline() if init_line.startswith('init'): file.write('ready\n') # Tell parent channel is ready. arguments_input = file.readline() log_debug('Received json arguments: %s' % arguments_input) # Parse JSON into python object. arguments = json.loads( arguments_input, object_hook=lambda d: namedtuple('arguments', d.keys()) (*d.values())) log_debug('Parsed arguments: %s' % json.dumps(arguments, ensure_ascii=False)) file.close() else: # Fail: did not receive proper initialization sequence. log_error('LLDB got unknown init line: %s' % init_line) sys.exit(2) return arguments
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 == \ lldb.eStateStopped: lldb_debugger.GetSelectedTarget().process.Detach() os._exit(0)
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 modules_updated(self): for module in self._target.modules: try: if module.uuid not in self._registered_modules: self._register_source_paths_for_module(module) self._registered_modules.add(module.uuid) except Exception as e: # Some module does not have uuid. log_debug('Can\'t register module: %s' % str(e))
def send_output_message_sync(self, level, text): self._send_output_message(level, text, is_sync=True) # Wait from response from client. response = self._file.readline() log_debug('Ipc response: %s' % response) response_object = json.loads(response) if int(response_object['message_id']) != self._message_id: log_error('Get wrong ipc response: %s' % response)
def send_notification(self, method, params=None): """ Send a notification over the socket to a Chrome Dev Tools client. """ notification_in_json = json.dumps({ 'method': method, 'params': params }) log_debug('send_notification: %s' % notification_in_json) self.send(notification_in_json)
def _send_paused_notification(self, process): self._debugger_store.thread_manager.update(process) thread = process.GetSelectedThread() log_debug('thread.GetStopReason(): %s' % serialize.StopReason_to_string(thread.GetStopReason())) params = { "callFrames": self._debugger_store.thread_manager.get_thread_stack(thread), "reason": serialize.StopReason_to_string(thread.GetStopReason()), "data": {}, } self._send_notification('Debugger.paused', params)
def send_notification(self, method, params=None): """ Send a notification over the socket to a Chrome Dev Tools client. """ notification_in_json = json.dumps({'method': method, 'params': params}, ensure_ascii=False) log_debug('send_notification: %s' % notification_in_json) if self._channelAvailable: self._send_helper(notification_in_json) else: self._caches.append(notification_in_json)
def _add_default_lldb_python_path(): if sys.platform == 'darwin': # Update pythonpath with likely location in the active Xcode app bundle. developer_dir = subprocess.check_output(['xcode-select', '--print-path']) xcode_path = developer_dir.strip() default_lldb_python_path = (os.path.join( xcode_path, _get_xcode_lldb_relative_path(xcode_path))) log_debug('find_lldb, default: %s' % default_lldb_python_path) sys.path.append(default_lldb_python_path) _add_fb_default_lldb_python_path()
def _send_paused_notification(self, process): self._debugger_store.thread_manager.update(process) self._update_stop_thread(process) thread = process.GetSelectedThread() log_debug('thread.GetStopReason(): %s' % serialize.StopReason_to_string(thread.GetStopReason())) params = { "callFrames": self._debugger_store.thread_manager.get_thread_stack(thread), "reason": serialize.StopReason_to_string(thread.GetStopReason()), "data": {}, } self._send_notification('Debugger.paused', params)
def send_notification(self, method, params=None): """ Send a notification over the socket to a Chrome Dev Tools client. """ notification_in_json = json.dumps({ 'method': method, 'params': params }) log_debug('send_notification: %s' % notification_in_json) if self._channelAvailable: self._send_helper(notification_in_json) else: self._caches.append(notification_in_json)
def received_message(self, message): log_debug('received_message: %s' % message.data); parsed = None try: parsed = json.loads(message.data) except Exception: # Print invalid JSON requests to stderr. log_error('Invalid JSON: %s' % message) response = self._generate_response(parsed) response_in_json = json.dumps(response); log_debug('response: %s' % response_in_json); self.send(response_in_json)
def received_message(self, message): log_debug('received_message: %s' % message.data) parsed = None try: parsed = json.loads(message.data) except Exception: # Print invalid JSON requests to stderr. log_error('Invalid JSON: %s' % message) response = self._generate_response(parsed) response_in_json = json.dumps(response) log_debug('response: %s' % response_in_json) self.send(response_in_json)
def interactive_loop(debugger): while (True): sys.stdout.write('dbg> ') command = sys.stdin.readline().rstrip() if len(command) == 0: continue elif command == 'q': debugger.Destroy(debugger) log_debug('bye~') break elif command == 'b': debugger.GetSelectedTarget().process.Stop() else: debugger.HandleCommand(command)
def find_lldb(): # Try to import, perhaps the paths are set up. try: import lldb return lldb except: pass # Search python binding with heuristics. lldb_pythonpath = _get_lldb_python_path() sys.path.append(lldb_pythonpath) # Try again. import lldb log_debug('find_lldb: %s' % str(lldb)) return lldb
def _handle_breakpoint_event(self, event): breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event) event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) log_debug('Breakpoint event: [%s] %s ' % ( 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, chrome_channel, ipc_channel, is_attach, basepath='.'): ''' chrome_channel: channel to send client chrome notification messages. ipc_channel: channel to send output/atom notification messages. debugger: lldb SBDebugger object. ''' self._debugger = debugger self._chrome_channel = chrome_channel self._ipc_channel = ipc_channel self._is_attach = is_attach self._file_manager = FileManager(chrome_channel) self._remote_object_manager = RemoteObjectManager() basepath = self._resolve_basepath_heuristic(basepath) log_debug('basepath: %s' % basepath) self._location_serializer = serialize.LocationSerializer( self._file_manager, basepath) self._thread_manager = ThreadManager(self)
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 get_lldb(): global _lldb if _lldb: return _lldb try: _add_default_lldb_python_path() # _add_custom_lldb_python_path() must be called after # _add_default_lldb_python_path() to take precedence. _add_custom_lldb_python_path() import lldb _lldb = lldb log_debug('find_lldb: %s' % str(lldb)) return _lldb except ImportError, error: log_error(_get_lldb_import_error_message()) os._exit(2)
def received_message(self, message): parsed = None try: parsed = json.loads(message.data) except Exception: # Print invalid JSON requests to stderr. log_error('Invalid JSON: %s' % message) should_log = self._is_debugger_protocol(parsed) if should_log: log_debug('received_message: %s' % message.data) response = self._generate_response(parsed) response_in_json = json.dumps(response, ensure_ascii=False) if should_log: log_debug('response: %s' % response_in_json) self.send(response_in_json)
def _send_output_message(self, level, text, is_sync): """ Send an output notification to Nuclide through ipc. """ if self._is_interactive: return self._message_id += 1 message = { 'id': self._message_id, 'type': 'Nuclide.userOutput', 'isSync': is_sync, 'message': { 'level': level, 'text': text, } } message_in_json = json.dumps(message, ensure_ascii=False) log_debug('send_output_message: %s' % message_in_json) self._file.write(message_in_json + '\n') self._file.flush()
def _send_output_message(self, level, text, is_sync): """ Send an output notification to Nuclide through ipc. """ if self._is_interactive: return self._message_id += 1 message = { 'id': self._message_id, 'type': 'Nuclide.userOutput', 'isSync': is_sync, 'message': { 'level': level, 'text': text, } } message_in_json = json.dumps(message) log_debug('send_output_message: %s' % message_in_json) self._file.write(message_in_json + '\n') self._file.flush()
def main(): arguments = parseArgs() debugger = lldb.SBDebugger.Create() is_attach = startDebugging(debugger, arguments) channel = NotificationChannel() debugger_store = DebuggerStore(channel, debugger, arguments.basepath) event_thread = LLDBListenerThread(debugger_store, is_attach=is_attach) event_thread.start() try: app = ChromeDevToolsDebuggerApp(debugger_store, arguments.port) log_debug('Port: %s' % app.debug_server.server_port) if arguments.interactive: app.start_nonblocking() interactive_loop(debugger) else: app.start_blocking() except KeyboardInterrupt: # Force app to exit on Ctrl-C. os._exit(1)
def _resolve_basepath_heuristic(self, basepath): '''Buck emits relative path in the symbol file so we need a way to resolve all source from_filespec into absolute path. This heuristic will try to guess the buck root from executable module path. Note: This heuristic assumes user run buck built binaries directly from buck-out/gen sub-directories instead of being deployed to some other folder. Hopefully this is true most of the time. TODO: we need a better way to discover buck built root repo in long term. ''' if basepath == '.': target = self._debugger.GetSelectedTarget() executable_file_path = target.executable.fullpath log_debug('executable_file_path: %s' % executable_file_path) executable_file_path = os.path.realpath( os.path.normpath(os.path.expanduser(executable_file_path))) BUCK_OUTPUT_IDENTIFY_REGEX = '/buck-out/' search_result = re.search(BUCK_OUTPUT_IDENTIFY_REGEX, executable_file_path) if search_result: basepath = executable_file_path[:search_result.start()] return basepath
def _resolve_basepath_heuristic(self, basepath): '''Buck emits relative path in the symbol file so we need a way to resolve all source from_filespec into absolute path. This heuristic will try to guess the buck root from executable module path. Note: This heuristic assumes user run buck built binaries directly from buck-out/gen sub-directories instead of being deployed to some other folder. Hopefully this is true most of the time. TODO: we need a better way to discover buck built root repo in long term. ''' if basepath == '.': target = self._debugger.GetSelectedTarget() executable_file_path = target.executable.fullpath log_debug('executable_file_path: %s' % executable_file_path) executable_file_path = os.path.realpath( os.path.normpath(os.path.expanduser(executable_file_path))) BUCK_OUTPUT_IDENTIFY_REGEX = '/buck-out/gen/' search_result = re.search(BUCK_OUTPUT_IDENTIFY_REGEX, executable_file_path) if search_result: basepath = executable_file_path[:search_result.start()] return basepath
def _generate_response(self, message): response = {} try: response['id'] = message['id'] response['result'] = self.handlers.handle( method=str(message['method']), params=message.get('params', {}), ) except UndefinedDomainError as e: response['error'] = 'Undefined domain: %s' % str(e) response['result'] = {} log_debug('Received message with %s' % response['error']) except UndefinedHandlerError as e: response['error'] = 'Undefined handler: %s' % str(e) response['result'] = {} log_debug('Received message with %s' % response['error']) except Exception as e: response['error'] = repr(e) response['result'] = {} response['stack'] = traceback.format_exc() traceback.print_exc(file=sys.stderr) return response
def main(): arguments = parse_args() debugger = lldb.SBDebugger.Create() is_interactive = getattr(arguments, 'interactive', False) ipc_channel = IpcChannel(is_interactive) is_attach = start_debugging(debugger, arguments, ipc_channel) 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)) 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 read_json_arguments_if_needed(arguments): '''If arguments_in_json is enabled we parse all the arguments from a separate input pipe. ''' if arguments.arguments_in_json: ARGUMENT_INPUT_FD = 3 buffering = 1 # 1 means line-buffered. file = os.fdopen(ARGUMENT_INPUT_FD, 'r+', buffering) init_line = file.readline() if init_line.startswith('init'): file.write('ready\n') # Tell parent channel is ready. arguments_input = file.readline() log_debug('Received json arguments: %s' % arguments_input) # Parse JSON into python object. arguments = json.loads( arguments_input, object_hook=lambda d: namedtuple('arguments', d.keys())(*d.values())) log_debug('Parsed arguments: %s' % json.dumps(arguments, ensure_ascii=False)) file.close() else: # Fail: did not receive proper initialization sequence. log_error('LLDB got unknown init line: %s' % init_line) sys.exit(2) return arguments
def _broadcast_process_state(self, process): # Reset the object group so old frame variable objects don't linger # forever. log_debug('_broadcast_process_state, process state: %d' % process.state) self._debugger_store.thread_manager.release() if process.state == lldb.eStateStepping or process.state == lldb.eStateRunning: self._debugger_store.channel.send_notification('Debugger.resumed', None) elif process.state == lldb.eStateExited: log_debug('Process exited: %s' % process.GetExitDescription()) self.should_quit = True else: self._debugger_store.thread_manager.update(process) thread = process.GetSelectedThread() log_debug('thread.GetStopReason(): %s' % serialize.StopReason_to_string(thread.GetStopReason())) self._sendPausedNotification(thread)
def _add_custom_lldb_python_path(): log_debug('find_lldb, custom: %s' % _custom_lldb_python_path) lldb_python_path, _ = os.path.split(_custom_lldb_python_path) sys.path.insert(0, lldb_python_path)
def send_notification(self, method, params=None): """ Send a notification over the socket to a Chrome Dev Tools client. """ notification_in_json = json.dumps({'method': method, 'params': params}); log_debug('send_notification: %s' % notification_in_json); self.send(notification_in_json)
def __lldb_init_module(debugger, internal_dict): # Print the server port on lldb import. app = ChromeDevToolsDebuggerApp(debugger) log_debug('chrome_debug(%s)' % app.debug_server.server_port) app.start_nonblocking()