ptrace (一) 所需基础知识 taro Posted on Mar 14 2021 ptrace Linux 最近看了emytymonkey大佬的项目,感觉有很多有意思的项目,都基于了同一个Linux系统调用---`ptrace`,让我来详细看看这个神秘而强大的`ptrace`是什么. 本篇文章基于大佬的详细解析 https://recursiveg.me/2014/04/programming-with-ptrace-part1/ # fork(), vfork() 与 clone() 我们知道,在linux中每个进程都有唯一的编号:`pid`,进程都是由父进程产生的,不会存在没有父进程的进程.所以进程是如何产生的呢? ## **fork()** > 一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。 原文链接:https://blog.csdn.net/jason314/article/details/5640969 <iframe src="https://tool.lu/coderunner/embed/aI7.html" width="650" height="550" frameborder="0" mozallowfullscreen webkitallowfullscreen allowfullscreen></iframe> > ?为什么只 print 了一次?应该是这个在线运行的问题 正常返回是这样的 ![](/api/file/getImage?fileId=604c92d0c9095f000e00000b) print被调用了两次,说明在fork后整个程序被执行了两次,一次父进程,一次子进程,那么作为当前执行进程的进程本身,怎样自己确认是子进程还是主进程呢? <iframe src="https://tool.lu/coderunner/embed/aI7.html" width="650" height="550" frameborder="0" mozallowfullscreen webkitallowfullscreen allowfullscreen></iframe> ![](/api/file/getImage?fileId=604c92d0c9095f000e00000c) 看函数返回值,当在子进程时,fork函数返回值为0,父进程中子进程的返回值为子进程的pid ## vfork() <iframe src="https://tool.lu/coderunner/embed/aI7.html" width="650" height="550" frameborder="0" mozallowfullscreen webkitallowfullscreen allowfullscreen></iframe> 可以观察一下 vfork 换成 fork 输出结果 简单来说,vfork()保存父进程fork钱变量,fork()不保存 ## clone 待填 # 信号 http://akaedu.github.io/book/ch33s01.html # 用户态?内核态? https://zhuanlan.zhihu.com/p/69554144 ![](/api/file/getImage?fileId=604c9b54c9095f000e000011) > 从图上我们可以看出来通过系统调用将Linux整个体系分为用户态和内核态(或者说内核空间和用户空间)。那内核态到底是什么呢?其实从本质上说就是我们所说的内核,它是一种特殊的软件程序,特殊在哪儿呢?控制计算机的硬件资源,例如协调CPU资源,分配内存资源,并且提供稳定的环境供应用程序运行。 用户态就是提供应用程序运行的空间,为了使应用程序访问到内核管理的资源例如CPU,内存,I/O。内核必须提供一组通用的访问接口,这些接口就叫系统调用。 # wait ## wait() ```c pid_t wait(int*status); ``` > 函数说明:wait()会暂时停止目前进程的执行(阻塞), 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值(exitcode). 子进程的结束状态值会由参数status 返回, 而子进程的pid也会一起返回. 如果不在意结束状态值, 则参数 status 可以设成NULL. 返回值:如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1. 失败原因存于errno 中. > > 当status为NULL时,只要有子进程退出,wait()退出阻塞(且返回值为退出的子进程的进程号),否则一直阻塞直到有子进程退出。当调用wait()函数的进程没有子进程时,返回-1 > > 如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常见的两个: > 1、WIFEXITED(status):这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。(请注意,虽然名字一样,这里的参数status并不同意wait唯一的参数——指向整数的指针status,而是那个指针指向的整数,切记不要搞混了。) > 2、WEXITSTATUS(status):当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7)退出,WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出,也就是说,WIFEXITED返回0,这个值就毫无意义了。 ## waitpid() ```c pid_twaitpid(pid_t pid, int *status, int options); ``` > 在一个子进程结束之前,wait使其调用者阻塞,waitpid使用WNOHANG参数以非阻塞方式等待子进程,waitpid可以指定所需要等待的子进程。 pid == - 1 等待任一子进程。于是在这一功能方面waitpid与wait等效。 pid > 0 等待其进程 I D 与 p i d 相等的子进程。 pid == 0 等待其组 I D 等于调用进程的组 I D 的任一子进程。 pid < - 1 等待其组 I D 等于 p i d 的绝对值的任一子进程。 # exec ```c int execl( constchar *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle( const char *path, const char *arg ,..., char * const envp[]); int execve(constchar * pathname char *const a rgv [], char *const envp []); int execv( constchar *path, char *const argv[]); int execvp(const char *file, char *const argv[]); ``` > 在用fork函数创建子进程后,子进程往往要调用一个exec函数以执行另一个程序 当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。 > E:指可以传递环境变量表 L:单独的参数传递,最后要有一个NULL V:传一个指针数组名 P:按照环境变量来查找 # 系统调用 >计算机系统的各种硬件资源是有限的,在现代多任务操作系统上同时运行的多个进程都需要访问这些资源,为了更好的管理这些资源进程是不允许直接操作的,所有对这些资源的访问都必须有操作系统控制。也就是说操作系统是使用这些资源的唯一入口,而这个入口就是操作系统提供的系统调用(System Call)。在linux中系统调用是用户空间访问内核的唯一手段,除异常和陷入外,他们是内核唯一的合法入口。 一般情况下应用程序通过应用编程接口API,而不是直接通过系统调用来编程。在Unix世界,最流行的API是基于POSIX标准的。 Nginx配置不当导致的安全问题 ptrace (二) HOOK 系统调用