Skip to content
Prev Previous commit
Next Next commit
Fix offsets, and make sure that we handle differently owned stack fra…
…mes properly (cstack owned skipped)
  • Loading branch information
sobolron committed Apr 24, 2025
commit 86943faf1f49016646f5857d30d31871e3b27ada
28 changes: 18 additions & 10 deletions examples/cpp/pyperf/PyOffsets.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extern const struct struct_offsets kPy27OffsetConfig = {
.f_code = 32,
.f_lineno = 124,
.f_localsplus = 376,
.owner = -1,
},
.PyCodeObject = {
.co_filename = 80,
Expand Down Expand Up @@ -97,6 +98,7 @@ extern const struct struct_offsets kPy36OffsetConfig = {
.f_code = 32,
.f_lineno = 124,
.f_localsplus = 376,
.owner = -1,
},
.PyCodeObject = {
.co_filename = 96,
Expand Down Expand Up @@ -137,6 +139,7 @@ extern const struct struct_offsets kPy37OffsetConfig = {
.f_code = 32,
.f_lineno = 108,
.f_localsplus = 360,
.owner = -1,
},
.PyCodeObject = {
.co_filename = 96,
Expand Down Expand Up @@ -177,6 +180,7 @@ extern const struct struct_offsets kPy38OffsetConfig = {
.f_code = 32,
.f_lineno = 108,
.f_localsplus = 360,
.owner = -1,
},
.PyCodeObject = {
.co_filename = 104,
Expand Down Expand Up @@ -220,6 +224,7 @@ extern const struct struct_offsets kPy310OffsetConfig = {
.f_code = 32,
.f_lineno = 100,
.f_localsplus = 352,
.owner = -1,
},
.PyCodeObject = {
.co_filename = 104,
Expand All @@ -239,7 +244,7 @@ extern const struct struct_offsets kPy311OffsetConfig = {
.String = {
// see https://github.com/python/cpython/blob/3.11/Include/cpython/unicodeobject.h#L69-L71
.data = 48, // sizeof(PyASCIIObject), which is an offset to string data
.size = -1,
.size = 16,
},
.PyTypeObject = {
.tp_name = 24
Expand All @@ -265,6 +270,7 @@ extern const struct struct_offsets kPy311OffsetConfig = {
.f_code = 32, // offsetof(_PyInterpreterFrame, f_code),
.f_lineno = -1, // N/A
.f_localsplus = 72, // offsetof(_PyInterpreterFrame, localsplus),
.owner = 69,
},
.PyCodeObject = {
.co_filename = 112,
Expand All @@ -283,8 +289,8 @@ extern const struct struct_offsets kPy312OffsetConfig = {
},
.String = {
// see https://github.com/python/cpython/blob/3.11/Include/cpython/unicodeobject.h#L69-L71
.data = 48, // sizeof(PyASCIIObject), which is an offset to string data
.size = -1,
.data = 40, // sizeof(PyASCIIObject), which is an offset to string data
.size = 16,
},
.PyTypeObject = {
.tp_name = 24
Expand All @@ -300,16 +306,17 @@ extern const struct struct_offsets kPy312OffsetConfig = {
.current_frame = 0
},
.PyInterpreterState = {
.tstate_head = 16, // offsetof(PyInterpreterState, threads.head),
.tstate_head = 64 + 8, // offsetof(PyInterpreterState, threads.head),
},
.PyRuntimeState = {
.interp_main = 48, // offsetof(_PyRuntimeState, interpreters.main),
.interp_main = 32 + 8 //48, // offsetof(_PyRuntimeState, interpreters.main),
},
.PyFrameObject = { // in Python 3.11 these fields are in PyInterpreterFrame
.f_back = 8, // offsetof(_PyInterpreterFrame, previous),
.f_code = 0, // offsetof(_PyInterpreterFrame, f_code),
.f_lineno = -1, // N/A
.f_localsplus = 72, // offsetof(_PyInterpreterFrame, localsplus),
.owner = 70,
},
.PyCodeObject = {
.co_filename = 112,
Expand All @@ -329,8 +336,8 @@ static const struct struct_offsets kPy313OffsetConfig = {
.ob_type = 8
},
.String = {
.data = 48,
.size = -1
.data = 40,
.size = 16
},
.PyTypeObject = {
.tp_name = 24
Expand All @@ -348,17 +355,18 @@ static const struct struct_offsets kPy313OffsetConfig = {
},
/* Interpreter / runtime */
.PyInterpreterState = {
.tstate_head = 16
.tstate_head = 7344
},
.PyRuntimeState = {
.interp_main = 48
.interp_main = 640
},
/* _PyInterpreterFrame “virtual” frame */
.PyFrameObject = {
.f_back = 8,
.f_code = 0,
.f_lineno = -1,
.f_localsplus = 72
.f_localsplus = 72,
.owner = 70,
},
/* PyCodeObject offsets unchanged since 3.11 */
.PyCodeObject = {
Expand Down
60 changes: 42 additions & 18 deletions examples/cpp/pyperf/PyPerfBPFProgram.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ struct struct_offsets {
int64_t f_code;
int64_t f_lineno;
int64_t f_localsplus;
int64_t owner;
} PyFrameObject;
struct {
int64_t co_filename;
Expand Down Expand Up @@ -198,6 +199,10 @@ struct sample_state {
#define CPU_BITS 10
#define COUNTER_BITS (31 - CPU_BITS)
#define MAX_SYMBOLS (1 << COUNTER_BITS)
#define FRAME_OWNED_BY_THREAD 0
#define FRAME_OWNED_BY_GENERATOR 1
#define FRAME_OWNED_BY_FRAME_OBJECT 2
#define FRAME_OWNED_BY_CSTACK 3
BPF_HASH(symbols, struct symbol, int32_t, __SYMBOLS_SIZE__);

// Table of processes currently being profiled.
Expand Down Expand Up @@ -542,14 +547,20 @@ get_first_arg_name(
char *argname,
size_t maxlen) {
int result = 0;
ssize_t ob_size; // Py_ssize_t;
ssize_t ob_size = 0; // Py_ssize_t;
// Roughly equivalnt to the following in GDB:
//
// ((PyTupleObject*)$frame->f_code->co_varnames)->ob_item[0]
//
int var_count;
result |= bpf_probe_read_user(&var_count, sizeof(var_count), code_ptr + 72); // co_nlocalsplus

void* args_ptr;
result |= bpf_probe_read_user(&args_ptr, sizeof(void*), code_ptr + offsets->PyCodeObject.co_varnames);
result |= bpf_probe_read_user(&ob_size, sizeof(ob_size), args_ptr + offsets->String.size); // String.size is PyVarObject.ob_size
if (result == 0 && var_count > 0) {
result |= bpf_probe_read_user(&args_ptr, sizeof(void*), code_ptr + offsets->PyCodeObject.co_varnames);
result |= bpf_probe_read_user(&ob_size, sizeof(ob_size), args_ptr + 16); // PyVarObject.ob_size has always been 16
}

if (result == 0 && ob_size > 0) {
result |= bpf_probe_read_user(&args_ptr, sizeof(void*), args_ptr + offsets->PyTupleObject.ob_item);
result |= bpf_probe_read_user_str(argname, maxlen, args_ptr + offsets->String.data);
Expand Down Expand Up @@ -626,7 +637,7 @@ read_symbol_names(
void* pystr_ptr;
// read PyCodeObject's filename into symbol
result |= bpf_probe_read_user(&pystr_ptr, sizeof(void*), code_ptr + offsets->PyCodeObject.co_filename);
result |= bpf_probe_read_user_str(&symbol->file, sizeof(symbol->file), pystr_ptr + offsets->String.data);
result |= bpf_probe_read_user_str(&symbol->file, sizeof(symbol->file), pystr_ptr + offsets->String.data);
if (result < 0) {
return result;
}
Expand Down Expand Up @@ -688,24 +699,37 @@ int read_python_stack(struct pt_regs* ctx) {
void *cur_frame;
void *cur_code_ptr;

#pragma unroll
#pragma unroll
for (int i = 0; i < PYTHON_STACK_FRAMES_PER_PROG; i++) {
cur_frame = state->frame_ptr;

// read PyCodeObject first, if that fails, then no point reading next frame
char owner;
bpf_probe_read_user(
&cur_code_ptr, sizeof(cur_code_ptr),
cur_frame + state->offsets.PyFrameObject.f_code);

// read current PyFrameObject filename/name
// The compiler substitutes a constant for `i` because the loop is unrolled. This guarantees we
// are always within the array bounds. On the other hand, `stack_len` is a variable, so the
// verifier can't guarantee it's within bounds without an explicit check.
const int32_t symbol_id = read_symbol(state, cur_frame, cur_code_ptr);
// to please the verifier...
if (event->stack_len < STACK_MAX_LEN) {
event->stack[event->stack_len++] = symbol_id;
}
&owner, sizeof(owner),
cur_frame + state->offsets.PyFrameObject.owner);
bpf_trace_printk("owner %d",owner);
if (owner == FRAME_OWNED_BY_THREAD ||
owner == FRAME_OWNED_BY_GENERATOR ||
owner == FRAME_OWNED_BY_FRAME_OBJECT){
// read PyCodeObject first, if that fails, then no point reading next frame
bpf_probe_read_user(
&cur_code_ptr, sizeof(cur_code_ptr),
cur_frame + state->offsets.PyFrameObject.f_code);

// read current PyFrameObject filename/name
// The compiler substitutes a constant for `i` because the loop is unrolled. This guarantees we
// are always within the array bounds. On the other hand, `stack_len` is a variable, so the
// verifier can't guarantee it's within bounds without an explicit check.
const int32_t symbol_id = read_symbol(state, cur_frame, cur_code_ptr);
// to please the verifier...
if (event->stack_len < STACK_MAX_LEN) {
event->stack[event->stack_len++] = symbol_id;
}
} else if (owner != FRAME_OWNED_BY_CSTACK) {
// This means frame ownership is unknown. Something is off.
event->stack[event->stack_len++] = -1;
} // If it's CSTACK we just skip.


// read next PyFrameObject pointer, update in place
bpf_probe_read_user(
Expand Down
1 change: 1 addition & 0 deletions examples/cpp/pyperf/PyPerfType.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ struct struct_offsets {
int64_t f_code;
int64_t f_lineno;
int64_t f_localsplus;
int64_t owner;
} PyFrameObject;
struct {
int64_t co_filename;
Expand Down