diff options
Diffstat (limited to 'python/helpers/pydev/pydev_monkey.py')
-rw-r--r-- | python/helpers/pydev/pydev_monkey.py | 220 |
1 files changed, 203 insertions, 17 deletions
diff --git a/python/helpers/pydev/pydev_monkey.py b/python/helpers/pydev/pydev_monkey.py index ed6fea53ff58..2b12ed27522c 100644 --- a/python/helpers/pydev/pydev_monkey.py +++ b/python/helpers/pydev/pydev_monkey.py @@ -1,10 +1,11 @@ import os -import shlex import sys import pydev_log import traceback -helpers = os.path.dirname(__file__).replace('\\', '/') +pydev_src_dir = os.path.dirname(__file__) + +from pydevd_constants import xrange def is_python(path): if path.endswith("'") or path.endswith('"'): @@ -38,7 +39,9 @@ def patch_args(args): if port is not None: new_args.extend(args) - new_args[indC + 1] = "import sys; sys.path.append('%s'); import pydevd; pydevd.settrace(host='%s', port=%s, suspend=False); %s"%(helpers, host, port, args[indC + 1]) + new_args[indC + 1] = ("import sys; sys.path.append(r'%s'); import pydevd; " + "pydevd.settrace(host='%s', port=%s, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True); %s") % ( + pydev_src_dir, host, port, args[indC + 1]) return new_args else: new_args.append(args[0]) @@ -52,14 +55,14 @@ def patch_args(args): new_args.append(args[i]) else: break - i+=1 + i += 1 if args[i].endswith('pydevd.py'): #no need to add pydevd twice return args for x in sys.original_argv: if sys.platform == "win32" and not x.endswith('"'): - arg = '"%s"'%x + arg = '"%s"' % x else: arg = x new_args.append(arg) @@ -68,7 +71,7 @@ def patch_args(args): while i < len(args): new_args.append(args[i]) - i+=1 + i += 1 return new_args except: @@ -82,26 +85,101 @@ def args_to_str(args): if x.startswith('"') and x.endswith('"'): quoted_args.append(x) else: + x = x.replace('"', '\\"') quoted_args.append('"%s"' % x) return ' '.join(quoted_args) -def remove_quotes(str): - if str.startswith('"') and str.endswith('"'): - return str[1:-1] - else: - return str -def str_to_args(str): - return [remove_quotes(x) for x in shlex.split(str)] +def str_to_args_windows(args): + # see http:#msdn.microsoft.com/en-us/library/a1y7w461.aspx + result = [] + + DEFAULT = 0 + ARG = 1 + IN_DOUBLE_QUOTE = 2 + + state = DEFAULT + backslashes = 0 + buf = '' + + args_len = len(args) + for i in xrange(args_len): + ch = args[i] + if (ch == '\\'): + backslashes+=1 + continue + elif (backslashes != 0): + if ch == '"': + while backslashes >= 2: + backslashes -= 2 + buf += '\\' + if (backslashes == 1): + if (state == DEFAULT): + state = ARG + + buf += '"' + backslashes = 0 + continue + # else fall through to switch + else: + # false alarm, treat passed backslashes literally... + if (state == DEFAULT): + state = ARG + + while backslashes > 0: + backslashes-=1 + buf += '\\' + # fall through to switch + if ch in (' ', '\t'): + if (state == DEFAULT): + # skip + continue + elif (state == ARG): + state = DEFAULT + result.append(buf) + buf = '' + continue + + if state in (DEFAULT, ARG): + if ch == '"': + state = IN_DOUBLE_QUOTE + else: + state = ARG + buf += ch + + elif state == IN_DOUBLE_QUOTE: + if ch == '"': + if (i + 1 < args_len and args[i + 1] == '"'): + # Undocumented feature in Windows: + # Two consecutive double quotes inside a double-quoted argument are interpreted as + # a single double quote. + buf += '"' + i+=1 + elif len(buf) == 0: + # empty string on Windows platform. Account for bug in constructor of JDK's java.lang.ProcessImpl. + result.append("\"\"") + state = DEFAULT + else: + state = ARG + else: + buf += ch + + else: + raise RuntimeError('Illegal condition') + + if len(buf) > 0 or state != DEFAULT: + result.append(buf) + + return result + def patch_arg_str_win(arg_str): - new_arg_str = arg_str.replace('\\', '/') - args = str_to_args(new_arg_str) + args = str_to_args_windows(arg_str) if not is_python(args[0]): return arg_str arg_str = args_to_str(patch_args(args)) - pydev_log.debug("New args: %s"% arg_str) + pydev_log.debug("New args: %s" % arg_str) return arg_str def monkey_patch_module(module, funcname, create_func): @@ -120,7 +198,8 @@ def warn_multiproc(): import pydev_log pydev_log.error_once( - "New process is launching. Breakpoints won't work.\n To debug that process please enable 'Attach to subprocess automatically while debugging' option in the debugger settings.\n") + "pydev debugger: New process is launching (breakpoints won't work in the new process).\n" + "pydev debugger: To debug that process please enable 'Attach to subprocess automatically while debugging?' option in the debugger settings.\n") def create_warn_multiproc(original_name): @@ -308,3 +387,110 @@ def patch_new_process_functions_with_warning(): except ImportError: import _winapi as _subprocess monkey_patch_module(_subprocess, 'CreateProcess', create_CreateProcessWarnMultiproc) + + + +class _NewThreadStartupWithTrace: + + def __init__(self, original_func): + self.original_func = original_func + + def __call__(self, *args, **kwargs): + from pydevd_comm import GetGlobalDebugger + global_debugger = GetGlobalDebugger() + if global_debugger is not None: + global_debugger.SetTrace(global_debugger.trace_dispatch) + + return self.original_func(*args, **kwargs) + +class _NewThreadStartupWithoutTrace: + + def __init__(self, original_func): + self.original_func = original_func + + def __call__(self, *args, **kwargs): + return self.original_func(*args, **kwargs) + +_UseNewThreadStartup = _NewThreadStartupWithTrace + +def _get_threading_modules(): + threading_modules = [] + from _pydev_imps import _pydev_thread + threading_modules.append(_pydev_thread) + try: + import thread as _thread + threading_modules.append(_thread) + except: + import _thread + threading_modules.append(_thread) + return threading_modules + +threading_modules = _get_threading_modules() + + + +def patch_thread_module(thread): + + if getattr(thread, '_original_start_new_thread', None) is None: + _original_start_new_thread = thread._original_start_new_thread = thread.start_new_thread + else: + _original_start_new_thread = thread._original_start_new_thread + + + class ClassWithPydevStartNewThread: + + def pydev_start_new_thread(self, function, args, kwargs={}): + ''' + We need to replace the original thread.start_new_thread with this function so that threads started + through it and not through the threading module are properly traced. + ''' + return _original_start_new_thread(_UseNewThreadStartup(function), args, kwargs) + + # This is a hack for the situation where the thread.start_new_thread is declared inside a class, such as the one below + # class F(object): + # start_new_thread = thread.start_new_thread + # + # def start_it(self): + # self.start_new_thread(self.function, args, kwargs) + # So, if it's an already bound method, calling self.start_new_thread won't really receive a different 'self' -- it + # does work in the default case because in builtins self isn't passed either. + pydev_start_new_thread = ClassWithPydevStartNewThread().pydev_start_new_thread + + try: + # We need to replace the original thread.start_new_thread with this function so that threads started through + # it and not through the threading module are properly traced. + thread.start_new_thread = pydev_start_new_thread + thread.start_new = pydev_start_new_thread + except: + pass + +def patch_thread_modules(): + for t in threading_modules: + patch_thread_module(t) + +def undo_patch_thread_modules(): + for t in threading_modules: + try: + t.start_new_thread = t._original_start_new_thread + except: + pass + + try: + t.start_new = t._original_start_new_thread + except: + pass + +def disable_trace_thread_modules(): + ''' + Can be used to temporarily stop tracing threads created with thread.start_new_thread. + ''' + global _UseNewThreadStartup + _UseNewThreadStartup = _NewThreadStartupWithoutTrace + + +def enable_trace_thread_modules(): + ''' + Can be used to start tracing threads created with thread.start_new_thread again. + ''' + global _UseNewThreadStartup + _UseNewThreadStartup = _NewThreadStartupWithTrace |