DTrace support: function calls, GC activity, line execution

Tested on macOS 10.11 dtrace, Ubuntu 16.04 SystemTap, and libbcc.

Largely based by an initial patch by Jesús Cea Avión, with some
influence from Dave Malcolm's SystemTap patch and Nikhil Benesch's
unification patch.

Things deliberately left out for simplicity:
- ustack helpers, I have no way of testing them at this point since
they are Solaris-specific
- PyFrameObject * in function__entry/function__return, this is
SystemTap-specific
- SPARC support
- dynamic tracing
- sys module dtrace facility introspection

All of those might be added later.
This commit is contained in:
Łukasz Langa 2016-09-09 17:37:37 -07:00
parent 39b42ae8db
commit a785c87d6e
31 changed files with 1305 additions and 18 deletions

View file

@ -15,6 +15,7 @@
#include "dictobject.h"
#include "frameobject.h"
#include "opcode.h"
#include "pydtrace.h"
#include "setobject.h"
#include "structmember.h"
@ -50,6 +51,9 @@ static void call_exc_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *);
static int maybe_call_line_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *, int *, int *, int *);
static void maybe_dtrace_line(PyFrameObject *, int *, int *, int *);
static void dtrace_function_entry(PyFrameObject *);
static void dtrace_function_return(PyFrameObject *);
static PyObject * cmp_outcome(int, PyObject *, PyObject *);
static PyObject * import_name(PyFrameObject *, PyObject *, PyObject *, PyObject *);
@ -822,7 +826,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
#ifdef LLTRACE
#define FAST_DISPATCH() \
{ \
if (!lltrace && !_Py_TracingPossible) { \
if (!lltrace && !_Py_TracingPossible && !PyDTrace_LINE_ENABLED()) { \
f->f_lasti = INSTR_OFFSET(); \
NEXTOPARG(); \
goto *opcode_targets[opcode]; \
@ -832,7 +836,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
#else
#define FAST_DISPATCH() \
{ \
if (!_Py_TracingPossible) { \
if (!_Py_TracingPossible && !PyDTrace_LINE_ENABLED()) { \
f->f_lasti = INSTR_OFFSET(); \
NEXTOPARG(); \
goto *opcode_targets[opcode]; \
@ -1042,6 +1046,9 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
}
}
if (PyDTrace_FUNCTION_ENTRY_ENABLED())
dtrace_function_entry(f);
co = f->f_code;
names = co->co_names;
consts = co->co_consts;
@ -1162,6 +1169,9 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
fast_next_opcode:
f->f_lasti = INSTR_OFFSET();
if (PyDTrace_LINE_ENABLED())
maybe_dtrace_line(f, &instr_lb, &instr_ub, &instr_prev);
/* line-by-line tracing support */
if (_Py_TracingPossible &&
@ -3620,6 +3630,8 @@ fast_yield:
/* pop frame */
exit_eval_frame:
if (PyDTrace_FUNCTION_RETURN_ENABLED())
dtrace_function_return(f);
Py_LeaveRecursiveCall();
f->f_executing = 0;
tstate->frame = f->f_back;
@ -5415,3 +5427,65 @@ _PyEval_RequestCodeExtraIndex(freefunc free)
tstate->co_extra_freefuncs[new_index] = free;
return new_index;
}
static void
dtrace_function_entry(PyFrameObject *f)
{
char* filename;
char* funcname;
int lineno;
filename = PyUnicode_AsUTF8(f->f_code->co_filename);
funcname = PyUnicode_AsUTF8(f->f_code->co_name);
lineno = PyCode_Addr2Line(f->f_code, f->f_lasti);
PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
}
static void
dtrace_function_return(PyFrameObject *f)
{
char* filename;
char* funcname;
int lineno;
filename = PyUnicode_AsUTF8(f->f_code->co_filename);
funcname = PyUnicode_AsUTF8(f->f_code->co_name);
lineno = PyCode_Addr2Line(f->f_code, f->f_lasti);
PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);
}
/* DTrace equivalent of maybe_call_line_trace. */
static void
maybe_dtrace_line(PyFrameObject *frame,
int *instr_lb, int *instr_ub, int *instr_prev)
{
int line = frame->f_lineno;
char *co_filename, *co_name;
/* If the last instruction executed isn't in the current
instruction window, reset the window.
*/
if (frame->f_lasti < *instr_lb || frame->f_lasti >= *instr_ub) {
PyAddrPair bounds;
line = _PyCode_CheckLineNumber(frame->f_code, frame->f_lasti,
&bounds);
*instr_lb = bounds.ap_lower;
*instr_ub = bounds.ap_upper;
}
/* If the last instruction falls at the start of a line or if
it represents a jump backwards, update the frame's line
number and call the trace function. */
if (frame->f_lasti == *instr_lb || frame->f_lasti < *instr_prev) {
frame->f_lineno = line;
co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
if (!co_filename)
co_filename = "?";
co_name = PyUnicode_AsUTF8(frame->f_code->co_name);
if (!co_name)
co_name = "?";
PyDTrace_LINE(co_filename, co_name, line);
}
*instr_prev = frame->f_lasti;
}