Linux内核进程创建trace钩子

在 Linux 内核中跟踪进程创建事件有多种方法,以下是几种常用的技术方案和实现思路:


一、使用 Tracepoints(推荐)

内核已内置 sched_process_fork tracepoint,可捕获进程创建事件。

1. 查看可用 tracepoints

1
cat /sys/kernel/debug/tracing/available_events | grep sched_process_fork

2. 通过 perf 工具快速测试

1
perf record -e sched:sched_process_fork -a

3. 内核模块示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <linux/tracepoint.h>
#include <trace/events/sched.h>

static void trace_fork_handler(void *ignore, struct task_struct *parent, struct task_struct *child)
{
printk("Process created: parent=%s[%d] -> child=%s[%d]\n",
parent->comm, parent->pid, child->comm, child->pid);
}

static int __init my_init(void)
{
register_trace_sched_process_fork(trace_fork_handler, NULL);
return 0;
}

static void __exit my_exit(void)
{
unregister_trace_sched_process_fork(trace_fork_handler, NULL);
}

module_init(my_init);
module_exit(my_exit);

二、使用 kprobes(动态Hook)

当需要更底层的跟踪时,可以 Hook do_fork(旧内核)或 kernel_clone(新内核)等函数。

内核模块示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <linux/kprobes.h>

static struct kprobe kp = {
.symbol_name = "do_fork", // 或 "kernel_clone"(根据内核版本)
};

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
// 参数解析依赖内核版本和体系结构
// 例如:do_fork 的参数可能通过寄存器传递
printk("Process fork triggered\n");
return 0;
}

static int __init my_init(void)
{
kp.pre_handler = handler_pre;
register_kprobe(&kp);
return 0;
}

static void __exit my_exit(void)
{
unregister_kprobe(&kp);
}

module_init(my_init);
module_exit(my_exit);

三、使用 eBPF(现代方案)

通过 eBPF 程序安全地附加到 tracepoint 或 kprobes。

1. 使用 tracepoint 的 eBPF 示例(需要 libbpf)

1
2
3
4
5
6
7
8
9
// 使用 SEC 宏附加到 tracepoint
SEC("tracepoint/sched/sched_process_fork")
int handle_fork(struct trace_event_raw_sched_process_fork *ctx)
{
struct task_struct *parent = (struct task_struct *)ctx->parent_pid;
struct task_struct *child = (struct task_struct *)ctx->child_pid;
bpf_printk("Fork: parent=%d -> child=%d\n", parent->pid, child->pid);
return 0;
}

2. 使用 kprobe 的 eBPF 示例

1
2
3
4
5
6
7
SEC("kprobe/do_fork")
int BPF_KPROBE(do_fork_hook)
{
pid_t parent_pid = bpf_get_current_pid_tgid() >> 32;
bpf_printk("do_fork called by PID %d\n", parent_pid);
return 0;
}

四、其他方法

  1. SystemTap

    1
    stap -e 'probe kernel.function("do_fork") { printf("fork by %s(%d)\n", execname(), pid()) }'
  2. Ftrace

    1
    2
    echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_fork/enable
    cat /sys/kernel/debug/tracing/trace_pipe

注意事项

  1. 内核版本差异

    • 进程创建函数在历史上经历过变化:
      • Linux 4.2 之前:do_fork
      • Linux 5.4+:kernel_clone
    • 使用前需确认目标内核符号。
  2. 安全性

    • 内核模块需要 root 权限加载。
    • eBPF 程序需要 CAP_BPF 权限(Linux 5.8+)。
  3. 性能影响

    • kprobes 在高频调用场景可能影响性能。
    • eBPF 程序需通过验证器,安全性更高。

总结

  • 简单监控:使用 perfftrace 工具。
  • 生产环境:eBPF + BCC/libbpf。
  • 深度调试:内核模块 + tracepoints/kprobes。

根据具体需求选择合适方案。