
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.
224 lines
6.7 KiB
C
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(¶m->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(¶m->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"; |