获取任意一个进程的共享内存的fd对应的资源,增加引用,实现数据的接管——包含非export的内核函数的模块内使用

一、背景

在之前的 memfd配合跨进程传输fd的例子及原理-CSDN博客 博客里,我们讲了跨进程通过socket传递fd的底层实现原理,并且给了一个例子来替代socket传输和转换部分的逻辑,直接使用底层fd和file的接口来通过模块ko来实现跨进程的fd的转换和重新映射。

这篇博客里,我们继续fd的转换和映射的主题,我们改写一下实现,不再需要共享内存的创建线程去显式地调用ioctl或者socket的send去做转换和存储逻辑,而是由另外一个进程,也就是共享内存的使用者线程,以remote的方式,只根据remote的进程的pid和remote进程上对应的fd,来转换得到对应实际的file资源,并增加其引用计数,这样该remote进程上所管理的资源可以被另外一个进程接管管理起来,从而该remote进程就可以退出。关于这里说的remote,也就是指操作者所访问的内容是属于某个进程上下文的,且这个进程上下文并不是自己。为什么叫remote,内核里有一个可用于获取进程上下文(非自己)上的任意地址空间的函数,叫pin_user_pages_remote,具体细节参考 非gdb方式观察应用程序的运行时的变量状态-CSDN博客

事实上,进行remote进程上fd对应资源的获取不仅可用于实现刚才说的接管共享内存的功能,还可以实现任何进程的fd对应资源的监控。fd的资源里除了“有名”内存,比如/dev/shm下的文件对应的共享内存,或者一个具体的物理上的文件,这些都是比较好跟踪和维护的,还有一种就是上篇博客里提到的memfd的共享内存,是一种特殊的匿名的共享内存,匿名共享内存不仅有memfd,还有dma-buf,dma-buf也是一个非常普通使用的一套共享内存机制,这个会在后面的博客里详细介绍其使用及原理。

当前这篇博客相比之前的 memfd配合跨进程传输fd的例子及原理-CSDN博客 博客里的实现方式,其一个明显的优势在于,当前这篇博客里的实现是无“嵌入”式的实现方式,也就是当前正在使用共享的进程并不需要因为做这个功能而去“埋下”共享内存相关资源的存储和转换的逻辑到其代码,而是由另外一个进程远程的去查找和获取,算是“解耦”的实现。

接下来,我们在第二章里,先会给出不使用非export的内核函数的方式来实现一个ko模块,来完成这个remote的fd的查找资源和获取资源的逻辑,实现的方法是把内核里没有export的symbol的一个函数把对应实现搬到ko里来。在第三章里,我们介绍一个相对通用的使用内核里非export的symbol的函数的方法,是基于kprobe的,所以它自然是依赖于kprobe选项打开的(所以它相比第二章的实现而言,是有一定局限性的),另外,我们在第三章里会提到kprobe的使用上的一些注意事项。

二、不使用非export的内核函数的方式来实现remote的fd对应资源的获取

我们先在 2.1 里讲解如何使用这个ko,然后在 2.2 里贴出源码,在 2.3 里做一些源码的介绍。

2.1 如何使用和效果展示

第二章这个例程里有三个部分,一个是用于获取remote的进程的fd对应的资源,返回当前进程的fd来对应这个资源的模块ko,一个是创建memfd匿名共享内存机制的进程,该进程与ko没有任何直接的代码上的联系,还有一个是借助该remote获取fd对应资源并转换成fd的这个ko,通过ioctl这个ko拿到转化后的fd,并通过拿到的fd,mmap出对应的内存,并读取共享内存上的内容的例子。

如下进行运行:

先insmod testremotefd.ko:

然后运行创建memfd匿名共享内存的进程:

然后,运行通过testremotefd.ko,传入remote进程的pid和进程上的fd号,获取remote进程上fd号对应的资源并转换成ioctl所在进程上的unused的fd资源,进行mmap,并读取的例程:

上图中./testgetremotefd后面的两个参数,第一个参数是remote进程的pid,第二个参数是remote进程上我们关心的资源对应的fd号。

例程里,创建memfd匿名共享内存的进程是打印了创建的memfd共享内存的fd(关于memfd的介绍,见之前的博客 memfd配合跨进程传输fd的例子及原理-CSDN博客 里 2.2 一节)。

下面这个截图展示了,在testmemfd创建了memfd共享内存以后,testgetremotefd程序接管了该共享内存以后,testmemfd退出后,testgetremotefd能继续使用该共享内存:

下图是表示,在testgetremotefd A接管了testmemfd创建的共享内存以后(testmemfd退出),再起了一个testgetremotefd B来通过读取testgetremotefd A里的是哪个fd,通过testremotefdko来获取到该共享内存资源,并能成功访问

2.1.1 关于如何获取任意一个进程的fd对应的mmap映射信息的方法

拿上面的运行的testmemfd的程序的例子,上面截图里显示该进程的pid是1892501,我们通过cat /proc/1892501/maps来查看,发现了一个我们感兴趣的映射的共享内存区域:

当然,我们可以用这个虚拟地址区域,来通过之前的博客 非gdb方式观察应用程序的运行时的变量状态_程序运行变量监控-CSDN博客 里的方法来访问这段内存,但是这个方法还是更偏向于调试,而不是服务接管功能。

我们看到这个/memfd:my_shared_mem (deleted)字样,去在/proc/<pid>/fd里去依次通过下图里的readlink方法去找fd对应的映射文件(下图里fd 3对应的是特殊的memfd的内存,它是unlinked的文件,所以显示的是deleted):

这样,我们就得知了remote进程上我们感兴趣的是fd是3对应的资源,就可以传入pid和fd号,来访问remote进程上资源对应的共享内存了:

2.2 实现源码

在 2.2.1 先贴出ko的实现,在 2.2.2 里贴出memfd的创建进程的实现,来展示其代码里是非常干净的仅仅使用了memfd的接口,没有其他代码,也没有通过socket进行传输fd,在 2.2.3 里贴出了使用ko来获取remote进程的fd对应资源的例程的代码。

2.2.1 testremoteko的ko源码

#include <linux/module.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/lockdep.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h> 
#include <linux/wait.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <trace/events/workqueue.h>
#include <linux/sched/clock.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <linux/fdtable.h>
#include <linux/interrupt.h>
#include <linux/tracepoint.h>
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>

typedef struct remotefd_env {
    struct proc_dir_entry*      remotefd;
} remotefd_env;

static remotefd_env _env;

static int remotefd_proc_open(struct inode *i_pinode, struct file *i_pfile)
{
	return 0;
}

#define REMOTEFD_NODE_TYPEMAGIC    0x69
#define REMOTEFD_IO_BASE           0x10

typedef struct remotefd_ioctl_converfd_get_of_pid_fd {
    int i_pid;
    int i_fd;
    int o_fd;
} remotefd_ioctl_converfd_get_of_pid_fd;

#define REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD  \
    _IOWR(REMOTEFD_NODE_TYPEMAGIC, REMOTEFD_IO_BASE + 1, remotefd_ioctl_converfd_get_of_pid_fd)


static inline struct file *__fget_files_rcu(struct files_struct *files,
	unsigned int fd, fmode_t mask)
{
	for (;;) {
		struct file *file;
		struct fdtable *fdt = rcu_dereference_raw(files->fdt);
		struct file __rcu **fdentry;

		if (unlikely(fd >= fdt->max_fds))
			return NULL;

		fdentry = fdt->fd + array_index_nospec(fd, fdt->max_fds);
		file = rcu_dereference_raw(*fdentry);
		if (unlikely(!file))
			return NULL;

		if (unlikely(file->f_mode & mask))
			return NULL;

		/*
		 * Ok, we have a file pointer. However, because we do
		 * this all locklessly under RCU, we may be racing with
		 * that file being closed.
		 *
		 * Such a race can take two forms:
		 *
		 *  (a) the file ref already went down to zero,
		 *      and get_file_rcu() fails. Just try again:
		 */
		if (unlikely(!get_file_rcu(file)))
			continue;

		/*
		 *  (b) the file table entry has changed under us.
		 *       Note that we don't need to re-check the 'fdt->fd'
		 *       pointer having changed, because it always goes
		 *       hand-in-hand with 'fdt'.
		 *
		 * If so, we need to put our ref and try again.
		 */
		if (unlikely(rcu_dereference_raw(files->fdt) != fdt) ||
		    unlikely(rcu_dereference_raw(*fdentry) != file)) {
			fput(file);
			continue;
		}

		/*
		 * Ok, we have a ref to the file, and checked that it
		 * still exists.
		 */
		return file;
	}
}

static struct file *__fget_files(struct files_struct *files, unsigned int fd,
				 fmode_t mask)
{
	struct file *file;

	rcu_read_lock();
	file = __fget_files_rcu(files, fd, mask);
	rcu_read_unlock();

	return file;
}

struct file *fget_task(struct task_struct *task, unsigned int fd)
{
	struct file *file = NULL;

	task_lock(task);
	if (task->files)
		file = __fget_files(task->files, fd, 0);
	task_unlock(task);

	return file;
}

struct file* fget_by_pid_and_fd(int i_pid, int i_fd)
{
	struct file* ret = NULL;
	struct pid* pid_struct;
	struct task_struct* ptask;
	if (i_fd < 0) {
        printk("fd is invalid\n");
        return NULL;
    }
    pid_struct = find_get_pid(i_pid);
	if (pid_struct) {
		ptask = get_pid_task(pid_struct, PIDTYPE_PID);
		if (ptask) {
			ret = fget_task(ptask, i_fd);
			put_task_struct(ptask);
		}
		else {
			printk("No exist task of i_pid[%d]!\n", i_pid);
            put_pid(pid_struct);
			return NULL;
		}
		put_pid(pid_struct);
	}
	else {
		printk("No exist task of i_pid[%d]!\n", i_pid);
		return NULL;
	}
	return ret;
}

static long remotefd_proc_ioctl(struct file *i_pfile, u32 i_cmd, long unsigned int i_arg)
{
    switch (i_cmd) {
    case REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD:
        {
            void __user* parg = (void __user*)i_arg;
            remotefd_ioctl_converfd_get_of_pid_fd get_of_pid_fd;
			struct file* pfile = NULL;
            if (copy_from_user(&get_of_pid_fd, parg, sizeof(get_of_pid_fd))) {
                printk("copy_from_user failed\n");
                return -EFAULT;
            }
            get_of_pid_fd.o_fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
			if (get_of_pid_fd.o_fd < 0) {
				printk("get_unused_fd_flags failed!\n");
				break;
			}
			pfile = fget_by_pid_and_fd(get_of_pid_fd.i_pid, get_of_pid_fd.i_fd);
			if (pfile == NULL) {
				put_unused_fd(get_of_pid_fd.o_fd);
				get_of_pid_fd.o_fd = -1;
			}
			else {
				fd_install(get_of_pid_fd.o_fd, pfile);
			}
            if (copy_to_user(parg, &get_of_pid_fd, sizeof(get_of_pid_fd))) {
                printk("copy_to_user failed\n");
                return -EFAULT;
            }
            return 0;
        }
    default:
        return -EINVAL;
    }
    return 0;
}

static int remotefd_proc_release(struct inode *i_inode, struct file *i_file)
{
    return 0;
}

static const struct proc_ops remotefd_proc_ops = {
    .proc_read = NULL,
    .proc_write = NULL,
    .proc_open = remotefd_proc_open,
    .proc_release = remotefd_proc_release,
    .proc_ioctl = remotefd_proc_ioctl,
};

#define PROC_REMOTEFD_NAME "remotefd"

static int __init remotefd_init(void)
{
    _env.remotefd = proc_create(PROC_REMOTEFD_NAME, 0666, NULL, &remotefd_proc_ops);
    return 0;
}

static void __exit remotefd_exit(void)
{
    remove_proc_entry(PROC_REMOTEFD_NAME, NULL);
}

module_init(remotefd_init);
module_exit(remotefd_exit);
MODULE_LICENSE("GPL v2");

2.2.2 testmemfd的源码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

using namespace std;

int main() {
    // Step 1: 创建 memfd 文件描述符
    char str[20];
    int memfd = memfd_create("my_shared_mem", MFD_CLOEXEC);
    if (memfd == -1) {
        perror("memfd_create failed");
        return 1;
    }
    printf("memfd=%d\n", memfd);

    // Step 2: 设置内存大小(比如 4096 字节)
    size_t mem_size = 2*1024*1024;
#if 1
    if (ftruncate(memfd, mem_size) == -1) {
        perror("ftruncate failed");
        close(memfd);
        return 1;
    }
#endif

    // Step 3: 映射内存
    void *paddr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0);
    if (paddr == MAP_FAILED) {
        perror("mmap failed");
        close(memfd);
        return 1;
    }
    mlock(paddr, mem_size);
    //for (int i = 0; i < mem_size; i++) {
    //	   *((char*)addr + i) = 0;
    //}

    // Step 4: 向共享内存写入数据
    int d = 0;
    snprintf((char *)paddr, mem_size, "Hello, shared memory! [%d]", d++);
    
    // 打印当前进程的内容
    printf("Parent process: %s\n", (char *)paddr);

    while (1)
    {
        snprintf((char *)paddr, mem_size, "Hello, shared memory! [%d]", d++);
        sleep(1);
    }

    // Step 5: 解除映射
    if (munmap(paddr, mem_size) == -1) {
        perror("munmap failed");
        close(memfd);
        return 1;
    }

    close(memfd);

    return 0;
}

2.2.3 testgetremotefd的源码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <sys/types.h>
#include <sys/ioctl.h>

using namespace std;

#define REMOTEFD_NODE_TYPEMAGIC    0x69
#define REMOTEFD_IO_BASE           0x10

typedef struct remotefd_ioctl_converfd_get_of_pid_fd {
    int i_pid;
    int i_fd;
    int o_fd;
} remotefd_ioctl_converfd_get_of_pid_fd;

#define REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD  \
    _IOWR(REMOTEFD_NODE_TYPEMAGIC, REMOTEFD_IO_BASE + 1, remotefd_ioctl_converfd_get_of_pid_fd)

#define BUFFER_SIZE 100

int main(int i_argc, char* i_argv[]) {
    int convertfd = open("/proc/remotefd", O_RDWR);
    remotefd_ioctl_converfd_get_of_pid_fd get;
    get.i_pid = std::atoi(i_argv[1]);
    get.i_fd = std::atoi(i_argv[2]);

    if (ioctl(convertfd, REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD, &get) < 0) {
        perror("ioctl");
        return 0;
    }
    if (get.o_fd < 0) {
        printf("fd is not valid\n");
        return 1;
    }
    void *mapped_memory = mmap(nullptr, BUFFER_SIZE, PROT_READ, MAP_SHARED, get.o_fd, 0);
    if (mapped_memory == MAP_FAILED) {
        perror("mmap");
        close(convertfd);
        return 1;
    }
    while (1) {
        std::cout << "Data from memfd: " << (char *)mapped_memory << std::endl;
        sleep(1);
    }
    munmap(mapped_memory, BUFFER_SIZE);
    close(get.o_fd);
    return 1;
}

2.3 代码分析

我们依次对 2.2 里的三个源码做分析。

2.3.1 ko源码分析

我们看到,ko源码里,只有一个ioctl的功能,即REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD,意思就是获取remote进程上指定fd号对应的资源并做当前进程的fd的映射。

在 memfd配合跨进程传输fd的例子及原理-CSDN博客 的博客里,我们讲到了fget_raw接口,用来获取本进程的fd对应的struct file指针。这篇博客说的是要获取remote进程,而不是本进程,所以不能用fget_raw接口,得用内核里的fget_task接口,但是fget_task接口并不是一个export symbol,在模块里无法直接使用,所以,我们把fget_task接口的内核里的实现一层层的搬到了模块里,fget_task用的是__fget_files接口:

__fget_files再调用了__fget_files_rcu:

__fget_files_rcu再通过task上的fdtable找到对应的file再返回:

有了这个通过task_struct的指针和其对应进程上下文上的fd号获取的file指针的函数以后,再配合通过pid找对应的task_struct指针,就可以完成要做的功能了:

2.3.2 testmemfd源码分析

这段源码其实就是为了展示里面啥也没做,仅仅调用了memfd_create的接口,ftruncate后,mmap和mlock,然后sleep 1不断地改写共享内存上的内容:

如上图,里面没有socket传输fd的逻辑,也没有ioctl到testremoteko的逻辑。

2.3.3 testgetremotefd源码分析

testgetremotefd的代码页是比较清晰的,仅仅是使用testremoteko的ioctl功能,根据程序的入参,ioctl传入pid和fd号,获取到资源,并分配一个本进程的fd作为ioctl的输出对应此资源,然后通过mmap进行映射,并打印共享内存上的值,如 2.1 里的成果展示,是可以看到共享内存上的值的变化的,因为testmemfd程序是每隔1秒变化一次共享内存上的内容的:

三、借助kprobe来使用非export的内核函数的方式来实现及kprobe使用注意事项

包括之前的博客,我们已经多次通过在模块里复制内核里实现的代码过来,来达到使用内核里非export的symbol的函数的目的。

这一章里,是展示了如何通过kprobe来获取没有export的symbol的函数指针,从而直接使用该函数。

在 3.1 一节里,我们给出了修改后的testremoteko的源码并进行分析和成果展示,在 3.2 里给出了kprobe使用中的注意事项。

3.1 通过kprobe来获取函数指针,从而直接使用内核里没有export的symbol的函数

在第二章里,我们已经说明了,我们需要使用内核里的非export symbol的fget_task函数,该函数,通过如下cat /proc/kallsyms是可以grep到的:

1a5439c45be4449ca1ed3bea07009ddb.png

只要是能通过上图方法grep到的符号,就可以通过kprobe来获取到函数指针,见 3.1.1 里的源码。

3.1.1 源码及源码分析

#include <linux/module.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/lockdep.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h> 
#include <linux/wait.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <trace/events/workqueue.h>
#include <linux/sched/clock.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <linux/fdtable.h>
#include <linux/interrupt.h>
#include <linux/tracepoint.h>
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>

typedef struct remotefd_env {
    struct proc_dir_entry*      remotefd;
} remotefd_env;

static remotefd_env _env;

static int remotefd_proc_open(struct inode *i_pinode, struct file *i_pfile)
{
	return 0;
}

#define REMOTEFD_NODE_TYPEMAGIC    0x69
#define REMOTEFD_IO_BASE           0x10

typedef struct remotefd_ioctl_converfd_get_of_pid_fd {
    int i_pid;
    int i_fd;
    int o_fd;
} remotefd_ioctl_converfd_get_of_pid_fd;

#define REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD  \
    _IOWR(REMOTEFD_NODE_TYPEMAGIC, REMOTEFD_IO_BASE + 1, remotefd_ioctl_converfd_get_of_pid_fd)

typedef struct file* (*fget_task_func)(struct task_struct *i_task, unsigned int i_fd);
fget_task_func _fget_task_func;

struct file* fget_by_pid_and_fd(int i_pid, int i_fd)
{
	struct file* ret = NULL;
	struct pid* pid_struct;
	struct task_struct* ptask;
	if (i_fd < 0) {
        printk("fd is invalid\n");
        return NULL;
    }
    pid_struct = find_get_pid(i_pid);
	if (pid_struct) {
		ptask = get_pid_task(pid_struct, PIDTYPE_PID);
		if (ptask) {
			ret = _fget_task_func(ptask, i_fd);
			put_task_struct(ptask);
		}
		else {
			printk("No exist task of i_pid[%d]!\n", i_pid);
			return NULL;
		}
		put_pid(pid_struct);
	}
	else {
		printk("No exist task of i_pid[%d]!\n", i_pid);
		return NULL;
	}
	return ret;
}

static long remotefd_proc_ioctl(struct file *i_pfile, u32 i_cmd, long unsigned int i_arg)
{
    switch (i_cmd) {
    case REMOTEFD_IOCTL_REMOTEFD_GET_OF_PID_FD:
        {
            void __user* parg = (void __user*)i_arg;
            remotefd_ioctl_converfd_get_of_pid_fd get_of_pid_fd;
			struct file* pfile = NULL;
            if (copy_from_user(&get_of_pid_fd, parg, sizeof(get_of_pid_fd))) {
                printk("copy_from_user failed\n");
                return -EFAULT;
            }
            get_of_pid_fd.o_fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
			if (get_of_pid_fd.o_fd < 0) {
				printk("get_unused_fd_flags failed!\n");
				break;
			}
			pfile = fget_by_pid_and_fd(get_of_pid_fd.i_pid, get_of_pid_fd.i_fd);
			if (pfile == NULL) {
				put_unused_fd(get_of_pid_fd.o_fd);
				get_of_pid_fd.o_fd = -1;
			}
			else {
				fd_install(get_of_pid_fd.o_fd, pfile);
			}
            if (copy_to_user(parg, &get_of_pid_fd, sizeof(get_of_pid_fd))) {
                printk("copy_to_user failed\n");
                return -EFAULT;
            }
            return 0;
        }
    default:
        return -EINVAL;
    }
    return 0;
}

static int remotefd_proc_release(struct inode *i_inode, struct file *i_file)
{
    return 0;
}

static const struct proc_ops remotefd_proc_ops = {
    .proc_read = NULL,
    .proc_write = NULL,
    .proc_open = remotefd_proc_open,
    .proc_release = remotefd_proc_release,
    .proc_ioctl = remotefd_proc_ioctl,
};

#define PROC_REMOTEFD_NAME "remotefd"

static int noop_pre_handler(struct kprobe *p, struct pt_regs *regs){
	return 0;
}

static int __init remotefd_init(void)
{
	int ret;
	struct kprobe kp;
	memset(&kp, 0, sizeof(kp));
	kp.symbol_name = "fget_task";
	kp.pre_handler = noop_pre_handler;
	kp.addr = NULL;	// 作为强调,提示使用symbol_name
	ret = register_kprobe(&kp);
	if (ret < 0) {
		printk("register_kprobe fail!\n");
		return -1;
	}
	printk("register_kprobe succeed!\n");
	_fget_task_func = (void*)kp.addr;
	unregister_kprobe(&kp);
    _env.remotefd = proc_create(PROC_REMOTEFD_NAME, 0666, NULL, &remotefd_proc_ops);
    return 0;
}

static void __exit remotefd_exit(void)
{
    remove_proc_entry(PROC_REMOTEFD_NAME, NULL);
}

module_init(remotefd_init);
module_exit(remotefd_exit);
MODULE_LICENSE("GPL v2");

我们只看和第二章里不同的部分的代码:

通过上图中的register_kprobe来获取到fget_task的函数指针。

另外,如果我们有多个非export symbol函数需要去获取函数指针的话,倒是可以先获取到kallsyms_lookup_name的函数指针,再通过kallsyms_lookup_name来获取多个函数指针。这样可以省去register_kprobe再unregister_kprobe这些冗余的操作。

3.1.2 成果展示

一样的,先insmod ko:

然后运行testmemfd,再运行testgetremotefd,可以看到效果和第二章里的效果是一样的,是可以成功读取memfd的共享内存的内容的:

3.2 kprobe使用的注意事项

主要有三个注意事项,一个是在通过register_kprobe来进行注册kprobe时,要把struct kprobe结构体的内容都清零,再去做symbol_name的赋值,因为,如果struct kprobe结构的symbol_name和addr同时有内容的话,就会出错,因为register_kprobe无法知道要选择哪一个方式来注册(通过名字找,还是通过地址找),另外,struct kprobe里如flags变量等如果是非零的话都会造成逻辑上的混乱。

第二个注意事项,register_kprobe所用到的struct kprobe这个实例,生命周期必须一值存在到unregister_kprobe函数调用后,因为register_kprobe要使用struct kprobe这个结构体里的内容来进行管理的,它并没有再去赋值一份出来,而是直接用的传入register_kprobe里的指针对应的内存来管理。

第三个注意事项,是unregister_kprobe是会等使用kprobe所注册关联的回调函数执行完后,才返回的,所以,它是可能睡眠的,如下图:

unregister_kprobe调用了unregister_kprobes,unregister_kprobes里调用了synchronize_rcu去等所有的变更结束,因为如下图,unregister_kprobes里用到的__unregister_kprobe_top函数里,使用了list_rcu_del来删除链表:

关于rcu的使用和原理介绍,见之前的博客 rcu的实例、注意事项及原理讲解-CSDN博客 。其实,就是因为unregister_kprobe里会去等所有的注册的回调在回调函数清除后的宽限期结束,我们才可以在 3.1 里使用栈里的内存来作为注册kprobe所用到的struct kprobe的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰克崔

打赏后可回答相关技术问题

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值