ptrace使用
Word Count: 1.3k(words)
Read Count: 6(minutes)
ptrace
系统调用提供了一种方法让追踪者tracer
对被追踪者tracee
进行观察与控制,我们通过一些例子来进行讲解。
首先,我们看这么一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <sys/reg.h> #include <stdio.h> #include <signal.h>
int main() { pid_t pid;
pid = fork();
if (pid == 0) { printf("child process pid %ld\n", getpid());
char *argv[] = {"ls", NULL}; execv("/bin/ls", argv); } else { printf("parent process pid %ld\n", getpid());
wait(NULL); printf("parent process exit\n"); }
return 0; }
|
大概说说这段代码做的事情。首先创建一个子进程,父进程调用wait
函数等待子进程退出。子进程调用execv
创建一个新的进程,并且在很短的时间内执行ls
命令。子进程退出后,父进程跟着退出了。
所以,输出结果如下:
1 2 3 4 5 6 7
| [root@97043d024896 test] [root@97043d024896 test] parent process pid 42037 child process pid 42038 a.out ptrace.c parent process exit [root@97043d024896 test]
|
我们来看看strace
信息:
1 2 3 4 5 6 7 8 9 10
| strace -f ./a.out
[pid 49922] exit_group(0) = ? [pid 49922] +++ exited with 0 +++ <... wait4 resumed>NULL, 0, NULL) = 49922 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=49922, si_uid=0, si_status=0, si_utime=0, si_stime=0} --- write(1, "parent process exit\n", 20parent process exit ) = 20 exit_group(0) = ? +++ exited with 0 +++
|
其中,49922
这个是子进程。可以看到,子进程退出前,父进程处于wait
状态,当父进程收到SIGCHLD
信号之后,父进程resume
了,然后父进程退出了。
现在,让我们使用ptrace
,看看进程的状态会发送什么变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <sys/reg.h> #include <stdio.h> #include <signal.h>
int main() { pid_t pid;
pid = fork();
if (pid == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); printf("child process pid %ld\n", getpid());
char *argv[] = {"ls", NULL}; execv("/bin/ls", argv); } else { printf("parent process pid %ld\n", getpid());
wait(NULL); wait(NULL);
printf("parent process exit\n"); }
return 0; }
|
我们在子进程里面调用了ptrace
函数,并且设置了PTRACE_TRACEME
,表示希望父进程跟踪这个子进程。运行结果如下:
1 2 3 4
| [root@97043d024896 test] parent process pid 43160 child process pid 43161
|
我们发现,子进程调用了execv
之后,不会立马执行ls
命令了。我们来看看进程的状态:
查看父进程的状态:
1 2 3
| [root@97043d024896 test] root 43160 0.0 0.0 4228 680 pts/7 S+ 13:16 0:00 ./a.out [root@97043d024896 test]
|
此时父进程的状态是S+
,表示处于睡眠状态。
查看子进程的状态:
1 2 3
| [root@97043d024896 test] root 43161 0.0 0.0 416 4 pts/7 t+ 13:16 0:00 ls [root@97043d024896 test]
|
此时子进程的状态是t+
,表示处于暂停状态。要么是因为一个job control
信号,要么是因为它正在被追踪。我们这个场景就是属于被追踪。
strace
信息如下:
1 2 3 4 5 6 7 8
| child process pid 51875 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 7), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb5a89b1000 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=51875, si_uid=0, si_status=SIGTRAP, si_utime=0, si_stime=0} --- write(1, "parent process pid 51874\n", 25parent process pid 51874 ) = 25 wait4(-1, NULL, 0, NULL) = 51875 wait4(-1,
|
此时,没有子进程退出的状态。并且,父进程也处于wait
信号的状态。
那么,如何让子进程恢复运行呢?代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <sys/reg.h> #include <stdio.h> #include <signal.h>
int main() { pid_t pid;
pid = fork();
if (pid == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); printf("child process pid %ld\n", getpid());
char *argv[] = {"ls", NULL}; execv("/bin/ls", argv); } else { printf("parent process pid %ld\n", getpid());
wait(NULL);
ptrace(PTRACE_CONT, pid, NULL, NULL);
wait(NULL);
printf("parent process exit\n"); }
return 0; }
|
结果如下:
1 2 3 4 5
| [root@97043d024896 test]# ./a.out parent process pid 49313 child process pid 49314 a.out ptrace.c parent process exit
|
此时,子进程成功的执行了ls
命令。
strace
信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| child process pid 52573 getpid() = 52572 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=52573, si_uid=0, si_status=SIGTRAP, si_utime=0, si_stime=0} --- fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 7), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6eb99d8000 write(1, "parent process pid 52572\n", 25parent process pid 52572 ) = 25 wait4(-1, NULL, 0, NULL) = 52573 ptrace(PTRACE_CONT, 52573, NULL, SIG_0) = 0 wait4(-1, a.out ptrace.c NULL, 0, NULL) = 52573 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52573, si_uid=0, si_status=0, si_utime=0, si_stime=0} --- write(1, "parent process exit\n", 20parent process exit ) = 20 exit_group(0) = ? +++ exited with 0 +++
|
我们发现,父进程收到了两次SIGCHLD
信号,说明子进程执行完ls
命令之后退出了。
那么,这里子进程暂停的原理是什么呢?在ptrace_event
这个函数里面:
1 2 3 4 5 6 7 8 9 10 11
| static inline void ptrace_event(int event, unsigned long message) { if (unlikely(ptrace_event_enabled(current, event))) { current->ptrace_message = message; ptrace_notify((event << 8) | SIGTRAP); } else if (event == PTRACE_EVENT_EXEC) { if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED) send_sig(SIGTRAP, current, 0); } }
|
我们发现,如果是exec
的话,就会发一个SIGTRAP
信号给当前进程。