Linux内核trace_sched_process_exec中获取cmdline

在 Linux 内核中,trace_sched_process_exec 跟踪点用于捕获进程执行(exec)事件。要获取进程的 cmdline(命令行参数),可以通过解析跟踪点参数或使用内核辅助函数实现。以下是详细方法和代码示例:


方法 1:通过 tracepoint 参数直接获取(推荐)

在较新内核(4.3+)中,trace_sched_process_exec 直接提供了 cmdline 字段:

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

TRACE_EVENT(sched_process_exec,
TP_PROTO(struct task_struct *p, pid_t old_pid, struct linux_binprm *bprm),
TP_ARGS(p, old_pid, bprm),
TP_STRUCT__entry(
__string( filename, bprm->filename )
__string( cmdline, bprm->p ) // 命令行参数存储在 bprm->p
__field( pid_t, pid )
__field( pid_t, old_pid )
),
TP_fast_assign(
__assign_str(filename, bprm->filename);
__assign_str(cmdline, bprm->p); // 直接获取 cmdline
__entry->pid = p->pid;
__entry->old_pid = old_pid;
),
TP_printk("filename=%s cmdline=%s pid=%d old_pid=%d",
__get_str(filename), __get_str(cmdline),
__entry->pid, __entry->old_pid)
);

方法 2:手动解析 argv(兼容旧内核)

若内核不支持直接获取 cmdline,需从 bprm->argv 逐项读取:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <linux/tracepoint.h>
#include <trace/events/sched.h>
#include <linux/uaccess.h> // copy_from_user

static void trace_sched_process_exec_probe(void *data, struct task_struct *p, pid_t old_pid, struct linux_binprm *bprm)
{
char cmdline[256] = {0}; // 存储拼接后的命令行
char __user *__user *argv = bprm->argv; // 用户空间指针数组
char *ptr = cmdline;
int len = sizeof(cmdline) - 1; // 保留结束符空间
int i;

// 遍历所有命令行参数
for (i = 0; i < bprm->argc; i++) {
char __user *argp;
if (get_user(argp, argv + i)) // 获取第 i 个参数的地址
break;

// 从用户空间复制参数
if (copy_from_user(ptr, argp, len))
break;

// 计算当前参数长度
size_t arg_len = strlen(ptr) + 1; // +1 用于空格或结束符
ptr += arg_len - 1; // 移动到字符串末尾

// 添加空格(非最后一个参数)
if (i < bprm->argc - 1 && len > 1) {
*ptr++ = ' ';
*ptr = '\0';
len--;
}
len -= arg_len;
if (len <= 0) break; // 缓冲区不足
}

printk("PID %d executed: %s\n", p->pid, cmdline);
}

// 注册跟踪点
static int __init trace_init(void)
{
register_trace_sched_process_exec(trace_sched_process_exec_probe, NULL);
return 0;
}

关键注意事项:

  1. 用户空间指针安全

    • 使用 copy_from_user() 安全复制数据,避免直接解引用用户空间指针。
    • 检查 get_user() 返回值,防止非法地址访问。
  2. 缓冲区大小

    • 预先分配固定缓冲区(如 cmdline[256]),避免溢出。
    • 生产环境建议使用动态分配(kvmalloc())或循环读取。
  3. 内核版本适配

    • 确认 struct linux_binprm 是否包含 p 字段(新内核)。
    • 旧内核需手动解析 argvargc
  4. 并发与内存屏障

    • exec 期间进程内存有效,但需确保在进程退出前完成读取。

替代方案:使用 procfs 接口

若只需在用户空间获取 cmdline,可直接读取 /proc/<pid>/cmdline

1
cat /proc/1234/cmdline | tr '\0' ' '  # 将空字符替换为空格

总结

方法 适用场景 优点 缺点
直接访问 bprm->p 内核 ≥4.3 高效安全 旧内核不支持
手动解析 bprm->argv 兼容旧内核 通用性强 需处理用户空间指针和缓冲区
读取 /proc 用户空间调试 无需内核模块 无法实时嵌入内核事件处理流程

根据内核版本和需求选择合适方案,优先使用直接访问 bprm->p 的方式以简化代码。

Linux 5.10内核获取进程的cmdline

在 Linux 5.10 内核中,获取进程的 cmdline 可以通过多种方式实现,具体取决于使用场景(如内核模块、跟踪点、procfs 接口等)。以下是几种常见的方法:


方法 1:通过 struct linux_binprm 获取(适用于 exec 事件)

execve() 系统调用期间,cmdline 参数会传递给 struct linux_binprm,可以通过 trace_sched_process_exec 跟踪点或直接解析 bprm 获取。

示例代码(内核模块)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <linux/module.h>
#include <linux/tracepoint.h>
#include <trace/events/sched.h>
#include <linux/uaccess.h> // copy_from_user

static void trace_exec_handler(void *ignore, struct task_struct *p, pid_t old_pid, struct linux_binprm *bprm)
{
char cmdline[256] = {0};
char *buf = cmdline;
int len = sizeof(cmdline) - 1; // 保留 \0
int i;

// 遍历 argv 并拼接 cmdline
for (i = 0; i < bprm->argc && len > 0; i++) {
char __user *argp;
if (get_user(argp, bprm->argv + i))
break;

// 复制单个参数到 buf
if (copy_from_user(buf, argp, len))
break;

// 计算当前参数长度
size_t arg_len = strnlen(buf, len);
buf += arg_len;
len -= arg_len;

// 如果不是最后一个参数,添加空格
if (i < bprm->argc - 1 && len > 0) {
*buf++ = ' ';
*buf = '\0';
len--;
}
}

pr_info("PID %d exec: %s\n", p->pid, cmdline);
}

static int __init trace_init(void)
{
register_trace_sched_process_exec(trace_exec_handler, NULL);
return 0;
}

static void __exit trace_exit(void)
{
unregister_trace_sched_process_exec(trace_exec_handler, NULL);
}

module_init(trace_init);
module_exit(trace_exit);
MODULE_LICENSE("GPL");

关键点

  • bprm->argv 是用户空间指针数组,必须用 copy_from_user() 读取。
  • bprm->argc 表示参数数量。
  • 需要处理缓冲区溢出(len 控制)。

方法 2:通过 struct task_struct 获取(适用于任意进程)

如果不在 exec 事件中,可以通过 struct task_struct 访问进程的 cmdline,但需要从 mm->arg_start 读取用户空间内存。

示例代码

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
29
30
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/uaccess.h>

void get_process_cmdline(struct task_struct *task)
{
char cmdline[256] = {0};
unsigned long len = 0;
struct mm_struct *mm;

// 获取进程的 mm_struct(需 RCU 保护)
mm = get_task_mm(task);
if (!mm)
return;

// 读取 arg_start 到 arg_end 之间的 cmdline
len = mm->arg_end - mm->arg_start;
if (len > sizeof(cmdline) - 1)
len = sizeof(cmdline) - 1;

if (len > 0) {
if (copy_from_user(cmdline, (char __user *)mm->arg_start, len))
pr_warn("Failed to read cmdline for PID %d\n", task->pid);
else
cmdline[len] = '\0'; // 确保字符串终止
}

pr_info("PID %d cmdline: %s\n", task->pid, cmdline);
mmput(mm); // 释放 mm
}

关键点

  • mm->arg_startmm->arg_end 存储了 cmdline 在用户空间的地址范围。
  • 必须用 copy_from_user() 读取,因为它在用户空间。
  • 需要 get_task_mm() 获取 mm_struct,并在完成后 mmput()

方法 3:直接读取 /proc/<pid>/cmdline(用户空间)

如果不需要在内核中处理,可以直接读取 /proc 文件系统:

1
cat /proc/1234/cmdline | tr '\0' ' '

或使用 C 代码:

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
29
30
31
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
return 1;
}

char path[64];
snprintf(path, sizeof(path), "/proc/%s/cmdline", argv[1]);

FILE *f = fopen(path, "r");
if (!f) {
perror("fopen");
return 1;
}

char cmdline[4096];
size_t len = fread(cmdline, 1, sizeof(cmdline) - 1, f);
fclose(f);

// 替换 '\0' 为空格
for (size_t i = 0; i < len; i++) {
if (cmdline[i] == '\0')
cmdline[i] = ' ';
}

printf("PID %s cmdline: %s\n", argv[1], cmdline);
return 0;
}

总结

方法 适用场景 优点 缺点
struct linux_binprm execve() 跟踪 直接获取 argv 仅适用于 exec 事件
struct task_struct 任意进程 通用性强 需处理用户空间内存
/proc/<pid>/cmdline 用户空间 简单可靠 无法用于内核模块
  • 推荐
    • 如果是 exec 事件(如 trace_sched_process_exec),使用方法 1。
    • 如果需要获取任意进程的 cmdline,使用方法 2(但要注意内存访问安全)。
    • 如果只是调试,使用方法 3(/proc 读取)。