大数跨境
0
0

修改进程信息的内核模块

修改进程信息的内核模块 YZWDDSG
2025-03-31
1

背景

有时候,某些进程可能会出现一些异常,需要排查定位异常原因。

获取异常进程的信息是比较容易的,ps top这些都可以简单查看,更详细的信息也可以通过kcore来分析。

但是,如果需要做一些实验来排查异常原因呢,比如需要修改进程的state或者尝试唤醒进程或者其他操作呢?

学习

可以写一个内核模块,通过加载模块+指定pid的方法来对指定的进程进行修改

userspace最容易获取到的进程的特定标识是什么呢?pid

那接下来就需要查看怎么通过pid来获取task_struct

可以看一下include/linux/pid.h中有一些函数声明

//通过pid获取pid struct
extern struct pid *find_get_pid(int nr);
//通过pid struct获取task_struct
extern struct task_struct *get_pid_task(struct pid *pid, enum pid_type);
//通过task_strcut获取pid struct
extern struct pid *get_task_pid(struct task_struct *task, enum pid_type type);
language-c复制代码

接下来看一下这几个函数的实现:

  • find_get_pid
//find_get_pid的实现
//看一下主要的实现在get_pid(find_vpid(nr))
struct pid *find_get_pid(pid_t nr)
{
struct pid *pid;

rcu_read_lock();
pid = get_pid(find_vpid(nr));
rcu_read_unlock();

return pid;
}

struct pid *find_vpid(int nr)
{
return find_pid_ns(nr, task_active_pid_ns(current));
}
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
return idr_find(&ns->idr, nr);
}
void *idr_find(const struct idr *idr, unsigned long id)
{
return radix_tree_lookup(&idr->idr_rt, id - idr->idr_base);
}


static inline struct pid *get_pid(struct pid *pid)
{
if (pid)
refcount_inc(&pid->count);
return pid;
}

//相关结构体
struct pid_namespace {
struct idr idr;
struct rcu_head rcu;
unsigned int pid_allocated;
  ...
}
struct idr {
struct radix_tree_rootidr_rt;
unsigned int idr_base;
unsigned int idr_next;
};
language-c复制代码

以上可以看到从pid获取pid struct的过程为:获取当前ns(这块就需要注意了,这里直接获取的current的ns,如果是docker之类的有ns隔离的环境,需要确定调用查询pid的进程和要查询的pid所属的进程是不是处于相同的ns中)->获取ns的idr->根据pid号和idr来查找基数(现在应该是使用xarray了)

所以这里我们也能看出,每个ns结构体使用一个基树/xarrya维护着所有的pid->pid struct的映射

噢,btw,看了一下,IDA和IDR可不只是用于namespace的pid管理呢?这俩还算一个比较高等级的抽象呢,可以用于很多需要分配id/id到指针映射的场景呢。其中IDR用于维护一个ID到指针的映射,IDA只用于分配ID

牛的牛的,之前没注意这块,后面还得单开一篇文章看看这块内容

注意,这里的get_pid对pid struct增加引用计数了,因此,使用完后还需要释放引用计数,使用put_pid

  • get_pid_task
struct task_struct *get_pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result;
rcu_read_lock();
result = pid_task(pid, type);
if (result)
get_task_struct(result);
rcu_read_unlock();
return result;
}

struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result = NULL;
if (pid) {
struct hlist_node *first;
first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
     lockdep_tasklist_lock_is_held());
if (first)
result = hlist_entry(first, struct task_struct, pid_links[(type)]);
}
return result;
}

static inline struct task_struct *get_task_struct(struct task_struct *t)
{
refcount_inc(&t->usage);
return t;
}

//相关结构体
struct pid
{

refcount_t count;
unsigned int level;
spinlock_t lock;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
struct hlist_head inodes;
/* wait queue for pidfd notifications */
wait_queue_head_t wait_pidfd;
struct rcu_head rcu;
struct upid numbers[];
};
language-c复制代码

可以看到,其实get_pid_task就是通过去遍历查找pid struct中的tasks链表来查找符合条件的任务

注意这里引用计数也增加了,所以不用的时候应该释放掉,通过put_task_struct

不过这块有些疑问,写在后面

  • get_task_pid
struct pid *get_task_pid(struct task_struct *task, enum pid_type type)
{
struct pid *pid;
rcu_read_lock();
pid = get_pid(rcu_dereference(*task_pid_ptr(task, type)));
rcu_read_unlock();
return pid;
}

static struct pid **task_pid_ptr(struct task_struct *task, enum pid_type type)
{
return (type == PIDTYPE_PID) ?
&task->thread_pid :
&task->signal->pids[type];
}
language-c复制代码

类似的,在task_struct中存着pid struct相关的信息呢,获取到这个这个之后再执行一下get_pid就好了

内核模块

通过以上学习,已经知道了pid号大概是怎么和task_struct相互转换的,所以剩下的就是转换成代码就好了

这里提供个大概框架

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/pid.h>
#include <linux/sched.h>
#include <linux/sched/task.h>

static int target_pid = -1;
module_param(target_pid, int0);

static int __init XXX(void)
{
        struct task_struct *t;
        struct pid *p;

        if (target_pid < 0) {
                pr_err("target pid < 0\n");
                return -EINVAL;
        }

        p = find_get_pid(target_pid);
        if (!p) {
                pr_err("get pid failed\n");
                return -EINVAL;
        }

        t = get_pid_task(p, PIDTYPE_PID);
        if (!t) {
                pr_err("get task_struct failed\n");
                put_pid(p);
                return -EINVAL;
        }
        
        /*
         *这里可以添加对task_struct的修改内容
         *比如修改状态、唤醒进程之类的操作
         */


        put_pid(p);
        put_task_struct(t);
        return -EINVAL;

}

module_init(XXX);

MODULE_LICENSE("GPL");
language-c复制代码

后记

  1. 再次注意,使用get_XXX等函数的时候,需要注意下是不是增加了引用计数,等不再使用的时候需要释放引用计数。pid struct、task_struct还有之前遇到的net_device、gendisk这些都是一样的。
  2. 为什么struct pid中的task不直接做成一个task_struct *类型而是搞一个链表呢?可能是为了进程组、多线程考虑?
  3. 接上面2,那如果在一个struct pid中有一个task链表中有几个相同的pid_type的任务,get_pid_task会返回什么呢?应该是返回的链表的第一个task。是否会出现一个task链表中有几个相同的pid_type的任务的情况?以及get_pid_task直接返回第一个task是否合理?

这个后续还得继续研究一下


【声明】内容源于网络
0
0
YZWDDSG
内核开发
内容 31
粉丝 0
YZWDDSG 内核开发
总阅读63
粉丝0
内容31