运行ptrace时偶尔会丢失PTRACE_EVENT_VFORK

Dav*_*ndy 12 c linux ptrace

对不起,我无法发布代码来重现这个.我的问题正是我不知道如何调试这个问题.

我正在使用ptrace PTRACE_O_TRACEFORK | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACECLONE跟踪一个进程,它是孩子(和孩子们的孩子).机制很像strace,但目的略有不同,因为我只是跟踪读取或修改的文件.

我的代码(用C编写)在x86-64架构上的Debian wheezy和Debian jessie上运行良好(在i386上也经过了较少的测试).当我尝试在Ubuntu Precise x86-64虚拟机(使用3.2.0内核)上编译和运行时,我遇到了麻烦.

在Precise机器上,我有时发现PTRACE_EVENT_VFORKvfork呼叫发生后我没有立即收到,而是开始接收事件(几个SIGSTOP事件和一些系统调用)而没有得到PTRACE_EVENT_VFORK事件.我没有看到正在执行的系统调用中有任何可疑的行为,并且行为是不可预测的.

我不知道该尝试将其减少到最小的错误情况,我真的不知道可能出现什么问题,从未见过这种丢失事件的行为.可以想象,差异不是内核,而是我正在跟踪的构建工具(这是python + gcc的组合).

有什么建议?

Art*_*Art 5

我最近正在从事类似的工作。我怀疑您早就解决了您的问题或放弃了,但是让我们在此为后代写一个答案。

您注册的各种事件会PTRACE_SETOPTIONS生成与正常ptrace事件不同的消息。但是仍会生成正常事件。一个正常的事件是新分支的进程开始停止,必须从跟踪程序继续进行。

这意味着,如果您已注册事件,则在进行分叉后,您观看的事件PTRACE_O_TRACEFORK(或VFORK)waitpid将为同一过程触发两次。

一个将具有以下状态:

WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == SIGSTOP)
Run Code Online (Sandbox Code Playgroud)

另一个将与:

WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == 0) &&
    ((status >> 16) == PTRACE_EVENT_FORK) /* or VFORK */
Run Code Online (Sandbox Code Playgroud)

内核似乎无法保证它们将以什么顺序到达。我发现它在系统上接近50/50。

为了处理这个问题,我的代码如下所示:

static void
proc_register(struct magic *pwi, pid_t pid, bool fork) {
    /*
     * When a new process starts two things happen:
     *  - We get a wait with STOPPED, SIGTRAP, PTRACE_EVENT_{CLONE,FORK,VFORK}
     *  - We get a wait with STOPPED, SIGSTOP
     *
     * Those can come in any order, so to get the proc in the right
     * state this function should be called twice on every new proc. If
     * it's called with fork first, we set the state to NEW_FORKED, if
     * it's called with STOP first, we set NEW_STOPPED. Then when the
     * other call comes, we set the state to TRACED and continue the
     * process.
     */
    if ((p = find_proc(pwi, pid)) == NULL) {
            p = calloc(1, sizeof(*p));
            p->pid = pid;
            TAILQ_INSERT_TAIL(&pwi->procs, p, list);
            if (fork) {
                    p->state = NEW_FORKED;
            } else {
                    p->state = NEW_STOPPED;
            }
    } else {
            assert((fork && p->state == NEW_STOPPED) || (!fork && p->state == NEW_FORKED));
            p->state = TRACED;
            int flags = PTRACE_O_TRACEEXEC|PTRACE_O_TRACEEXIT|PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK;

            if (ptrace(PTRACE_SETOPTIONS, pid, NULL, flags))
                    err(1, "ptrace(SETOPTIONS, %d)", pid);
            if (ptrace(PTRACE_CONT, pid, NULL, signal) == -1)
                    err(1, "ptrace(CONT, %d, %d)", pid, signal);
    }
}
[...]
    pid = waitpid(-1, &status, __WALL);
    if (WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == SIGSTOP)) {
            proc_register(magic, pid, false);
    } else if (WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == 0) && ((status >> 16) == PTRACE_EVENT_FORK)) {
            proc_register(magic, pid, true);
    } else {
            /* ... */
    }
Run Code Online (Sandbox Code Playgroud)

进行这项工作的关键是在PTRACE_CONT我们收到两个事件之前不要发送。当弄清楚它是如何工作时,我发送PTRACE_CONT了太多信息,内核欣然接受了它们,有时甚至导致我的进程在PTRACE_EVENT_FORK到达之前就退出了。这使得调试非常困难。

注意:我没有找到任何有关此的文档,也没有说这是应该的方法。我刚刚发现,这使事情像今天一样运转。YMMV。