diff --git a/agent/src/ebpf/Makefile b/agent/src/ebpf/Makefile index 6dace6516cd..7988aef7791 100644 --- a/agent/src/ebpf/Makefile +++ b/agent/src/ebpf/Makefile @@ -75,6 +75,7 @@ OBJS := user/elf.o \ user/symbol.o \ user/go_tracer.o \ user/ssl_tracer.o \ + user/python_probe.o \ user/ring.o \ user/btf_vmlinux.o \ user/load.o \ diff --git a/agent/src/ebpf/kernel/Makefile b/agent/src/ebpf/kernel/Makefile index 630ec015b04..c4e304303a5 100644 --- a/agent/src/ebpf/kernel/Makefile +++ b/agent/src/ebpf/kernel/Makefile @@ -48,6 +48,8 @@ EBPF_CLAGS ?= -I. -Ivmlinux -Iinclude -D__BPF_TRACING__ -D GROUP_LEADER_OFFSET_O -DSTART_BOOTTIME_OFFSET_OVERRIDE=0 \ -DSTART_BOOTTIME_VARNAME=real_start_time +socket_trace.bpf.c: uprobe_base.bpf.c go_tls.bpf.c go_http2.bpf.c openssl.bpf.c python_uprobe.bpf.c + %.elf: %.c $(call msg,BPF,$@,$(CORE)) $(Q)$(CLANG) $(EBPF_CLAGS) $(EXTRA_EBPF_CLAGS) -std=gnu99 -Wimplicit-function-declaration \ diff --git a/agent/src/ebpf/kernel/perf_profiler.bpf.c b/agent/src/ebpf/kernel/perf_profiler.bpf.c index 6d138a34654..ba80dd29dbb 100644 --- a/agent/src/ebpf/kernel/perf_profiler.bpf.c +++ b/agent/src/ebpf/kernel/perf_profiler.bpf.c @@ -72,9 +72,9 @@ MAP_PERARRAY(heap, __u32, unwind_state_t, 1) MAP_PERARRAY(python_symbol_index, __u32, __u32, 1) MAP_HASH(python_stack, __u64, stack_trace_t, STACK_MAP_ENTRIES) -static inline __attribute__((always_inline)) bool comm_eq(char *a, char *b) { +static inline __attribute__((always_inline)) bool comm_eq_n(char *a, char *b, int n) { #pragma unroll - for (int i = 0; i < TASK_COMM_LEN; i++) { + for (int i = 0; i < TASK_COMM_LEN && i < n; i++) { if (a[i] == '\0' || b[i] == '\0') { return a[i] == b[i]; } @@ -419,20 +419,25 @@ int bpf_perf_event(struct bpf_perf_event_data *ctx) /* * CPU idle stacks will not be collected. */ - if (key->tgid == key->pid && key->pid == 0) + if (key->tgid == key->pid && key->pid == 0) { return 0; + } key->cpu = bpf_get_smp_processor_id(); bpf_get_current_comm(&key->comm, sizeof(key->comm)); key->timestamp = bpf_ktime_get_ns(); - if (comm_eq(key->comm, "python3")) { + if (comm_eq_n(key->comm, "python3", 7) || comm_eq_n(key->comm, "pt_main", 7)) { + __builtin_memcpy(key->comm, "python3", 7); + key->comm[7] = '\0'; bpf_tail_call(ctx, &NAME(progs_jmp_perf_map), PROG_PYTHON_FRAME_PTR_IDX); } return get_stack_and_output_perf(ctx, state); } +MAP_HASH(python_thread_state_map, __u32, __u64, 65536) + PROGPE(python_frame_ptr)(struct bpf_perf_event_data *ctx) { __u32 zero = 0; unwind_state_t *state = heap__lookup(&zero); @@ -440,10 +445,21 @@ PROGPE(python_frame_ptr)(struct bpf_perf_event_data *ctx) { return 0; } - __u64 thread_state_addr = 140737353904984; + // __u64 thread_state_addr = 140737353904984; + __u64 thread_state_addr = 9851320; if (bpf_probe_read_user(&state->thread_state, sizeof(void *), (void *)thread_state_addr) != 0) { goto finish; } + if (state->thread_state != NULL) { + python_thread_state_map__update(&state->key.tgid, (__u64 *)&state->thread_state); + } else { + __u64 *entry = python_thread_state_map__lookup(&state->key.tgid); + if (entry) { + state->thread_state = (void *)*entry; + } else { + goto finish; + } + } if (bpf_probe_read_user(&state->key.itid, sizeof(__u32), state->thread_state + py_offsets.py_thread_state.thread_id) != 0) { goto finish; @@ -456,7 +472,7 @@ PROGPE(python_frame_ptr)(struct bpf_perf_event_data *ctx) { bpf_tail_call(ctx, &NAME(progs_jmp_perf_map), PROG_PYTHON_WALK_STACK_IDX); finish: - return 0; + return get_stack_and_output_perf(ctx, state); } PROGPE(python_walk_stack)(struct bpf_perf_event_data *ctx) { diff --git a/agent/src/ebpf/kernel/python_uprobe.bpf.c b/agent/src/ebpf/kernel/python_uprobe.bpf.c new file mode 100644 index 00000000000..934089c8c13 --- /dev/null +++ b/agent/src/ebpf/kernel/python_uprobe.bpf.c @@ -0,0 +1,17 @@ +#if 0 +SEC("uprobe/python_save_thread_state_address") +int uprobe_python_save_thread_state_address(struct pt_regs *ctx) { + __u64 zero = 0; + python_thread_state_map__update(&zero, &zero); + bpf_debug("PyEval_SaveThread"); + return 0; +} + +SEC("uprobe/pyeval_evalframedefault") +int uprobe_pyeval_evalframedefault(struct pt_regs *ctx) { + __u64 zero = 0; + python_thread_state_map__update(&zero, &zero); + bpf_debug("_PyEval_EvalFrameDefault"); + return 0; +} +#endif diff --git a/agent/src/ebpf/kernel/socket_trace.bpf.c b/agent/src/ebpf/kernel/socket_trace.bpf.c index 31ca19d27b5..d0470fa093c 100644 --- a/agent/src/ebpf/kernel/socket_trace.bpf.c +++ b/agent/src/ebpf/kernel/socket_trace.bpf.c @@ -2595,3 +2595,4 @@ PROGTP(io_event) (void *ctx) { #include "go_tls.bpf.c" #include "go_http2.bpf.c" #include "openssl.bpf.c" +#include "python_uprobe.bpf.c" diff --git a/agent/src/ebpf/user/log.c b/agent/src/ebpf/user/log.c index f82a1eedf7e..96ef903506c 100644 --- a/agent/src/ebpf/user/log.c +++ b/agent/src/ebpf/user/log.c @@ -95,7 +95,7 @@ void _ebpf_error(int how_to_die, char *function_name, char *file_path, { char msg[MSG_SZ] = {}; uint16_t len = 0; - uint16_t max = MSG_SZ; + int16_t max = MSG_SZ; va_list va; if (function_name) { @@ -130,7 +130,7 @@ void _ebpf_info(char *fmt, ...) { char msg[MSG_SZ] = {}; uint16_t len = 0; - uint16_t max = MSG_SZ; + int16_t max = MSG_SZ; va_list va; len += snprintf(msg + len, max - len, "[eBPF] INFO "); diff --git a/agent/src/ebpf/user/profile/stringifier.c b/agent/src/ebpf/user/profile/stringifier.c index 0c4657cd732..dfed60306bf 100644 --- a/agent/src/ebpf/user/profile/stringifier.c +++ b/agent/src/ebpf/user/profile/stringifier.c @@ -553,6 +553,58 @@ static inline char *alloc_stack_trace_str(int len) return trace_str; } +bool is_python_to_c(char *fname) { + return strcmp(fname, "PyCFunction_Call") == 0; +} + +bool is_c_to_python(char *fname) { + return strcmp(fname, "PyVectorcall_Call") == 0; +} + +void merge_interpreter_and_user_trace(char *trace_str, int len, char *i_trace, char *u_trace) +{ + char *i_sp = NULL, *u_sp = NULL, *i_frame = NULL, *u_frame = NULL; + int offset = 0; + bool in_c = false, seen_eval_frame = false; + + // ebpf_warning("\n%s\n%s", i_trace, u_trace); + // + i_frame = strtok_r(i_trace, ";", &i_sp); + offset += snprintf(trace_str + offset, len - offset, "%s;", i_frame); + + i_frame = strtok_r(NULL, ";", &i_sp); + u_frame = strtok_r(u_trace, ";", &u_sp); + while (u_frame) { + if (in_c) { + if (is_c_to_python(u_frame)) { + in_c = false; + seen_eval_frame = true; + } else { + offset += snprintf(trace_str + offset, len - offset, "%s;", u_frame); + } + } else { + if (strcmp(u_frame, "_PyEval_EvalFrameDefault") == 0) { + if (seen_eval_frame && i_frame) { + offset += snprintf(trace_str + offset, len - offset, "%s;", i_frame); + i_frame = strtok_r(NULL, ";", &i_sp); + } + seen_eval_frame = true; + } else if (is_python_to_c(u_frame)) { + in_c = true; + seen_eval_frame = false; + } + } + u_frame = strtok_r(NULL, ";", &u_sp); + } + if (in_c) { + while (u_frame) { + offset += snprintf(trace_str + offset, len - offset, "%s;", u_frame); + u_frame = strtok_r(NULL, ";", &u_sp); + } + } + trace_str[offset - 1] = '\0'; +} + char *resolve_and_gen_stack_trace_str(struct bpf_tracer *t, struct stack_trace_key_t *v, const char *stack_map_name, @@ -667,6 +719,14 @@ char *resolve_and_gen_stack_trace_str(struct bpf_tracer *t, return trace_str; } + if (v->intpstack != 0) { + if (i_trace_str) { + len += strlen(i_trace_str); + } else { + len += strlen(i_err_tag); + } + } + if (v->kernstack >= 0) { if (k_trace_str) { len += strlen(k_trace_str); @@ -683,14 +743,6 @@ char *resolve_and_gen_stack_trace_str(struct bpf_tracer *t, } } - if (v->intpstack != 0) { - if (i_trace_str) { - len += strlen(i_trace_str); - } else { - len += strlen(i_err_tag); - } - } - trace_str = alloc_stack_trace_str(len); if (trace_str == NULL) { ebpf_warning("No available memory space.\n"); @@ -700,16 +752,22 @@ char *resolve_and_gen_stack_trace_str(struct bpf_tracer *t, int offset = 0; bool last_stack = false; - if (v->userstack >= 0) { - offset += snprintf(trace_str + offset, len - offset, "%s%s", last_stack ? ";" : "", u_trace_str ? u_trace_str : u_err_tag); - last_stack = true; - } - if (v->intpstack != 0) { - offset += snprintf(trace_str + offset, len - offset, "%s%s", last_stack ? ";" : "", i_trace_str ? i_trace_str : i_err_tag); + if (i_trace_str && u_trace_str) { + merge_interpreter_and_user_trace(trace_str + offset, len - offset, i_trace_str, u_trace_str); last_stack = true; + } else { + if (v->intpstack != 0) { + offset += snprintf(trace_str + offset, len - offset, "%s%s", last_stack ? ";" : "", i_trace_str ? i_trace_str : i_err_tag); + last_stack = true; + } + if (v->userstack >= 0) { + offset += snprintf(trace_str + offset, len - offset, "%s%s", last_stack ? ";" : "", u_trace_str ? u_trace_str : u_err_tag); + last_stack = true; + } } if (v->kernstack >= 0) { offset += snprintf(trace_str + offset, len - offset, "%s%s", last_stack ? ";" : "", k_trace_str ? k_trace_str : k_err_tag); + last_stack = true; } return trace_str; diff --git a/agent/src/ebpf/user/python_probe.c b/agent/src/ebpf/user/python_probe.c new file mode 100644 index 00000000000..8d2e8223a70 --- /dev/null +++ b/agent/src/ebpf/user/python_probe.c @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2022 Yunshan Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "python_probe.h" +#include "tracer.h" +#include "socket.h" +#include "common.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "symbol.h" + +struct python_process_create_event { + struct list_head list; + int pid; + uint32_t expire_time; + struct bpf_tracer *tracer; +}; + +static struct list_head proc_events_list; +static pthread_mutex_t proc_events_list_mutex; + +static struct symbol python_syms[] = { +#if 0 + { + .type = OTHER_UPROBE, + .symbol = "PyEval_SaveThread", + .probe_func = "uprobe_python_save_thread_state_address", + .is_probe_ret = false, + }, + { + .type = OTHER_UPROBE, + .symbol = "_PyEval_EvalFrameDefault", + .probe_func = "uprobe_pyeval_evalframedefault", + .is_probe_ret = false, + }, +#endif +}; + +#if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2 +#define bcc_use_symbol_type (65535 | (1 << STT_PPC64_ELFV2_SYM_LEP)) +#else +#define bcc_use_symbol_type (65535) +#endif + +static struct bcc_symbol_option bcc_elf_foreach_sym_option = { + .use_debug_file = 0, + .check_debug_file_crc = 0, + .lazy_symbolize = 1, + .use_symbol_type = bcc_use_symbol_type, +}; + +struct elf_symbol { + uint64_t addr; + uint64_t size; + const char *name; +}; + +static int fill_elf_symbol(const char *name, uint64_t addr, + uint64_t size, void *payload) +{ + struct elf_symbol *p = payload; + char *pos; + if ((pos = strstr(name, p->name))) { + if (pos[strlen(p->name)] == '\0') { + p->addr = addr; + p->size = size; + return -1; + } + } + return 0; +} + +static int add_probe_sym_to_tracer_probes(int pid, const char *path, + struct tracer_probes_conf *conf) +{ + int ret = 0; + int idx = 0; + struct symbol_uprobe *probe_sym = NULL; + struct symbol *cur = NULL; + struct elf_symbol payload; + + for (idx = 0; idx < NELEMS(python_syms); ++idx) { + memset(&payload, 0, sizeof(payload)); + cur = &python_syms[idx]; + + // Use memory on the stack, no need to allocate on the heap + payload.name = cur->symbol; + ret = bcc_elf_foreach_sym(path, fill_elf_symbol, + &bcc_elf_foreach_sym_option, + &payload); + if (ret) + break; + + if (!payload.addr || !payload.size) + continue; + + // This memory will be maintained in conf, no need to release + probe_sym = calloc(1, sizeof(struct symbol_uprobe)); + if (!probe_sym) + continue; + + // Data comes from symbolic information + probe_sym->entry = payload.addr; + probe_sym->size = payload.size; + + // Data comes from global variables + probe_sym->type = cur->type; + probe_sym->isret = cur->is_probe_ret; + probe_sym->probe_func = strdup(cur->probe_func); + probe_sym->name = strdup(cur->symbol); + + // Data comes from function input parameters + probe_sym->binary_path = strdup(path); + probe_sym->pid = pid; + + if (probe_sym->probe_func && probe_sym->name && + probe_sym->binary_path) { + add_uprobe_symbol(pid, probe_sym, conf); + } else { + free((void *)probe_sym->probe_func); + free((void *)probe_sym->name); + free((void *)probe_sym->binary_path); + } + } + return 0; +} + +static void python_parse_and_register(int pid, struct tracer_probes_conf *conf) +{ + char *path = "/home/jinjie/opt/bin/python3"; + + if (pid <= 1) + goto out; + + if (!is_user_process(pid)) + goto out; + + ebpf_info("python uprobe, pid:%d, path:%s\n", pid, path); + add_probe_sym_to_tracer_probes(pid, path, conf); + +out: + return; +} + +static void add_event_to_proc_list(struct bpf_tracer *tracer, int pid) +{ + static const uint32_t PROC_EVENT_HANDLE_DELAY = 120; + struct python_process_create_event *event = NULL; + + event = calloc(1, sizeof(struct python_process_create_event)); + if (!event) { + ebpf_warning("no memory.\n"); + return; + } + + event->tracer = tracer; + event->pid = pid; + event->expire_time = get_sys_uptime() + PROC_EVENT_HANDLE_DELAY; + + pthread_mutex_lock(&proc_events_list_mutex); + list_add_tail(&event->list, &proc_events_list); + pthread_mutex_unlock(&proc_events_list_mutex); + return; +} + +static struct python_process_create_event *get_first_event(void) +{ + struct python_process_create_event *event = NULL; + pthread_mutex_lock(&proc_events_list_mutex); + if (!list_empty(&proc_events_list)) { + event = list_first_entry(&proc_events_list, + struct python_process_create_event, list); + } + pthread_mutex_unlock(&proc_events_list_mutex); + return event; +} + +static void remove_event(struct python_process_create_event *event) +{ + pthread_mutex_lock(&proc_events_list_mutex); + list_head_del(&event->list); + pthread_mutex_unlock(&proc_events_list_mutex); +} + +static void clear_python_probes_by_pid(struct bpf_tracer *tracer, int pid) +{ + struct probe *probe; + struct list_head *p, *n; + struct symbol_uprobe *sym_uprobe; + + list_for_each_safe (p, n, &tracer->probes_head) { + probe = container_of(p, struct probe, list); + if (!(probe->type == UPROBE && probe->private_data != NULL)) + continue; + sym_uprobe = probe->private_data; + + if (sym_uprobe->type != OTHER_UPROBE) + continue; + + if (sym_uprobe->pid != pid) + continue; + + if (probe_detach(probe)) { + ebpf_warning("probe_detach failed, path:%s, name:%s\n", + sym_uprobe->binary_path, sym_uprobe->name); + } + free_probe_from_tracer(probe); + } +} + +int collect_python_uprobe_syms_from_procfs(struct tracer_probes_conf *conf) +{ + struct dirent *entry = NULL; + DIR *fddir = NULL; + int pid = 0; + char *path = NULL; + + init_list_head(&proc_events_list); + pthread_mutex_init(&proc_events_list_mutex, NULL); + + fddir = opendir("/proc/"); + if (!fddir) { + ebpf_warning("Failed to open %s.\n"); + return ETR_PROC_FAIL; + } + + while ((entry = readdir(fddir))) { + if (entry->d_type != DT_DIR) + continue; + pid = atoi(entry->d_name); + if (pid <= 1) { + continue; + } + path = get_elf_path_by_pid(pid); + if (!path) { + continue; + } + if (strstr(path, "python3")) { + python_parse_and_register(pid, conf); + } + free(path); + } + + closedir(fddir); + return ETR_OK; +} + +void python_process_exec(int pid) +{ + struct bpf_tracer *tracer = NULL; + char *path = get_elf_path_by_pid(pid); + if (!path) return; + bool matched = strstr(path, "python3") != NULL; + free(path); + if (!matched) + return; + + tracer = find_bpf_tracer(SK_TRACER_NAME); + if (tracer == NULL) + return; + + if (tracer->state != TRACER_RUNNING) + return; + + if (tracer->probes_count > OPEN_FILES_MAX) { + ebpf_warning("Probes count too many. The maximum is %d\n", + OPEN_FILES_MAX); + return; + } + + add_event_to_proc_list(tracer, pid); +} + +void python_process_exit(int pid) +{ + struct bpf_tracer *tracer = NULL; + + tracer = find_bpf_tracer(SK_TRACER_NAME); + if (tracer == NULL) + return; + + if (tracer->state != TRACER_RUNNING) + return; + + pthread_mutex_lock(&tracer->mutex_probes_lock); + clear_python_probes_by_pid(tracer, pid); + pthread_mutex_unlock(&tracer->mutex_probes_lock); +} + +void python_events_handle(void) +{ + struct python_process_create_event *event = NULL; + struct bpf_tracer *tracer = NULL; + int count = 0; + do { + event = get_first_event(); + if (!event) + break; + + if (get_sys_uptime() < event->expire_time) + break; + + tracer = event->tracer; + if (tracer) { + pthread_mutex_lock(&tracer->mutex_probes_lock); + python_parse_and_register(event->pid, tracer->tps); + tracer_uprobes_update(tracer); + tracer_hooks_process(tracer, HOOK_ATTACH, &count); + pthread_mutex_unlock(&tracer->mutex_probes_lock); + } + + remove_event(event); + free(event); + + } while (true); +} diff --git a/agent/src/ebpf/user/python_probe.h b/agent/src/ebpf/user/python_probe.h new file mode 100644 index 00000000000..455bc61296e --- /dev/null +++ b/agent/src/ebpf/user/python_probe.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Yunshan Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _BPF_PYTHON_PROBE_H_ +#define _BPF_PYTHON_PROBE_H_ + +#include "tracer.h" + +// Scan /proc/ to get all processes when the agent starts; +int collect_python_uprobe_syms_from_procfs(struct tracer_probes_conf *conf); + +void python_process_exec(int pid); + +void python_process_exit(int pid); + +void python_events_handle(void); + +#endif diff --git a/agent/src/ebpf/user/socket.c b/agent/src/ebpf/user/socket.c index c5878857f74..26bccbe6527 100644 --- a/agent/src/ebpf/user/socket.c +++ b/agent/src/ebpf/user/socket.c @@ -32,6 +32,7 @@ #include "log.h" #include "go_tracer.h" #include "ssl_tracer.h" +#include "python_probe.h" #include "load.h" #include "btf_vmlinux.h" #include "config.h" @@ -194,6 +195,8 @@ static void socket_tracer_set_probes(struct tracer_probes_conf *tps) collect_go_uprobe_syms_from_procfs(tps); collect_ssl_uprobe_syms_from_procfs(tps); + + collect_python_uprobe_syms_from_procfs(tps); } /* ========================================================== @@ -602,12 +605,14 @@ static void process_event(struct process_event_t *e) update_proc_info_cache(e->pid, PROC_EXEC); go_process_exec(e->pid); ssl_process_exec(e->pid); + python_process_exec(e->pid); } else if (e->meta.event_type == EVENT_TYPE_PROC_EXIT) { /* Cache for updating process information used in * symbol resolution. */ update_proc_info_cache(e->pid, PROC_EXIT); go_process_exit(e->pid); ssl_process_exit(e->pid); + python_process_exec(e->pid); } } @@ -1202,6 +1207,7 @@ static void process_events_handle_main(__unused void *arg) go_process_events_handle(); ssl_events_handle(); + python_events_handle(); check_datadump_timeout(); /* check and clean symbol cache */ exec_proc_info_cache_update();