forked from qt-creator/qt-creator
		
	
		
			
				
	
	
		
			400 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
############################################################################
 | 
						|
#
 | 
						|
# Copyright (C) 2021 The Qt Company Ltd.
 | 
						|
# Contact: https://www.qt.io/licensing/
 | 
						|
#
 | 
						|
# This file is part of Qt Creator.
 | 
						|
#
 | 
						|
# Commercial License Usage
 | 
						|
# Licensees holding valid commercial Qt licenses may use this file in
 | 
						|
# accordance with the commercial license agreement provided with the
 | 
						|
# Software or, alternatively, in accordance with the terms contained in
 | 
						|
# a written agreement between you and The Qt Company. For licensing terms
 | 
						|
# and conditions see https://www.qt.io/terms-conditions. For further
 | 
						|
# information use the contact form at https://www.qt.io/contact-us.
 | 
						|
#
 | 
						|
# GNU General Public License Usage
 | 
						|
# Alternatively, this file may be used under the terms of the GNU
 | 
						|
# General Public License version 3 as published by the Free Software
 | 
						|
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
 | 
						|
# included in the packaging of this file. Please review the following
 | 
						|
# information to ensure the GNU General Public License requirements will
 | 
						|
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
 | 
						|
#
 | 
						|
############################################################################
 | 
						|
 | 
						|
import gdb
 | 
						|
import sys
 | 
						|
import time
 | 
						|
 | 
						|
# for ProcessName capture
 | 
						|
try:
 | 
						|
    import psutil
 | 
						|
except:
 | 
						|
    psutil = None
 | 
						|
 | 
						|
# Caps types
 | 
						|
Address, \
 | 
						|
Caller, \
 | 
						|
Callstack, \
 | 
						|
FilePos, \
 | 
						|
Function, \
 | 
						|
Pid, \
 | 
						|
ProcessName, \
 | 
						|
Tick, \
 | 
						|
Tid, \
 | 
						|
ThreadName, \
 | 
						|
Expression, \
 | 
						|
    = range(0, 11)
 | 
						|
 | 
						|
class GDBTracepoint(gdb.Breakpoint):
 | 
						|
    """
 | 
						|
    Python Breakpoint extension for "tracepoints", breakpoints that do not stop the inferior
 | 
						|
    """
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def create(args, onModified, onHit, onExpression):
 | 
						|
        """
 | 
						|
        Static creator function
 | 
						|
        """
 | 
						|
        tp_kwargs = {}
 | 
						|
        if 'temporary' in args.keys():
 | 
						|
            tp_kwargs['temporary'] = args['temporary']
 | 
						|
        spec = args['spec']
 | 
						|
        tp = GDBTracepoint(spec, **tp_kwargs)
 | 
						|
        tp.onModified = onModified
 | 
						|
        tp.onHit = onHit
 | 
						|
        tp.onExpression = onExpression
 | 
						|
        if 'ignore_count' in args.keys():
 | 
						|
            tp.ignore_count = args['ignore_count']
 | 
						|
        if 'enabled' in args.keys():
 | 
						|
            tp.enabled = args['enabled']
 | 
						|
        if 'thread' in args.keys():
 | 
						|
            tp.thread = args['thread']
 | 
						|
        if 'condition' in args.keys():
 | 
						|
            tp.condition = args['condition']
 | 
						|
        if 'caps' in args.keys():
 | 
						|
            for ce in args['caps']:
 | 
						|
                tp.addCaps(ce[0], ce[1])
 | 
						|
        return tp
 | 
						|
 | 
						|
    def __init__(self, spec, **kwargs):
 | 
						|
        """
 | 
						|
        Constructor
 | 
						|
        """
 | 
						|
        kwargs['internal'] = True
 | 
						|
        super(GDBTracepoint, self).__init__(spec, **kwargs)
 | 
						|
        self.caps = []
 | 
						|
 | 
						|
    _hexSize = 8 if sys.maxsize > 2**32 else 4
 | 
						|
    _hasMonotonicTime = False if sys.version_info[0] <= 2 or (sys.version_info[0] == 3 and sys.version_info[1] < 3) else True
 | 
						|
 | 
						|
    def dicts(self):
 | 
						|
        """
 | 
						|
        Returns dictionareis for mi representation
 | 
						|
        """
 | 
						|
        results = []
 | 
						|
        result = {}
 | 
						|
        result['number'] = str(self.number)
 | 
						|
        result['enabled'] = 'y' if self.enabled else 'n'
 | 
						|
        result['type'] = 'pseudo_tracepoint'
 | 
						|
        result['disp'] = 'del' if self.temporary else 'keep'
 | 
						|
        result['times'] = str(self.hit_count)
 | 
						|
        result['original-location'] = self.location
 | 
						|
        try:
 | 
						|
            d = gdb.decode_line(self.location)
 | 
						|
            if d[1] is None:
 | 
						|
                result['addr'] = '<PENDING>'
 | 
						|
                result['pending'] = self.location
 | 
						|
                results.append(result)
 | 
						|
            else:
 | 
						|
                if len(d[1]) > 1:
 | 
						|
                    result['addr'] = '<MULTIPLE>'
 | 
						|
                    results.append(result)
 | 
						|
                    for i, sl in enumerate(d[1]):
 | 
						|
                        result_ = {}
 | 
						|
                        result_['number'] = result['number'] + "." + str(i + 1)
 | 
						|
                        result_['enabled'] = 'y' if self.enabled else 'n'
 | 
						|
                        if sl.pc is None:
 | 
						|
                            result_['addr'] = '<no address>'
 | 
						|
                        else:
 | 
						|
                            result_['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2)
 | 
						|
                        if sl.symtab and sl.symtab.is_valid():
 | 
						|
                            func = self._getFunctionFromAddr(sl.pc)
 | 
						|
                            if func:
 | 
						|
                                result_['func'] = func.print_name
 | 
						|
                            result_['file'] = sl.symtab.filename
 | 
						|
                            result_['fullname'] = sl.symtab.fullname()
 | 
						|
                            result_['line'] = sl.line
 | 
						|
                        results.append(result_)
 | 
						|
                else:
 | 
						|
                    sl = d[1][0]
 | 
						|
                    if sl.pc is None:
 | 
						|
                        result['addr'] = '<no address>'
 | 
						|
                    else:
 | 
						|
                        result['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2)
 | 
						|
                    if sl.symtab and sl.symtab.is_valid():
 | 
						|
                        func = self._getFunctionFromAddr(sl.pc)
 | 
						|
                        if func:
 | 
						|
                            result['func'] = func.print_name
 | 
						|
                        result['file'] = sl.symtab.filename
 | 
						|
                        result['fullname'] = sl.symtab.fullname()
 | 
						|
                        result['line'] = sl.line
 | 
						|
                    results.append(result)
 | 
						|
        except Exception as e:
 | 
						|
            import traceback
 | 
						|
            traceback.print_exc()
 | 
						|
            result['addr'] = '<PENDING>'
 | 
						|
            result['pending'] = self.location
 | 
						|
            results.append(result)
 | 
						|
        return results
 | 
						|
 | 
						|
    def addCaps(self, capsType, expression=None):
 | 
						|
        """
 | 
						|
        Adds capture expressions for a tracepoint
 | 
						|
 | 
						|
        :param caps_type:   Type of capture
 | 
						|
        :param expression:  Expression for Expression caps type
 | 
						|
        """
 | 
						|
        if capsType != Expression:
 | 
						|
            expression = None
 | 
						|
        else:
 | 
						|
            if expression is None:
 | 
						|
                expression = ''
 | 
						|
        self.caps.append((self.capsMap[capsType], expression))
 | 
						|
 | 
						|
    def stop(self):
 | 
						|
        """
 | 
						|
        Overridden stop function, this evaluates conditions and captures data from the inferior
 | 
						|
 | 
						|
        :return: Always False
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            self.onModified(self)
 | 
						|
            result = {}
 | 
						|
            result['number'] = self.number
 | 
						|
            try:
 | 
						|
                if self.condition:
 | 
						|
                    try:
 | 
						|
                        result = gdb.parse_and_eval(self.condition)
 | 
						|
                        if result.type.code == gdb.TYPE_CODE_BOOL and str(result) == 'false':
 | 
						|
                            return False
 | 
						|
                    except:
 | 
						|
                        pass
 | 
						|
                if self.ignore_count > 0:
 | 
						|
                    return False
 | 
						|
                if self.thread and gdb.selected_thread().global_num != self.thread:
 | 
						|
                    return False
 | 
						|
            except Exception as e:
 | 
						|
                result['warning'] = str(e)
 | 
						|
                self.onHit(self, result)
 | 
						|
                return False
 | 
						|
            if len(self.caps) > 0:
 | 
						|
                caps = []
 | 
						|
                try:
 | 
						|
                    for func, expr in self.caps:
 | 
						|
                        if expr is None:
 | 
						|
                            caps.append(func(self))
 | 
						|
                        else:
 | 
						|
                            caps.append(func(self, expr))
 | 
						|
                except Exception as e:
 | 
						|
                    result['warning'] = str(e)
 | 
						|
                    self.onHit(self, result)
 | 
						|
                    return False
 | 
						|
                result['caps'] = caps
 | 
						|
            self.onHit(self, result)
 | 
						|
            return False
 | 
						|
        except:
 | 
						|
            # Always return false, regardless...
 | 
						|
            return False
 | 
						|
 | 
						|
    def _getFunctionFromAddr(self, addr):
 | 
						|
        try:
 | 
						|
            block = gdb.block_for_pc(addr)
 | 
						|
            while block and not block.function:
 | 
						|
                block = block.superblock
 | 
						|
            if block is None:
 | 
						|
                return None
 | 
						|
            return block.function
 | 
						|
        except:
 | 
						|
            return None
 | 
						|
 | 
						|
    def _getAddress(self):
 | 
						|
        """
 | 
						|
        Capture function for Address
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            frame = gdb.selected_frame()
 | 
						|
            if not (frame is None) and (frame.is_valid()):
 | 
						|
                return '{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2)
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
        return '<null address>'
 | 
						|
 | 
						|
    def _getCaller(self):
 | 
						|
        """
 | 
						|
        Capture function for Caller
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            frame = gdb.selected_frame()
 | 
						|
            if not (frame is None) and (frame.is_valid()):
 | 
						|
                frame = frame.older()
 | 
						|
                if not (frame is None) and (frame.is_valid()):
 | 
						|
                    name = frame.name()
 | 
						|
                    if name is None:
 | 
						|
                        return '<unknown caller>'
 | 
						|
                    return name
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
        return '<unknown caller>'
 | 
						|
 | 
						|
    def _getCallstack(self):
 | 
						|
        """
 | 
						|
        Capture function for Callstack
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            frames = []
 | 
						|
            frame = gdb.selected_frame()
 | 
						|
            if (frame is None) or (not frame.is_valid()):
 | 
						|
                frames.append('<unknown frame>')
 | 
						|
                return str(frames)
 | 
						|
            while not (frame is None):
 | 
						|
                func = frame.function()
 | 
						|
                if func is None:
 | 
						|
                    frames.append('{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2))
 | 
						|
                else:
 | 
						|
                    sl = frame.find_sal()
 | 
						|
                    if sl is None:
 | 
						|
                        frames.append(func.symtab.filename)
 | 
						|
                    else:
 | 
						|
                        frames.append(func.symtab.filename + ':' + str(sl.line))
 | 
						|
                frame = frame.older()
 | 
						|
            return frames
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
 | 
						|
    def _getFilePos(self):
 | 
						|
        """
 | 
						|
        Capture function for FilePos
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            frame = gdb.selected_frame()
 | 
						|
            if (frame is None) or (not frame.is_valid()):
 | 
						|
                return '<unknown file pos>'
 | 
						|
            sl = frame.find_sal()
 | 
						|
            if sl is None:
 | 
						|
                return '<unknown file pos>'
 | 
						|
            return sl.symtab.filename + ':' + str(sl.line)
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
 | 
						|
    def _getFunction(self):
 | 
						|
        """
 | 
						|
        Capture function for Function
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            frame = gdb.selected_frame()
 | 
						|
            if not (frame is None):
 | 
						|
                return str(frame.name())
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
        return '<unknown function>'
 | 
						|
 | 
						|
    def _getPid(self):
 | 
						|
        """
 | 
						|
        Capture function for Pid
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            thread = gdb.selected_thread()
 | 
						|
            if not (thread is None):
 | 
						|
                (pid, lwpid, tid) = thread.ptid
 | 
						|
                return str(pid)
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
        return '<unknown pid>'
 | 
						|
 | 
						|
    def _getProcessName(slef):
 | 
						|
        """
 | 
						|
        Capture for ProcessName
 | 
						|
        """
 | 
						|
        # gdb does not expose process name, neither does (standard) python
 | 
						|
        # You can use for example psutil, but it might not be present.
 | 
						|
        # Default to name of thread with ID 1
 | 
						|
        inf = gdb.selected_inferior()
 | 
						|
        if psutil is None:
 | 
						|
            try:
 | 
						|
                if inf is None:
 | 
						|
                    return '<unknown process name>'
 | 
						|
                threads = filter(lambda t: t.num == 1, list(inf.threads()))
 | 
						|
                if len(threads) < 1:
 | 
						|
                    return '<unknown process name>'
 | 
						|
                thread = threads[0]
 | 
						|
                # use thread name
 | 
						|
                return thread.name
 | 
						|
            except Exception as e:
 | 
						|
                return str(e)
 | 
						|
        else:
 | 
						|
            return psutil.Process(inf.pid).name()
 | 
						|
 | 
						|
    def _getTick(self):
 | 
						|
        """
 | 
						|
        Capture function for Tick
 | 
						|
        """
 | 
						|
        if self._hasMonotonicTime:
 | 
						|
            return str(int(time.monotonic() * 1000))
 | 
						|
        else:
 | 
						|
            return '<monotonic time not available>'
 | 
						|
 | 
						|
    def _getTid(self):
 | 
						|
        """
 | 
						|
        Capture function for Tid
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            thread = gdb.selected_thread()
 | 
						|
            if not (thread is None):
 | 
						|
                (pid, lwpid, tid) = thread.ptid
 | 
						|
                if tid == 0:
 | 
						|
                    return str(lwpid)
 | 
						|
                else:
 | 
						|
                    return str(tid)
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
        return '<unknown tid>'
 | 
						|
 | 
						|
    def _getThreadName(self):
 | 
						|
        """
 | 
						|
        Capture function for ThreadName
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            thread = gdb.selected_thread()
 | 
						|
            if not (thread is None):
 | 
						|
                return str(thread.name)
 | 
						|
        except Exception as e:
 | 
						|
            return str(e)
 | 
						|
        return '<unknown thread name>'
 | 
						|
 | 
						|
    def _getExpression(self, expression):
 | 
						|
        """
 | 
						|
        Capture function for Expression
 | 
						|
 | 
						|
        :param expr: The expression to evaluate
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            value = gdb.parse_and_eval(expression)
 | 
						|
            if value:
 | 
						|
                return self.onExpression(self, expression, value)
 | 
						|
        except Exception as e:
 | 
						|
            return self.onExpression(self, expression, e)
 | 
						|
 | 
						|
    capsMap = {Address: _getAddress,
 | 
						|
               Caller: _getCaller,
 | 
						|
               Callstack: _getCallstack,
 | 
						|
               FilePos: _getFilePos,
 | 
						|
               Function: _getFunction,
 | 
						|
               Pid: _getPid,
 | 
						|
               ProcessName: _getProcessName,
 | 
						|
               Tid: _getTid,
 | 
						|
               Tick: _getTick,
 | 
						|
               ThreadName: _getThreadName,
 | 
						|
               Expression: _getExpression}
 |