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 ) __field( pid_t, pid ) __field( pid_t, old_pid ) ), TP_fast_assign( __assign_str(filename, bprm->filename); __assign_str(cmdline, bprm->p); __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>
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)) break;
if (copy_from_user(ptr, argp, len)) break;
size_t arg_len = strlen(ptr) + 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; }
|
关键注意事项:
用户空间指针安全:
- 使用
copy_from_user()
安全复制数据,避免直接解引用用户空间指针。
- 检查
get_user()
返回值,防止非法地址访问。
缓冲区大小:
- 预先分配固定缓冲区(如
cmdline[256]
),避免溢出。
- 生产环境建议使用动态分配(
kvmalloc()
)或循环读取。
内核版本适配:
- 确认
struct linux_binprm
是否包含 p
字段(新内核)。
- 旧内核需手动解析
argv
和 argc
。
并发与内存屏障:
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>
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; int i;
for (i = 0; i < bprm->argc && len > 0; i++) { char __user *argp; if (get_user(argp, bprm->argv + i)) break;
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 = get_task_mm(task); if (!mm) return;
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->arg_start
和 mm->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);
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
读取)。