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
Example #2
0
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)
Example #3
0
    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
Example #7
0
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'
Example #8
0
    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
Example #10
0
    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)
Example #11
0
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()
Example #13
0
    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)
Example #14
0
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)
Example #15
0
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
Example #16
0
    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)
Example #17
0
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
Example #18
0
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)
Example #19
0
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
Example #20
0
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
Example #22
0
    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)
Example #23
0
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
Example #24
0
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
Example #25
0
 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)
Example #26
0
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
Example #27
0
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
Example #28
0
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
Example #29
0
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
Example #30
0
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
Example #31
0
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__
Example #32
0
    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)
Example #33
0
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