def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=None): ''' :param trace_obj: This is the traceback from which the list should be created. :param frame: This is the first frame to be considered (i.e.: topmost frame). If None is passed, all the frames from the traceback are shown (so, None should be passed for unhandled exceptions). :param exception_type: If this is an unhandled exception or user unhandled exception, we'll not trim the stack to create from the passed frame, rather, we'll just mark the frame in the frames list. ''' lst = [] tb = trace_obj if tb is not None and tb.tb_frame is not None: f = tb.tb_frame.f_back while f is not None: lst.insert(0, (f, f.f_lineno)) f = f.f_back while tb is not None: lst.append((tb.tb_frame, tb.tb_lineno)) tb = tb.tb_next frames_list = None for tb_frame, tb_lineno in reversed(lst): if frames_list is None and ( (frame is tb_frame) or (frame is None) or (exception_type == EXCEPTION_TYPE_USER_UNHANDLED)): frames_list = FramesList() if frames_list is not None: frames_list.append(tb_frame) frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno if frames_list is None and frame is not None: # Fallback (shouldn't happen in practice). pydev_log.info( 'create_frames_list_from_traceback did not find topmost frame in list.' ) frames_list = create_frames_list_from_frame(frame) frames_list.exc_type = exc_type frames_list.exc_desc = exc_desc frames_list.trace_obj = trace_obj if exception_type == EXCEPTION_TYPE_USER_UNHANDLED: frames_list.current_frame = frame elif exception_type == EXCEPTION_TYPE_UNHANDLED: if len(frames_list) > 0: frames_list.current_frame = frames_list.last_frame() return frames_list
def _run_with_unblock_threads(original_func, py_db, curr_thread, frame, expression, is_exec): on_timeout_unblock_threads = None timeout_tracker = py_db.timeout_tracker # : :type timeout_tracker: TimeoutTracker if py_db.multi_threads_single_notification: unblock_threads_timeout = pydevd_constants.PYDEVD_UNBLOCK_THREADS_TIMEOUT else: unblock_threads_timeout = -1 # Don't use this if threads are managed individually. if unblock_threads_timeout >= 0: pydev_log.info('Doing evaluate with unblock threads timeout: %s.', unblock_threads_timeout) tid = get_current_thread_id(curr_thread) def on_timeout_unblock_threads(): on_timeout_unblock_threads.called = True pydev_log.info('Resuming threads after evaluate timeout.') resume_threads('*', except_thread=curr_thread) py_db.threads_suspended_single_notification.on_thread_resume(tid) on_timeout_unblock_threads.called = False try: if on_timeout_unblock_threads is None: return _run_with_interrupt_thread(original_func, py_db, curr_thread, frame, expression, is_exec) else: with timeout_tracker.call_on_timeout(unblock_threads_timeout, on_timeout_unblock_threads): return _run_with_interrupt_thread(original_func, py_db, curr_thread, frame, expression, is_exec) finally: if on_timeout_unblock_threads is not None and on_timeout_unblock_threads.called: mark_thread_suspended(curr_thread, CMD_SET_BREAK) py_db.threads_suspended_single_notification.increment_suspend_time() suspend_all_threads(py_db, except_thread=curr_thread) py_db.threads_suspended_single_notification.on_thread_suspend(tid, CMD_SET_BREAK)
def _iter_visible_frames_info(self, py_db, frames_list): assert frames_list.__class__ == FramesList for frame in frames_list: if frame.f_code is None: pydev_log.info('Frame without f_code: %s', frame) continue # IronPython sometimes does not have it! method_name = frame.f_code.co_name # method name (if in method) or ? if global if method_name is None: pydev_log.info('Frame without co_name: %s', frame) continue # IronPython sometimes does not have it! abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_frame(frame) if py_db.get_file_type(frame, abs_path_real_path_and_base) == py_db.PYDEV_FILE: # Skip pydevd files. frame = frame.f_back continue frame_id = id(frame) lineno = frames_list.frame_id_to_lineno.get(frame_id, frame.f_lineno) filename_in_utf8, lineno, changed = py_db.source_mapping.map_to_client(abs_path_real_path_and_base[0], lineno) new_filename_in_utf8, applied_mapping = pydevd_file_utils.norm_file_to_client(filename_in_utf8) applied_mapping = applied_mapping or changed yield frame_id, frame, method_name, abs_path_real_path_and_base[0], new_filename_in_utf8, lineno, applied_mapping
def process_net_command_json(self, py_db, json_contents, send_response=True): ''' Processes a debug adapter protocol json command. ''' DEBUG = False try: request = self.from_json(json_contents, update_ids_from_dap=True) except KeyError as e: request = self.from_json(json_contents, update_ids_from_dap=False) error_msg = str(e) if error_msg.startswith("'") and error_msg.endswith("'"): error_msg = error_msg[1:-1] # This means a failure updating ids from the DAP (the client sent a key we didn't send). def on_request(py_db, request): error_response = { 'type': 'response', 'request_seq': request.seq, 'success': False, 'command': request.command, 'message': error_msg, } return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),)) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(),) on_request = getattr(self, method_name, None) if on_request is None: print('Unhandled: %s not available in PyDevJsonCommandProcessor.\n' % (method_name,)) return if DEBUG: print('Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' % (method_name,)) with py_db._main_lock: if request.__class__ == PydevdAuthorizeRequest: authorize_request = request # : :type authorize_request: PydevdAuthorizeRequest access_token = authorize_request.arguments.debugServerAccessToken py_db.authentication.login(access_token) if not py_db.authentication.is_authenticated(): response = Response( request.seq, success=False, command=request.command, message='Client not authenticated.', body={}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) return cmd = on_request(py_db, request) if cmd is not None and send_response: py_db.writer.add_command(cmd)
def get_variable_presentation(setting, default): value = variable_presentation.get(setting, default) if value not in ('group', 'inline', 'hide'): pydev_log.info( 'The value set for "%s" (%s) in the variablePresentation is not valid. Valid values are: "group", "inline", "hide"' % ( setting, value,)) value = default return value
def _load_python_helper_lib_uncached(): if (not IS_CPYTHON or ctypes is None or sys.version_info[:2] > (3, 9) or hasattr(sys, 'gettotalrefcount') or LOAD_NATIVE_LIB_FLAG in ENV_FALSE_LOWER_VALUES): return None if IS_WINDOWS: if IS_64BIT_PROCESS: suffix = 'amd64' else: suffix = 'x86' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s.dll' % (suffix, )) elif IS_LINUX: if IS_64BIT_PROCESS: suffix = 'amd64' else: suffix = 'x86' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_linux_%s.so' % (suffix, )) elif IS_MAC: if IS_64BIT_PROCESS: suffix = 'x86_64.dylib' else: suffix = 'x86.dylib' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s' % (suffix, )) else: pydev_log.info('Unable to set trace to all threads in platform: %s', sys.platform) return None if not os.path.exists(filename): pydev_log.critical('Expected: %s to exist.', filename) return None try: # Load as pydll so that we don't release the gil. lib = ctypes.pydll.LoadLibrary(filename) pydev_log.info( 'Successfully Loaded helper lib to set tracing to all threads.') return lib except: if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: # Only show message if tracing is on (we don't have pre-compiled # binaries for all architectures -- i.e.: ARM). pydev_log.exception('Error loading: %s', filename) return None
def test_pydevd_log(): from _pydev_bundle import pydev_log try: import StringIO as io except: import io from _pydev_bundle.pydev_log import log_context stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always') pydev_log.info('never') assert stream.getvalue() == 'always\n' stream = io.StringIO() with log_context(1, stream=stream): pydev_log.critical('always') pydev_log.info('this too') assert stream.getvalue() == 'always\nthis too\n' stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always %s', 1) assert stream.getvalue() == 'always 1\n' stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always %s %s', 1, 2) assert stream.getvalue() == 'always 1 2\n' stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always %s %s', 1) # Even if there's an error in the formatting, don't fail, just print the message and args. assert stream.getvalue() == 'always %s %s - (1,)\n' stream = io.StringIO() with log_context(0, stream=stream): try: raise RuntimeError() except: pydev_log.exception('foo') assert 'foo\n' in stream.getvalue() assert 'raise RuntimeError()' in stream.getvalue() stream = io.StringIO() with log_context(0, stream=stream): pydev_log.error_once('always %s %s', 1) # Even if there's an error in the formatting, don't fail, just print the message and args. assert stream.getvalue() == 'always %s %s - (1,)\n'
def process_net_command_json(self, py_db, json_contents): ''' Processes a debug adapter protocol json command. ''' DEBUG = False try: request = self.from_json(json_contents, update_ids_from_dap=True) except KeyError as e: request = self.from_json(json_contents, update_ids_from_dap=False) error_msg = str(e) if error_msg.startswith("'") and error_msg.endswith("'"): error_msg = error_msg[1:-1] # This means a failure updating ids from the DAP (the client sent a key we didn't send). def on_request(py_db, request): error_response = { 'type': 'response', 'request_seq': request.seq, 'success': False, 'command': request.command, 'message': error_msg, } return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True), )) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(), ) on_request = getattr(self, method_name, None) if on_request is None: print( 'Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name, )) return if DEBUG: print( 'Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name, )) py_db._main_lock.acquire() try: cmd = on_request(py_db, request) if cmd is not None: py_db.writer.add_command(cmd) finally: py_db._main_lock.release()
def load_python_helper_lib(): if not IS_CPYTHON or ctypes is None or sys.version_info[:2] > (3, 9): return None if IS_WINDOWS: if IS_64BIT_PROCESS: suffix = 'amd64' else: suffix = 'x86' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s.dll' % (suffix, )) elif IS_LINUX: if IS_64BIT_PROCESS: suffix = 'amd64' else: suffix = 'x86' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_linux_%s.so' % (suffix, )) elif IS_MACOS: if IS_64BIT_PROCESS: suffix = 'x86_64.dylib' else: suffix = 'x86.dylib' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s' % (suffix, )) else: pydev_log.info('Unable to set trace to all threads in platform: %s' % sys.platform) return None if not os.path.exists(filename): pydev_log.error('Expected: %s to exist.' % filename) return None try: # Load as pydll so that we don't release the gil. lib = ctypes.pydll.LoadLibrary(filename) return lib except: # Only show message if tracing is on (we don't have pre-compiled # binaries for all architectures -- i.e.: ARM). pydev_log.error('Error loading: %s' % filename) return None
def remove_breakpoint(self, py_db, filename, breakpoint_type, breakpoint_id): ''' :param str filename: Note: must be already translated for the server. :param str breakpoint_type: One of: 'python-line', 'django-line', 'jinja2-line'. :param int breakpoint_id: ''' file_to_id_to_breakpoint = None if breakpoint_type == 'python-line': breakpoints = py_db.breakpoints file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint elif py_db.plugin is not None: result = py_db.plugin.get_breakpoints(py_db, breakpoint_type) if result is not None: file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint breakpoints = result if file_to_id_to_breakpoint is None: pydev_log.critical( 'Error removing breakpoint. Cannot handle breakpoint of type %s', breakpoint_type) else: try: id_to_pybreakpoint = file_to_id_to_breakpoint.get(filename, {}) if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: existing = id_to_pybreakpoint[breakpoint_id] pydev_log.info( 'Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id)) del id_to_pybreakpoint[breakpoint_id] py_db.consolidate_breakpoints(filename, id_to_pybreakpoint, breakpoints) if py_db.plugin is not None: py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks( ) except KeyError: pydev_log.info( "Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n", filename, breakpoint_id, dict_keys(id_to_pybreakpoint)) py_db.on_breakpoints_changed(removed=True)
def test_pydevd_log(): from _pydev_bundle import pydev_log try: import StringIO as io except: import io from _pydev_bundle.pydev_log import log_context stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always') pydev_log.info('never') assert stream.getvalue() == 'always\n' stream = io.StringIO() with log_context(1, stream=stream): pydev_log.critical('always') pydev_log.info('this too') assert stream.getvalue() == 'always\nthis too\n' stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always %s', 1) assert stream.getvalue() == 'always 1\n' stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always %s %s', 1, 2) assert stream.getvalue() == 'always 1 2\n' stream = io.StringIO() with log_context(0, stream=stream): pydev_log.critical('always %s %s', 1) # Even if there's an error in the formatting, don't fail, just print the message and args. assert stream.getvalue() == 'always %s %s - (1,)\n' stream = io.StringIO() with log_context(0, stream=stream): try: raise RuntimeError() except: pydev_log.exception('foo') assert 'foo\n' in stream.getvalue() assert 'raise RuntimeError()' in stream.getvalue()
def process_net_command_json(self, py_db, json_contents): ''' Processes a debug adapter protocol json command. ''' DEBUG = False try: request = self.from_json(json_contents, update_ids_from_dap=True) except KeyError as e: request = self.from_json(json_contents, update_ids_from_dap=False) error_msg = str(e) if error_msg.startswith("'") and error_msg.endswith("'"): error_msg = error_msg[1:-1] # This means a failure updating ids from the DAP (the client sent a key we didn't send). def on_request(py_db, request): error_response = { 'type': 'response', 'request_seq': request.seq, 'success': False, 'command': request.command, 'message': error_msg, } return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),)) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(),) on_request = getattr(self, method_name, None) if on_request is None: print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,)) return if DEBUG: print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,)) py_db._main_lock.acquire() try: cmd = on_request(py_db, request) if cmd is not None: py_db.writer.add_command(cmd) finally: py_db._main_lock.release()
def remove_plugins_exception_breakpoint(self, py_db, exception_type, exception): # I.e.: no need to initialize lazy (if we didn't have it in the first place, we can't remove # anything from it anyways). plugin = py_db.plugin if plugin is None: return supported_type = plugin.remove_exception_breakpoint(py_db, exception_type, exception) if supported_type: py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks() else: pydev_log.info('No exception of type: %s was previously registered.', exception_type) py_db.on_breakpoints_changed(removed=True)
def _run_with_interrupt_thread(original_func, py_db, curr_thread, frame, expression, is_exec): on_interrupt_threads = None timeout_tracker = py_db.timeout_tracker # : :type timeout_tracker: TimeoutTracker interrupt_thread_timeout = pydevd_constants.PYDEVD_INTERRUPT_THREAD_TIMEOUT if interrupt_thread_timeout > 0: on_interrupt_threads = pydevd_timeout.create_interrupt_this_thread_callback() pydev_log.info('Doing evaluate with interrupt threads timeout: %s.', interrupt_thread_timeout) if on_interrupt_threads is None: return original_func(py_db, frame, expression, is_exec) else: with timeout_tracker.call_on_timeout(interrupt_thread_timeout, on_interrupt_threads): return original_func(py_db, frame, expression, is_exec)
def pydevd_find_thread_by_id(thread_id): try: threads = threading.enumerate() for i in threads: tid = get_thread_id(i) if thread_id == tid or thread_id.endswith('|' + tid): return i # This can happen when a request comes for a thread which was previously removed. pydev_log.info("Could not find thread %s.", thread_id) pydev_log.info("Available: %s.", ([get_thread_id(t) for t in threads], )) except: pydev_log.exception() return None
def remove_breakpoint(self, py_db, filename, breakpoint_type, breakpoint_id): ''' :param str filename: Note: must be already translated for the server. :param str breakpoint_type: One of: 'python-line', 'django-line', 'jinja2-line'. :param int breakpoint_id: ''' file_to_id_to_breakpoint = None if breakpoint_type == 'python-line': breakpoints = py_db.breakpoints file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint elif py_db.plugin is not None: result = py_db.plugin.get_breakpoints(py_db, breakpoint_type) if result is not None: file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint breakpoints = result if file_to_id_to_breakpoint is None: pydev_log.critical('Error removing breakpoint. Cannot handle breakpoint of type %s', breakpoint_type) else: try: id_to_pybreakpoint = file_to_id_to_breakpoint.get(filename, {}) if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: existing = id_to_pybreakpoint[breakpoint_id] pydev_log.info('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % ( filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id)) del id_to_pybreakpoint[breakpoint_id] py_db.consolidate_breakpoints(filename, id_to_pybreakpoint, breakpoints) if py_db.plugin is not None: py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks() except KeyError: pydev_log.info("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n", filename, breakpoint_id, dict_keys(id_to_pybreakpoint)) py_db.on_breakpoints_changed(removed=True)
def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc): ''' :param trace_obj: This is the traceback from which the list should be created. :param frame: This is the first frame to be considered (i.e.: topmost frame). ''' lst = [] tb = trace_obj if tb is not None and tb.tb_frame is not None: f = tb.tb_frame.f_back while f is not None: lst.insert(0, (f, f.f_lineno)) f = f.f_back while tb is not None: lst.append((tb.tb_frame, tb.tb_lineno)) tb = tb.tb_next frames_list = None for tb_frame, tb_lineno in reversed(lst): if frames_list is None and (frame is tb_frame or frame is None): frames_list = FramesList() if frames_list is not None: frames_list.append(tb_frame) frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno if frames_list is None and frame is not None: # Fallback (shouldn't happen in practice). pydev_log.info( 'create_frames_list_from_traceback did not find topmost frame in list.' ) frames_list = create_frames_list_from_frame(frame) frames_list.exc_type = exc_type frames_list.exc_desc = exc_desc frames_list.trace_obj = trace_obj return frames_list
def resume_threads(thread_id, except_thread=None): pydev_log.info('Resuming threads: %s (except thread: %s)', thread_id, except_thread) threads = [] if thread_id == '*': threads = pydevd_utils.get_non_pydevd_threads() elif thread_id.startswith('__frame__:'): pydev_log.critical("Can't make tasklet run: %s", thread_id) else: threads = [pydevd_find_thread_by_id(thread_id)] for t in threads: if t is None or t is except_thread: pydev_log.info('Skipped resuming thread: %s', t) continue internal_run_thread( t, set_additional_thread_info=set_additional_thread_info)
def _load_python_helper_lib_uncached(): if (not IS_CPYTHON or sys.version_info[:2] > (3, 10) or hasattr(sys, 'gettotalrefcount') or LOAD_NATIVE_LIB_FLAG in ENV_FALSE_LOWER_VALUES): pydev_log.info('Helper lib to set tracing to all threads not loaded.') return None try: filename = get_python_helper_lib_filename() if filename is None: return None # Load as pydll so that we don't release the gil. lib = ctypes.pydll.LoadLibrary(filename) pydev_log.info('Successfully Loaded helper lib to set tracing to all threads.') return lib except: if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: # Only show message if tracing is on (we don't have pre-compiled # binaries for all architectures -- i.e.: ARM). pydev_log.exception('Error loading: %s', filename) return None
def suspend_all_threads(py_db, except_thread): ''' Suspend all except the one passed as a parameter. :param except_thread: ''' pydev_log.info('Suspending all threads except: %s', except_thread) all_threads = pydevd_utils.get_non_pydevd_threads() for t in all_threads: if getattr(t, 'pydev_do_not_trace', None): pass # skip some other threads, i.e. ipython history saving thread from debug console else: if t is except_thread: continue info = mark_thread_suspended(t, CMD_THREAD_SUSPEND) frame = info.get_topmost_frame(t) # Reset the tracing as in this case as it could've set scopes to be untraced. if frame is not None: try: py_db.set_trace_for_frame_and_parents(frame) finally: frame = None
def get_topmost_frame(self, thread): ''' Gets the topmost frame for the given thread. Note that it may be None and callers should remove the reference to the frame as soon as possible to avoid disturbing user code. ''' # sys._current_frames(): dictionary with thread id -> topmost frame current_frames = _current_frames() topmost_frame = current_frames.get(thread.ident) if topmost_frame is None: # Note: this is expected for dummy threads (so, getting the topmost frame should be # treated as optional). pydev_log.info( 'Unable to get topmost frame for thread: %s, thread.ident: %s, id(thread): %s\nCurrent frames: %s.\n' 'GEVENT_SUPPORT: %s', thread, thread.ident, id(thread), current_frames, SUPPORT_GEVENT, ) return topmost_frame
def remove_breakpoint(self, py_db, received_filename, breakpoint_type, breakpoint_id): ''' :param str received_filename: Note: must be sent as it was received in the protocol. It may be translated in this function. :param str breakpoint_type: One of: 'python-line', 'django-line', 'jinja2-line'. :param int breakpoint_id: ''' for key, val in dict_items(py_db.api_received_breakpoints): original_filename, existing_breakpoint_id = key _new_filename, _api_add_breakpoint_params = val if received_filename == original_filename and existing_breakpoint_id == breakpoint_id: del py_db.api_received_breakpoints[key] break else: pydev_log.info( 'Did not find breakpoint to remove: %s (breakpoint id: %s)', received_filename, breakpoint_id) file_to_id_to_breakpoint = None received_filename = self.filename_to_server(received_filename) canonical_normalized_filename = pydevd_file_utils.canonical_normalized_path(received_filename) if breakpoint_type == 'python-line': breakpoints = py_db.breakpoints file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint elif py_db.plugin is not None: result = py_db.plugin.get_breakpoints(py_db, breakpoint_type) if result is not None: file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint breakpoints = result if file_to_id_to_breakpoint is None: pydev_log.critical('Error removing breakpoint. Cannot handle breakpoint of type %s', breakpoint_type) else: try: id_to_pybreakpoint = file_to_id_to_breakpoint.get(canonical_normalized_filename, {}) if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: existing = id_to_pybreakpoint[breakpoint_id] pydev_log.info('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % ( canonical_normalized_filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id)) del id_to_pybreakpoint[breakpoint_id] py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, breakpoints) if py_db.plugin is not None: py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks() except KeyError: pydev_log.info("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n", canonical_normalized_filename, breakpoint_id, dict_keys(id_to_pybreakpoint)) py_db.on_breakpoints_changed(removed=True)
def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=None): ''' :param trace_obj: This is the traceback from which the list should be created. :param frame: This is the first frame to be considered (i.e.: topmost frame). If None is passed, all the frames from the traceback are shown (so, None should be passed for unhandled exceptions). :param exception_type: If this is an unhandled exception or user unhandled exception, we'll not trim the stack to create from the passed frame, rather, we'll just mark the frame in the frames list. ''' lst = [] tb = trace_obj if tb is not None and tb.tb_frame is not None: f = tb.tb_frame.f_back while f is not None: lst.insert(0, (f, f.f_lineno)) f = f.f_back while tb is not None: lst.append((tb.tb_frame, tb.tb_lineno)) tb = tb.tb_next curr = exc_desc memo = set() while True: initial = curr try: curr = getattr(initial, '__cause__', None) except Exception: curr = None if curr is None: try: curr = getattr(initial, '__context__', None) except Exception: curr = None if curr is None or id(curr) in memo: break # The traceback module does this, so, let's play safe here too... memo.add(id(curr)) tb = getattr(curr, '__traceback__', None) while tb is not None: # Note: we don't use the actual tb.tb_frame because if the cause of the exception # uses the same frame object, the id(frame) would be the same and the frame_id_to_lineno # would be wrong as the same frame needs to appear with 2 different lines. lst.append((_DummyFrameWrapper(tb.tb_frame, tb.tb_lineno, None), tb.tb_lineno)) tb = tb.tb_next frames_list = None for tb_frame, tb_lineno in reversed(lst): if frames_list is None and ( (frame is tb_frame) or (frame is None) or (exception_type == EXCEPTION_TYPE_USER_UNHANDLED)): frames_list = FramesList() if frames_list is not None: frames_list.append(tb_frame) frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno if frames_list is None and frame is not None: # Fallback (shouldn't happen in practice). pydev_log.info( 'create_frames_list_from_traceback did not find topmost frame in list.' ) frames_list = create_frames_list_from_frame(frame) frames_list.exc_type = exc_type frames_list.exc_desc = exc_desc frames_list.trace_obj = trace_obj if exception_type == EXCEPTION_TYPE_USER_UNHANDLED: frames_list.current_frame = frame elif exception_type == EXCEPTION_TYPE_UNHANDLED: if len(frames_list) > 0: frames_list.current_frame = frames_list.last_frame() return frames_list
def set_trace_to_threads(tracing_func, thread_idents=None, create_dummy_thread=True): assert tracing_func is not None ret = 0 # Note: use sys._current_frames() keys to get the thread ids because it'll return # thread ids created in C/C++ where there's user code running, unlike the APIs # from the threading module which see only threads created through it (unless # a call for threading.current_thread() was previously done in that thread, # in which case a dummy thread would've been created for it). if thread_idents is None: thread_idents = set(sys._current_frames().keys()) for t in threading.enumerate(): # PY-44778: ignore pydevd threads and also add any thread that wasn't found on # sys._current_frames() as some existing threads may not appear in # sys._current_frames() but may be available through the `threading` module. if getattr(t, 'pydev_do_not_trace', False): thread_idents.discard(t.ident) else: thread_idents.add(t.ident) curr_ident = thread.get_ident() curr_thread = threading._active.get(curr_ident) if curr_ident in thread_idents and len(thread_idents) != 1: # The current thread must be updated first (because we need to set # the reference to `curr_thread`). thread_idents = list(thread_idents) thread_idents.remove(curr_ident) thread_idents.insert(0, curr_ident) for thread_ident in thread_idents: # If that thread is not available in the threading module we also need to create a # dummy thread for it (otherwise it'll be invisible to the debugger). if create_dummy_thread: if thread_ident not in threading._active: class _DummyThread(threading._DummyThread): def _set_ident(self): # Note: Hack to set the thread ident that we want. if IS_PY2: self._Thread__ident = thread_ident else: self._ident = thread_ident t = _DummyThread() # Reset to the base class (don't expose our own version of the class). t.__class__ = threading._DummyThread if thread_ident == curr_ident: curr_thread = t with threading._active_limbo_lock: # On Py2 it'll put in active getting the current indent, not using the # ident that was set, so, we have to update it (should be harmless on Py3 # so, do it always). threading._active[thread_ident] = t threading._active[curr_ident] = curr_thread if t.ident != thread_ident: # Check if it actually worked. pydev_log.critical( 'pydevd: creation of _DummyThread with fixed thread ident did not succeed.' ) # Some (ptvsd) tests failed because of this, so, leave it always disabled for now. # show_debug_info = 1 if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1 else 0 show_debug_info = 0 # Hack to increase _Py_TracingPossible. # See comments on py_custom_pyeval_settrace.hpp proceed = thread.allocate_lock() proceed.acquire() def dummy_trace(frame, event, arg): return dummy_trace def increase_tracing_count(): set_trace = TracingFunctionHolder._original_tracing or sys.settrace set_trace(dummy_trace) proceed.release() start_new_thread = pydev_monkey.get_original_start_new_thread(thread) start_new_thread(increase_tracing_count, ()) proceed.acquire() # Only proceed after the release() is done. proceed = None # Note: The set_trace_func is not really used anymore in the C side. set_trace_func = TracingFunctionHolder._original_tracing or sys.settrace lib = _load_python_helper_lib() if lib is None: # This is the case if it's not CPython. pydev_log.info( 'Unable to load helper lib to set tracing to all threads (unsupported python vm).' ) ret = -1 else: try: result = lib.AttachDebuggerTracing( ctypes.c_int(show_debug_info), ctypes.py_object(set_trace_func), ctypes.py_object(tracing_func), ctypes.c_uint(thread_ident), ctypes.py_object(None), ) except: if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: # There is no need to show this unless debug tracing is enabled. pydev_log.exception('Error attaching debugger tracing') ret = -1 else: if result != 0: pydev_log.info( 'Unable to set tracing for existing thread. Result: %s', result) ret = result return ret
def on_timeout_unblock_threads(): on_timeout_unblock_threads.called = True pydev_log.info('Resuming threads after evaluate timeout.') resume_threads('*', except_thread=curr_thread) py_db.threads_suspended_single_notification.on_thread_resume(tid)
def get_python_helper_lib_filename(): # Note: we have an independent (and similar -- but not equal) version of this method in # `add_code_to_python_process.py` which should be kept synchronized with this one (we do a copy # because the `pydevd_attach_to_process` is mostly independent and shouldn't be imported in the # debugger -- the only situation where it's imported is if the user actually does an attach to # process, through `attach_pydevd.py`, but this should usually be called from the IDE directly # and not from the debugger). libdir = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process') arch = '' if IS_WINDOWS: # prefer not using platform.machine() when possible (it's a bit heavyweight as it may # spawn a subprocess). arch = os.environ.get("PROCESSOR_ARCHITEW6432", os.environ.get('PROCESSOR_ARCHITECTURE', '')) if not arch: arch = platform.machine() if not arch: pydev_log.info('platform.machine() did not return valid value.' ) # This shouldn't happen... return None if IS_WINDOWS: extension = '.dll' suffix_64 = 'amd64' suffix_32 = 'x86' elif IS_LINUX: extension = '.so' suffix_64 = 'amd64' suffix_32 = 'x86' elif IS_MAC: extension = '.dylib' suffix_64 = 'x86_64' suffix_32 = 'x86' else: pydev_log.info('Unable to set trace to all threads in platform: %s', sys.platform) return None if arch.lower() not in ('amd64', 'x86', 'x86_64', 'i386', 'x86'): # We don't support this processor by default. Still, let's support the case where the # user manually compiled it himself with some heuristics. # # Ideally the user would provide a library in the format: "attach_<arch>.<extension>" # based on the way it's currently compiled -- see: # - windows/compile_windows.bat # - linux_and_mac/compile_linux.sh # - linux_and_mac/compile_mac.sh try: found = [ name for name in os.listdir(libdir) if name.startswith('attach_') and name.endswith(extension) ] except: if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: # There is no need to show this unless debug tracing is enabled. pydev_log.exception('Error listing dir: %s', libdir) return None expected_name = 'attach_' + arch + extension expected_name_linux = 'attach_linux_' + arch + extension filename = None if expected_name in found: # Heuristic: user compiled with "attach_<arch>.<extension>" filename = os.path.join(libdir, expected_name) elif IS_LINUX and expected_name_linux in found: # Heuristic: user compiled with "attach_linux_<arch>.<extension>" filename = os.path.join(libdir, expected_name_linux) elif len( found ) == 1: # Heuristic: user removed all libraries and just left his own lib. filename = os.path.join(libdir, found[0]) else: # Heuristic: there's one additional library which doesn't seem to be our own. Find the odd one. filtered = [ name for name in found if not name.endswith((suffix_64 + extension, suffix_32 + extension)) ] if len(filtered ) == 1: # If more than one is available we can't be sure... filename = os.path.join(libdir, found[0]) if filename is None: pydev_log.info( 'Unable to set trace to all threads in arch: %s (did not find a %s lib in %s).', arch, expected_name, libdir) return None pydev_log.info('Using %s lib in arch: %s.', filename, arch) else: # Happy path for which we have pre-compiled binaries. if IS_64BIT_PROCESS: suffix = suffix_64 else: suffix = suffix_32 if IS_WINDOWS or IS_MAC: # just the extension changes prefix = 'attach_' elif IS_LINUX: # prefix = 'attach_linux_' # historically it has a different name else: pydev_log.info( 'Unable to set trace to all threads in platform: %s', sys.platform) return None filename = os.path.join(libdir, '%s%s%s' % (prefix, suffix, extension)) if not os.path.exists(filename): pydev_log.critical('Expected: %s to exist.', filename) return None return filename
def set_trace_to_threads(tracing_func): lib = load_python_helper_lib() if lib is None: # This is the case if it's not CPython. pydev_log.info( 'Unable to load helper lib to set tracing to all threads (unsupported python vm).' ) return -1 pydev_log.info( 'Successfully Loaded helper lib to set tracing to all threads.') ret = 0 set_trace_func = TracingFunctionHolder._original_tracing or sys.settrace # Note: use sys._current_frames() keys to get the thread ids because it'll return # thread ids created in C/C++ where there's user code running, unlike the APIs # from the threading module which see only threads created through it (unless # a call for threading.current_thread() was previously done in that thread, # in which case a dummy thread would've been created for it). thread_idents = set(sys._current_frames().keys()) thread_idents = thread_idents.difference( # Ignore pydevd threads. set(t.ident for t in threading.enumerate() if getattr(t, 'pydev_do_not_trace', False))) curr_ident = thread.get_ident() curr_thread = threading._active.get(curr_ident) for thread_ident in thread_idents: # If that thread is not available in the threading module we also need to create a # dummy thread for it (otherwise it'll be invisible to the debugger). if thread_ident not in threading._active: class _DummyThread(threading._DummyThread): def _set_ident(self): # Note: Hack to set the thread ident that we want. if IS_PY2: self._Thread__ident = thread_ident else: self._ident = thread_ident t = _DummyThread() # Reset to the base class (don't expose our own version of the class). t.__class__ = threading._DummyThread with threading._active_limbo_lock: # On Py2 it'll put in active getting the current indent, not using the # ident that was set, so, we have to update it (should be harmless on Py3 # so, do it always). threading._active[thread_ident] = t threading._active[curr_ident] = curr_thread if t.ident != thread_ident: # Check if it actually worked. pydev_log.critical( 'pydevd: creation of _DummyThread with fixed thread ident did not succeed.' ) # Some (ptvsd) tests failed because of this, so, leave it always disabled for now. # show_debug_info = 1 if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1 else 0 show_debug_info = 0 # Hack to increase _Py_TracingPossible. # See comments on py_custom_pyeval_settrace.hpp proceed = thread.allocate_lock() proceed.acquire() def dummy_trace(frame, event, arg): return dummy_trace def increase_tracing_count(): SetTrace(dummy_trace) proceed.release() start_new_thread = pydev_monkey.get_original_start_new_thread(thread) start_new_thread(increase_tracing_count, ()) proceed.acquire() # Only proceed after the release() is done. proceed = None result = lib.AttachDebuggerTracing( ctypes.c_int(show_debug_info), ctypes.py_object(set_trace_func), ctypes.py_object(tracing_func), ctypes.c_uint(thread_ident), ctypes.py_object(None), ) if result != 0: pydev_log.info( 'Unable to set tracing for existing thread. Result: %s', result) ret = result return ret
def set_trace_to_threads(tracing_func, target_threads=None): if not IS_CPYTHON or ctypes is None or sys.version_info[:2] > (3, 7): return -1 if IS_WINDOWS: if IS_64BIT_PROCESS: suffix = 'amd64' else: suffix = 'x86' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s.dll' % (suffix, )) elif IS_LINUX: if IS_64BIT_PROCESS: suffix = 'amd64' else: suffix = 'x86' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_linux_%s.so' % (suffix, )) elif IS_MAC: if IS_64BIT_PROCESS: suffix = 'x86_64.dylib' else: suffix = 'x86.dylib' filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s' % (suffix, )) else: pydev_log.info('Unable to set trace to all threads in platform: %s', sys.platform) return -1 if not os.path.exists(filename): pydev_log.critical('Expected: %s to exist.', filename) return -1 try: lib = ctypes.cdll.LoadLibrary(filename) except: pydev_log.exception('Error loading: %s', filename) return -1 if hasattr(sys, 'getswitchinterval'): get_interval, set_interval = sys.getswitchinterval, sys.setswitchinterval else: get_interval, set_interval = sys.getcheckinterval, sys.setcheckinterval prev_value = get_interval() ret = 0 try: # Prevent going to any other thread... if we switch the thread during this operation we # could potentially corrupt the interpreter. set_interval(2**15) set_trace_func = TracingFunctionHolder._original_tracing or sys.settrace if target_threads is None: target_threads = list(threading.enumerate()) for t in target_threads: if t and not getattr(t, 'pydev_do_not_trace', None): show_debug_info = 0 result = lib.AttachDebuggerTracing( ctypes.c_int(show_debug_info), ctypes.py_object(set_trace_func), ctypes.py_object(tracing_func), ctypes.c_uint(t.ident)) if result != 0: pydev_log.info( 'Unable to set tracing for existing threads. Result: %s', result) ret = result finally: set_interval(prev_value) return ret
def patch_args(args, is_exec=False): ''' :param list args: Arguments to patch. :param bool is_exec: If it's an exec, the current process will be replaced (this means we have to keep the same ppid). ''' try: pydev_log.debug("Patching args: %s", args) original_args = args try: unquoted_args = remove_quotes_from_args(args) except InvalidTypeInArgsException as e: pydev_log.info('Unable to monkey-patch subprocess arguments because a type found in the args is invalid: %s', e) return original_args # Internally we should reference original_args (if we want to return them) or unquoted_args # to add to the list which will be then quoted in the end. del args from pydevd import SetupHolder if not unquoted_args: return original_args if not is_python(unquoted_args[0]): pydev_log.debug("Process is not python, returning.") return original_args # Note: we create a copy as string to help with analyzing the arguments, but # the final list should have items from the unquoted_args as they were initially. args_as_str = _get_str_type_compatible('', unquoted_args) params_with_value_in_separate_arg = ( '--check-hash-based-pycs', '--jit' # pypy option ) # All short switches may be combined together. The ones below require a value and the # value itself may be embedded in the arg. # # i.e.: Python accepts things as: # # python -OQold -qmtest # # Which is the same as: # # python -O -Q old -q -m test # # or even: # # python -OQold "-vcimport sys;print(sys)" # # Which is the same as: # # python -O -Q old -v -c "import sys;print(sys)" params_with_combinable_arg = set(('W', 'X', 'Q', 'c', 'm')) module_name = None before_module_flag = '' module_name_i_start = -1 module_name_i_end = -1 code = None code_i = -1 code_i_end = -1 code_flag = '' filename = None filename_i = -1 ignore_next = True # start ignoring the first (the first entry is the python executable) for i, arg_as_str in enumerate(args_as_str): if ignore_next: ignore_next = False continue if arg_as_str.startswith('-'): if arg_as_str == '-': # Contents will be read from the stdin. This is not currently handled. pydev_log.debug('Unable to fix arguments to attach debugger on subprocess when reading from stdin ("python ... -").') return original_args if arg_as_str.startswith(params_with_value_in_separate_arg): if arg_as_str in params_with_value_in_separate_arg: ignore_next = True continue break_out = False for j, c in enumerate(arg_as_str): # i.e.: Python supports -X faulthandler as well as -Xfaulthandler # (in one case we have to ignore the next and in the other we don't # have to ignore it). if c in params_with_combinable_arg: remainder = arg_as_str[j + 1:] if not remainder: ignore_next = True if c == 'm': # i.e.: Something as # python -qm test # python -m test # python -qmtest before_module_flag = arg_as_str[:j] # before_module_flag would then be "-q" if before_module_flag == '-': before_module_flag = '' module_name_i_start = i if not remainder: module_name = unquoted_args[i + 1] module_name_i_end = i + 1 else: # i.e.: python -qmtest should provide 'test' as the module_name module_name = unquoted_args[i][j + 1:] module_name_i_end = module_name_i_start break_out = True break elif c == 'c': # i.e.: Something as # python -qc "import sys" # python -c "import sys" # python "-qcimport sys" code_flag = arg_as_str[:j + 1] # code_flag would then be "-qc" if not remainder: # arg_as_str is something as "-qc", "import sys" code = unquoted_args[i + 1] code_i_end = i + 2 else: # if arg_as_str is something as "-qcimport sys" code = remainder # code would be "import sys" code_i_end = i + 1 code_i = i break_out = True break else: break if break_out: break else: # It doesn't start with '-' and we didn't ignore this entry: # this means that this is the file to be executed. filename = unquoted_args[i] filename_i = i # When executing .zip applications, don't attach the debugger. extensions = _get_str_type_compatible(filename, ['.zip', '.pyz', '.pyzw']) for ext in extensions: if filename.endswith(ext): pydev_log.debug('Executing a PyZip (debugger will not be attached to subprocess).') return original_args if _is_managed_arg(filename): # no need to add pydevd twice pydev_log.debug('Skipped monkey-patching as pydevd.py is in args already.') return original_args break else: # We didn't find the filename (something is unexpected). pydev_log.debug('Unable to fix arguments to attach debugger on subprocess (filename not found).') return original_args if code_i != -1: host, port = _get_host_port() if port is not None: new_args = [] new_args.extend(unquoted_args[:code_i]) new_args.append(code_flag) new_args.append(_get_python_c_args(host, port, code, unquoted_args, SetupHolder.setup)) new_args.extend(unquoted_args[code_i_end:]) return quote_args(new_args) first_non_vm_index = max(filename_i, module_name_i_start) if first_non_vm_index == -1: pydev_log.debug('Unable to fix arguments to attach debugger on subprocess (could not resolve filename nor module name).') return original_args # Original args should be something as: # ['X:\\pysrc\\pydevd.py', '--multiprocess', '--print-in-debugger-startup', # '--vm_type', 'python', '--client', '127.0.0.1', '--port', '56352', '--file', 'x:\\snippet1.py'] from _pydevd_bundle.pydevd_command_line_handling import setup_to_argv new_args = [] new_args.extend(unquoted_args[:first_non_vm_index]) if before_module_flag: new_args.append(before_module_flag) add_module_at = len(new_args) + 1 new_args.extend(setup_to_argv( _get_setup_updated_with_protocol_and_ppid(SetupHolder.setup, is_exec=is_exec) )) new_args.append('--file') if module_name is not None: assert module_name_i_start != -1 assert module_name_i_end != -1 # Always after 'pydevd' (i.e.: pydevd "--module" --multiprocess ...) new_args.insert(add_module_at, '--module') new_args.append(module_name) new_args.extend(unquoted_args[module_name_i_end + 1:]) elif filename is not None: assert filename_i != -1 new_args.append(filename) new_args.extend(unquoted_args[filename_i + 1:]) else: raise AssertionError('Internal error (unexpected condition)') return quote_args(new_args) except: pydev_log.exception('Error patching args (debugger not attached to subprocess).') return original_args
def _separate_future_imports(code): ''' :param code: The code from where we want to get the __future__ imports (note that it's possible that there's no such entry). :return tuple(str, str): The return is a tuple(future_import, code). If the future import is not available a return such as ('', code) is given, otherwise, the future import will end with a ';' (so that it can be put right before the pydevd attach code). ''' try: node = ast.parse(code, '<string>', 'exec') visitor = _LastFutureImportFinder() visitor.visit(node) if visitor.last_future_import_found is None: return '', code node = visitor.last_future_import_found offset = -1 if hasattr(node, 'end_lineno') and hasattr(node, 'end_col_offset'): # Python 3.8 onwards has these (so, use when possible). line, col = node.end_lineno, node.end_col_offset offset = _get_offset_from_line_col(code, line - 1, col) # ast lines are 1-based, make it 0-based. else: # end line/col not available, let's just find the offset and then search # for the alias from there. line, col = node.lineno, node.col_offset offset = _get_offset_from_line_col(code, line - 1, col) # ast lines are 1-based, make it 0-based. if offset >= 0 and node.names: from_future_import_name = node.names[-1].name i = code.find(from_future_import_name, offset) if i < 0: offset = -1 else: offset = i + len(from_future_import_name) if offset >= 0: for i in range(offset, len(code)): if code[i] in (' ', '\t', ';', ')', '\n'): offset += 1 else: break future_import = code[:offset] code_remainder = code[offset:] # Now, put '\n' lines back into the code remainder (we had to search for # `\n)`, but in case we just got the `\n`, it should be at the remainder, # not at the future import. while future_import.endswith('\n'): future_import = future_import[:-1] code_remainder = '\n' + code_remainder if not future_import.endswith(';'): future_import += ';' return future_import, code_remainder # This shouldn't happen... pydev_log.info('Unable to find line %s in code:\n%r', line, code) return '', code except: pydev_log.exception('Error getting from __future__ imports from: %r', code) return '', code
def _patch_threading_to_hide_pydevd_threads(): ''' Patches the needed functions on the `threading` module so that the pydevd threads are hidden. Note that we patch the functions __code__ to avoid issues if some code had already imported those variables prior to the patching. ''' found_load_names = _collect_load_names(threading.enumerate) # i.e.: we'll only apply the patching if the function seems to be what we expect. new_threading_enumerate = None if found_load_names == set( ('_active_limbo_lock', '_limbo', '_active', 'values', 'list')): pydev_log.debug( 'Applying patching to hide pydevd threads (Py3 version).') def new_threading_enumerate(): with _active_limbo_lock: ret = list(_active.values()) + list(_limbo.values()) return [ t for t in ret if not getattr(t, 'is_pydev_daemon_thread', False) ] elif found_load_names == set( ('_active_limbo_lock', '_limbo', '_active', 'values')): pydev_log.debug( 'Applying patching to hide pydevd threads (Py2 version).') def new_threading_enumerate(): with _active_limbo_lock: ret = _active.values() + _limbo.values() return [ t for t in ret if not getattr(t, 'is_pydev_daemon_thread', False) ] else: pydev_log.info( 'Unable to hide pydevd threads. Found names in threading.enumerate: %s', found_load_names) if new_threading_enumerate is not None: def pydevd_saved_threading_enumerate(): with threading._active_limbo_lock: return list(threading._active.values()) + list( threading._limbo.values()) _pydev_saved_modules.pydevd_saved_threading_enumerate = pydevd_saved_threading_enumerate threading.enumerate.__code__ = new_threading_enumerate.__code__ # We also need to patch the active count (to match what we have in the enumerate). def new_active_count(): # Note: as this will be executed in the `threading` module, `enumerate` will # actually be threading.enumerate. return len(enumerate()) threading.active_count.__code__ = new_active_count.__code__ # When shutting down, Python (on some versions) may do something as: # # def _pickSomeNonDaemonThread(): # for t in enumerate(): # if not t.daemon and t.is_alive(): # return t # return None # # But in this particular case, we do want threads with `is_pydev_daemon_thread` to appear # explicitly due to the pydevd `CheckAliveThread` (because we want the shutdown to wait on it). # So, it can't rely on the `enumerate` for that anymore as it's patched to not return pydevd threads. if hasattr(threading, '_pickSomeNonDaemonThread'): def new_pick_some_non_daemon_thread(): with _active_limbo_lock: # Ok for py2 and py3. threads = list(_active.values()) + list(_limbo.values()) for t in threads: if not t.daemon and t.is_alive(): return t return None threading._pickSomeNonDaemonThread.__code__ = new_pick_some_non_daemon_thread.__code__
def process_net_command_json(self, py_db, json_contents, send_response=True): ''' Processes a debug adapter protocol json command. ''' DEBUG = False try: if isinstance(json_contents, bytes): json_contents = json_contents.decode('utf-8') request = self.from_json(json_contents, update_ids_from_dap=True) except Exception as e: try: loaded_json = json.loads(json_contents) request = Request(loaded_json.get('command', '<unknown>'), loaded_json['seq']) except: # There's not much we can do in this case... pydev_log.exception('Error loading json: %s', json_contents) return error_msg = str(e) if error_msg.startswith("'") and error_msg.endswith("'"): error_msg = error_msg[1:-1] # This means a failure processing the request (but we were able to load the seq, # so, answer with a failure response). def on_request(py_db, request): error_response = { 'type': 'response', 'request_seq': request.seq, 'success': False, 'command': request.command, 'message': error_msg, } return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True), )) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(), ) on_request = getattr(self, method_name, None) if on_request is None: print( 'Unhandled: %s not available in PyDevJsonCommandProcessor.\n' % (method_name, )) return if DEBUG: print( 'Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' % (method_name, )) with py_db._main_lock: if request.__class__ == PydevdAuthorizeRequest: authorize_request = request # : :type authorize_request: PydevdAuthorizeRequest access_token = authorize_request.arguments.debugServerAccessToken py_db.authentication.login(access_token) if not py_db.authentication.is_authenticated(): response = Response(request.seq, success=False, command=request.command, message='Client not authenticated.', body={}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) return cmd = on_request(py_db, request) if cmd is not None and send_response: py_db.writer.add_command(cmd)
def set_trace_to_threads(tracing_func): lib = load_python_helper_lib() if lib is None: # This is the case if it's not CPython. return -1 if hasattr(sys, 'getswitchinterval'): get_interval, set_interval = sys.getswitchinterval, sys.setswitchinterval else: get_interval, set_interval = sys.getcheckinterval, sys.setcheckinterval prev_value = get_interval() ret = 0 try: if not IS_PY37_OR_GREATER: # Prevent going to any other thread... if we switch the thread during this operation we # could potentially corrupt the interpreter. # Note: on CPython 3.7 onwards this is not needed (we have a different implementation # for setting the tracing for other threads in this case). set_interval(2 ** 15) set_trace_func = TracingFunctionHolder._original_tracing or sys.settrace # Note: use sys._current_frames() keys to get the thread ids because it'll return # thread ids created in C/C++ where there's user code running, unlike the APIs # from the threading module which see only threads created through it (unless # a call for threading.current_thread() was previously done in that thread, # in which case a dummy thread would've been created for it). thread_idents = set(sys._current_frames().keys()) thread_idents = thread_idents.difference( # Ignore pydevd threads. set(t.ident for t in threading.enumerate() if getattr(t, 'pydev_do_not_trace', False)) ) curr_ident = thread.get_ident() curr_thread = threading._active.get(curr_ident) for thread_ident in thread_idents: # If that thread is not available in the threading module we also need to create a # dummy thread for it (otherwise it'll be invisible to the debugger). if thread_ident not in threading._active: class _DummyThread(threading._DummyThread): def _set_ident(self): # Note: Hack to set the thread ident that we want. if IS_PY2: self._Thread__ident = thread_ident else: self._ident = thread_ident t = _DummyThread() # Reset to the base class (don't expose our own version of the class). t.__class__ = threading._DummyThread with threading._active_limbo_lock: # On Py2 it'll put in active getting the current indent, not using the # ident that was set, so, we have to update it (should be harmless on Py3 # so, do it always). threading._active[thread_ident] = t threading._active[curr_ident] = curr_thread if t.ident != thread_ident: # Check if it actually worked. pydev_log.error('pydevd: creation of _DummyThread with fixed thread ident did not succeed.') # Some (ptvsd) tests failed because of this, so, leave it always disabled for now. # show_debug_info = 1 if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1 else 0 show_debug_info = 0 if IS_PY37_OR_GREATER: # Hack to increase _Py_TracingPossible. # See comments on py_settrace_37.hpp proceed = thread.allocate_lock() proceed.acquire() def dummy_trace_on_py37(frame, event, arg): return dummy_trace_on_py37 def increase_tracing_count_on_py37(): SetTrace(dummy_trace_on_py37) proceed.release() start_new_thread = pydev_monkey.get_original_start_new_thread(thread) start_new_thread(increase_tracing_count_on_py37, ()) proceed.acquire() # Only proceed after the release() is done. proceed = None result = lib.AttachDebuggerTracing( ctypes.c_int(show_debug_info), ctypes.py_object(set_trace_func), ctypes.py_object(tracing_func), ctypes.c_uint(thread_ident), ctypes.py_object(None), ) if result != 0: pydev_log.info('Unable to set tracing for existing threads. Result: %s' % result) ret = result finally: if not IS_PY37_OR_GREATER: set_interval(prev_value) return ret