Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proc: Simplify eBPF backend implementation #3325

Merged
merged 1 commit into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 30 additions & 32 deletions pkg/proc/internal/ebpf/bpf/include/function_vals.bpf.h
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
#include <stdbool.h>

// function_parameter stores information about a single parameter to a function.
typedef struct function_parameter {
// Type of the parameter as defined by the reflect.Kind enum.
unsigned int kind;
// Size of the variable in bytes.
unsigned int size;

// Offset from stack pointer. This should only be set from the Go side.
int offset;

// If true, the parameter is passed in a register.
bool in_reg;
// The number of register pieces the parameter is passed in.
int n_pieces;
// If in_reg is true, this represents the registers that the parameter is passed in.
// This is an array because the number of registers may vary and the parameter may be
// passed in multiple registers.
int reg_nums[6];

// The following are filled in by the eBPF program.
size_t daddr; // Data address.
char val[0x30]; // Value of the parameter.
char deref_val[0x30]; // Dereference value of the parameter.
// Type of the parameter as defined by the reflect.Kind enum.
unsigned int kind;
// Size of the variable in bytes.
unsigned int size;

// Offset from stack pointer. This should only be set from the Go side.
int offset;

// If true, the parameter is passed in a register.
bool in_reg;
// The number of register pieces the parameter is passed in.
int n_pieces;
// If in_reg is true, this represents the registers that the parameter is passed in.
// This is an array because the number of registers may vary and the parameter may be
// passed in multiple registers.
int reg_nums[6];

// The following are filled in by the eBPF program.
size_t daddr; // Data address.
char val[0x30]; // Value of the parameter.
char deref_val[0x30]; // Dereference value of the parameter.
} function_parameter_t;

// function_parameter_list holds info about the function parameters and
// stores information on up to 6 parameters.
typedef struct function_parameter_list {
unsigned int goid_offset; // Offset of the `goid` struct member.
long long g_addr_offset; // Offset of the Goroutine struct from the TLS segment.
int goroutine_id;
unsigned int goid_offset; // Offset of the `goid` struct member.
long long g_addr_offset; // Offset of the Goroutine struct from the TLS segment.
int goroutine_id;

unsigned long long int fn_addr;
bool is_ret;
unsigned long long int fn_addr;
bool is_ret;

unsigned int n_parameters; // number of parameters.
function_parameter_t params[6]; // list of parameters.
unsigned int n_parameters; // number of parameters.
function_parameter_t params[6]; // list of parameters.

unsigned int n_ret_parameters; // number of return parameters.
function_parameter_t ret_params[6]; // list of return parameters.
unsigned int n_ret_parameters; // number of return parameters.
function_parameter_t ret_params[6]; // list of return parameters.
} function_parameter_list_t;
10 changes: 3 additions & 7 deletions pkg/proc/internal/ebpf/bpf/include/trace.bpf.h
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
#include "vmlinux.h"
#include "function_vals.bpf.h"
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

#define BPF_MAX_VAR_SIZ (1 << 29)

// Ring buffer to handle communication of variable values back to userspace.
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, BPF_MAX_VAR_SIZ);
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, BPF_MAX_VAR_SIZ);
} events SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, BPF_MAX_VAR_SIZ);
} heap SEC(".maps");

// Map which uses instruction address as key and function parameter info as the value.
struct {
__uint(max_entries, 42);
Expand Down
183 changes: 81 additions & 102 deletions pkg/proc/internal/ebpf/bpf/trace.bpf.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "include/trace.bpf.h"
#include <string.h>

#define STRING_KIND 24

Expand All @@ -12,8 +11,8 @@ int parse_string_param(struct pt_regs *ctx, function_parameter_t *param) {
u64 str_len;
size_t str_addr;

memcpy(&str_addr, param->val, sizeof(str_addr));
memcpy(&str_len, param->val + sizeof(str_addr), sizeof(str_len));
__builtin_memcpy(&str_addr, param->val, sizeof(str_addr));
__builtin_memcpy(&str_len, param->val + sizeof(str_addr), sizeof(str_len));
param->daddr = str_addr;

if (str_addr != 0) {
Expand Down Expand Up @@ -42,72 +41,72 @@ int parse_param_stack(struct pt_regs *ctx, function_parameter_t *param) {
__always_inline
void get_value_from_register(struct pt_regs *ctx, void *dest, int reg_num) {
switch (reg_num) {
case 0: // RAX
memcpy(dest, &ctx->ax, sizeof(ctx->ax));
break;
case 1: // RDX
memcpy(dest, &ctx->dx, sizeof(ctx->dx));
break;
case 2: // RCX
memcpy(dest, &ctx->cx, sizeof(ctx->cx));
break;
case 3: // RBX
memcpy(dest, &ctx->bx, sizeof(ctx->bx));
break;
case 4: // RSI
memcpy(dest, &ctx->si, sizeof(ctx->si));
break;
case 5: // RDI
memcpy(dest, &ctx->di, sizeof(ctx->di));
break;
case 6: // RBP
memcpy(dest, &ctx->bp, sizeof(ctx->bp));
break;
case 7: // RSP
memcpy(dest, &ctx->sp, sizeof(ctx->sp));
break;
case 8: // R8
memcpy(dest, &ctx->r8, sizeof(ctx->r8));
break;
case 9: // R9
memcpy(dest, &ctx->r9, sizeof(ctx->r9));
break;
case 10: // R10
memcpy(dest, &ctx->r10, sizeof(ctx->r10));
break;
case 11: // R11
memcpy(dest, &ctx->r11, sizeof(ctx->r11));
break;
case 12: // R12
memcpy(dest, &ctx->r12, sizeof(ctx->r12));
break;
case 13: // R13
memcpy(dest, &ctx->r13, sizeof(ctx->r13));
break;
case 14: // R14
memcpy(dest, &ctx->r14, sizeof(ctx->r14));
break;
case 15: // R15
memcpy(dest, &ctx->r15, sizeof(ctx->r15));
break;
case 0: // RAX
__builtin_memcpy(dest, &ctx->ax, sizeof(ctx->ax));
break;
case 1: // RDX
__builtin_memcpy(dest, &ctx->dx, sizeof(ctx->dx));
break;
case 2: // RCX
__builtin_memcpy(dest, &ctx->cx, sizeof(ctx->cx));
break;
case 3: // RBX
__builtin_memcpy(dest, &ctx->bx, sizeof(ctx->bx));
break;
case 4: // RSI
__builtin_memcpy(dest, &ctx->si, sizeof(ctx->si));
break;
case 5: // RDI
__builtin_memcpy(dest, &ctx->di, sizeof(ctx->di));
break;
case 6: // RBP
__builtin_memcpy(dest, &ctx->bp, sizeof(ctx->bp));
break;
case 7: // RSP
__builtin_memcpy(dest, &ctx->sp, sizeof(ctx->sp));
break;
case 8: // R8
__builtin_memcpy(dest, &ctx->r8, sizeof(ctx->r8));
break;
case 9: // R9
__builtin_memcpy(dest, &ctx->r9, sizeof(ctx->r9));
break;
case 10: // R10
__builtin_memcpy(dest, &ctx->r10, sizeof(ctx->r10));
break;
case 11: // R11
__builtin_memcpy(dest, &ctx->r11, sizeof(ctx->r11));
break;
case 12: // R12
__builtin_memcpy(dest, &ctx->r12, sizeof(ctx->r12));
break;
case 13: // R13
__builtin_memcpy(dest, &ctx->r13, sizeof(ctx->r13));
break;
case 14: // R14
__builtin_memcpy(dest, &ctx->r14, sizeof(ctx->r14));
break;
case 15: // R15
__builtin_memcpy(dest, &ctx->r15, sizeof(ctx->r15));
break;
}
}

__always_inline
int parse_param_registers(struct pt_regs *ctx, function_parameter_t *param) {
switch (param->n_pieces) {
case 6:
get_value_from_register(ctx, param->val+40, param->reg_nums[5]);
case 5:
get_value_from_register(ctx, param->val+32, param->reg_nums[4]);
case 4:
get_value_from_register(ctx, param->val+24, param->reg_nums[3]);
case 3:
get_value_from_register(ctx, param->val+16, param->reg_nums[2]);
case 2:
get_value_from_register(ctx, param->val+8, param->reg_nums[1]);
case 1:
get_value_from_register(ctx, param->val, param->reg_nums[0]);
case 6:
get_value_from_register(ctx, param->val+40, param->reg_nums[5]);
case 5:
get_value_from_register(ctx, param->val+32, param->reg_nums[4]);
case 4:
get_value_from_register(ctx, param->val+24, param->reg_nums[3]);
case 3:
get_value_from_register(ctx, param->val+16, param->reg_nums[2]);
case 2:
get_value_from_register(ctx, param->val+8, param->reg_nums[1]);
case 1:
get_value_from_register(ctx, param->val, param->reg_nums[0]);
}
return 0;
}
Expand Down Expand Up @@ -142,37 +141,17 @@ int parse_param(struct pt_regs *ctx, function_parameter_t *param) {

__always_inline
int get_goroutine_id(function_parameter_list_t *parsed_args) {
// Since eBPF programs have such strict stack requirements
// me must implement our own heap using a ringbuffer.
// Reserve some memory in our "heap" for the task_struct.
struct task_struct *task;
task = bpf_ringbuf_reserve(&heap, sizeof(struct task_struct), 0);
if (!task) {
return 0;
}
size_t g_addr;
__u64 goid;

// Get the current task.
__u64 task_ptr = bpf_get_current_task();
if (!task_ptr)
{
bpf_ringbuf_discard(task, 0);
return 0;
}
// The bpf_get_current_task helper returns us the address of the task_struct in
// kernel memory. Use the bpf_probe_read_kernel helper to read the struct out of
// kernel memory.
bpf_probe_read_kernel(task, sizeof(struct task_struct), (void*)(task_ptr));

task = (struct task_struct *)bpf_get_current_task();
// Get the Goroutine ID which is stored in thread local storage.
__u64 goid;
size_t g_addr;
bpf_probe_read_user(&g_addr, sizeof(void *), (void*)(task->thread.fsbase+parsed_args->g_addr_offset));
bpf_probe_read_user(&g_addr, sizeof(void *), (void*)(BPF_CORE_READ(task, thread.fsbase)+parsed_args->g_addr_offset));
bpf_probe_read_user(&goid, sizeof(void *), (void*)(g_addr+parsed_args->goid_offset));
parsed_args->goroutine_id = goid;

// Free back up the memory we reserved for the task_struct.
bpf_ringbuf_discard(task, 0);

return 1;
}

Expand All @@ -181,18 +160,18 @@ void parse_params(struct pt_regs *ctx, unsigned int n_params, function_parameter
// Since we cannot loop in eBPF programs let's take adavantage of the
// fact that in C switch cases will pass through automatically.
switch (n_params) {
case 6:
parse_param(ctx, &params[5]);
case 5:
parse_param(ctx, &params[4]);
case 4:
parse_param(ctx, &params[3]);
case 3:
parse_param(ctx, &params[2]);
case 2:
parse_param(ctx, &params[1]);
case 1:
parse_param(ctx, &params[0]);
case 6:
parse_param(ctx, &params[5]);
case 5:
parse_param(ctx, &params[4]);
case 4:
parse_param(ctx, &params[3]);
case 3:
parse_param(ctx, &params[2]);
case 2:
parse_param(ctx, &params[1]);
case 1:
parse_param(ctx, &params[0]);
}
}

Expand All @@ -207,7 +186,7 @@ int uprobe__dlv_trace(struct pt_regs *ctx) {
return 1;
}

parsed_args = bpf_ringbuf_reserve(&events, sizeof(function_parameter_list_t), 0);
parsed_args = bpf_ringbuf_reserve(&events, sizeof(function_parameter_list_t), 0);
if (!parsed_args) {
return 1;
}
Expand All @@ -220,8 +199,8 @@ int uprobe__dlv_trace(struct pt_regs *ctx) {
parsed_args->n_parameters = args->n_parameters;
parsed_args->n_ret_parameters = args->n_ret_parameters;
parsed_args->is_ret = args->is_ret;
memcpy(parsed_args->params, args->params, sizeof(args->params));
memcpy(parsed_args->ret_params, args->ret_params, sizeof(args->ret_params));
__builtin_memcpy(parsed_args->params, args->params, sizeof(args->params));
__builtin_memcpy(parsed_args->ret_params, args->ret_params, sizeof(args->ret_params));

if (!get_goroutine_id(parsed_args)) {
bpf_ringbuf_discard(parsed_args, 0);
Expand Down
1 change: 1 addition & 0 deletions pkg/proc/internal/ebpf/testhelper/testhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package testhelper

// #include <stdbool.h>
// #include "../bpf/include/function_vals.bpf.h"
import "C"

Expand Down
3 changes: 0 additions & 3 deletions pkg/proc/internal/ebpf/trace_bpfel_x86.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified pkg/proc/internal/ebpf/trace_bpfel_x86.o
Binary file not shown.