为什么这个节目的ptrace系统调用说回来-38? [英] Why does this ptrace program say syscall returned -38?

查看:142
本文介绍了为什么这个节目的ptrace系统调用说回来-38?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一样的<一个href=\"http://stackoverflow.com/questions/7462230/why-wifsignaledstatus-fail-to-detect-signals-while-tracing-a-process-with-ptrac\">this除了一个我快 EXECL(/斌/ LS,LS,NULL);

其结果显然是错误的,与每一个系统调用返回 -38

  [用户@测试]#./test_trace
系统调用59调用RDI(0),RSI(0),RDX(0)
系统调用12 -38返回
系统调用12调用RDI(0),RSI(0),RDX(140737288485480)
系统调用9 -38返回
系统调用9调用RDI(0),RSI(4096),RDX(3)
系统调用9 -38返回
系统调用9调用RDI(0),RSI(4096),RDX(3)
系统调用21 -38返回
系统调用21调用RDI(233257948048),RSI(4),RDX(233257828696)
...

任何人都知道的原因?

更新

现在的问题是:

 的execve调用RDI(4203214),RSI(140733315680464),RDX(140733315681192)
用的execve返回0
用的execve返回0
...

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


解决方案

在code不占的通知EXEC 从孩子,和所以最终处理系统调用条目系统调用退出,退出系统调用系统调用的入口。这就是为什么你看到系统调用12返回系统调用12名为等( -38 ENOSYS 这是由内核的系统调用入口$ C $投入RAX作为默认的返回值角)

由于 ptrace的(2)手册页声明:


  

PTRACE_TRACEME


  
  

    

表示,这个过程是由其父进行跟踪。任何信号(除SIGKILL)发送到这个过程会导致它停止,并通过等待被通知其父()。 此外,所有后续调用对exec()通过这种方法将导致SIGTRAP被发送到它下,给予父机会新的程序开始执行之前进行控制。 [...]


  

您说您正在运行原来的code是一样的<一个href=\"http://stackoverflow.com/questions/7462230/why-wifsignaledstatus-fail-to-detect-signals-while-tracing-a-process-with-ptrac\">this除了我正在 EXECL(/斌/ LS,LS,NULL)之一; 。那么,它显然不是,因为你和x86_64的,而不是32位的工作,并已经改变了的消息最少。

但是,假设你没有改变别人太多,的第一个的时间等待()醒来父,它不适合系统调用的进入或退出 - 父还没有执行 ptrace的(PTRACE_SYSCALL,...)呢。相反,你会看到这个通知,孩子已经执行 EXEC (在x86_64,系统调用59 的execve

在code不正确间$ P $点,作为系统调用项。的然后的调用 ptrace的(PTRACE_SYSCALL,...),并在下一次的家长被唤醒它的的对系统调用入口(系统调用12),但code报告为系统调用退出。

请注意,在这个原始的情况下,你永远不会看到的execve 系统调用的入口/出口 - 只有其他通知 - 因为家长不执行 ptrace函数(PTRACE_SYSCALL,...),直到它发生了。

如果您的的安排code,使的execve 系统调用的入口/出口被抓住,你会看到新的行为你观察。家长将被唤醒的的次数:一次的execve 进入系统调用(由于使用 ptrace的(PTRACE_SYSCALL的.. 。),曾经为的execve 系统调用出口(也因使用中的ptrace(PTRACE_SYSCALL,...),并为 EXEC 的通知第三次(这恰好是这样)。


下面是一个完整的例子(于x86或x86_64),它负责通过先停止子显示 EXEC 本身的行为:

 的#include&LT;&stdio.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&;&unistd.h中GT;
#包括LT&; SYS / types.h中&GT;
#包括LT&; SYS / wait.h&GT;
#包括LT&; SYS / ptrace.h&GT;
#包括LT&; SYS / reg.h&GT;#IFDEF __x86_64__
的#define SC_NUMBER(8 * ORIG_RAX)
#定义SC_RET code(8 * RAX)
#其他
的#define SC_NUMBER(4 * ORIG_EAX)
#定义SC_RET code(4 * EAX)
#万一静态无效子(无效)
{
    / *请求跟踪由家长:* /
    的ptrace(PTRACE_TRACEME,0,NULL,NULL);    / *做任何事,给父母一个机会一睹Exec之前停止:* /
    杀号(getpid(),SIGSTOP);    / *现在的exec:* /
    execl的(/斌/ LS,LS,NULL);
}静态无效的父(将为pid_t child_pid)
{
    INT状态;
    长sc_number,sc_ret code;    而(1)
    {
        / *等待子状态的改变:* /
        等待(安培;状态);        如果(WIFEXITED(状态)){
            的printf(儿童与退出状态%d个\\ N,WEXITSTATUS(状态));
            出口(0);
        }
        如果(WIFSIGNALED(状态)){
            的printf(儿童因信号%d个\\ n退出,WTERMSIG(状态));
            出口(0);
        }
        如果(!WIFSTOPPED(状态)){
            输出(等待()返回未处理状态为0x%X \\ n,地位);
            出口(0);
        }
        如果(WSTOPSIG(状态)== SIGTRAP){
            / *请注意,有为什么孩子可能会停止*三*原因
             *与SIGTRAP:
             * 1)系统调用入口
             * 2)系统调用退出
             * 3)孩子调用exec
             * /
            sc_number = ptrace函数(PTRACE_PEEKUSER,child_pid,SC_NUMBER,NULL);
            sc_ret code = ptrace的(PTRACE_PEEKUSER,child_pid,SC_RET code,NULL);
            的printf(SIGTRAP:系统调用%LD,RC =%LD \\ N,sc_number,sc_ret code);
        }其他{
            的printf(儿童停止由于信号%d个\\ N,WSTOPSIG(状态));
        }
        fflush(标准输出);        / *恢复孩子,请求再次停在系统调用进入/退出
         *(除其他原因,它可能停止):
         * /
        的ptrace(PTRACE_SYSCALL,child_pid,NULL,NULL);
    }
}INT主要(无效)
{
    将为pid_t PID =叉();    如果(PID == 0)
        儿童();
    其他
        父(PID);    返回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的()的所有血淋淋的细节非常有趣,我所知道的最好的文档是
<一href=\"http://strace.git.sourceforge.net/git/gitweb.cgi?p=strace/strace;a=blob;f=README-linux-ptrace;hb=HEAD\"><$c$c>README-linux-ptrace文件中的 strace的 来源。因为它说的API是复杂而微妙的有怪癖......

It's the same as this one except that I'm running execl("/bin/ls", "ls", NULL);.

The result is obviously wrong as every syscall returns with -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)
...

Anyone knows the reason?

UPDATE

Now the problem is :

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

execve returned 0 twice,why?

解决方案

The code doesn't account for the notification of the exec from the child, and so ends up handling syscall entry as syscall exit, and syscall exit as syscall entry. That's why you see "syscall 12 returned" before "syscall 12 called", etc. (-38 is ENOSYS which is put into RAX as a default return value by the kernel's syscall entry code.)

As the ptrace(2) man page states:

PTRACE_TRACEME

Indicates that this process is to be traced by its parent. Any signal (except SIGKILL) delivered to this process will cause it to stop and its parent to be notified via wait(). Also, all subsequent calls to exec() by this process will cause a SIGTRAP to be sent to it, giving the parent a chance to gain control before the new program begins execution. [...]

You said that the original code you were running was "the same as this one except that I'm running execl("/bin/ls", "ls", NULL);". Well, it clearly isn't, because you're working with x86_64 rather than 32-bit and have changed the messages at least.

But, assuming you didn't change too much else, the first time the wait() wakes up the parent, it's not for syscall entry or exit - the parent hasn't executed ptrace(PTRACE_SYSCALL,...) yet. Instead, you're seeing this notification that the child has performed an exec (on x86_64, syscall 59 is execve).

The code incorrectly interprets that as syscall entry. Then it calls ptrace(PTRACE_SYSCALL,...), and the next time the parent is woken it is for a syscall entry (syscall 12), but the code reports it as syscall exit.

Note that in this original case, you never see the execve syscall entry/exit - only the additional notification - because the parent does not execute ptrace(PTRACE_SYSCALL,...) until after it happens.

If you do arrange the code so that the execve syscall entry/exit are caught, you will see the new behaviour that you observe. The parent will be woken three times: once for execve syscall entry (due to use of ptrace(PTRACE_SYSCALL,...), once for execve syscall exit (also due to use of ptrace(PTRACE_SYSCALL,...), and a third time for the exec notification (which happens anyway).


Here is a complete example (for x86 or x86_64) which takes care to show the behaviour of the exec itself by stopping the child first:

#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;
}

which gives something like this (this is for 64-bit - system call numbers are different for 32-bit; in particular execve is 11, rather than 59):

Child stopped due to signal 19
SIGTRAP: syscall 59, rc = -38
SIGTRAP: syscall 59, rc = 0
SIGTRAP: syscall 59, rc = 0
SIGTRAP: syscall 63, rc = -38
SIGTRAP: syscall 63, rc = 0
SIGTRAP: syscall 12, rc = -38
SIGTRAP: syscall 12, rc = 5324800
...

Signal 19 is the explicit SIGSTOP; the child stops three times for the execve as just described above; then twice (entry and exit) for other system calls.

If you're really interesting in all the gory details of ptrace(), the best documentation I'm aware of is the README-linux-ptrace file in the strace source. As it says, the "API is complex and has subtle quirks"....

这篇关于为什么这个节目的ptrace系统调用说回来-38?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆