delve/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c
Derek Parker 1b2f7f0051
pkg/proc: Parse Goroutine ID in eBPF tracer (#2654)
This patch enables the eBPF tracer backend to parse the ID of the
Goroutine which hit the uprobe. This implementation is specific to AMD64
and will have to be generalized further in order to be used on other
architectures.
2021-08-24 14:53:27 +02:00

224 lines
6.7 KiB
C

#include "trace.bpf.h"
#include <string.h>
#define STRING_KIND 24
// parse_string_param will parse a string parameter. The parsed value of the string
// will be put into param->deref_val. This function expects the string struct
// which contains a pointer to the string and the length of the string to have
// already been read from memory and passed in as param->val.
__always_inline
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));
param->daddr = str_addr;
if (str_addr != 0) {
if (str_len > 0x30) {
str_len = 0x30;
}
int ret = bpf_probe_read_user(&param->deref_val, str_len, (void *)(str_addr));
if (ret < 0) {
return 1;
}
}
return 0;
}
__always_inline
int parse_param_stack(struct pt_regs *ctx, function_parameter_t *param) {
long ret;
size_t addr = ctx->sp + param->offset;
ret = bpf_probe_read_user(&param->val, param->size, (void *)(addr));
if (ret < 0) {
return 1;
}
return 0;
}
__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;
}
}
__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]);
}
return 0;
}
__always_inline
int parse_param(struct pt_regs *ctx, function_parameter_t *param) {
if (param->size > 0x30) {
return 0;
}
// Parse the initial value of the parameter.
// If the parameter is a basic type, we will be finished here.
// If the parameter is a more complex type such as a string or
// a slice we will need some further processing below.
int ret = 0;
if (param->in_reg) {
parse_param_registers(ctx, param);
} else {
parse_param_stack(ctx, param);
}
if (ret != 0) {
return ret;
}
switch (param->kind) {
case STRING_KIND:
return parse_string_param(ctx, param);
}
return 0;
}
__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;
}
// 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));
// 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(&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;
}
SEC("uprobe/dlv_trace")
int uprobe__dlv_trace(struct pt_regs *ctx) {
function_parameter_list_t *args;
function_parameter_list_t *parsed_args;
function_parameter_t param;
uint64_t key = ctx->ip;
args = bpf_map_lookup_elem(&arg_map, &key);
if (!args) {
return 1;
}
parsed_args = bpf_ringbuf_reserve(&events, sizeof(function_parameter_list_t), 0);
if (!parsed_args) {
return 1;
}
memcpy(parsed_args, args, sizeof(function_parameter_list_t));
if (!get_goroutine_id(parsed_args)) {
bpf_ringbuf_discard(parsed_args, 0);
return 1;
}
// Since we cannot loop in eBPF programs let's take adavantage of the
// fact that in C switch cases will pass through automatically.
switch (args->n_parameters) {
case 6:
parse_param(ctx, &parsed_args->params[5]);
case 5:
parse_param(ctx, &parsed_args->params[4]);
case 4:
parse_param(ctx, &parsed_args->params[3]);
case 3:
parse_param(ctx, &parsed_args->params[2]);
case 2:
parse_param(ctx, &parsed_args->params[1]);
case 1:
parse_param(ctx, &parsed_args->params[0]);
}
bpf_ringbuf_submit(parsed_args, 0);
return 0;
}
char _license[] SEC("license") = "GPL";