【问题标题】:Why does this ptrace program say syscall returned -38?为什么这个 ptrace 程序说 syscall 返回 -38?
【发布时间】:2011-11-22 19:27:57
【问题描述】:

除了我运行的是execl("/bin/ls", "ls", NULL);之外,它与this one相同。

结果显然是错误的,因为每个系统调用都返回-38

[user@ test]# ./test_trace 
syscall 59 called with rdi(0), rsi(0), rdx(0)
syscall 12 returned with -38
syscall 12 called with rdi(0), rsi(0), rdx(140737288485480)
syscall 9 returned with -38
syscall 9 called with rdi(0), rsi(4096), rdx(3)
syscall 9 returned with -38
syscall 9 called with rdi(0), rsi(4096), rdx(3)
syscall 21 returned with -38
syscall 21 called with rdi(233257948048), rsi(4), rdx(233257828696)
...

有人知道原因吗?

更新

现在的问题是:

execve called with rdi(4203214), rsi(140733315680464), rdx(140733315681192)
execve returned with 0
execve returned with 0
...

execve 返回了0 两次,为什么?

【问题讨论】:

标签: c linux ptrace


【解决方案1】:

代码不考虑来自子级的exec 通知,因此最终将系统调用条目作为系统调用退出处理,将系统调用退出作为系统调用条目处理。这就是为什么你会看到 "syscall 12 returned" before "syscall 12 called" 等(-38ENOSYS,它作为内核的系统调用入口代码的默认返回值放入 RAX。 )

正如ptrace(2) man page 所说:

PTRACE_TRACEME

表示此进程将由其父进程跟踪。传递给此进程的任何信号(SIGKILL 除外)都将导致它停止并通过 wait() 通知其父进程。 此外,此进程对 exec() 的所有后续调用都将导致向其发送 SIGTRAP,从而使父进程有机会在新程序开始执行之前获得控制权。 [...]

您说您运行的原始代码“与this one 相同,只是我运行的是execl("/bin/ls", "ls", NULL);”。好吧,显然不是,因为您使用的是 x86_64 而不是 32 位,并且至少更改了消息。

但是,假设您没有进行太多其他更改,第一次 wait() 唤醒父级,它不是用于系统调用进入或退出 - 父级尚未执行 @987654333 @ 然而。相反,您会看到该孩子执行了 exec 的通知(在 x86_64 上,系统调用 59 是 execve)。

代码错误地将其解释为系统调用条目。 然后它调用ptrace(PTRACE_SYSCALL,...),下次唤醒父级时一个系统调用条目(系统调用12),但代码报告它为系统调用退出。

请注意,在这种原始情况下,您永远不会看到 execve 系统调用进入/退出 - 只有附加通知 - 因为父级直到发生 ptrace(PTRACE_SYSCALL,...) 才会执行。

如果您这样做安排代码以便捕获execve 系统调用进入/退出,您将看到您观察到的新行为。父级将被唤醒三次次:一次用于execve 系统调用进入(由于使用ptrace(PTRACE_SYSCALL,...),一次用于execve 系统调用退出(也由于使用ptrace(PTRACE_SYSCALL,...),以及exec 通知的第三次(无论如何都会发生)。


这是一个完整的示例(对于 x86 或 x86_64),它通过首先停止子节点来注意显示 exec 本身的行为:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/reg.h>

#ifdef __x86_64__
#define SC_NUMBER  (8 * ORIG_RAX)
#define SC_RETCODE (8 * RAX)
#else
#define SC_NUMBER  (4 * ORIG_EAX)
#define SC_RETCODE (4 * EAX)
#endif

static void child(void)
{
    /* Request tracing by parent: */
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);

    /* Stop before doing anything, giving parent a chance to catch the exec: */
    kill(getpid(), SIGSTOP);

    /* Now exec: */
    execl("/bin/ls", "ls", NULL);
}

static void parent(pid_t child_pid)
{
    int status;
    long sc_number, sc_retcode;

    while (1)
    {
        /* Wait for child status to change: */
        wait(&status);

        if (WIFEXITED(status)) {
            printf("Child exit with status %d\n", WEXITSTATUS(status));
            exit(0);
        }
        if (WIFSIGNALED(status)) {
            printf("Child exit due to signal %d\n", WTERMSIG(status));
            exit(0);
        }
        if (!WIFSTOPPED(status)) {
            printf("wait() returned unhandled status 0x%x\n", status);
            exit(0);
        }
        if (WSTOPSIG(status) == SIGTRAP) {
            /* Note that there are *three* reasons why the child might stop
             * with SIGTRAP:
             *  1) syscall entry
             *  2) syscall exit
             *  3) child calls exec
             */
            sc_number = ptrace(PTRACE_PEEKUSER, child_pid, SC_NUMBER, NULL);
            sc_retcode = ptrace(PTRACE_PEEKUSER, child_pid, SC_RETCODE, NULL);
            printf("SIGTRAP: syscall %ld, rc = %ld\n", sc_number, sc_retcode);
        } else {
            printf("Child stopped due to signal %d\n", WSTOPSIG(status));
        }
        fflush(stdout);

        /* Resume child, requesting that it stops again on syscall enter/exit
         * (in addition to any other reason why it might stop):
         */
        ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL);
    }
}

int main(void)
{
    pid_t pid = fork();

    if (pid == 0)
        child();
    else
        parent(pid);

    return 0;
}

它给出了这样的东西(这是针对 64 位的 - 系统调用号对于 32 位是不同的;特别是 execve 是 11,而不是 59):

孩子因信号 19 而停止
SIGTRAP:系统调用 59,rc = -38
SIGTRAP:系统调用 59,rc = 0
SIGTRAP:系统调用 59,rc = 0
SIGTRAP:系统调用 63,rc = -38
SIGTRAP:系统调用 63,rc = 0
SIGTRAP:系统调用 12,rc = -38
SIGTRAP:系统调用 12,rc = 5324800
...

信号 19 是显式的SIGSTOP;如上所述,孩子为execve 停止三次 次;然后两次(进入和退出)用于其他系统调用。

如果您对ptrace() 的所有血腥细节真的很感兴趣,那么我所知道的最好的文档是 README-linux-ptrace 文件在 strace 源中。正如它所说,“API 很复杂并且有一些微妙的怪癖”......

【讨论】:

  • 现在问题是execve 返回了0 两次,为什么?
  • @new_perl:execve 系统调用发生一次。孩子在跟踪一个系统调用发生时停止了三次(一次进入,一次退出,一次作为exec 发生的通知)。这就是上面示例中的三个syscall 59 行。
  • eax 中的值-38 还不足以区分系统调用的进入/退出吗? (ptrace 手册页状态“跟踪器无法区分 Syscall-enter-stop 和 syscall-exit-stop。”)
【解决方案2】:

您可以使用 perror 或 strerror 打印上一个系统错误的可读描述。此错误描述将​​大大帮助您。

【讨论】:

    【解决方案3】:

    我想说的是,您正在检查 eax 或其 64 位等效项(大概是 rax),以获取系统调用的返回码。有一个额外的插槽用于保存这个名为orig_eax 的寄存器,用于重新启动系统调用。

    我对这些东西进行了很多研究,但终生无法找到我的发现。以下是一些相关问题:

    更新0

    再四处寻找,看来我的记忆是正确的。你会在here in the kernel source 找到你需要的一切(主站点已关闭,幸运的是,torvalds 现在在 github 上镜像了 linux)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 2015-07-11
      • 1970-01-01
      相关资源
      最近更新 更多