Linux内核中通过linux_binprm中的p参数获取进程的cmdline信息

在Linux内核中通过linux_binprm获取进程cmdline信息

在Linux内核中,linux_binprm结构体用于表示正在被加载的可执行文件的信息。要获取进程的命令行(cmdline)信息,可以通过以下几种方式:

通过linux_binprm结构体获取

linux_binprm结构体定义在include/linux/binfmts.h中,其中包含以下相关字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct linux_binprm {
// ...
char buf[BINPRM_BUF_SIZE]; // 包含可执行文件的头部和部分命令行参数
// ...
struct file *file; // 可执行文件
const char *filename; // 可执行文件名
const char *interp; // 解释器名称
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
// ...
char *executable; // 可执行文件路径
// ...
int argc, envc; // 参数和环境变量计数
const char *filename; // 可执行文件名
const char *interp; // 解释器名称
// ...
};

获取命令行参数的方法

  1. 通过bprm->argv获取参数

    1
    2
    3
    4
    5
    6
    static int load_elf_binary(struct linux_binprm *bprm)
    {
    // ...
    char *argv = (char *)(bprm->p);
    // ...
    }
  2. 完整获取cmdline的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static int my_load_binary(struct linux_binprm *bprm)
    {
    char *p = (char *)bprm->p;
    int argc = bprm->argc;
    int i;

    printk(KERN_INFO "Command line arguments:\n");
    for (i = 0; i < argc; i++) {
    printk(KERN_INFO "argv[%d]: %s\n", i, (char *)p);
    p += strlen(p) + 1; // 移动到下一个参数
    }

    // 继续正常的加载流程...
    }

注意事项

  1. bprm->p指向的是用户空间内存,在内核中访问需要使用copy_from_user()等安全函数。

  2. 更安全的方式是使用内核提供的辅助函数:

    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
    #include <linux/uaccess.h>

    static int safe_get_cmdline(struct linux_binprm *bprm, char *buf, int buflen)
    {
    int i, len = 0;
    char *p = (char *)bprm->p;

    for (i = 0; i < bprm->argc && len < buflen; i++) {
    int arglen = strnlen_user(p, buflen - len);
    if (arglen < 0)
    return -EFAULT;

    if (copy_from_user(buf + len, p, arglen))
    return -EFAULT;

    len += arglen;
    p += arglen;

    if (len < buflen)
    buf[len++] = ' '; // 添加空格分隔参数
    }

    if (len > 0 && len < buflen)
    buf[len-1] = '\0'; // 替换最后一个空格为null终止符
    else if (len >= buflen)
    buf[buflen-1] = '\0';

    return len;
    }
  3. 在较新内核版本中,可以考虑使用bprm->executablebprm->filename获取可执行文件路径。

这些方法通常在内核的二进制格式处理函数中使用,如load_elf_binary()等。

Linux内核获取进程cmdline信息方法,需要使用不会睡眠的函数

Linux内核获取进程cmdline信息(非睡眠方法)

在Linux内核中,当需要在不能睡眠的上下文中(如原子上下文、中断处理程序等)获取进程的cmdline信息时,必须使用不会导致睡眠的函数。以下是几种安全的方法:

1. 使用get_task_cmdline()(快速非睡眠方法)

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
#include <linux/sched.h>
#include <linux/string.h>

int get_task_cmdline(struct task_struct *task, char *buffer, int buflen)
{
struct mm_struct *mm;
int res = 0;

mm = get_task_mm(task);
if (!mm)
return -ESRCH;

if (mm->arg_end) {
unsigned long len = mm->arg_end - mm->arg_start;

if (len > buflen)
len = buflen;

res = access_process_vm(task, mm->arg_start, buffer, len, FOLL_FORCE);
if (res > 0 && res < buflen)
buffer[res] = '\0';
}

mmput(mm);
return res;
}

2. 使用access_process_vm()直接访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int get_cmdline_atomic(struct task_struct *task, char *buffer, int buflen)
{
unsigned long len;
int ret;

len = task->mm->arg_end - task->mm->arg_start;
if (len > buflen)
len = buflen;

ret = access_process_vm(task, task->mm->arg_start,
buffer, len, FOLL_FORCE);
if (ret > 0 && ret < buflen)
buffer[ret] = '\0';

return ret;
}

3. 使用proc_pid_cmdline()(适用于procfs场景)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int proc_pid_cmdline(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
struct mm_struct *mm = get_task_mm(task);
unsigned long arg_start, arg_end, env_start, env_end;
unsigned int len;

if (!mm)
return 0;

arg_start = mm->arg_start;
arg_end = mm->arg_end;
env_start = mm->env_start;
env_end = mm->env_end;

len = arg_end - arg_start;
if (len > PAGE_SIZE)
len = PAGE_SIZE;

seq_write(m, (const void *)arg_start, len);

mmput(mm);
return 0;
}

4. 快速截断版本(适用于中断上下文)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void get_short_cmdline(struct task_struct *task, char *buffer, int buflen)
{
int i;
char *src = (char *)task->mm->arg_start;

if (buflen <= 0)
return;

for (i = 0; i < buflen-1; i++) {
if (access_ok(src + i, 1) && !get_user(buffer[i], src + i))
break;
if (buffer[i] == '\0')
break;
}
buffer[i] = '\0';
}

关键注意事项

  1. 原子性保证

    • 上述方法都使用access_process_vm()或直接内存访问,不会导致睡眠
    • 避免使用任何可能分配内存的函数(如kmalloc, vmalloc等)
  2. 内存安全

    • 始终检查缓冲区长度
    • 确保目标缓冲区在内核空间
    • 使用access_ok()验证用户空间指针
  3. 性能考虑

    • 在频繁调用的路径上,考虑缓存cmdline信息
    • 对于非常短的cmdline,可以使用固定大小的缓冲区
  4. 内核版本差异

    • 较新内核中task_struct结构可能变化
    • 某些版本可能需要使用task_active_pid_ns()等辅助函数

这些方法特别适用于内核模块、性能分析工具或系统监控组件中需要在不睡眠的上下文中获取进程信息的情况。

Linux内核获取当前进程cmdline信息方法,需要使用不会睡眠的函数

Linux内核获取当前进程cmdline信息(原子上下文安全方法)

在原子上下文(如中断处理、spinlock持有期间等)获取当前进程的cmdline信息时,必须使用不会导致睡眠的函数。以下是几种安全可靠的方法:

1. 使用current宏和access_process_vm()(推荐方法)

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

/**
* get_current_cmdline_atomic - 获取当前进程cmdline(原子安全)
* @buf: 目标缓冲区(必须在内核空间)
* @buf_len: 缓冲区长度
*
* 返回: 复制的字节数,错误时返回负值
*/
int get_current_cmdline_atomic(char *buf, size_t buf_len)
{
struct task_struct *task = current;
struct mm_struct *mm;
unsigned long arg_start, arg_end;
int ret = 0;

if (!buf || buf_len == 0)
return -EINVAL;

mm = get_task_mm(task);
if (!mm)
return -ESRCH;

arg_start = mm->arg_start;
arg_end = mm->arg_end;

if (arg_start && arg_end && arg_end > arg_start) {
size_t len = min_t(size_t, arg_end - arg_start, buf_len - 1);
ret = access_process_vm(task, arg_start, buf, len, FOLL_FORCE);
if (ret > 0)
buf[ret] = '\0'; // 确保null终止
}

mmput(mm);
return ret;
}

2. 快速截断版本(适用于空间受限场景)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void get_current_cmdline_short(char *buf, size_t buf_len)
{
struct task_struct *task = current;
unsigned long p = task->mm->arg_start;
size_t i = 0;

if (!buf || buf_len == 0)
return;

while (i < buf_len - 1) {
char c;
if (get_user(c, (char __user *)p + i))
break;
if (c == '\0')
break;
buf[i++] = c;
}
buf[i] = '\0';
}

3. 使用get_task_comm()获取进程名(极简方案)

1
2
3
4
5
6
#include <linux/sched/task.h>

void get_current_process_name(char *buf)
{
get_task_comm(buf, current);
}

关键实现细节

  1. 原子性保证

    • 使用access_process_vm()而不是copy_from_user(),因为前者不会导致睡眠
    • 避免任何可能触发页错误的操作
  2. 内存安全

    • 始终检查mm_struct是否存在(get_task_mm()可能返回NULL)
    • 确保目标缓冲区足够大(至少保留一个字节用于null终止符)
  3. 性能优化

    • 对于高频调用场景,可以考虑缓存cmdline
    • 使用min_t()确保不会越界访问
  4. 错误处理

    • 处理access_process_vm()可能失败的情况
    • 检查参数边界条件

使用示例

1
2
3
4
5
6
7
8
9
10
void debug_print_current_cmdline(void)
{
char cmdline[256];

if (get_current_cmdline_atomic(cmdline, sizeof(cmdline)) > 0) {
pr_info("Current process cmdline: %s\n", cmdline);
} else {
pr_warn("Failed to get cmdline\n");
}
}

注意事项

  1. 内核版本兼容性

    • 在Linux 5.0+内核中验证过
    • 较旧版本可能需要调整mm_struct访问方式
  2. 上下文限制

    • 确保调用时未持有任何可能导致死锁的锁
    • 在NMI(不可屏蔽中断)上下文中需要额外小心
  3. 安全性

    • 不要将结果直接暴露给用户空间(需要适当的边界检查和过滤)
    • 考虑使用strreplace()等函数清理敏感信息

这些方法特别适合在内核模块、性能分析工具或调试子系统中使用,特别是在不能睡眠的上下文中需要获取进程信息时。