【问题标题】:occasionally missing PTRACE_EVENT_VFORK when running ptrace运行 ptrace 时偶尔会丢失 PTRACE_EVENT_VFORK
【发布时间】:2015-07-11 21:32:32
【问题描述】:

很抱歉,我无法发布代码来重现此内容。我的问题恰恰是我不知道如何调试这个问题。

我正在使用 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 机器上,我有时发现在 vfork 调用发生后我没有立即收到 PTRACE_EVENT_VFORK,而是开始接收事件(几个 SIGSTOP 事件,和一些系统调用)而没有收到PTRACE_EVENT_VFORK 事件。我没有看到正在执行的系统调用有任何可疑之处,而且这种行为是不可预测的。

我不确定如何尝试将其减少到最小的错误情况,而且我真的不知道可能出了什么问题,因为以前从未见过这种丢失事件的行为。可以想象,区别不在于内核,而在于我正在跟踪的构建工具(它是python + gcc的组合)。

有什么建议吗?

【问题讨论】:

  • 如果这里没有人可以提供帮助,请尝试在 linux-kernel 邮件列表中询问。 (不太可能有帮助,但值得一试。)作为ptrace 的替代方法,您可以使用LD_PRELOAD trick 拦截对openreadwriteclose 的调用。还有祝你好运;这听起来很糟糕。
  • 我避免使用 LD_PRELOAD,因为我希望我的代码能够跟踪静态链接的二进制文件。坦率地说,我害怕 linux-kernel!大声笑:)
  • 我同意 LD_PRELOAD 不是一种理智/有效的方式来做到这一点。不幸的是,我不知道 vfork 跟踪失败的原因。但是,如果您可以使用 seccomp 跟踪模式而不是传统的 ptrace 样式,那么它可能更不容易出错并且更便携。
  • @Nemo,关于静态链接的二进制文件,用 go 编译的任何东西都是主要的反例,这是一个越来越大的漏洞。有机会我会试试你的建议。
  • @DavidRoundy:ptrace 竞争条件是fixed in 3.4,但我不确定它是否会导致您看到的情况。当且仅当您的进程是多线程时,我可以想到另一种可能性,即两个不同的任务导致同一进程停止,而 vfork 一个失败了(STOP/CONT 信号不嵌套或排队)。

标签: c linux ptrace


【解决方案1】:

我最近也在做类似的事情。我怀疑你早就解决了你的问题或放弃了,但让我们在这里写一个答案供后人参考。

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

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

一个人的状态是:

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 到达之前很久就退出了。这使得调试非常困难。

注意我还没有找到任何关于此的文档或任何说这是应该的方式。我刚刚发现这使事情像今天一样工作。 YMMV。

【讨论】:

    【解决方案2】:

    我已经多次访问此页面(出于不同的原因)。如果跟踪器跟踪了很多跟踪,并且有很多事件(例如当SECCOMP 设置为RET_TRACE 时)。 waitpid(-1, ...) 等待 any 跟踪可能不是最好的选择,因为可能有很多跟踪正在改变状态,尤其是在 SMP 系统中(还有谁仍在运行 UP 系统),这意味着可能有大量事件在很短的时间内到达,OP 是对的,这些事件可能是无序的:某些事件或信号甚至可能在 PTRACE_EVENT_FORK 之前发生。

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

    【讨论】:

      猜你喜欢
      • 2020-05-02
      • 2013-02-19
      • 1970-01-01
      • 2021-08-20
      • 2020-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多