【问题标题】:Execute/invoke user-space program, and get its pid, from a kernel module从内核模块执行/调用用户空间程序,并获取其 pid
【发布时间】:2014-03-07 06:48:36
【问题描述】:

我查看了Kernel APIs, Part 1: Invoking user - space applications from the kernelExecuting a user-space function from the kernel space - Stack Overflow - 这是一个小内核模块callmodule.c,证明:

// http://people.ee.ethz.ch/~arkeller/linux/code/usermodehelper.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>

static int __init callmodule_init(void)
{
    int ret = 0;
    char userprog[] = "/path/to/mytest";
    char *argv[] = {userprog, "2", NULL };
    char *envp[] = {"HOME=/", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };

    printk("callmodule: init %s\n", userprog);
    /* last parameter: 1 -> wait until execution has finished, 0 go ahead without waiting*/
    /* returns 0 if usermode process was started successfully, errorvalue otherwise*/
    /* no possiblity to get return value of usermode process*/
    ret = call_usermodehelper(userprog, argv, envp, UMH_WAIT_EXEC);
    if (ret != 0)
        printk("error in call to usermodehelper: %i\n", ret);
    else
        printk("everything all right\n");
        return 0;
}

static void __exit callmodule_exit(void)
{
    printk("callmodule: exit\n");
}

module_init(callmodule_init);
module_exit(callmodule_exit);
MODULE_LICENSE("GPL");

...Makefile:

obj-m += callmodule.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

当我通过sudo insmod ./callmodule.ko &amp;&amp; sudo rmmod callmodule 运行它时,我进入/var/log/syslog

Feb 10 00:42:45 mypc kernel: [71455.260355] callmodule: init /path/to/mytest
Feb 10 00:42:45 mypc kernel: [71455.261218] everything all right
Feb 10 00:42:45 mypc kernel: [71455.286131] callmodule: exit

... 这显然意味着一切顺利。 (使用 Linux 2.6.38-16-generic #67-Ubuntu SMP)

我的问题是 - 我怎样才能获得从内核模块实例化的进程的 PID?除了call_usermodehelper,还有没有类似的进程,可以让我在内核空间实例化一个用户空间进程,并获取它的pid?


注意,可能无法使用call_usermodehelper获取实例化进程PID:

Re: call_usermodehelper's pid ? — Linux Kernel Newbies

我想从内部创建一个用户空间进程 一个内核模块,并且能够杀死它,发送信号 对它,等等......

我可以知道它的 pid 吗?

不,你不能。但是由于在实现内部 pid 是已知的, 使它可用的补丁不会太难(注意,错误 在内核中始终为负,pid 为正,限制为 2**16)。 不过,您必须修改所有期望 0 成功的调用者。

我浏览了一下源代码,似乎最终有一个调用链:call_usermodehelper -> call_usermodehelper_setup -> __call_usermodehelper,看起来像:

static void __call_usermodehelper(struct work_struct *work)
{
    struct subprocess_info *sub_info =
        container_of(work, struct subprocess_info, work);
    // ...
    if (wait == UMH_WAIT_PROC)
        pid = kernel_thread(wait_for_helper, sub_info,
                    CLONE_FS | CLONE_FILES | SIGCHLD);
    else
        pid = kernel_thread(____call_usermodehelper, sub_info,
                    CLONE_VFORK | SIGCHLD);
...

...所以使用了内核线程的PID,但它没有保存在任何地方;此外,work_structsubprocess_info 都没有 pid 字段(task_struct 有,但这里似乎没有使用 task_struct)。记录这个 pid 需要更改内核源代码 - 我想避免这种情况,这就是为什么我也对 call_usermodehelper 以外的方法感兴趣的原因......

【问题讨论】:

  • 我希望有人能解释一下这个问题到底是什么“离题”:它是关于 Linux 内核的,对于“Unix 和 Linux”堆栈交换来说,这还不够吗????但是我已经足够知道这是一种浪费的努力,当你受到密切关注时......至少也许有人可以建议是否有更合适的堆栈站点可以将它移到等等 - 但同样,我'已经够久了。
  • 也从这里 - 谢谢@slm;如果我在移动帖子时以某种方式获得了移动解释,我当然不会像我在上面的评论中那样抱怨:) ...干杯!
  • 您能告诉我们您需要 PID 的原因吗?可能还有其他方法可以实现您想要做的事情......或者不是;-)
  • 谢谢,@ChristopheAugier - 基本上,我正在尝试解决stackoverflow.com/questions/21629045/… - 现在,我想我可以编写一个内核模块来生成一个用户进程,“暂停”它,检索其main 的绝对地址,并在ftrace 中设置为kprobe。恢复用户进程,然后让开。当然,要做到这一点,首先我必须得到生成进程的 PID,这就是这个问题的意义所在。干杯!

标签: c linux linux-kernel


【解决方案1】:

我对@9​​87654321@中的实现的理解的初步回答。

如果您查看call_usermodehelper 的代码,您会看到它调用call_usermodehelper_setup,然后调用call_usermodehelper_exec

call_usermodehelper_setup 将一个 init 函数作为参数,该函数将在 do_execve 之前执行。我相信当init函数被执行时current的值会给你用户进程的task_struct

所以要获得 pid,您需要:

  1. 在您的代码中复制call_usermodehelper 的实现。
  2. 定义一个初始化函数,您将把它作为参数传递给call_usermodehelper_setup
  3. 在 init 函数中检索当前的 task_struct 并依次检索 PID。

【讨论】:

  • 非常感谢@ChristopheAugier - 当我看到你的答案时,我的答案发布到了here 的一半,所以我首先完成了......那个答案使用pid_task(find_vpid(... 来获得@来自 pid 的 987654334@,但可能按照您的建议直接检索task_struct 会更好,我猜?不过,如果有更直接的方法可以做到这一点,我仍然会感兴趣......再次感谢 - 干杯!
【解决方案2】:

嗯,这很乏味......至少在我的平台上,至少在我的平台上,这是一种相当老套的方法,如callmodule.c(可以使用与上面相同的 Makefile)。由于我无法相信 this 是应该这样做的方式,因此仍然欢迎更正确的答案(希望也可以使用我可以测试的代码示例)。但至少,对于 2.6.38 版本,它仅作为内核模块完成工作 - 无需修补内核本身 - 这对我来说非常重要。

基本上,我复制了所有函数(以“B”后缀重命名),直到 PID 可用为止。然后我使用带有额外字段的subprocess_info 的副本来保存它(尽管这不是绝对必要的:为了不弄乱与返回值相关的函数签名,我必须将 pid 保存为全局变量;把它留作练习)。现在,当我在/var/log/syslog 中运行sudo insmod ./callmodule.ko &amp;&amp; sudo rmmod callmodule 时,我得到:

Feb 10 18:53:02 mypc kernel: [ 2942.891886] callmodule: > init /path/to/mytest
Feb 10 18:53:02 mypc kernel: [ 2942.891912] callmodule: symbol @ 0xc1065b60 is wait_for_helper+0x0/0xb0
Feb 10 18:53:02 mypc kernel: [ 2942.891923] callmodule: symbol @ 0xc1065ed0 is ____call_usermodehelper+0x0/0x90
Feb 10 18:53:02 mypc kernel: [ 2942.891932] callmodule:a: pid 0
Feb 10 18:53:02 mypc kernel: [ 2942.891937] callmodule:b: pid 0
Feb 10 18:53:02 mypc kernel: [ 2942.893491] callmodule: : pid 23306
Feb 10 18:53:02 mypc kernel: [ 2942.894474] callmodule:c: pid 23306
Feb 10 18:53:02 mypc kernel: [ 2942.894483] callmodule: everything all right; pid 23306
Feb 10 18:53:02 mypc kernel: [ 2942.894494] callmodule: pid task a: ec401940 c: mytest p: [23306] s: runnable
Feb 10 18:53:02 mypc kernel: [ 2942.894502] callmodule: parent task a: f40aa5e0 c: kworker/u:1 p: [14] s: stopped
Feb 10 18:53:02 mypc kernel: [ 2942.894510] callmodule: - mytest [23306]
Feb 10 18:53:02 mypc kernel: [ 2942.918500] callmodule: < exit

这里的一个令人讨厌的问题是,一旦你开始复制函数,在某个时间你会遇到一个点,即使用未导出的内核函数,就像在这种情况下 wait_for_helper。我所做的基本上是查看/proc/kallsyms(记住sudo!)以获取绝对地址,例如wait_for_helper,然后将内核模块中的那些硬编码为函数指针 - 似乎有效。另一个问题是内核源代码中的函数引用enum umh_wait,它不能用作模块的参数(需要简单地转换为使用int)。

因此模块启动用户空间进程,获取 PID(注意到“What the kernel calls PIDs are actually kernel-level thread ids (often called TIDs) ... What's considered a PID in the POSIX sense of "process", on the other hand, is called a "thread group ID" or "TGID" in the kernel.),获取相应的 task_struct 及其父级,并尝试列出父进程和衍生进程本身的所有子进程。所以我可以看到kworker/u:1 通常是父级,它除了mytest 之外没有其他子级 - 而且由于mytest 非常简单(在我的情况下,只是一次写入磁盘文件),它不会产生任何线程它自己的,所以它也没有孩子。

我遇到了几个需要重新启动的糟糕问题 - 我认为它们现在已经解决,但以防万一,请注意购买者。

这是callmodule.c 代码(末尾有一些注释/链接):

// callmodule.c with pid, url: https://stackoverflow.com/questions/21668727/

#include <linux/module.h>
#include <linux/slab.h> //kzalloc
#include <linux/syscalls.h> // SIGCHLD, ... sys_wait4, ...
#include <linux/kallsyms.h> // kallsyms_lookup, print_symbol

// global variable - to avoid intervening too much in the return of call_usermodehelperB:
static int callmodule_pid;

// >>>>>>>>>>>>>>>>>>>>>>

// modified kernel functions - taken from
// http://lxr.missinglinkelectronics.com/linux+v2.6.38/+save=include/linux/kmod.h
// http://lxr.linux.no/linux+v2.6.38/+save=kernel/kmod.c

// define a modified struct (with extra pid field) here:
struct subprocess_infoB {
    struct work_struct work;
    struct completion *complete;
    char *path;
    char **argv;
    char **envp;
    int wait; //enum umh_wait wait;
    int retval;
    int (*init)(struct subprocess_info *info);
    void (*cleanup)(struct subprocess_info *info);
    void *data;
  pid_t pid;
};

// forward declare:
struct subprocess_infoB *call_usermodehelper_setupB(char *path, char **argv,
                          char **envp, gfp_t gfp_mask);

static inline int
call_usermodehelper_fnsB(char *path, char **argv, char **envp,
            int wait, //enum umh_wait wait,
            int (*init)(struct subprocess_info *info),
            void (*cleanup)(struct subprocess_info *), void *data)
{
    struct subprocess_info *info;
    struct subprocess_infoB *infoB;
    gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
  int ret;

  populate_rootfs_wait(); // is in linux-headers-2.6.38-16-generic/include/linux/kmod.h

    infoB = call_usermodehelper_setupB(path, argv, envp, gfp_mask);
  printk(KBUILD_MODNAME ":a: pid %d\n", infoB->pid);
  info = (struct subprocess_info *) infoB;

    if (info == NULL)
        return -ENOMEM;

    call_usermodehelper_setfns(info, init, cleanup, data);
  printk(KBUILD_MODNAME ":b: pid %d\n", infoB->pid);

  // this must be called first, before infoB->pid is populated (by __call_usermodehelperB):
  ret = call_usermodehelper_exec(info, wait);

  // assign global pid here, so rest of the code has it:
  callmodule_pid = infoB->pid;
  printk(KBUILD_MODNAME ":c: pid %d\n", callmodule_pid);

    return ret;
}

static inline int
call_usermodehelperB(char *path, char **argv, char **envp, int wait) //enum umh_wait wait)
{
    return call_usermodehelper_fnsB(path, argv, envp, wait,
                       NULL, NULL, NULL);
}

/* This is run by khelper thread  */
static void __call_usermodehelperB(struct work_struct *work)
{
    struct subprocess_infoB *sub_infoB =
        container_of(work, struct subprocess_infoB, work);
    int wait = sub_infoB->wait; // enum umh_wait wait = sub_info->wait;
    pid_t pid;
    struct subprocess_info *sub_info;
  // hack - declare function pointers, to use for wait_for_helper/____call_usermodehelper
  int (*ptrwait_for_helper)(void *data);
  int (*ptr____call_usermodehelper)(void *data);
  // assign function pointers to verbatim addresses as obtained from /proc/kallsyms
  ptrwait_for_helper = (void *)0xc1065b60;
  ptr____call_usermodehelper = (void *)0xc1065ed0;

  sub_info = (struct subprocess_info *)sub_infoB;

    /* CLONE_VFORK: wait until the usermode helper has execve'd
     * successfully We need the data structures to stay around
     * until that is done.  */
    if (wait == UMH_WAIT_PROC)
        pid = kernel_thread((*ptrwait_for_helper), sub_info, //(wait_for_helper, sub_info,
                    CLONE_FS | CLONE_FILES | SIGCHLD);
    else
        pid = kernel_thread((*ptr____call_usermodehelper), sub_info, //(____call_usermodehelper, sub_info,
                    CLONE_VFORK | SIGCHLD);

  printk(KBUILD_MODNAME ": : pid %d\n", pid);
  // grab and save the pid here:
  sub_infoB->pid = pid;
    switch (wait) {
    case UMH_NO_WAIT:
        call_usermodehelper_freeinfo(sub_info);
        break;

    case UMH_WAIT_PROC:
        if (pid > 0)
            break;
        /* FALLTHROUGH */
    case UMH_WAIT_EXEC:
        if (pid < 0)
            sub_info->retval = pid;
        complete(sub_info->complete);
    }
}

/**
 * call_usermodehelper_setup - prepare to call a usermode helper
 */
struct subprocess_infoB *call_usermodehelper_setupB(char *path, char **argv,
                          char **envp, gfp_t gfp_mask)
{
    struct subprocess_infoB *sub_infoB;
    sub_infoB = kzalloc(sizeof(struct subprocess_infoB), gfp_mask);
    if (!sub_infoB)
        goto out;

    INIT_WORK(&sub_infoB->work, __call_usermodehelperB);
    sub_infoB->path = path;
    sub_infoB->argv = argv;
    sub_infoB->envp = envp;
  out:
    return sub_infoB;
}

// <<<<<<<<<<<<<<<<<<<<<<

static int __init callmodule_init(void)
{
    int ret = 0;
  char userprog[] = "/path/to/mytest";
    char *argv[] = {userprog, "2", NULL };
    char *envp[] = {"HOME=/", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
  struct task_struct *p;
  struct task_struct *par;
  struct task_struct *pc;
  struct list_head *children_list_head;
  struct list_head *cchildren_list_head;
  char *state_str;

    printk(KBUILD_MODNAME ": > init %s\n", userprog);
    /* last parameter: 1 -> wait until execution has finished, 0 go ahead without waiting*/
    /* returns 0 if usermode process was started successfully, errorvalue otherwise*/
    /* no possiblity to get return value of usermode process*/
  // note - only one argument allowed for print_symbol
  print_symbol(KBUILD_MODNAME ": symbol @ 0xc1065b60 is %s\n", 0xc1065b60); // shows wait_for_helper+0x0/0xb0
  print_symbol(KBUILD_MODNAME ": symbol @ 0xc1065ed0 is %s\n", 0xc1065ed0); // shows ____call_usermodehelper+0x0/0x90
    ret = call_usermodehelperB(userprog, argv, envp, UMH_WAIT_EXEC);
    if (ret != 0)
        printk(KBUILD_MODNAME ": error in call to usermodehelper: %i\n", ret);
    else
        printk(KBUILD_MODNAME ": everything all right; pid %d\n", callmodule_pid);
  // find the task:
  // note: sometimes p may end up being NULL here, causing kernel oops -
  // just exit prematurely in that case
  rcu_read_lock();
  p = pid_task(find_vpid(callmodule_pid), PIDTYPE_PID);
  rcu_read_unlock();
  if (p == NULL) {
    printk(KBUILD_MODNAME ": p is NULL - exiting\n");
    return 0;
  }
  // p->comm should be the command/program name (as per userprog)
  // (out here that task is typically in runnable state)
  state_str = (p->state==-1)?"unrunnable":((p->state==0)?"runnable":"stopped");
  printk(KBUILD_MODNAME ": pid task a: %p c: %s p: [%d] s: %s\n",
    p, p->comm, p->pid, state_str);

  // find parent task:
  // parent task could typically be: c: kworker/u:1 p: [14] s: stopped
  par = p->parent;
  if (par == NULL) {
    printk(KBUILD_MODNAME ": par is NULL - exiting\n");
    return 0;
  }
  state_str = (par->state==-1)?"unrunnable":((par->state==0)?"runnable":"stopped");
  printk(KBUILD_MODNAME ": parent task a: %p c: %s p: [%d] s: %s\n",
    par, par->comm, par->pid, state_str);

  // iterate through parent's (and our task's) child processes:
  rcu_read_lock(); // read_lock(&tasklist_lock);
  list_for_each(children_list_head, &par->children){
    p = list_entry(children_list_head, struct task_struct, sibling);
    printk(KBUILD_MODNAME ": - %s [%d] \n", p->comm, p->pid);
    // note: trying to print "%p",p here results with oops/segfault:
    // printk(KBUILD_MODNAME ": - %s [%d] %p\n", p->comm, p->pid, p);
    if (p->pid == callmodule_pid) {
      list_for_each(cchildren_list_head, &p->children){
        pc = list_entry(cchildren_list_head, struct task_struct, sibling);
        printk(KBUILD_MODNAME ": - - %s [%d] \n", pc->comm, pc->pid);
      }
    }
  }
  rcu_read_unlock(); //~ read_unlock(&tasklist_lock);

  return 0;
}

static void __exit callmodule_exit(void)
{
    printk(KBUILD_MODNAME ": < exit\n");
}

module_init(callmodule_init);
module_exit(callmodule_exit);
MODULE_LICENSE("GPL");


/*

NOTES:

  // assign function pointers to verbatim addresses as obtained from /proc/kallsyms:
  // ( cast to void* to avoid "warning: assignment makes pointer from integer without a cast",
  // see also https://stackoverflow.com/questions/3941793/what-is-guaranteed-about-the-size-of-a-function-pointer )

// $ sudo grep 'wait_for_helper\|____call_usermodehelper' /proc/kallsyms
// c1065b60 t wait_for_helper
// c1065ed0 t ____call_usermodehelper
// protos:
// static int wait_for_helper(void *data)
// static int ____call_usermodehelper(void *data)
// see also:
// http://www.linuxforu.com/2012/02/function-pointers-and-callbacks-in-c-an-odyssey/


// from include/linux/kmod.h:

//~ enum umh_wait {
    //~ UMH_NO_WAIT = -1,   /* don't wait at all * /
    //~ UMH_WAIT_EXEC = 0,  /* wait for the exec, but not the process * /
    //~ UMH_WAIT_PROC = 1,  /* wait for the process to complete * /
//~ };

// however, note:
// /usr/src/linux-headers-2.6.38-16-generic/include/linux/kmod.h:
// #define UMH_NO_WAIT  0 ; UMH_WAIT_EXEC   1 ; UMH_WAIT_PROC   2 ; UMH_KILLABLE    4 !
// those defines end up here, regardless of the enum definition above
// (NB: 0,1,2,4 enumeration starts from kmod.h?v=3.4 on lxr.free-electrons.com !)
// also, note, in "generic" include/, prototypes of call_usermodehelper(_fns)
// use int wait, and not enum umh_wait wait ...

// seems these cannot be used from a module, nonetheless:
//~ extern int wait_for_helper(void *data);
//~ extern int ____call_usermodehelper(void *data);
// we probably would have to (via http://www.linuxconsulting.ro/pidwatcher/)
// edit /usr/src/linux/kernel/ksyms.c and add:
//EXPORT_SYMBOL(wait_for_helper);
// but that is kernel re-compilation...

// https://stackoverflow.com/questions/19360298/triggering-user-space-with-kernel
// You should not be using PIDs to identify processes within the kernel. The process can exit and a different process re-use that PID. Instead, you should be using a pointer to the task_struct for the process (rather than storing current->pid at registration time, just store current)

# reports task name from the pid (pid_task(find_get_pid(..)):
http://tuxthink.blogspot.dk/2012/07/module-to-find-task-from-its-pid.html

  // find the task:
    //~ rcu_read_lock();
  // uprobes uses this - but find_task_by_pid is not exported for modules:
    //~ p = find_task_by_pid(callmodule_pid);
    //~ if (p)
        //~ get_task_struct(p);
    //~ rcu_read_unlock();
  // see: [http://www.gossamer-threads.com/lists/linux/kernel/1260996 find_task_by_pid() problem | Linux | Kernel]

  // https://stackoverflow.com/questions/18408766/make-a-system-call-to-get-list-of-processes
  // this macro loops through *all* processes; our callmodule_pid should be listed by it
  //~ for_each_process(p)
    //~ pr_info("%s [%d]\n", p->comm, p->pid);

  // [https://lists.debian.org/debian-devel/2008/05/msg00034.html Re: problems for making kernel module]
  // note - WARNING: "tasklist_lock" ... undefined; because tasklist_lock removed in 2.6.1*:
  // "tasklist_lock protects the kernel internal task list.  Modules have no business looking at it";
  // https://stackoverflow.com/questions/13002444/list-all-threads-within-the-current-process
  // "all methods that loop over the task lists need to be wrapped in rcu_read_lock(); / rcu_read_unlock(); to be correct."
  // https://stackoverflow.com/questions/19208487/kernel-module-that-iterates-over-all-tasks-using-depth-first-tree
  // https://stackoverflow.com/questions/5728592/how-can-i-get-the-children-process-list-in-kernel-code
  // https://stackoverflow.com/questions/1446239/traversing-task-struct-children-in-linux-kernel
  // https://stackoverflow.com/questions/8207160/kernel-how-to-iterate-the-children-of-the-current-process
  // https://stackoverflow.com/questions/10262017/linux-kernel-list-list-head-init-vs-init-list-head
  // https://stackoverflow.com/questions/16230524/explain-list-for-each-entry-and-list-for-each-entry-safe "list_entry is just an alias for container_of"

*/

【讨论】:

    猜你喜欢
    • 2012-06-04
    • 2012-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    相关资源
    最近更新 更多