Linux 如何跟踪系统调用的进程?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11081859/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to trace a process for system calls?
提问by 1der
I am trying to code a program that traces itself for system calls. I am having a difficult time making this work. I tried calling a fork() to create an instance of itself (the code), then monitor the resulting child process.
我正在尝试编写一个跟踪系统调用的程序。我很难完成这项工作。我尝试调用 fork() 来创建它自己的实例(代码),然后监视生成的子进程。
The goal is for the parent process to return the index of every system call made by the child process and output it to the screen. Somehow it is not working as planned.
目标是让父进程返回子进程进行的每个系统调用的索引并将其输出到屏幕。不知何故,它没有按计划工作。
Here is the code:
这是代码:
#include <unistd.h> /* for read(), write(), close(), fork() */
#include <fcntl.h> /* for open() */
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
pid_t child;
long orig_eax;
child = fork();
if (0 == child)
{
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
if (argc != 3) {
fprintf(stderr, "Usage: copy <filefrom> <fileto>\n");
return 1;
}
int c;
size_t file1_fd, file2_fd;
if ((file1_fd = open(argv[1], O_RDONLY)) < 0) {
fprintf(stderr, "copy: can't open %s\n", argv[1]);
return 1;
}
if ((file2_fd = open(argv[2], O_WRONLY | O_CREAT)) < 0) {
fprintf(stderr, "copy: can't open %s\n", argv[2]);
return 1;
}
while (read(file1_fd, &c, 1) > 0)
write(file2_fd, &c, 1);
}
else
{
wait(NULL);
orig_eax = ptrace (PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
printf("copy made a system call %ld\n", orig_eax);
ptrace(PTRACE_CONT, child, NULL, NULL);
}
return 0;
}
This code was based on this code:
此代码基于此代码:
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h> /* For constants
ORIG_EAX etc */
int main()
{
pid_t child;
long orig_eax;
child = fork();
if(child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("/bin/ls", "ls", NULL);
}
else {
wait(NULL);
orig_eax = ptrace(PTRACE_PEEKUSER,
child, 4 * ORIG_EAX,
NULL);
printf("The child made a "
"system call %ld\n", orig_eax);
ptrace(PTRACE_CONT, child, NULL, NULL);
}
return 0;
}
The output of this one is:
这个输出是:
The child made a system call 11
which is the index for the exec system call.
这是 exec 系统调用的索引。
According to the man pages for wait():
根据 wait() 的手册页:
All of these system calls are used to wait for state changes in a child
of the calling process, and obtain information about the child whose
state has changed. A state change is considered to be: the child terminated;
the child was stopped by a signal; or the child was resumed by
a signal.
The way I understand it is that every time a system call is invoked by a user program, the kernel will first inspect if the process is being traced prior to executing the system call routine and pauses that process with a signal and returns control to the parent. Wouldn't that be a state change already?
我的理解是,每次用户程序调用系统调用时,内核将首先检查该进程是否在执行系统调用例程之前被跟踪,并用信号暂停该进程并将控制权返回给父进程. 那不是已经改变了状态吗?
回答by Paul Rubel
In your parent how many calls do you want to monitor? If you want more than one you're going to need some kind of loop.
在您的父母中,您要监控多少个电话?如果您想要多个,您将需要某种循环。
Note the line in the example, it's important:
请注意示例中的这一行,这很重要:
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
Looking at the man pagethe child needs to either do a PTRACE_TRACEME
and an exec
, or the parent needs to trace using PTRACE_ATTACH
. I don't see either in your code:
查看手册页,孩子需要执行 aPTRACE_TRACEME
和 an exec
,或者父母需要使用PTRACE_ATTACH
. 我在你的代码中没有看到:
The parent can initiate a trace by calling fork(2) and having the resulting child do a PTRACE_TRACEME, followed (typically) by an exec(3). Alternatively, the parent may commence trace of an existing process using PTRACE_ATTACH.
父进程可以通过调用 fork(2) 并让结果子进程执行 PTRACE_TRACEME,然后(通常)执行 exec(3) 来启动跟踪。或者,父进程可以使用 PTRACE_ATTACH 开始跟踪现有进程。
回答by Yusuf Khan
you are basically trying to write strace binary in linux, which traces the system calls of the process. Linux provides ptrace(2) system call for this. ptrace system call takes 4 arguement and the first arguement tells what you need to do. OS communicates with the parent process with signals and child process is stopped by sending SIGSTOP. broadly you need to follow below steps.
您基本上是在尝试在 linux 中编写 strace 二进制文件,它跟踪进程的系统调用。Linux 为此提供了 ptrace(2) 系统调用。ptrace 系统调用需要 4 个参数,第一个参数说明您需要做什么。操作系统通过信号与父进程通信,子进程通过发送 SIGSTOP 停止。一般来说,您需要遵循以下步骤。
if(fork() == 0 )
{
//child process
ptrace(PTRACE_TRACEME, 0,0, 0);
exec(...);
}
else
{
start:
wait4(...);
if (WIFSIGNALED(status)) {
//done
}
if (WIFEXITED(status)) {
//done
}
if(flag == startup)
{
flag = startupdone;
ptrace(PTRACE_SYSCALL, pid,0, 0) ;
goto start;
}
if (if (WSTOPSIG(status) == SIGTRAP) {) {
//extract the register
ptrace(PTRACE_GETREGS,pid,(char *)®s,0)
}
Note the register reading and interpretation will depend on your architecture. The above code is just an example to get it right you need to dig deeper. have a look at strace code for further understanding.
请注意,寄存器读取和解释将取决于您的架构。上面的代码只是一个例子,你需要更深入地挖掘它。查看strace代码以进一步理解。
回答by Chris Dodd
The problem is that when the child calls ptrace(TRACEME)
it sets itself up for tracing but doesn't actually stop -- it keeps going until it calls exec
(in which case it stops with a SIGTRAP), or it gets some other signal. So in order for you to have the parent see what it does WITHOUT an exec call, you need to arrange for the child to receive a signal. The easiest way to do that is probably to have the child call raise(SIGCONT);
(or any other signal) immediately after calling ptrace(TRACEME)
问题是,当孩子调用ptrace(TRACEME)
它时,它会为跟踪设置自己,但实际上并没有停止——它会一直运行,直到它调用exec
(在这种情况下它用 SIGTRAP 停止),或者它得到一些其他信号。因此,为了让父母在没有 exec 调用的情况下看到它的作用,您需要安排孩子接收信号。最简单的方法可能是raise(SIGCONT);
在调用后立即让子调用(或任何其他信号)ptrace(TRACEME)
Now in the parent you just wait (once) and assume that the child is now stopped at a system call. This won't be the case if it stopped at a signal, so you instead need to call wait(&status)
to get the child status and call WIFSTOPPED(status)
and WSTOPSIG(status)
to see WHY it has stopped. If it has stopped due to a syscall, the signal will be SIGTRAP.
现在在父级中,您只需等待(一次)并假设子级现在在系统调用处停止。如果它在信号处停止,情况就不会如此,因此您需要调用wait(&status)
以获取子状态并调用WIFSTOPPED(status)
并WSTOPSIG(status)
查看它为什么停止。如果它由于系统调用而停止,则信号将为 SIGTRAP。
If you want to see multiple system calls in the client, you'll need to do all of this in a loop; something like:
如果你想在客户端看到多个系统调用,你需要在一个循环中完成所有这些;就像是:
while(1) {
wait(&status);
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
// stopped before or after a system call -- query the child and print out info
}
if (WIFEXITED(status) || WIFSIGNALED(status)) {
// child has exited or terminated
break;
}
ptrace(PTRACE_SYSCALL, 0, 0, 0); // ignore any signal and continue the child
}
Note that it will stop TWICE for each system call -- once before the system call and a second time just after the system call completes.
请注意,它会为每个系统调用停止两次——一次在系统调用之前,第二次是在系统调用完成之后。
回答by Marcos Valle
Just putting together what Chris Dodd said:
将克里斯·多德 (Chris Dodd) 所说的放在一起:
#include <unistd.h> /* for read(), write(), close(), fork() */
#include <fcntl.h> /* for open() */
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
pid_t child;
int status;
long orig_eax;
child = fork();
if (0 == child)
{
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
raise(SIGCONT);
if (argc != 3) {
fprintf(stderr, "Usage: copy <filefrom> <fileto>\n");
return 1;
}
int c;
size_t file1_fd, file2_fd;
if ((file1_fd = open(argv[1], O_RDONLY)) < 0) {
fprintf(stderr, "copy: can't open %s\n", argv[1]);
return 1;
}
if ((file2_fd = open(argv[2], O_WRONLY | O_CREAT)) < 0) {
fprintf(stderr, "copy: can't open %s\n", argv[2]);
return 1;
}
while (read(file1_fd, &c, 1) > 0)
write(file2_fd, &c, 1);
}
else
{
while(1){
wait(&status);
if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP){
orig_eax = ptrace(PTRACE_PEEKUSER, child, sizeof(long) * ORIG_EAX, NULL);
printf("copy made a system call %ld\n", orig_eax);
}
if(WIFEXITED(status) || WIFSIGNALED(status)){
break;
}
ptrace(PTRACE_SYSCALL, child, 0, 0);
}
}
return 0;
}