我正在尝试编写一个跟踪系统调用的程序。我很难完成这项工作。我尝试调用fork()来创建自己的实例(代码),然后监视生成的子进程。
目标是父进程返回子进程进行的每个系统调用的索引并将其输出到屏幕。不知何故,它没有按计划运作。
这是代码:
#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;
}
此代码基于以下代码:
#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 child made a system call 11
这是exec系统调用的索引。
根据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.
我理解的方式是,每次用户程序调用系统调用时,内核将首先检查是否在执行系统调用例程之前跟踪进程,并使用信号暂停该进程并将控制权返回给父进程。这不是状态改变吗?
问题是,当孩子调用ptrace(TRACEME)
时,它会设置自己进行跟踪,但实际上并没有停止 - 它一直持续到它调用exec
(在这种情况下它停止了SIGTRAP),或者它得到一些其他信号。因此,为了让您的父母看到它做什么没有执行电话,您需要安排孩子接收信号。最简单的方法是让孩子在调用raise(SIGCONT);
后立即调用ptrace(TRACEME)
(或任何其他信号)
现在在父级中,您只需等待(一次)并假设孩子现在在系统调用时停止。如果它停在一个信号上就不会出现这种情况,所以你需要调用wait(&status)
来获取子状态并调用WIFSTOPPED(status)
和WSTOPSIG(status)
来查看它为什么停止了。如果由于系统调用而停止,则信号将为SIGTRAP。
如果要在客户端中看到多个系统调用,则需要在循环中执行所有这些操作;就像是:
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
}
请注意,它将为每个系统调用停止TWICE - 一次在系统调用之前,第二次在系统调用完成之后。
你基本上是想在linux中编写strace二进制文件,它跟踪进程的系统调用。 Linux为此提供了ptrace(2)系统调用。 ptrace系统调用需要4个争论,第一个争论告诉你需要做什么。 OS通过信号与父进程通信,并通过发送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)
}
请注意,寄存器读取和解释取决于您的体系结构。上面的代码只是一个例子,你需要深入挖掘它。看一下strace代码以便进一步理解。
把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;
}
在您的父母中,您想要监控多少个电话?如果你想要不止一个,你将需要某种循环。
注意示例中的行,重要的是:
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
看着man page,孩子需要做PTRACE_TRACEME
和exec
,或者父母需要使用PTRACE_ATTACH
追踪。我在你的代码中没有看到:
父进程可以通过调用fork(2)并让生成的子进行PTRACE_TRACEME来启动跟踪,然后(通常)执行exec(3)。或者,父母可以使用PTRACE_ATTACH开始跟踪现有过程。