You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:20:03