Linux 访问进程地址空间函数 access_process_vm

文章目录

  • 一、源码解析
  • 二、Linux内核 用途
    • 2.1 ptrace请求
    • 2.2 进程的命令行
  • 参考资料

一、源码解析

/*** get_task_mm - acquire a reference to the task's mm** Returns %NULL if the task has no mm.  Checks PF_KTHREAD (meaning* this kernel workthread has transiently adopted a user mm with use_mm,* to do its AIO) is not set and if so returns a reference to it, after* bumping up the use count.  User must release the mm via mmput()* after use.  Typically used by /proc and ptrace.*/
struct mm_struct *get_task_mm(struct task_struct *task)
{struct mm_struct *mm;task_lock(task);mm = task->mm;if (mm) {if (task->flags & PF_KTHREAD)mm = NULL;elseatomic_inc(&mm->mm_users);}task_unlock(task);return mm;
}
EXPORT_SYMBOL_GPL(get_task_mm);
/** Access another process' address space.* Source/target buffer must be kernel space,* Do not walk the page table directly, use get_user_pages*/
int access_process_vm(struct task_struct *tsk, unsigned long addr,void *buf, int len, int write)
{struct mm_struct *mm;int ret;mm = get_task_mm(tsk);if (!mm)return 0;ret = __access_remote_vm(tsk, mm, addr, buf, len, write);mmput(mm);return ret;
}

其功能主要在__access_remote_vm函数:

/** Access another process' address space as given in mm.  If non-NULL, use the* given task for page fault accounting.*/
static int __access_remote_vm(struct task_struct *tsk, struct mm_struct *mm,unsigned long addr, void *buf, int len, int write)
{struct vm_area_struct *vma;void *old_buf = buf;down_read(&mm->mmap_sem);/* ignore errors, just check how much was successfully transferred */while (len) {int bytes, ret, offset;void *maddr;struct page *page = NULL;ret = get_user_pages(tsk, mm, addr, 1,write, 1, &page, &vma);if (ret <= 0) {/** Check if this is a VM_IO | VM_PFNMAP VMA, which* we can access using slightly different code.*/
#ifdef CONFIG_HAVE_IOREMAP_PROTvma = find_vma(mm, addr);if (!vma || vma->vm_start > addr)break;if (vma->vm_ops && vma->vm_ops->access)ret = vma->vm_ops->access(vma, addr, buf,len, write);if (ret <= 0)
#endifbreak;bytes = ret;} else {bytes = len;offset = addr & (PAGE_SIZE-1);if (bytes > PAGE_SIZE-offset)bytes = PAGE_SIZE-offset;maddr = kmap(page);if (write) {copy_to_user_page(vma, page, addr,maddr + offset, buf, bytes);set_page_dirty_lock(page);} else {copy_from_user_page(vma, page, addr,buf, maddr + offset, bytes);}kunmap(page);page_cache_release(page);}len -= bytes;buf += bytes;addr += bytes;}up_read(&mm->mmap_sem);return buf - old_buf;
}

__access_remote_vm 函数用于访问另一个进程的地址空间。以下是对该函数的中文说明:
函数开始时使用 down_read(&mm->mmap_sem) 获取内存描述符的 mmap 信号量的读取锁。这确保了同步,并防止对内存映射的并发修改。

接着,函数进入一个循环,直到传输完整个长度 len 的数据或发生错误为止。

在循环内部,函数调用 get_user_pages 来从目标进程的地址空间中检索与给定地址 addr 对应的页面。get_user_pages 函数尝试锁定页面并检索它们。如果返回值 ret 小于等于零,则表示出现错误或地址空间的末尾。

如果 ret 小于等于零,则函数检查地址处的虚拟内存区域(VMA)是否是一种特殊类型(VM_IO | VM_PFNMAP),需要使用不同的访问方式。这个检查用于处理无法直接访问页面的特定情况。如果 VMA 是特殊类型,并且有关联的 access 函数,则调用该函数执行访问操作。如果 access 函数返回的值也是非正数,则终止循环。

如果页面检索成功(ret 是正数),函数继续执行数据传输操作。它根据剩余长度 len 和当前页面的偏移量计算要传输的字节数。然后使用 kmap 将页面映射到内核地址空间以进行直接访问。

如果设置了 write 标志,函数使用 copy_to_user_page 将数据从缓冲区 buf 复制到指定地址 addr 处的映射页面。它还使用 set_page_dirty_lock 将页面标记为脏页。

如果未设置 write 标志,函数使用 copy_from_user_page 将数据从映射页面复制到指定地址 addr 处的缓冲区 buf。

在完成数据传输后,函数使用 kunmap 解除页面在内核地址空间的映射,并使用 page_cache_release 释放页面。

然后,函数更新下一次循环迭代的剩余长度 len、缓冲区指针 buf 和地址 addr。

循环结束后,函数使用 up_read(&mm->mmap_sem) 释放对 mmap 信号量的读取锁。

最后,函数返回传输的字节数(buf - old_buf),表示成功传输的总大小。

__access_remote_vm 函数的作用是允许在内核中访问另一个进程的地址空间。通过该函数,内核可以直接读取或写入指定进程的内存数据,而无需通过用户空间或进程间通信来实现。

这种功能在某些情况下非常有用,例如:
(1)调试:允许调试器在内核级别访问目标进程的内存,以查看其状态、变量值和数据结构。

(2)进程间通信:某些进程间通信机制可能需要在内核中进行数据交换,例如通过共享内存或管道进行高效的数据传输。

(3)内核模块开发:内核模块可能需要读取或修改其他进程的内存数据,以实现特定的功能或扩展性。

(4)效率优化:在某些情况下,直接在内核中访问另一个进程的内存可以提高性能,避免了用户空间和内核空间之间的数据复制开销。

二、Linux内核 用途

2.1 ptrace请求

PTRACE_PEEKTEXT和PTRACE_PEEKDATA是用于在被跟踪进程的内存中读取数据的ptrace系统调用的请求选项。
PTRACE_POKETEXT和PTRACE_POKEDATA是用于向被跟踪进程的内存写入数据的ptrace系统调用的请求选项。

这两个选项父进程读取子进程内存地址空间数据或者写数据到子进程内存地址空间都是用到了access_process_vm函数。

SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,unsigned long, data)
{struct task_struct *child;child = ptrace_get_task_struct(pid);arch_ptrace(child, request, addr, data);	
}
long arch_ptrace(struct task_struct *child, long request,unsigned long addr, unsigned long data)
{ptrace_request(child, request, addr, data);
}
int ptrace_request(struct task_struct *child, long request,unsigned long addr, unsigned long data)
{switch (request) {case PTRACE_PEEKTEXT:case PTRACE_PEEKDATA:return generic_ptrace_peekdata(child, addr, data);case PTRACE_POKETEXT:case PTRACE_POKEDATA:return generic_ptrace_pokedata(child, addr, data);}
}

generic_ptrace_peekdata用来读取子进程的虚拟地址空间地址:

int generic_ptrace_peekdata(struct task_struct *tsk, unsigned long addr,unsigned long data)
{unsigned long tmp;int copied;copied = access_process_vm(tsk, addr, &tmp, sizeof(tmp), 0);if (copied != sizeof(tmp))return -EIO;return put_user(tmp, (unsigned long __user *)data);
}

对于读取子进程的虚拟地址空间,调用access_process_vm时,第五个参数 write = 0,代表是从该进程的虚拟地址空间读取内容。
函数的主要逻辑如下:
(1)声明一个局部变量 tmp,用于存储从目标进程内存中读取的数据。
(2)调用 access_process_vm 函数,传递目标进程的 task_struct 指针(tsk)、目标虚拟地址(addr)、存储数据的缓冲区指针(&tmp)、要读取的数据大小(sizeof(tmp))和读取标志(0)。
(3)access_process_vm 函数尝试访问目标进程的内存,并将读取的数据存储到 tmp 变量中。它返回实际复制的字节数。
(4)检查返回的 copied 是否等于 sizeof(tmp),即判断是否成功复制了全部数据。如果不等于,则表示读取失败,返回错误码 -EIO。
(5)调用 put_user 函数,将 tmp 变量的值复制到用户空间的 data 变量中。
(6)返回复制操作的结果。

这个函数的作用是从另一个进程的指定虚拟地址处读取数据,并将其存储到 data 变量中。它使用了 access_process_vm 函数来访问目标进程的内存,并使用 put_user 函数将读取的数据复制到用户空间。

generic_ptrace_pokedata用来写入数据到子进程的虚拟地址空间地址:

int generic_ptrace_pokedata(struct task_struct *tsk, unsigned long addr,unsigned long data)
{int copied;copied = access_process_vm(tsk, addr, &data, sizeof(data), 1);return (copied == sizeof(data)) ? 0 : -EIO;
}

对于写入数据到子进程的虚拟地址空间地址,调用access_process_vm时,第五个参数 write = 1,代表是向该进程的虚拟地址空间写入新内容。

函数的主要逻辑如下:
(1)声明一个局部变量 copied,用于存储复制的字节数。
(2)调用 access_process_vm 函数,传递目标进程的 task_struct 指针(tsk)、目标虚拟地址(addr)、源数据的指针(&data)、要复制的数据大小(sizeof(data))和写入标志(1)。
(3)access_process_vm 函数尝试访问目标进程的内存,并将源数据复制到目标进程的指定地址处。它返回实际复制的字节数。
(4)检查返回的 copied 是否等于 sizeof(data),即判断是否成功复制了全部数据。如果相等,则表示写入操作成功,返回0;否则,返回错误码 -EIO。

这个函数的作用是向另一个进程的指定虚拟地址处写入数据。它使用了 access_process_vm 函数来访问目标进程的内存,并尝试将源数据复制到目标进程中。函数返回0表示写入操作成功,返回非零错误码表示写入失败。

2.2 进程的命令行

该函数用来获取进程的命令行:

static int proc_pid_cmdline(struct task_struct *task, char * buffer)
{int res = 0;unsigned int len;struct mm_struct *mm = get_task_mm(task);if (!mm)goto out;if (!mm->arg_end)goto out_mm;	/* Shh! No looking before we're done */len = mm->arg_end - mm->arg_start;if (len > PAGE_SIZE)len = PAGE_SIZE;res = access_process_vm(task, mm->arg_start, buffer, len, 0);// If the nul at the end of args has been overwritten, then// assume application is using setproctitle(3).if (res > 0 && buffer[res-1] != '\0' && len < PAGE_SIZE) {len = strnlen(buffer, res);if (len < res) {res = len;} else {len = mm->env_end - mm->env_start;if (len > PAGE_SIZE - res)len = PAGE_SIZE - res;res += access_process_vm(task, mm->env_start, buffer+res, len, 0);res = strnlen(buffer, res);}}
out_mm:mmput(mm);
out:return res;
}

proc_pid_cmdline用于获取指定进程的命令行参数。

struct mm_struct {.....unsigned long arg_start, arg_end, env_start, env_end;.....	
}

struct mm_struct 用于描述进程的地址空间,该结构体包含了和进程地址空间有关的全部细信息。每个进程都有一个对应的 mm_struct 结构体,它存储了进程的地址空间布局、内存映射、页表等关键信息。

arg_start、arg_end、env_start 和 env_end 是用于描述进程的命令行参数和环境变量的起始地址和结束地址。以便访问和操作进程的命令行参数和环境变量。

函数的主要逻辑如下:
(1)调用 get_task_mm 函数获取目标进程的内存描述符(mm_struct)。
(2)计算命令行参数的长度,即 arg_end - arg_start。如果长度超过了页面大小(PAGE_SIZE),将长度截断为页面大小。
(3)调用 access_process_vm 函数,传递目标进程的 task_struct 指针(task)、命令行参数的起始地址(mm->arg_start)、存储命令行参数的缓冲区指针(buffer)、要复制的数据大小(len)和读取标志(0)。
(4)检查返回的 res 是否大于0,并且缓冲区中最后一个字节是否不等于空字符(‘\0’),以及长度是否小于页面大小。如果满足这些条件,说明可能发生了 setproctitle 的情况,需要进一步处理:
使用 strnlen 函数在缓冲区中查找第一个空字符(‘\0’),并将其位置存储在 len 变量中。
如果找到的空字符位置小于 res,说明命令行参数中存在空字符,将其作为有效长度。
否则,计算环境变量的长度(env_end - env_start)。
如果环境变量长度超过了剩余缓冲区的大小(PAGE_SIZE - res),将长度截断为剩余缓冲区的大小。
调用 access_process_vm 函数,从环境变量的起始地址(mm->env_start)开始,将环境变量的内容复制到缓冲区中(buffer+res),并返回实际复制的字节数。
使用 strnlen 函数在缓冲区中查找第一个空字符(‘\0’),并将其位置存储在 res 变量中。

这个函数的作用是获取指定进程的命令行参数,并将其存储在提供的缓冲区中。它首先获取进程的内存描述符,然后通过访问进程的内存来获取命令行参数。如果发现命令行参数中的空字符被覆盖,它还会尝试获取环境变量并将其添加到缓冲区中。最后,函数返回存储的命令行参数的长度。

参考资料

Linux 3.10.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/69962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue修饰符的用法

Vue修饰符是指在Vue模板中用于改变指令行为的特殊后缀。修饰符以.开头&#xff0c;用于指示指令应该如何绑定或响应事件。Vue修饰符在一些常见的指令中使用&#xff0c;例如v-on和v-model。常见的Vue修饰符包括&#xff1a; .prevent&#xff1a;阻止默认事件的发生。.stop&am…

AggregateFunction结合自定义触发器实现点击率计算

背景&#xff1a; 接上一篇文章&#xff0c;ProcessWindowFunction 结合自定义触发器会有状态过大的问题&#xff0c;本文就使用AggregateFunction结合自定义触发器来实现&#xff0c;这样就不会导致状态过大的问题了 AggregateFunction结合自定义触发器实现 flink对于每个窗…

小白开始学习C++

​​​​第一节&#xff1a;控制台输出hello word&#xff01; #include<iostream> //引入库文件 int main() { //控制台输出 hello word! 之后回车 std::cout << "hello word!\n"; #include<iostream> //引入库文件int main() {//控制…

Python3 循环语句

Python3 循环语句 本章节将为大家介绍 Python 循环语句的使用。 Python 中的循环语句有 for 和 while。 Python 循环语句的控制结构图如下所示&#xff1a; while 循环 Python 中 while 语句的一般形式&#xff1a; while 判断条件(condition)&#xff1a;执行语句(statem…

【LeetCode算法系列题解】第61~65题

CONTENTS LeetCode 61. 旋转链表&#xff08;中等&#xff09;LeetCode 62. 不同路径&#xff08;中等&#xff09;LeetCode 63. 不同路径 II&#xff08;中等&#xff09;LeetCode 64. 最小路径和&#xff08;中等&#xff09;LeetCode 65. 有效数字&#xff08;困难&#xff…

py脚本解决ArcGIS Server服务内存过大的问题

在一台服务器上&#xff0c;使用ArcGIS Server发布地图服务&#xff0c;但是地图服务较多&#xff0c;在发布之后&#xff0c;服务器的内存持续处在95%上下的高位状态&#xff0c;导致服务器运行状态不稳定&#xff0c;经常需要重新启动。重新启动后重新进入这种内存高位的陷阱…

回复:c#的Winform如何让ComboBox不显示下拉框?https://bbs.csdn.net/topics/392565412

组合框.Parent this;组合框.Items.AddRange(new object[] { "111", "222", "333", "444" });组合框.DropDownHeight 1;组合框.SelectedIndex 0;//组合框.DropDownStyle ComboBoxStyle.Simple; ComboBox 组合框 new ComboBox();Li…

51单片机电子钟六位数码管显示整点提醒仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机电子钟六位数码管显示整点提醒仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图参考元器件清单 5. 设计报告6. 设计资料内容清单 51单片机电子钟六位数码管显示整点提醒仿真设计( proteus仿真程序原理图报告…

AOP进阶-连接点

连接点 在Spring中用JoinPoint抽象了连接点&#xff0c;用它可以获取方法执行时的相关信息&#xff0c;如目标类名、方法名、方法参数等 对于Around通知&#xff0c;获取连接点信息只能使用 ProceedingJoinPoint对于其它四种通知&#xff0c;获取连接点信息只能使用JoinPoint&…

Go语言高级编程:深度挖掘

Go语言高级编程&#xff1a;深度挖掘 欢迎继续深入Go语言的高级编程领域。在这篇博客中&#xff0c;我们将更深入地探讨Go语言的一些高级主题和技术&#xff0c;包括性能优化、错误处理、反射和自定义数据结构。 性能优化 Go语言因其出色的性能而广受欢迎&#xff0c;但要达…

c++中继承多态virtual和override

目录 virtual&#xff1a; 易错点&#xff1a; 未声明虚函数&#xff1a; 忘记使用 override 关键字&#xff1a; 内存泄漏&#xff1a; 基类指针不指向任何对象&#xff1a; 访问权限问题&#xff1a; 不正确的类设计&#xff1a; 不正确的对象切片&#xff1a; 混淆…

C高级-Linux终端基础指令

在线下载软件 检测网络 ping baidu.com在下载软件前&#xff0c;需将Linux系统中的软件源更新成国内的软件源&#xff1a;清华源、阿里源、163源、中科大源… 更新软件列表 将系统中的软件源更新为国内的软件源后&#xff0c;使用命令sudo apt-get update 使Ubuntu连接到国…

[HDCTF 2023]YamiYami

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言涉及知识点解题详细过程session伪造反弹shell 前言 从暑假末尾一直搁置&#xff0c;当时卡在反弹shell搞得离flag就差一步。不过最近一两天学习完反弹shell的知…

8.(Python数模)(预测模型一)马尔科夫链预测

Python实现马尔科夫链预测 马尔科夫链原理 马尔科夫链是一种进行预测的方法&#xff0c;常用于系统未来时刻情况只和现在有关&#xff0c;而与过去无关。 用下面这个例子来讲述马尔科夫链。 如何预测下一时刻计算机发生故障的概率&#xff1f; 当前状态只存在0&#xff08;故…

肖sir__设计测试用例方法之_(白盒测试)

白盒测试技术 一、定义&#xff1a; 白盒测试也叫透明盒测试&#xff0c;检查程序内部结构及路径一是否符合规格说明&#xff0c;二是否符合其代码规范。 因此&#xff0c;也叫结构测试或者逻辑驱动测试。 二、白盒测试常见方法&#xff1a; a、语句覆盖&#xff1b; b、判断覆…

虚拟机扩容

系统环境centos8&#xff0c;分两步&#xff0c;第一步先在vmware扩容&#xff0c;第二部在虚拟机内部扩容 1.vmware分配磁盘空间 2.虚拟机内部扩容 查看当前磁盘信息&#xff0c;这个是扩容之前的&#xff0c;扩容完成才会显示新的 df -h查看系统分区信息 fdisk -l查看目录…

C语言基础知识理论版(很详细)

文章目录 前述一、数据1.1 数据类型1.2 数据第一种数据&#xff1a;常量第二种数据&#xff1a;变量第三种数据&#xff1a;表达式1、算术运算符及算术表达式2、赋值运算符及赋值表达式3、自增、自减运算符4、逗号运算符及其表达式&#xff08;‘顺序求值’表达式&#xff09;5…

Spring Boot日志基础使用 设置日志级别

然后 我们来说日志 日志在实际开发中还是非常重要的 即可记录项目状态和一些特殊情况发生 因为 我们这里不是将项目 所以 讲的也不会特别深 基本还是将Spring Boot的日志设置或控制这一类的东西 相对业务的领域我们就不涉及了 日志 log 初期最明显的作用在于 开发中 你可以用…

深入浅出了解BeanFactory 和 ApplicationContext

一.区别 BeanFactory和ApplicationContext是Spring的两大核心接口&#xff0c;都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。 1.依赖关系 BeanFactory&#xff1a;是Spring里面最底层的接口&#xff0c;包含了各种Bean的定义&#xff0c;读取bean…

Mac 手动安装 sshpass

1. 下载安装包 https://sourceforge.net/projects/sshpass/ 解压并进入到安装包目录 tar -zxvf sshpass-xx.xx.tar.gz cd sshpass-xx.xx2. 检验环境&#xff0c;编译源码安装 ./configuremake&&make install3. 检测安装是否成功 ▶ sshpass Usage: sshpass [-f|-…