-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; です。プログラム。

このコードをコンパイルして実行すると、子プロセスが、のfopen("log") 呼び出しによって開かれたファイルを mmap() しようとしたことがわかります。これは、失敗したアサーションによってわかります。

さらに調査したところ、これは子プロセスのロード中に発生することがわかりました。

これは 2 つの理由で奇妙です。

  1. fork() の後に発生したため、子供は fopen() 呼び出しにまったく気付かないはずです
  2. ローダーがこのファイルを mmap しようとするのはなぜですか? 実行可能ファイルでさえありません。

私は glibc で dl-load.c を見回しましたが、この種の動作を引き起こすものは何も見当たりませんでした。

何か案は?

ありがとう

4

2 に答える 2

1

これはバグのあるコードです。問題は、親コンテキストの下で子ファイル記述子を解釈したという事実にありました。正しいことは、子の open() 呼び出しを追跡し、ファイル名から子 fd へのマッピングを維持することです。

これを理解するのを手伝ってくれたnneonneoに感謝します。

于 2012-10-08T02:56:22.657 に答える
0

あなたのコードはバグがあります。観察された動作は、プログラムがトレースデータを誤って解釈した結果でした。

プログラムを実行すると、最初の5行で問題がすぐにわかります。

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

何だって?トレースプログラムによると、syscall 45は入力される前に終了しました。これは、明らかにsyscallトレースの誤った解釈です。したがって、その時点以降、実際にはシステムコール後に状態をトレースするため、すべてのトレースデータは無効になります。したがって、子供が開いたと結論付けたのは当然のことです(FWIW、私のマシンでのプログラムの出力はこの動作を示していませんでしたが、データが誤って解釈されているため、何かが起こる可能性があります)。log

では、なぜsyscall 11(execve)が3回表示されるのでしょうか。答えは次のman 2 ptraceとおりです。

また、このプロセスによる後続のすべての呼び出しexecve(2)により、SIGTRAPが送信され、新しいプログラムの実行が開始される前に親が制御を取得できるようになります。親がトレースすることを期待していない場合、プロセスはおそらくこの要求を行うべきではありません。(pid、addr、およびdataは無視されます。)

基本的に、syscallが表示された場合、正常な呼び出しが戻った後に子を再開するにはexecve、追加の呼び出しを実行する必要があります(たとえば、この最小トレースの例がどのように実行するかを参照してください)。これを行うと、誤った呼び出しが消えるはずです。ptracemmap

于 2012-10-06T06:50:50.803 に答える