Skip to content

Commit

Permalink
proc: Simplify eBPF backend implementation
Browse files Browse the repository at this point in the history
* Remove standard C headers since we have vmlinux.h already
* Simplify get_goroutine_id() implementation, this reduces a map
  and thus reduces runtime memory comsumption.

While at it, unify all indention using 4 spaces.

Signed-off-by: Hengqi Chen <[email protected]>
  • Loading branch information
chenhengqi committed Apr 6, 2023
1 parent 4c40e81 commit 9cb3e29
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 144 deletions.
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.

0 comments on commit 9cb3e29

Please sign in to comment.