Example #1
0
class Proxy:
    '''
    Contains code to control the target indirectly.
    '''
    def __init__(self, dsn: str):
        self.database = DB(dsn)
        self.session_id = None

    def _run_cmd(self, cmd: str, args: List) -> List:
        args = ','.join([str(arg) for arg in args])
        return self.database.run_sql(f'SELECT * FROM {cmd}({args})',
                                     fetch_result=True)

    def attach(self, port: int) -> int:
        '''
        Attach to an opened debugger port.
        '''
        result = self._run_cmd('pldbg_attach_to_port', [port])
        self.session_id = result[0][0]

    def cont(self):
        '''
        Continue execution until the next breakpoint.
        '''
        result = self._run_cmd('pldbg_continue', [self.session_id])
        logger.debug(f'Continue result: {result}')

    def abort(self):
        '''
        Abort waiting for the target.
        '''
        result = self._run_cmd('pldbg_abort_target', [self.session_id])
        logger.debug(f'Abort result: {result}')

    def get_variables(self) -> List[Variable]:
        '''
        Get variables of the currently active frame in the active session.
        '''
        result = self._run_cmd('pldbg_get_variables', [self.session_id])
        return [Variable(*row) for row in result]

    def step_over(self) -> Breakpoint:
        '''
        Step over a call until next blocking statement.
        '''
        result = self._run_cmd('pldbg_step_over', [self.session_id])
        return Breakpoint(*result[0])

    def step_into(self) -> Breakpoint:
        '''
        Step into a call, stop at next blocking statement.
        '''
        result = self._run_cmd('pldbg_step_into', [self.session_id])
        return Breakpoint(*result[0])

    def get_source(self, oid) -> str:
        '''
        Get source of the provided OID.
        '''
        result = self._run_cmd('pldbg_get_source', [self.session_id, oid])
        return result[0][0]

    def get_stack(self) -> List[Frame]:
        '''
        Get current stack of the active session.
        '''
        result = self._run_cmd('pldbg_get_stack', [self.session_id])
        return [Frame(*row) for row in result]

    def get_breakpoints(self) -> List[Breakpoint]:
        '''
        Get all breakpoints of the current session.
        '''
        result = self._run_cmd('pldbg_get_breakpoints', [self.session_id])
        return [Breakpoint(*row) for row in result]

    def set_breakpoint(self, oid, line_number):
        '''
        Set a breakpoint for the provided OID at given line number.
        '''
        result = self._run_cmd('pldbg_set_breakpoint',
                               [self.session_id, oid, line_number])
        logger.debug(f'Set breakpoint result: {result}')
Example #2
0
class Target:
    '''
    This is the target. It controls/contains the code to be debugged.
    '''
    def __init__(self, dsn: str):
        self.database = DB(dsn, is_async=True)
        self.notice_queue = Queue()
        self.oid = None
        self.executor = None
        self.port = None

    def cleanup(self):
        '''
        Cleanup routine for the target.
        '''
        self.database.cleanup()

    def get_notices(self) -> List[str]:
        '''
        Get all notices the target might have. Reads from an internal queue,
        does not use the DB itself since it is likely blocked.
        '''
        notices = []
        while not self.notice_queue.empty():
            notices.append(self.notice_queue.get())
        logger.debug(f'Target notices: {notices}')
        return notices

    @classmethod
    def _parse_port(cls, port_raw: str) -> int:
        return int(port_raw.split(':')[-1])

    def wait_for_shutdown(self):
        '''
        Wait until the target completed fully.
        '''
        self.executor.join()

    def _run_executor_thread(self, func_call, func_oid):
        self.executor = Thread(target=self._run, args=(func_call, func_oid))
        self.executor.daemon = True
        self.executor.start()

    @classmethod
    def assert_valid_function_call(cls, func_call: str) -> bool:
        return re.match(r'[_a-zA-Z0-9]+\([^\)]*\)(\.[^\)]*\))?',
                        func_call) is not None

    def start(self, func_call: str) -> bool:
        '''
        Start target debugging. Resolve the function to be debugged, find its
        OID and eventually start a thread calling it.
        '''

        print(func_call)
        if not Target.assert_valid_function_call(func_call):
            logger.error(f'Function call seems incomplete: {func_call}')
            return False

        func_name, func_args = Target._parse_func_call(func_call)
        func_oid = get_func_oid_by_name(self.database, func_name)
        if not func_oid:
            logger.error('Function OID not found. Either function is not '
                         'defined or there are multiple with the same name '
                         'which is currently not support')
            return False

        logger.debug(f'Function OID is: {func_oid}')
        self._run_executor_thread(func_call, func_oid)

        # Wait here until the executor started
        logger.debug('Waiting for port')
        self.port = Target._parse_port(self.notice_queue.get())
        logger.debug(f'Port is: {self.port}')

        return True

    def _run(self, func_call: str, func_oid: int):
        '''
        Start a debugging session. Consumes the function call to debug with its
        arguments and returns the session ID once the debugger started.
        '''
        self.oid = func_oid
        self.database.run_sql(f'SELECT * FROM pldbg_oid_debug({func_oid})')

        while True:
            logger.debug('Starting target function')

            try:
                result = self.database.run_sql(f'SELECT * FROM {func_call}',
                                               fetch_result=True,
                                               notice_queue=self.notice_queue)

                # This will now wait here until the function finishes. It will
                # eventually restart immediately. Otherwise, the proxy process
                # hangs until it hits a timeout.

                logger.debug(f'Target result: {result}')

            except QueryCanceled:
                logger.info('Stopped target query')
                break

    @classmethod
    def _parse_func_call(cls, func_call: str) -> Tuple[str, List[str]]:
        '''
        Take a function call and return the function name and its arguments.
        '''
        func_call = func_call.replace(' ', '').replace('(',
                                                       ',').replace(')', '')
        func_call = func_call.split(',')
        return func_call[0], func_call[1:]