1############################################################################
2#
3# Copyright (C) 2021 The Qt Company Ltd.
4# Contact: https://www.qt.io/licensing/
5#
6# This file is part of Qt Creator.
7#
8# Commercial License Usage
9# Licensees holding valid commercial Qt licenses may use this file in
10# accordance with the commercial license agreement provided with the
11# Software or, alternatively, in accordance with the terms contained in
12# a written agreement between you and The Qt Company. For licensing terms
13# and conditions see https://www.qt.io/terms-conditions. For further
14# information use the contact form at https://www.qt.io/contact-us.
15#
16# GNU General Public License Usage
17# Alternatively, this file may be used under the terms of the GNU
18# General Public License version 3 as published by the Free Software
19# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20# included in the packaging of this file. Please review the following
21# information to ensure the GNU General Public License requirements will
22# be met: https://www.gnu.org/licenses/gpl-3.0.html.
23#
24############################################################################
25
26import gdb
27import sys
28import time
29
30# for ProcessName capture
31try:
32    import psutil
33except:
34    psutil = None
35
36# Caps types
37Address, \
38Caller, \
39Callstack, \
40FilePos, \
41Function, \
42Pid, \
43ProcessName, \
44Tick, \
45Tid, \
46ThreadName, \
47Expression, \
48    = range(0, 11)
49
50class GDBTracepoint(gdb.Breakpoint):
51    """
52    Python Breakpoint extension for "tracepoints", breakpoints that do not stop the inferior
53    """
54
55    @staticmethod
56    def create(args, onModified, onHit, onExpression):
57        """
58        Static creator function
59        """
60        tp_kwargs = {}
61        if 'temporary' in args.keys():
62            tp_kwargs['temporary'] = args['temporary']
63        spec = args['spec']
64        tp = GDBTracepoint(spec, **tp_kwargs)
65        tp.onModified = onModified
66        tp.onHit = onHit
67        tp.onExpression = onExpression
68        if 'ignore_count' in args.keys():
69            tp.ignore_count = args['ignore_count']
70        if 'enabled' in args.keys():
71            tp.enabled = args['enabled']
72        if 'thread' in args.keys():
73            tp.thread = args['thread']
74        if 'condition' in args.keys():
75            tp.condition = args['condition']
76        if 'caps' in args.keys():
77            for ce in args['caps']:
78                tp.addCaps(ce[0], ce[1])
79        return tp
80
81    def __init__(self, spec, **kwargs):
82        """
83        Constructor
84        """
85        kwargs['internal'] = True
86        super(GDBTracepoint, self).__init__(spec, **kwargs)
87        self.caps = []
88
89    _hexSize = 8 if sys.maxsize > 2**32 else 4
90    _hasMonotonicTime = False if sys.version_info[0] <= 2 or (sys.version_info[0] == 3 and sys.version_info[1] < 3) else True
91
92    def dicts(self):
93        """
94        Returns dictionareis for mi representation
95        """
96        results = []
97        result = {}
98        result['number'] = str(self.number)
99        result['enabled'] = 'y' if self.enabled else 'n'
100        result['type'] = 'pseudo_tracepoint'
101        result['disp'] = 'del' if self.temporary else 'keep'
102        result['times'] = str(self.hit_count)
103        result['original-location'] = self.location
104        try:
105            d = gdb.decode_line(self.location)
106            if d[1] is None:
107                result['addr'] = '<PENDING>'
108                result['pending'] = self.location
109                results.append(result)
110            else:
111                if len(d[1]) > 1:
112                    result['addr'] = '<MULTIPLE>'
113                    results.append(result)
114                    for i, sl in enumerate(d[1]):
115                        result_ = {}
116                        result_['number'] = result['number'] + "." + str(i + 1)
117                        result_['enabled'] = 'y' if self.enabled else 'n'
118                        if sl.pc is None:
119                            result_['addr'] = '<no address>'
120                        else:
121                            result_['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2)
122                        if sl.symtab and sl.symtab.is_valid():
123                            func = self._getFunctionFromAddr(sl.pc)
124                            if func:
125                                result_['func'] = func.print_name
126                            result_['file'] = sl.symtab.filename
127                            result_['fullname'] = sl.symtab.fullname()
128                            result_['line'] = sl.line
129                        results.append(result_)
130                else:
131                    sl = d[1][0]
132                    if sl.pc is None:
133                        result['addr'] = '<no address>'
134                    else:
135                        result['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2)
136                    if sl.symtab and sl.symtab.is_valid():
137                        func = self._getFunctionFromAddr(sl.pc)
138                        if func:
139                            result['func'] = func.print_name
140                        result['file'] = sl.symtab.filename
141                        result['fullname'] = sl.symtab.fullname()
142                        result['line'] = sl.line
143                    results.append(result)
144        except Exception as e:
145            import traceback
146            traceback.print_exc()
147            result['addr'] = '<PENDING>'
148            result['pending'] = self.location
149            results.append(result)
150        return results
151
152    def addCaps(self, capsType, expression=None):
153        """
154        Adds capture expressions for a tracepoint
155
156        :param caps_type:   Type of capture
157        :param expression:  Expression for Expression caps type
158        """
159        if capsType != Expression:
160            expression = None
161        else:
162            if expression is None:
163                expression = ''
164        self.caps.append((self.capsMap[capsType], expression))
165
166    def stop(self):
167        """
168        Overridden stop function, this evaluates conditions and captures data from the inferior
169
170        :return: Always False
171        """
172        try:
173            self.onModified(self)
174            result = {}
175            result['number'] = self.number
176            try:
177                if self.condition:
178                    try:
179                        result = gdb.parse_and_eval(self.condition)
180                        if result.type.code == gdb.TYPE_CODE_BOOL and str(result) == 'false':
181                            return False
182                    except:
183                        pass
184                if self.ignore_count > 0:
185                    return False
186                if self.thread and gdb.selected_thread().global_num != self.thread:
187                    return False
188            except Exception as e:
189                result['warning'] = str(e)
190                self.onHit(self, result)
191                return False
192            if len(self.caps) > 0:
193                caps = []
194                try:
195                    for func, expr in self.caps:
196                        if expr is None:
197                            caps.append(func(self))
198                        else:
199                            caps.append(func(self, expr))
200                except Exception as e:
201                    result['warning'] = str(e)
202                    self.onHit(self, result)
203                    return False
204                result['caps'] = caps
205            self.onHit(self, result)
206            return False
207        except:
208            # Always return false, regardless...
209            return False
210
211    def _getFunctionFromAddr(self, addr):
212        try:
213            block = gdb.block_for_pc(addr)
214            while block and not block.function:
215                block = block.superblock
216            if block is None:
217                return None
218            return block.function
219        except:
220            return None
221
222    def _getAddress(self):
223        """
224        Capture function for Address
225        """
226        try:
227            frame = gdb.selected_frame()
228            if not (frame is None) and (frame.is_valid()):
229                return '{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2)
230        except Exception as e:
231            return str(e)
232        return '<null address>'
233
234    def _getCaller(self):
235        """
236        Capture function for Caller
237        """
238        try:
239            frame = gdb.selected_frame()
240            if not (frame is None) and (frame.is_valid()):
241                frame = frame.older()
242                if not (frame is None) and (frame.is_valid()):
243                    name = frame.name()
244                    if name is None:
245                        return '<unknown caller>'
246                    return name
247        except Exception as e:
248            return str(e)
249        return '<unknown caller>'
250
251    def _getCallstack(self):
252        """
253        Capture function for Callstack
254        """
255        try:
256            frames = []
257            frame = gdb.selected_frame()
258            if (frame is None) or (not frame.is_valid()):
259                frames.append('<unknown frame>')
260                return str(frames)
261            while not (frame is None):
262                func = frame.function()
263                if func is None:
264                    frames.append('{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2))
265                else:
266                    sl = frame.find_sal()
267                    if sl is None:
268                        frames.append(func.symtab.filename)
269                    else:
270                        frames.append(func.symtab.filename + ':' + str(sl.line))
271                frame = frame.older()
272            return frames
273        except Exception as e:
274            return str(e)
275
276    def _getFilePos(self):
277        """
278        Capture function for FilePos
279        """
280        try:
281            frame = gdb.selected_frame()
282            if (frame is None) or (not frame.is_valid()):
283                return '<unknown file pos>'
284            sl = frame.find_sal()
285            if sl is None:
286                return '<unknown file pos>'
287            return sl.symtab.filename + ':' + str(sl.line)
288        except Exception as e:
289            return str(e)
290
291    def _getFunction(self):
292        """
293        Capture function for Function
294        """
295        try:
296            frame = gdb.selected_frame()
297            if not (frame is None):
298                return str(frame.name())
299        except Exception as e:
300            return str(e)
301        return '<unknown function>'
302
303    def _getPid(self):
304        """
305        Capture function for Pid
306        """
307        try:
308            thread = gdb.selected_thread()
309            if not (thread is None):
310                (pid, lwpid, tid) = thread.ptid
311                return str(pid)
312        except Exception as e:
313            return str(e)
314        return '<unknown pid>'
315
316    def _getProcessName(slef):
317        """
318        Capture for ProcessName
319        """
320        # gdb does not expose process name, neither does (standard) python
321        # You can use for example psutil, but it might not be present.
322        # Default to name of thread with ID 1
323        inf = gdb.selected_inferior()
324        if psutil is None:
325            try:
326                if inf is None:
327                    return '<unknown process name>'
328                threads = filter(lambda t: t.num == 1, list(inf.threads()))
329                if len(threads) < 1:
330                    return '<unknown process name>'
331                thread = threads[0]
332                # use thread name
333                return thread.name
334            except Exception as e:
335                return str(e)
336        else:
337            return psutil.Process(inf.pid).name()
338
339    def _getTick(self):
340        """
341        Capture function for Tick
342        """
343        if self._hasMonotonicTime:
344            return str(int(time.monotonic() * 1000))
345        else:
346            return '<monotonic time not available>'
347
348    def _getTid(self):
349        """
350        Capture function for Tid
351        """
352        try:
353            thread = gdb.selected_thread()
354            if not (thread is None):
355                (pid, lwpid, tid) = thread.ptid
356                if tid == 0:
357                    return str(lwpid)
358                else:
359                    return str(tid)
360        except Exception as e:
361            return str(e)
362        return '<unknown tid>'
363
364    def _getThreadName(self):
365        """
366        Capture function for ThreadName
367        """
368        try:
369            thread = gdb.selected_thread()
370            if not (thread is None):
371                return str(thread.name)
372        except Exception as e:
373            return str(e)
374        return '<unknown thread name>'
375
376    def _getExpression(self, expression):
377        """
378        Capture function for Expression
379
380        :param expr: The expression to evaluate
381        """
382        try:
383            value = gdb.parse_and_eval(expression)
384            if value:
385                return self.onExpression(self, expression, value)
386        except Exception as e:
387            return self.onExpression(self, expression, e)
388
389    capsMap = {Address: _getAddress,
390               Caller: _getCaller,
391               Callstack: _getCallstack,
392               FilePos: _getFilePos,
393               Function: _getFunction,
394               Pid: _getPid,
395               ProcessName: _getProcessName,
396               Tid: _getTid,
397               Tick: _getTick,
398               ThreadName: _getThreadName,
399               Expression: _getExpression}
400