运行ptrace时偶尔会丢失PTRACE_EVENT_VFORK

问题描述 投票:11回答:2

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

我正在使用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内核)上编译和运行时,我遇到了麻烦。

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

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

有什么建议?

c linux ptrace
2个回答
5
投票

我最近在做类似的事情。我怀疑你很久以前就解决了你的问题或者放弃了,但我们在这里为后代写一个答案。

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

这意味着如果您注册了使用PTRACE_O_TRACEFORK(或VFORK)注册的事件,waitpid将在fork之后针对同一进程触发两次。

一个将具有以下状态:

WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == SIGSTOP)

另一个将与:

WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == 0) &&
    ((status >> 16) == PTRACE_EVENT_FORK) /* or VFORK */

内核似乎没有任何保证,它们将以何种顺序到达。我发现它在我的系统上接近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 {
            /* ... */
    }

做这项工作的关键是在我们收到两个事件之前不发送PTRACE_CONT。当弄清楚它是如何工作的时候,我发送的PTRACE_CONT太多了,内核很乐意接受它们,有时甚至导致我的进程在PTRACE_EVENT_FORK到来之前就已经存在了。这使得调试变得非常困难。

注:我没有找到任何关于这个或任何说这是应该的方式的文档。我刚刚发现,这使事情就像现在一样有效。因人而异。


2
投票

我已多次碰到这个页面(出于不同的原因)。如果跟踪器跟踪大量的跟踪,并且有很多事件(例如当SECCOMP设置为RET_TRACE时)。 waitpid(-1, ...)可能不是等待任何痕迹的最好的事情,因为可能有很多痕迹改变状态,特别是在SMP系统中(还有其他人仍在运行UP系统),这意味着可能会有很多事件以非常短的数量到达时间,OP是对的,事件可能是乱序的:某些事件或信号甚至可能在PTRACE_EVENT_FORK之前发生。

但是,当跟踪器调用waitpid(specific_pid_greater_than_zero,...)时,情况并非如此(没有无序事件):我们只等待特定的tracee。你认为程序模型可能看起来不那么优雅/简单,你甚至可能需要跟踪跟踪状态(阻止与否),并决定何时/哪个跟踪继续(PTRACE_CONT),但奖励不要担心hacky方式处理无序事件(也很难做到正确)。

© www.soinside.com 2019 - 2024. All rights reserved.