-
-
Notifications
You must be signed in to change notification settings - Fork 242
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
415 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_cython.c
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
329 changes: 329 additions & 0 deletions
329
plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,329 @@ | ||
from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER, \ | ||
GlobalDebuggerHolder | ||
import threading | ||
from _pydevd_bundle.pydevd_additional_thread_info import _set_additional_thread_info_lock, PyDBAdditionalThreadInfo | ||
from pydevd_file_utils import NORM_PATHS_AND_BASE_CONTAINER, \ | ||
get_abs_path_real_path_and_base_from_file | ||
import dis | ||
from collections import namedtuple | ||
import sys | ||
from types import CodeType | ||
from typing import Dict, Optional, Set | ||
|
||
DEBUGGER_ID = sys.monitoring.DEBUGGER_ID | ||
monitor = sys.monitoring | ||
|
||
USING_FRAME_MONITORING = False | ||
if IS_PY312_OR_GREATER: | ||
import sys | ||
USING_FRAME_MONITORING = hasattr(sys, 'monitoring') | ||
|
||
_thread_local_info = threading.local() | ||
_get_ident = threading.get_ident | ||
_thread_active = threading._active | ||
|
||
|
||
class ThreadInfo: | ||
|
||
additional_info: PyDBAdditionalThreadInfo | ||
is_pydevd_thread: bool | ||
fully_initialized: bool | ||
can_create_dummy_thread: bool | ||
|
||
def initialize_can_create_dummy_thread(self, code): | ||
self.additional_info = None | ||
self.is_pydevd_thread = False | ||
self.fully_initialized = False | ||
self.thread_trace_func = None | ||
|
||
basename = code.co_filename | ||
i = basename.rfind('/') | ||
j = basename.rfind('\\') | ||
if j > i: | ||
i = j | ||
if i >= 0: | ||
basename = basename[i + 1:] | ||
# remove ext | ||
i = basename.rfind('.') | ||
if i >= 0: | ||
basename = basename[:i] | ||
|
||
co_name = code.co_name | ||
|
||
# In these cases we cannot create a dummy thread (an actual | ||
# thread will be created later or tracing will already be set). | ||
if basename == 'threading' and co_name in ('__bootstrap', '_bootstrap', '__bootstrap_inner', '_bootstrap_inner'): | ||
self.can_create_dummy_thread = False | ||
elif basename == 'pydev_monkey' and co_name == '__call__': | ||
self.can_create_dummy_thread = False | ||
elif basename == 'pydevd' and co_name in ('run', 'main', '_exec'): | ||
self.can_create_dummy_thread = False | ||
elif basename == 'pydevd_tracing': | ||
self.can_create_dummy_thread = False | ||
else: | ||
self.can_create_dummy_thread = True | ||
|
||
def initialize_if_possible(self): | ||
# Don't call threading.currentThread because if we're too early in the process | ||
# we may create a dummy thread. | ||
thread_ident = _get_ident() | ||
t = _thread_active.get(thread_ident) | ||
if t is None: | ||
assert self.can_create_dummy_thread | ||
# Initialize the dummy thread and set the tracing (both are needed to | ||
# actually stop on breakpoints). | ||
t = threading.current_thread() | ||
|
||
if getattr(t, 'is_pydev_daemon_thread', False): | ||
self.is_pydevd_thread = True | ||
self.fully_initialized = True | ||
else: | ||
try: | ||
additional_info = t.additional_info | ||
if additional_info is None: | ||
raise AttributeError() | ||
except: | ||
with _set_additional_thread_info_lock: | ||
# If it's not there, set it within a lock to avoid any racing | ||
# conditions. | ||
additional_info = getattr(t, 'additional_info', None) | ||
if additional_info is None: | ||
additional_info = PyDBAdditionalThreadInfo() | ||
t.additional_info = additional_info | ||
self.additional_info = additional_info | ||
self.fully_initialized = True | ||
|
||
|
||
class FuncCodeInfo: | ||
|
||
co_filename: str | ||
co_name: str | ||
canonical_normalized_filename: str | ||
always_skip_code: bool | ||
breakpoint_found: bool | ||
breakpoints_hit_at_lines: Set[int] | ||
function_breakpoint: bool | ||
|
||
# When breakpoints_mtime != PyDb.mtime the validity of breakpoints have | ||
# to be re-evaluated (if invalid a new FuncCodeInfo must be created and | ||
# tracing can't be disabled for the related frames). | ||
breakpoints_mtime: int | ||
|
||
def __init__(self): | ||
self.co_filename = '' | ||
self.canonical_normalized_filename = '' | ||
self.always_skip_code = False | ||
|
||
self.breakpoint_found = False | ||
self.breakpoints_mtime = -1 | ||
|
||
self.breakpoints_hit_at_lines = set() | ||
self.function_breakpoint = False | ||
|
||
|
||
def get_thread_info(code) -> Optional[ThreadInfo]: | ||
''' | ||
Provides thread-related info. | ||
May return None if the thread is still not active. | ||
''' | ||
try: | ||
# Note: changing to a `dict[thread.ident] = thread_info` had almost no | ||
# effect in the performance. | ||
return _thread_local_info.thread_info | ||
except: | ||
thread_info = ThreadInfo() | ||
thread_info.initialize_can_create_dummy_thread(code) | ||
if not thread_info.can_create_dummy_thread: | ||
return None | ||
thread_info.initialize_if_possible() | ||
|
||
_thread_local_info.thread_info = thread_info | ||
return _thread_local_info.thread_info | ||
|
||
|
||
_CodeLineInfo = namedtuple('_CodeLineInfo', 'line_to_offset, first_line, last_line') | ||
|
||
|
||
# Note: this method has a version in cython too | ||
def _get_code_line_info(code_obj): | ||
line_to_offset = {} | ||
first_line = None | ||
last_line = None | ||
|
||
for offset, line in dis.findlinestarts(code_obj): | ||
line_to_offset[line] = offset | ||
|
||
if line_to_offset: | ||
first_line = min(line_to_offset) | ||
last_line = max(line_to_offset) | ||
return _CodeLineInfo(line_to_offset, first_line, last_line) | ||
|
||
|
||
code_to_func_code_info: Dict[CodeType, 'FuncCodeInfo'] = {} | ||
|
||
|
||
def get_func_code_info(thread_info: ThreadInfo, code_obj, code_to_func_code_info=code_to_func_code_info) -> FuncCodeInfo: | ||
''' | ||
Provides code-object related info. | ||
Note that it contains informations on the breakpoints for a given function. | ||
If breakpoints change a new FuncCodeInfo instance will be created. | ||
''' | ||
main_debugger = GlobalDebuggerHolder.global_dbg | ||
|
||
func_code_info_obj = code_to_func_code_info.get(code_obj) | ||
if func_code_info_obj is not None: | ||
if func_code_info_obj.breakpoints_mtime == main_debugger.mtime: | ||
# if DEBUG: | ||
# print('get_func_code_info: matched mtime', f_code.co_name, f_code.co_filename) | ||
return func_code_info_obj | ||
|
||
co_filename: str = code_obj.co_filename | ||
co_name: str = code_obj.co_name | ||
cache_file_type: dict | ||
cache_file_type_key: tuple | ||
|
||
func_code_info = FuncCodeInfo() | ||
func_code_info.breakpoints_mtime = main_debugger.mtime | ||
|
||
func_code_info.co_filename = co_filename | ||
func_code_info.co_name = co_name | ||
|
||
# Compute whether to always skip this. | ||
try: | ||
abs_path_real_path_and_base = NORM_PATHS_AND_BASE_CONTAINER[co_filename] | ||
except: | ||
abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_file(co_filename) | ||
|
||
func_code_info.canonical_normalized_filename = abs_path_real_path_and_base[1] | ||
|
||
cache_file_type = main_debugger.get_cache_file_type() | ||
# Note: this cache key must be the same from PyDB.get_file_type() -- see it for comments | ||
# on the cache. | ||
cache_file_type_key = (code_obj.co_firstlineno, abs_path_real_path_and_base[0], code_obj) | ||
try: | ||
file_type = cache_file_type[cache_file_type_key] # Make it faster | ||
except: | ||
file_type = main_debugger.get_file_type_from_code(code_obj, abs_path_real_path_and_base) # we don't want to debug anything related to pydevd | ||
|
||
if file_type is not None: | ||
func_code_info.always_skip_code = True | ||
|
||
if not func_code_info.always_skip_code: | ||
if main_debugger is not None: | ||
|
||
breakpoints: dict = main_debugger.breakpoints.get(func_code_info.canonical_normalized_filename) | ||
function_breakpoint: object = main_debugger.function_breakpoint_name_to_breakpoint.get(func_code_info.co_name) | ||
# print('\n---') | ||
# print(main_debugger.breakpoints) | ||
# print(func_code_info.canonical_normalized_filename) | ||
# print(main_debugger.breakpoints.get(func_code_info.canonical_normalized_filename)) | ||
if function_breakpoint: | ||
# Go directly into tracing mode | ||
func_code_info.breakpoint_found = True | ||
func_code_info.function_breakpoint = True | ||
|
||
if breakpoints: | ||
# if DEBUG: | ||
# print('found breakpoints', code_obj_py.co_name, breakpoints) | ||
|
||
breakpoints_hit_at_lines = set() | ||
code_line_info = _get_code_line_info(code_obj) | ||
line_to_offset = code_line_info.line_to_offset | ||
|
||
for breakpoint_line in breakpoints: | ||
if breakpoint_line in line_to_offset: | ||
breakpoints_hit_at_lines.add(breakpoint_line) | ||
|
||
func_code_info.breakpoint_found = bool(breakpoints_hit_at_lines) | ||
func_code_info.breakpoints_hit_at_lines = breakpoints_hit_at_lines | ||
|
||
code_to_func_code_info[code_obj] = func_code_info_obj | ||
|
||
return func_code_info | ||
|
||
|
||
def _start_method(code, instruction_offset): | ||
try: | ||
thread_info = _thread_local_info.thread_info | ||
except: | ||
thread_info = get_thread_info(code) | ||
|
||
additional_info = thread_info.additional_info | ||
if thread_info.is_pydevd_thread: | ||
func_code_info: FuncCodeInfo = get_func_code_info(thread_info, code) | ||
if func_code_info.always_skip_code: | ||
return monitor.DISABLE | ||
return # We can't disable it here as another thread could use it too. | ||
|
||
STATE_SUSPEND: int = 2 | ||
CMD_STEP_INTO: int = 107 | ||
CMD_STEP_OVER: int = 108 | ||
CMD_STEP_OVER_MY_CODE: int = 159 | ||
CMD_STEP_INTO_MY_CODE: int = 144 | ||
CMD_STEP_INTO_COROUTINE: int = 206 | ||
CMD_SMART_STEP_INTO: int = 128 | ||
can_skip: bool = True | ||
|
||
# We know the frame depth. | ||
frame = sys._getframe(1) | ||
|
||
main_debugger: object = GlobalDebuggerHolder.global_dbg | ||
if main_debugger is None: | ||
return monitor.DISABLE | ||
|
||
if additional_info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE, CMD_SMART_STEP_INTO): | ||
# Stepping (must have line tracing enabled). | ||
enable_line_tracing(code) | ||
|
||
if additional_info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and main_debugger.show_return_values and frame.f_back is additional_info.pydev_step_stop: | ||
# Show return values on step over. | ||
enable_return_tracing(code) | ||
|
||
if main_debugger.break_on_caught_exceptions or main_debugger.break_on_user_uncaught_exceptions or main_debugger.has_plugin_exception_breaks: | ||
enable_exception_tracing(code) | ||
|
||
if main_debugger.signature_factory: | ||
pass | ||
|
||
func_code_info: FuncCodeInfo = get_func_code_info(thread_info, frame, code) | ||
# if DEBUG: | ||
# print('get_bytecode_while_frame_eval always skip', func_code_info.always_skip_code) | ||
if not func_code_info.always_skip_code: | ||
if main_debugger.has_plugin_line_breaks or main_debugger.has_plugin_exception_breaks: | ||
can_skip = main_debugger.plugin.can_skip(main_debugger, frame) | ||
|
||
if not can_skip: | ||
if main_debugger.has_plugin_line_breaks: | ||
enable_line_tracing(code) | ||
|
||
if main_debugger.has_plugin_exception_breaks: | ||
enable_exception_tracing(code) | ||
|
||
if func_code_info.breakpoint_found: | ||
enable_line_tracing(code) | ||
|
||
|
||
def start_monitoring(): | ||
DEBUGGER_ID = sys.monitoring.DEBUGGER_ID | ||
if not sys.monitoring.get_tool(DEBUGGER_ID): | ||
sys.monitoring.use_tool_id(DEBUGGER_ID, 'pydevd') | ||
sys.monitoring.set_events(DEBUGGER_ID, sys.monitoring.events.PY_START | sys.monitoring.events.PY_RESUME) | ||
|
||
sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.PY_START, _start_method) | ||
sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.PY_RESUME, _start_method) | ||
|
||
# sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.LINE, self._line_callback) | ||
# | ||
# sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.PY_RETURN, self._return_callback) | ||
# | ||
# sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.RAISE, self._raise_callback) | ||
|
||
# Activate exception raise callback if exception breakpoints are registered. | ||
current_events = sys.monitoring.get_events(DEBUGGER_ID) | ||
sys.monitoring.set_events(DEBUGGER_ID, current_events | sys.monitoring.events.RAISE) | ||
|
||
|
||
def stop_monitoring(): | ||
sys.monitoring.set_events(sys.monitoring.DEBUGGER_ID, 0) |
Oops, something went wrong.