def stop_on_entry(self): main_thread = pydevd_utils.get_main_thread() if main_thread is None: pydev_log.critical('Could not find main thread while setting Stop on Entry.') else: info = set_additional_thread_info(main_thread) info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE info.pydev_stop_on_entry = True
def on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' if not self._launch_or_attach_request_done: pydev_log.critical('Missing launch request or attach request before configuration done request.') self.api.run(py_db) self.api.notify_configuration_done(py_db) configuration_done_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True)
def _load_modules(self): self.loaded_extensions = [] if extensions: for module_loader, name, ispkg in pkgutil.walk_packages(extensions.__path__, extensions.__name__ + '.'): mod_name = name.split('.')[-1] if not ispkg and mod_name.startswith('pydevd_plugin'): try: __import__(name) module = sys.modules[name] self.loaded_extensions.append(module) except ImportError: pydev_log.critical('Unable to load extension: %s', name)
def cmd_remove_break(self, py_db, cmd_id, seq, text): # command to remove some breakpoint # text is type\file\tid. Remove from breakpoints dictionary breakpoint_type, filename, breakpoint_id = text.split('\t', 2) filename = self.api.filename_to_server(filename) try: breakpoint_id = int(breakpoint_id) except ValueError: pydev_log.critical('Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s', breakpoint_id) else: self.api.remove_breakpoint(py_db, filename, breakpoint_type, breakpoint_id)
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 on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' if not self._launch_or_attach_request_done: pydev_log.critical( 'Missing launch request or attach request before configuration done request.' ) self.api.run(py_db) self.api.notify_configuration_done(py_db) configuration_done_response = pydevd_base_schema.build_response( request) return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True)
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 _get_path_with_real_case(filename): # Note: this previously made: # convert_to_long_pathname(convert_to_short_pathname(filename)) # but this is no longer done because we can't rely on getting the shortname # consistently (there are settings to disable it on Windows). # So, using approach which resolves by listing the dir. if IS_PY2 and isinstance(filename, unicode): # noqa filename = filename.encode(getfilesystemencoding()) if '~' in filename: filename = convert_to_long_pathname(filename) if filename.startswith('<') or not os_path_exists(filename): return filename # Not much we can do. drive, parts = os.path.splitdrive(os.path.normpath(filename)) drive = drive.upper() while parts.startswith(os.path.sep): parts = parts[1:] drive += os.path.sep parts = parts.lower().split(os.path.sep) try: return _resolve_listing(drive, iter(parts)) except FileNotFoundError: _listdir_cache.clear() # Retry once after clearing the cache we have. try: return _resolve_listing(drive, iter(parts)) except FileNotFoundError: if os_path_exists(filename): # This is really strange, ask the user to report as error. pydev_log.critical( 'pydev debugger: critical: unable to get real case for file. Details:\n' 'filename: %s\ndrive: %s\nparts: %s\n' '(please create a ticket in the tracker to address this).', filename, drive, parts ) pydev_log.exception() # Don't fail, just return the original file passed. return filename
def _show_debug_info(cls, cmd_id, seq, text): with cls._show_debug_info_lock: # Only one thread each time (rlock). if cls._showing_debug_info: # avoid recursing in the same thread (just printing could create # a new command when redirecting output). return cls._showing_debug_info += 1 try: out_message = 'sending cmd --> ' out_message += "%20s" % ID_TO_MEANING.get(str(cmd_id), 'UNKNOWN') out_message += ' ' out_message += text.replace('\n', ' ') try: pydev_log.critical('%s\n', out_message) except: pass finally: cls._showing_debug_info -= 1
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 exec_on_timeout(self): # Note: lock should already be obtained when executing this function. kwargs = self.kwargs on_timeout = self.on_timeout if not self.disposed: self.disposed = True self.kwargs = None self.on_timeout = None try: if _DEBUG: pydev_log.critical( 'pydevd_timeout: Calling on timeout: %s with kwargs: %s', on_timeout, kwargs) on_timeout(**kwargs) except Exception: pydev_log.exception( 'pydevd_timeout: Exception on callback timeout.')
def _show_debug_info(cls, cmd_id, seq, text): with cls._show_debug_info_lock: # Only one thread each time (rlock). if cls._showing_debug_info: # avoid recursing in the same thread (just printing could create # a new command when redirecting output). return cls._showing_debug_info += 1 try: out_message = 'sending cmd (%s) --> ' % (get_protocol(),) out_message += "%20s" % ID_TO_MEANING.get(str(cmd_id), 'UNKNOWN') out_message += ' ' out_message += text.replace('\n', ' ') try: pydev_log.critical('%s\n', out_message) except: pass finally: cls._showing_debug_info -= 1
def process_handles(self): ''' :return int: Returns the time we should be waiting for to process the next event properly. ''' with self._lock: if _DEBUG: pydev_log.critical('pydevd_timeout: Processing handles') self._event.clear() handles = self._handles new_handles = self._handles = [] # Do all the processing based on this time (we want to consider snapshots # of processing time -- anything not processed now may be processed at the # next snapshot). curtime = time.time() min_handle_timeout = None for handle in handles: if curtime < handle.abs_timeout and not handle.disposed: # It still didn't time out. if _DEBUG: pydev_log.critical( 'pydevd_timeout: Handle NOT processed: %s', handle) new_handles.append(handle) if min_handle_timeout is None: min_handle_timeout = handle.abs_timeout elif handle.abs_timeout < min_handle_timeout: min_handle_timeout = handle.abs_timeout else: if _DEBUG: pydev_log.critical( 'pydevd_timeout: Handle processed: %s', handle) # Timed out (or disposed), so, let's execute it (should be no-op if disposed). handle.exec_on_timeout() if min_handle_timeout is None: return None else: timeout = min_handle_timeout - curtime if timeout <= 0: pydev_log.critical( 'pydevd_timeout: Expected timeout to be > 0. Found: %s', timeout) return timeout
def on_setdebuggerproperty_request(self, py_db, request): args = request.arguments.kwargs if 'dontTraceStartPatterns' in args and 'dontTraceEndPatterns' in args: start_patterns = tuple(args['dontTraceStartPatterns']) end_patterns = tuple(args['dontTraceEndPatterns']) if self._can_set_dont_trace_pattern(py_db, start_patterns, end_patterns): def dont_trace_files_property_request(abs_path): result = abs_path.startswith(start_patterns) or \ abs_path.endswith(end_patterns) return result dont_trace_files_property_request.start_patterns = start_patterns dont_trace_files_property_request.end_patterns = end_patterns py_db.dont_trace_external_files = dont_trace_files_property_request else: # Don't trace pattern cannot be changed after it is set once. There are caches # throughout the debugger which rely on always having the same file type. message = ("Calls to set or change don't trace patterns (via setDebuggerProperty) are not " "allowed since debugging has already started or don't trace patterns are already set.") pydev_log.critical(message) response_args = {'success':False, 'body': {}, 'message': message} response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) # TODO: Support other common settings. Note that not all of these might be relevant to python. # JustMyCodeStepping: 0 or 1 # AllowOutOfProcessSymbols: 0 or 1 # DisableJITOptimization: 0 or 1 # InterpreterOptions: 0 or 1 # StopOnExceptionCrossingManagedBoundary: 0 or 1 # WarnIfNoUserCodeOnLaunch: 0 or 1 # EnableStepFiltering: true of false response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) return NetCommand(CMD_RETURN, 0, response, is_json=True)
def _get_except_target_info(instructions, exception_end_instruction_index, offset_to_instruction_idx): next_3 = [ j_instruction.opname for j_instruction in instructions[exception_end_instruction_index: exception_end_instruction_index + 3] ] # print('next_3:', [(j_instruction.opname, j_instruction.argval) for j_instruction in instructions[exception_end_instruction_index:exception_end_instruction_index + 3]]) if next_3 == ['POP_TOP', 'POP_TOP', 'POP_TOP']: # try..except without checking exception. try: jump_instruction = instructions[exception_end_instruction_index - 1] if jump_instruction.opname not in ('JUMP_FORWARD', 'JUMP_ABSOLUTE'): return None except IndexError: pass if jump_instruction.opname == 'JUMP_ABSOLUTE': # On latest versions of Python 3 the interpreter has a go-backwards step, # used to show the initial line of a for/while, etc (which is this # JUMP_ABSOLUTE)... we're not really interested in it, but rather on where # it points to. except_end_instruction = instructions[ offset_to_instruction_idx[jump_instruction.argval]] idx = offset_to_instruction_idx[except_end_instruction.argval] # Search for the POP_EXCEPT which should be at the end of the block. for pop_except_instruction in reversed(instructions[:idx]): if pop_except_instruction.opname == 'POP_EXCEPT': except_end_instruction = pop_except_instruction return _TargetInfo(except_end_instruction) else: return None # i.e.: Continue outer loop else: # JUMP_FORWARD i = offset_to_instruction_idx[jump_instruction.argval] try: # i.e.: the jump is to the instruction after the block finishes (so, we need to # get the previous instruction as that should be the place where the exception # block finishes). except_end_instruction = instructions[i - 1] except: pydev_log.critical( 'Error when computing try..except block end.') return None return _TargetInfo(except_end_instruction) elif next_3 and next_3[0] == 'DUP_TOP': # try..except AssertionError. iter_in = instructions[exception_end_instruction_index + 1:] for j, jump_if_not_exc_instruction in enumerate(iter_in): if jump_if_not_exc_instruction.opname == 'JUMP_IF_NOT_EXC_MATCH': # Python 3.9 except_end_instruction = instructions[ offset_to_instruction_idx[ jump_if_not_exc_instruction.argval]] return _TargetInfo(except_end_instruction, jump_if_not_exc_instruction) elif jump_if_not_exc_instruction.opname == 'COMPARE_OP' and jump_if_not_exc_instruction.argval == 'exception match': # Python 3.8 and before try: next_instruction = iter_in[j + 1] except: continue if next_instruction.opname == 'POP_JUMP_IF_FALSE': except_end_instruction = instructions[ offset_to_instruction_idx[next_instruction.argval]] return _TargetInfo(except_end_instruction, next_instruction) else: return None # i.e.: Continue outer loop else: # i.e.: we're not interested in try..finally statements, only try..except. return None
def cmd_set_break(self, py_db, cmd_id, seq, text): # func name: 'None': match anything. Empty: match global, specified: only method context. # command to add some breakpoint. # text is filename\tline. Add to breakpoints dictionary suspend_policy = u"NONE" # Can be 'NONE' or 'ALL' is_logpoint = False hit_condition = None if py_db._set_breakpoints_with_id: try: try: breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split( u'\t', 9) except ValueError: # not enough values to unpack # No suspend_policy passed (use default). breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split( u'\t', 8) is_logpoint = is_logpoint == u'True' except ValueError: # not enough values to unpack breakpoint_id, btype, filename, line, func_name, condition, expression = text.split( u'\t', 6) breakpoint_id = int(breakpoint_id) line = int(line) # We must restore new lines and tabs as done in # AbstractDebugTarget.breakpointAdded condition = condition.replace(u"@_@NEW_LINE_CHAR@_@", u'\n').\ replace(u"@_@TAB_CHAR@_@", u'\t').strip() expression = expression.replace(u"@_@NEW_LINE_CHAR@_@", u'\n').\ replace(u"@_@TAB_CHAR@_@", u'\t').strip() else: # Note: this else should be removed after PyCharm migrates to setting # breakpoints by id (and ideally also provides func_name). btype, filename, line, func_name, suspend_policy, condition, expression = text.split( u'\t', 6) # If we don't have an id given for each breakpoint, consider # the id to be the line. breakpoint_id = line = int(line) condition = condition.replace(u"@_@NEW_LINE_CHAR@_@", u'\n'). \ replace(u"@_@TAB_CHAR@_@", u'\t').strip() expression = expression.replace(u"@_@NEW_LINE_CHAR@_@", u'\n'). \ replace(u"@_@TAB_CHAR@_@", u'\t').strip() if condition is not None and (len(condition) <= 0 or condition == u"None"): condition = None if expression is not None and (len(expression) <= 0 or expression == u"None"): expression = None if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == u"None"): hit_condition = None result = self.api.add_breakpoint(py_db, self.api.filename_to_str(filename), btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) error_code = result.error_code if error_code: translated_filename = result.translated_filename if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: pydev_log.critical( 'pydev debugger: warning: Trying to add breakpoint to file that does not exist: %s (will have no effect).' % (translated_filename, )) elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: pydev_log.critical( 'pydev debugger: warning: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).' % (translated_filename, )) else: # Shouldn't get here. pydev_log.critical( 'pydev debugger: warning: Breakpoint not validated (reason unknown -- please report as error): %s.' % (translated_filename, ))
def _norm_file_to_client(filename, cache=norm_filename_to_client_container): # The result of this method will be passed to eclipse # So, this would be 'NormFileFromPythonToEclipse' try: return cache[filename] except KeyError: # used to translate a path from the debug server to the client translated = _NormFile(filename) # After getting the real path, let's get it with the path with # the real case and then obtain a new normalized copy, just in case # the path is different now. translated_proper_case = get_path_with_real_case(translated) translated = _NormFile(translated_proper_case) path_mapping_applied = False if IS_WINDOWS: if translated.lower() != translated_proper_case.lower(): translated_proper_case = translated if DEBUG_CLIENT_SERVER_TRANSLATION: pydev_log.critical( 'pydev debugger: _NormFile changed path (from: %s to %s)', translated_proper_case, translated) for i, (eclipse_prefix, python_prefix) in enumerate(paths_from_eclipse_to_python): if translated.startswith(python_prefix): if DEBUG_CLIENT_SERVER_TRANSLATION: pydev_log.critical( 'pydev debugger: replacing to client: %s', translated) # Note: use the non-normalized version. eclipse_prefix = initial_paths[i][0] translated = eclipse_prefix + translated_proper_case[ len(python_prefix):] if DEBUG_CLIENT_SERVER_TRANSLATION: pydev_log.critical( 'pydev debugger: sent to client: %s', translated) path_mapping_applied = True break else: if DEBUG_CLIENT_SERVER_TRANSLATION: pydev_log.critical( 'pydev debugger: to client: unable to find matching prefix for: %s in %s', translated, [x[1] for x in paths_from_eclipse_to_python]) translated = translated_proper_case if eclipse_sep != python_sep: translated = translated.replace(python_sep, eclipse_sep) translated = _path_to_expected_str(translated) # The resulting path is not in the python process, so, we cannot do a _NormFile here, # only at the beginning of this method. cache[filename] = (translated, path_mapping_applied) if translated not in _client_filename_in_utf8_to_source_reference: if path_mapping_applied: source_reference = 0 else: source_reference = _next_source_reference() pydev_log.debug( 'Created source reference: %s for untranslated path: %s', source_reference, filename) _client_filename_in_utf8_to_source_reference[ translated] = source_reference _source_reference_to_server_filename[ source_reference] = filename return (translated, path_mapping_applied)
return library_dir # Note: we can't call sysconfig.get_path from _NormPath (it deadlocks on Python 2.7) so, we # need to get the library dir during module loading. _library_dir = _get_library_dir() # defined as a list of tuples where the 1st element of the tuple is the path in the client machine # and the 2nd element is the path in the server machine. # see module docstring for more details. try: PATHS_FROM_ECLIPSE_TO_PYTHON = json.loads( os.environ.get('PATHS_FROM_ECLIPSE_TO_PYTHON', '[]')) except Exception: pydev_log.critical( 'Error loading PATHS_FROM_ECLIPSE_TO_PYTHON from environment variable.' ) pydev_log.exception() PATHS_FROM_ECLIPSE_TO_PYTHON = [] else: if not isinstance(PATHS_FROM_ECLIPSE_TO_PYTHON, list): pydev_log.critical( 'Expected PATHS_FROM_ECLIPSE_TO_PYTHON loaded from environment variable to be a list.' ) PATHS_FROM_ECLIPSE_TO_PYTHON = [] else: # Converting json lists to tuple PATHS_FROM_ECLIPSE_TO_PYTHON = [ tuple(x) for x in PATHS_FROM_ECLIPSE_TO_PYTHON ]
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 on_critical(msg): from _pydev_bundle import pydev_log pydev_log.critical(msg)
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 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 cmd_set_break(self, py_db, cmd_id, seq, text): # func name: 'None': match anything. Empty: match global, specified: only method context. # command to add some breakpoint. # text is filename\tline. Add to breakpoints dictionary suspend_policy = "NONE" # Can be 'NONE' or 'ALL' is_logpoint = False hit_condition = None if py_db._set_breakpoints_with_id: try: try: breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split('\t', 9) except ValueError: # not enough values to unpack # No suspend_policy passed (use default). breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split('\t', 8) is_logpoint = is_logpoint == 'True' except ValueError: # not enough values to unpack breakpoint_id, btype, filename, line, func_name, condition, expression = text.split('\t', 6) breakpoint_id = int(breakpoint_id) line = int(line) # We must restore new lines and tabs as done in # AbstractDebugTarget.breakpointAdded condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\ replace("@_@TAB_CHAR@_@", '\t').strip() expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').\ replace("@_@TAB_CHAR@_@", '\t').strip() else: # Note: this else should be removed after PyCharm migrates to setting # breakpoints by id (and ideally also provides func_name). btype, filename, line, func_name, suspend_policy, condition, expression = text.split('\t', 6) # If we don't have an id given for each breakpoint, consider # the id to be the line. breakpoint_id = line = int(line) condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n'). \ replace("@_@TAB_CHAR@_@", '\t').strip() expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n'). \ replace("@_@TAB_CHAR@_@", '\t').strip() if condition is not None and (len(condition) <= 0 or condition == "None"): condition = None if expression is not None and (len(expression) <= 0 or expression == "None"): expression = None if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == "None"): hit_condition = None filename = self.api.filename_to_server(filename) func_name = self.api.to_str(func_name) error_code = self.api.add_breakpoint( py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) if error_code: if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: pydev_log.critical('pydev debugger: warning: Trying to add breakpoint to file that does not exist: %s (will have no effect).' % (filename,)) elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: pydev_log.critical('pydev debugger: warning: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).' % (filename,)) else: # Shouldn't get here. pydev_log.critical('pydev debugger: warning: Breakpoint not validated (reason unknown -- please report as error): %s.' % (filename,))
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
library_dir = os.path.dirname(os.__file__) return library_dir # Note: we can't call sysconfig.get_path from _apply_func_and_normalize_case (it deadlocks on Python 2.7) so, we # need to get the library dir during module loading. _library_dir = _get_library_dir() # defined as a list of tuples where the 1st element of the tuple is the path in the client machine # and the 2nd element is the path in the server machine. # see module docstring for more details. try: PATHS_FROM_ECLIPSE_TO_PYTHON = json.loads(os.environ.get('PATHS_FROM_ECLIPSE_TO_PYTHON', '[]')) except Exception: pydev_log.critical('Error loading PATHS_FROM_ECLIPSE_TO_PYTHON from environment variable.') pydev_log.exception() PATHS_FROM_ECLIPSE_TO_PYTHON = [] else: if not isinstance(PATHS_FROM_ECLIPSE_TO_PYTHON, list): pydev_log.critical('Expected PATHS_FROM_ECLIPSE_TO_PYTHON loaded from environment variable to be a list.') PATHS_FROM_ECLIPSE_TO_PYTHON = [] else: # Converting json lists to tuple PATHS_FROM_ECLIPSE_TO_PYTHON = [tuple(x) for x in PATHS_FROM_ECLIPSE_TO_PYTHON] # example: # PATHS_FROM_ECLIPSE_TO_PYTHON = [ # (r'd:\temp\temp_workspace_2\test_python\src\yyy\yyy', # r'd:\temp\temp_workspace_2\test_python\src\hhh\xxx') # ]
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 add_breakpoint(self, py_db, filename, breakpoint_type, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint): ''' :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: :param int line: :param condition: Either None or the condition to activate the breakpoint. :param str func_name: If "None" (str), may hit in any context. Empty string will hit only top level. Any other value must match the scope of the method to be matched. :param str expression: None or the expression to be evaluated. :param suspend_policy: Either "NONE" (to suspend only the current thread when the breakpoint is hit) or "ALL" (to suspend all threads when a breakpoint is hit). :param str hit_condition: An expression where `@HIT@` will be replaced by the number of hits. i.e.: `@HIT@ == x` or `@HIT@ >= x` :param bool is_logpoint: If True and an expression is passed, pydevd will create an io message command with the result of the evaluation. ''' assert filename.__class__ == str # i.e.: bytes on py2 and str on py3 assert func_name.__class__ == str # i.e.: bytes on py2 and str on py3 if not pydevd_file_utils.exists(filename): pydev_log.critical('pydev debugger: warning: trying to add breakpoint'\ ' to file that does not exist: %s (will have no effect)\n' % (filename,)) return if breakpoint_type == 'python-line': added_breakpoint = LineBreakpoint(line, condition, func_name, expression, suspend_policy, hit_condition=hit_condition, is_logpoint=is_logpoint) breakpoints = py_db.breakpoints file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint supported_type = True else: result = None plugin = py_db.get_plugin_lazy_init() if plugin is not None: result = plugin.add_breakpoint('add_line_breakpoint', py_db, breakpoint_type, filename, line, condition, expression, func_name, hit_condition=hit_condition, is_logpoint=is_logpoint) if result is not None: supported_type = True added_breakpoint, breakpoints = result file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint else: supported_type = False if not supported_type: raise NameError(breakpoint_type) if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', filename, line, func_name) if filename in file_to_id_to_breakpoint: id_to_pybreakpoint = file_to_id_to_breakpoint[filename] else: id_to_pybreakpoint = file_to_id_to_breakpoint[filename] = {} id_to_pybreakpoint[breakpoint_id] = added_breakpoint 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() py_db.on_breakpoints_changed()
def add_breakpoint( self, py_db, filename, breakpoint_type, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint): ''' :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: :param int line: :param condition: Either None or the condition to activate the breakpoint. :param str func_name: If "None" (str), may hit in any context. Empty string will hit only top level. Any other value must match the scope of the method to be matched. :param str expression: None or the expression to be evaluated. :param suspend_policy: Either "NONE" (to suspend only the current thread when the breakpoint is hit) or "ALL" (to suspend all threads when a breakpoint is hit). :param str hit_condition: An expression where `@HIT@` will be replaced by the number of hits. i.e.: `@HIT@ == x` or `@HIT@ >= x` :param bool is_logpoint: If True and an expression is passed, pydevd will create an io message command with the result of the evaluation. ''' assert filename.__class__ == str # i.e.: bytes on py2 and str on py3 assert func_name.__class__ == str # i.e.: bytes on py2 and str on py3 if not pydevd_file_utils.exists(filename): pydev_log.critical('pydev debugger: warning: trying to add breakpoint'\ ' to file that does not exist: %s (will have no effect)\n' % (filename,)) return if breakpoint_type == 'python-line': added_breakpoint = LineBreakpoint(line, condition, func_name, expression, suspend_policy, hit_condition=hit_condition, is_logpoint=is_logpoint) breakpoints = py_db.breakpoints file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint supported_type = True else: result = None plugin = py_db.get_plugin_lazy_init() if plugin is not None: result = plugin.add_breakpoint('add_line_breakpoint', py_db, breakpoint_type, filename, line, condition, expression, func_name, hit_condition=hit_condition, is_logpoint=is_logpoint) if result is not None: supported_type = True added_breakpoint, breakpoints = result file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint else: supported_type = False if not supported_type: raise NameError(breakpoint_type) if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', filename, line, func_name) if filename in file_to_id_to_breakpoint: id_to_pybreakpoint = file_to_id_to_breakpoint[filename] else: id_to_pybreakpoint = file_to_id_to_breakpoint[filename] = {} id_to_pybreakpoint[breakpoint_id] = added_breakpoint 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() py_db.on_breakpoints_changed()