xref: /llvm-project/lldb/examples/python/pytracer.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1import sys
2import inspect
3from collections import OrderedDict
4
5
6class TracebackFancy:
7    def __init__(self, traceback):
8        self.t = traceback
9
10    def getFrame(self):
11        return FrameFancy(self.t.tb_frame)
12
13    def getLineNumber(self):
14        return self.t.tb_lineno if self.t is not None else None
15
16    def getNext(self):
17        return TracebackFancy(self.t.tb_next)
18
19    def __str__(self):
20        if self.t is None:
21            return ""
22        str_self = "%s @ %s" % (self.getFrame().getName(), self.getLineNumber())
23        return str_self + "\n" + self.getNext().__str__()
24
25
26class ExceptionFancy:
27    def __init__(self, frame):
28        self.etraceback = frame.f_exc_traceback
29        self.etype = frame.exc_type
30        self.evalue = frame.f_exc_value
31
32    def __init__(self, tb, ty, va):
33        self.etraceback = tb
34        self.etype = ty
35        self.evalue = va
36
37    def getTraceback(self):
38        return TracebackFancy(self.etraceback)
39
40    def __nonzero__(self):
41        return (
42            self.etraceback is not None
43            or self.etype is not None
44            or self.evalue is not None
45        )
46
47    def getType(self):
48        return str(self.etype)
49
50    def getValue(self):
51        return self.evalue
52
53
54class CodeFancy:
55    def __init__(self, code):
56        self.c = code
57
58    def getArgCount(self):
59        return self.c.co_argcount if self.c is not None else 0
60
61    def getFilename(self):
62        return self.c.co_filename if self.c is not None else ""
63
64    def getVariables(self):
65        return self.c.co_varnames if self.c is not None else []
66
67    def getName(self):
68        return self.c.co_name if self.c is not None else ""
69
70    def getFileName(self):
71        return self.c.co_filename if self.c is not None else ""
72
73
74class ArgsFancy:
75    def __init__(self, frame, arginfo):
76        self.f = frame
77        self.a = arginfo
78
79    def __str__(self):
80        args, varargs, kwargs = self.getArgs(), self.getVarArgs(), self.getKWArgs()
81        ret = ""
82        count = 0
83        size = len(args)
84        for arg in args:
85            ret = ret + ("%s = %s" % (arg, args[arg]))
86            count = count + 1
87            if count < size:
88                ret = ret + ", "
89        if varargs:
90            if size > 0:
91                ret = ret + " "
92            ret = ret + "varargs are " + str(varargs)
93        if kwargs:
94            if size > 0:
95                ret = ret + " "
96            ret = ret + "kwargs are " + str(kwargs)
97        return ret
98
99    def getNumArgs(wantVarargs=False, wantKWArgs=False):
100        args, varargs, keywords, values = self.a
101        size = len(args)
102        if varargs and wantVarargs:
103            size = size + len(self.getVarArgs())
104        if keywords and wantKWArgs:
105            size = size + len(self.getKWArgs())
106        return size
107
108    def getArgs(self):
109        args, _, _, values = self.a
110        argWValues = OrderedDict()
111        for arg in args:
112            argWValues[arg] = values[arg]
113        return argWValues
114
115    def getVarArgs(self):
116        _, vargs, _, _ = self.a
117        if vargs:
118            return self.f.f_locals[vargs]
119        return ()
120
121    def getKWArgs(self):
122        _, _, kwargs, _ = self.a
123        if kwargs:
124            return self.f.f_locals[kwargs]
125        return {}
126
127
128class FrameFancy:
129    def __init__(self, frame):
130        self.f = frame
131
132    def getCaller(self):
133        return FrameFancy(self.f.f_back)
134
135    def getLineNumber(self):
136        return self.f.f_lineno if self.f is not None else 0
137
138    def getCodeInformation(self):
139        return CodeFancy(self.f.f_code) if self.f is not None else None
140
141    def getExceptionInfo(self):
142        return ExceptionFancy(self.f) if self.f is not None else None
143
144    def getName(self):
145        return self.getCodeInformation().getName() if self.f is not None else ""
146
147    def getFileName(self):
148        return self.getCodeInformation().getFileName() if self.f is not None else ""
149
150    def getLocals(self):
151        return self.f.f_locals if self.f is not None else {}
152
153    def getArgumentInfo(self):
154        return (
155            ArgsFancy(self.f, inspect.getargvalues(self.f))
156            if self.f is not None
157            else None
158        )
159
160
161class TracerClass:
162    def callEvent(self, frame):
163        pass
164
165    def lineEvent(self, frame):
166        pass
167
168    def returnEvent(self, frame, retval):
169        pass
170
171    def exceptionEvent(self, frame, exception, value, traceback):
172        pass
173
174    def cCallEvent(self, frame, cfunct):
175        pass
176
177    def cReturnEvent(self, frame, cfunct):
178        pass
179
180    def cExceptionEvent(self, frame, cfunct):
181        pass
182
183
184tracer_impl = TracerClass()
185
186
187def the_tracer_entrypoint(frame, event, args):
188    if tracer_impl is None:
189        return None
190    if event == "call":
191        call_retval = tracer_impl.callEvent(FrameFancy(frame))
192        if not call_retval:
193            return None
194        return the_tracer_entrypoint
195    elif event == "line":
196        line_retval = tracer_impl.lineEvent(FrameFancy(frame))
197        if not line_retval:
198            return None
199        return the_tracer_entrypoint
200    elif event == "return":
201        tracer_impl.returnEvent(FrameFancy(frame), args)
202    elif event == "exception":
203        exty, exva, extb = args
204        exception_retval = tracer_impl.exceptionEvent(
205            FrameFancy(frame), ExceptionFancy(extb, exty, exva)
206        )
207        if not exception_retval:
208            return None
209        return the_tracer_entrypoint
210    elif event == "c_call":
211        tracer_impl.cCallEvent(FrameFancy(frame), args)
212    elif event == "c_return":
213        tracer_impl.cReturnEvent(FrameFancy(frame), args)
214    elif event == "c_exception":
215        tracer_impl.cExceptionEvent(FrameFancy(frame), args)
216    return None
217
218
219def enable(t=None):
220    global tracer_impl
221    if t:
222        tracer_impl = t
223    sys.settrace(the_tracer_entrypoint)
224
225
226def disable():
227    sys.settrace(None)
228
229
230class LoggingTracer:
231    def callEvent(self, frame):
232        print(
233            "call "
234            + frame.getName()
235            + " from "
236            + frame.getCaller().getName()
237            + " @ "
238            + str(frame.getCaller().getLineNumber())
239            + " args are "
240            + str(frame.getArgumentInfo())
241        )
242
243    def lineEvent(self, frame):
244        print(
245            "running "
246            + frame.getName()
247            + " @ "
248            + str(frame.getLineNumber())
249            + " locals are "
250            + str(frame.getLocals())
251            + " in "
252            + frame.getFileName()
253        )
254
255    def returnEvent(self, frame, retval):
256        print(
257            "return from "
258            + frame.getName()
259            + " value is "
260            + str(retval)
261            + " locals are "
262            + str(frame.getLocals())
263        )
264
265    def exceptionEvent(self, frame, exception):
266        print(
267            "exception %s %s raised from %s @ %s"
268            % (
269                exception.getType(),
270                str(exception.getValue()),
271                frame.getName(),
272                frame.getLineNumber(),
273            )
274        )
275        print("tb: " + str(exception.getTraceback()))
276
277
278# the same functionality as LoggingTracer, but with a little more
279# lldb-specific smarts
280
281
282class LLDBAwareTracer:
283    def callEvent(self, frame):
284        if frame.getName() == "<module>":
285            return
286        if frame.getName() == "run_one_line":
287            print(
288                "call run_one_line(%s)"
289                % (frame.getArgumentInfo().getArgs()["input_string"])
290            )
291            return
292        if "Python.framework" in frame.getFileName():
293            print("call into Python at " + frame.getName())
294            return
295        if (
296            frame.getName() == "__init__"
297            and frame.getCaller().getName() == "run_one_line"
298            and frame.getCaller().getLineNumber() == 101
299        ):
300            return False
301        strout = "call " + frame.getName()
302        if frame.getCaller().getFileName() == "":
303            strout += " from LLDB - args are "
304            args = frame.getArgumentInfo().getArgs()
305            for arg in args:
306                if arg == "dict" or arg == "internal_dict":
307                    continue
308                strout = strout + ("%s = %s " % (arg, args[arg]))
309        else:
310            strout += (
311                " from "
312                + frame.getCaller().getName()
313                + " @ "
314                + str(frame.getCaller().getLineNumber())
315                + " args are "
316                + str(frame.getArgumentInfo())
317            )
318        print(strout)
319
320    def lineEvent(self, frame):
321        if frame.getName() == "<module>":
322            return
323        if frame.getName() == "run_one_line":
324            print(
325                "running run_one_line(%s) @ %s"
326                % (
327                    frame.getArgumentInfo().getArgs()["input_string"],
328                    frame.getLineNumber(),
329                )
330            )
331            return
332        if "Python.framework" in frame.getFileName():
333            print(
334                "running into Python at "
335                + frame.getName()
336                + " @ "
337                + str(frame.getLineNumber())
338            )
339            return
340        strout = (
341            "running "
342            + frame.getName()
343            + " @ "
344            + str(frame.getLineNumber())
345            + " locals are "
346        )
347        if frame.getCaller().getFileName() == "":
348            locals = frame.getLocals()
349            for local in locals:
350                if local == "dict" or local == "internal_dict":
351                    continue
352                strout = strout + ("%s = %s " % (local, locals[local]))
353        else:
354            strout = strout + str(frame.getLocals())
355        strout = strout + " in " + frame.getFileName()
356        print(strout)
357
358    def returnEvent(self, frame, retval):
359        if frame.getName() == "<module>":
360            return
361        if frame.getName() == "run_one_line":
362            print(
363                "return from run_one_line(%s) return value is %s"
364                % (frame.getArgumentInfo().getArgs()["input_string"], retval)
365            )
366            return
367        if "Python.framework" in frame.getFileName():
368            print(
369                "return from Python at "
370                + frame.getName()
371                + " return value is "
372                + str(retval)
373            )
374            return
375        strout = (
376            "return from "
377            + frame.getName()
378            + " return value is "
379            + str(retval)
380            + " locals are "
381        )
382        if frame.getCaller().getFileName() == "":
383            locals = frame.getLocals()
384            for local in locals:
385                if local == "dict" or local == "internal_dict":
386                    continue
387                strout = strout + ("%s = %s " % (local, locals[local]))
388        else:
389            strout = strout + str(frame.getLocals())
390        strout = strout + " in " + frame.getFileName()
391        print(strout)
392
393    def exceptionEvent(self, frame, exception):
394        if frame.getName() == "<module>":
395            return
396        print(
397            "exception %s %s raised from %s @ %s"
398            % (
399                exception.getType(),
400                str(exception.getValue()),
401                frame.getName(),
402                frame.getLineNumber(),
403            )
404        )
405        print("tb: " + str(exception.getTraceback()))
406
407
408def f(x, y=None):
409    if x > 0:
410        return 2 + f(x - 2)
411    return 35
412
413
414def g(x):
415    return 1.134 / x
416
417
418def print_keyword_args(**kwargs):
419    # kwargs is a dict of the keyword args passed to the function
420    for key, value in kwargs.items():
421        print("%s = %s" % (key, value))
422
423
424def total(initial=5, *numbers, **keywords):
425    count = initial
426    for number in numbers:
427        count += number
428    for key in keywords:
429        count += keywords[key]
430    return count
431
432
433if __name__ == "__main__":
434    enable(LoggingTracer())
435    f(5)
436    f(5, 1)
437    print_keyword_args(first_name="John", last_name="Doe")
438    total(10, 1, 2, 3, vegetables=50, fruits=100)
439    try:
440        g(0)
441    except:
442        pass
443    disable()
444