-2

考虑以下代码(由于公众需求重新编译:):

#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <syscall.h>
#include <unistd.h>

#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <sys/wait.h>

static FILE* logfile = 0;

int main(int argc, char * argv[], char * envp[]) {
pid_t pid = fork();
if (pid == 0) { /* child */
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1)
        assert(0 && "ptrace traceme failed");

    /* signal the parent that the child is ready */
    kill(getpid(), SIGSTOP);
    execve("a.out", argv, envp);
} else { /* parent */
    int status = 0, ret = 0;

    if ((logfile = fopen("log","a+")) == 0)
        assert(0 && "failed to open logfile");

    struct stat logfile_stat_buf;
    if (stat("log", &logfile_stat_buf) != 0)
        assert(0 && "failed to stat logfile");

    /* sync the parent and the child */
    if (waitpid(pid, &status, __WALL | __WCLONE) < 0)
        assert(0 && "waiting on child failed");


    ptrace(PTRACE_SYSCALL, pid, 0, 0);

    while (waitpid(pid, &status, __WALL | __WCLONE) > 0) {
        /* syscall entry */
        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, pid, NULL, &regs);
        /* check to see if it's a mmap call
         * void *mmap2(void *addr, size_t length, int prot,int flags, int fd, off_t pgoffset); 
         */
        printf("Child entereing a syscall %d\n", regs.orig_eax);
        if (regs.orig_eax == SYS_mmap2) {
            /* stat the file for the inode */
            int fd = regs.edi;
            struct stat stat_buf;
            if ((fstat(fd, &stat_buf) == 0) && (stat_buf.st_ino == logfile_stat_buf.st_ino))
                assert(0 && "child trying to mmap parent inode");
        }

        ptrace(PTRACE_SYSCALL, pid, 0, 0);
        waitpid(pid, &status, __WALL | __WCLONE);
        ptrace(PTRACE_GETREGS, pid, NULL, &regs);
        /* syscall exit */
        printf("Child exiting a syscall %d\n", regs.orig_eax);
        ptrace(PTRACE_SYSCALL, pid, 0, 0);
    }

    if (fclose(logfile) != 0)
        assert(0 && "failed to close logfile");
}

return 0;
}

a.out 程序是一个简单的 main() { return 0; } 程序。

如果您编译并运行此代码,您将看到子进程尝试 mmap()fopen("log") 调用打开的文件。您将通过失败的断言看到这一点。

我进一步研究并发现这发生在加载子进程期间。

这很奇怪,原因有两个:

  1. 孩子根本不应该知道 fopen() 调用,因为它发生在 fork() 之后
  2. 为什么加载程序会尝试映射此文件?它甚至不是可执行文件。

我在 glibc 中查看了 dl-load.c ,但没有看到任何应该调用这种行为的东西。

有任何想法吗?

谢谢

4

2 回答 2

1

这是一个错误的代码,问题在于我在父上下文下解释了子文件描述符。正确的做法是跟踪子 open() 调用并保留文件名到子 fd 的映射。

感谢 nneonneo 帮助我解决这个问题。

于 2012-10-08T02:56:22.657 回答
0

你的代码有问题;您观察到的行为是程序误解跟踪数据的结果。

运行你的程序,我们可以立即在前五行看到一个问题:

Child entereing a syscall 11
Child exiting a syscall 11
Child entereing a syscall 11
Child exiting a syscall 45
Child entereing a syscall 45

说什么?根据您的跟踪程序,系统调用 45在进入之前就退出了,这显然是对系统调用跟踪的误解。因此,从那时起,您的所有跟踪数据都是无效的,因为您实际上是在跟踪系统调用后的状态。因此,您得出孩子打开的结论也就不足为奇了(FWIW,您的程序在我的机器上的输出没有显示这种行为,但由于数据被误解,任何事情都可能发生)。log

那么为什么 syscall 11 ( execve) 会出现 3 次呢?答案在man 2 ptrace

此外,此进程的所有后续调用都execve(2)将导致向其发送 SIGTRAP,从而使父进程有机会在新程序开始执行之前获得控制权。如果它的父进程不希望跟踪它,那么它可能不应该发出这个请求。(pid、addr 和 data 被忽略。)

基本上,当您看到execve系统调用时,您必须在成功调用返回后执行额外的ptrace调用以恢复子进程(例如,请参阅这个最小跟踪示例是如何做到的)。一旦你这样做了,不正确的mmap调用就会消失。

于 2012-10-06T06:50:50.803 回答