为何sys_enter_execve中execve无法获取程序名,加printf却可行?
eBPF追踪execve时程序名获取异常问题
我在开发eBPF程序时发现一个奇怪的现象:调用execve时有时无法获取程序名,但用execv或直接调用syscall(SYS_execve,...)却能正常获取。两段仅差printf语句的用户态代码,结果完全不同,相关代码如下:
1. eBPF追踪代码
static u32 ebpf_getppid(void) { struct task_struct *task = (struct task_struct *)bpf_get_current_task(); struct task_struct *parent = (struct task_struct *)BPF_CORE_READ(task, real_parent); return BPF_CORE_READ(parent, tgid); } SEC("tp/syscalls/sys_enter_execve") int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter *ctx) { struct epm_command command = {}; const char *filename = (const char *)BPF_CORE_READ(ctx, args[0]); const unsigned long *argv_ptr = (const unsigned long *)BPF_CORE_READ(ctx, args[1]); const unsigned long *envp_ptr = (const unsigned long *)BPF_CORE_READ(ctx, args[2]); char temp[128] = {0}; for(int i = 0; i < 4; i++){ bpf_printk("args[%d]: 0x%lx\n", i, BPF_CORE_READ(ctx, args[i])); } command.process_id = ebpf_getppid(); command.timestamp = bpf_ktime_get_ns(); bpf_get_current_comm(&command.process_name, sizeof(command.process_name)); bpf_probe_read_str(&command.call_prog_name, sizeof(command.call_prog_name), filename); bpf_printk("Parent Process name: %s\n", command.process_name); bpf_printk("Call Process name: %s\n", command.call_prog_name); for(int i = 0; i < 64; i++) { unsigned long arg_ptr = 0; __builtin_memset(temp, 0, sizeof(temp)); bpf_probe_read_str(&arg_ptr, sizeof(arg_ptr), &argv_ptr[i]); if(arg_ptr == 0) { break; } bpf_probe_read_str(temp, sizeof(temp), (void *)arg_ptr); bpf_printk("argv[%d]: %s\n", i, temp); } for(int i = 0; i < 64; i++) { unsigned long env_ptr = 0; __builtin_memset(temp, 0, sizeof(temp)); bpf_probe_read_str(&env_ptr, sizeof(env_ptr), &envp_ptr[i]); if(env_ptr == 0) { break; } bpf_probe_read_str(temp, sizeof(temp), (void *)env_ptr); bpf_printk("envp[%d]: %s\n", i, temp); } bpf_map_update_elem(&epm_execve_map, &command.process_id, &command, BPF_ANY); return 0; }
2. 无法获取程序名的用户态代码
int main() { char *args[] = {"/usr/bin/ls", "-l", NULL, NULL}; char *envp[] = {NULL}; execve("/usr/bin/ls", args, envp); return 0; }
3. 可获取程序名的用户态代码
int main() { char *args[] = {"/usr/bin/ls", "-l", NULL, NULL}; char *envp[] = {NULL}; printf("args addr: %p\n", args); printf("envp addr: %p\n", envp); execve("/usr/bin/ls", args, envp); return 0; }
两段用户态代码仅差添加printf打印args和envp地址,请问该差异导致程序名获取结果不同的具体原因是什么?
问题解答
核心原因是编译器激进的优化策略:
- 对于无printf的版本,编译器分析到
args和envp仅在调用execve时作为参数传递,且之后没有被使用,会将这些变量分配到栈的临时区域,甚至直接将指针值通过寄存器传递给execve,不会在栈上保留完整的数组内容。当execve进入内核触发tracepoint时,用户态的栈帧可能已经被内核栈覆盖,或者这部分临时内存已经被标记为可回收,导致eBPF通过bpf_probe_read_str读取时无法获取有效数据。 - 添加printf语句后,编译器因为需要打印
args和envp的地址,会强制将这两个数组保留在栈的稳定区域,不会对其进行内存回收或寄存器替换优化。此时execve调用时,栈上的args、envp以及指向的字符串都处于有效状态,eBPF可以正常读取到程序名。
另外,execv或直接调用syscall(SYS_execve)能正常工作的原因是:这两种调用方式的参数传递逻辑更直接,编译器难以进行激进优化,参数对应的内存区域会被保留到系统调用触发时。
内容的提问来源于stack exchange,提问作者Yong Fu
相关产品推荐
相关产品推荐

