对不起,我无法发布代码来重现这个。我的问题正是我不知道如何调试这个问题。
我正在使用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的组合)。
有什么建议?
我最近在做类似的事情。我怀疑你很久以前就解决了你的问题或者放弃了,但我们在这里为后代写一个答案。
您在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
到来之前就已经存在了。这使得调试变得非常困难。
注:我没有找到任何关于这个或任何说这是应该的方式的文档。我刚刚发现,这使事情就像现在一样有效。因人而异。
我已多次碰到这个页面(出于不同的原因)。如果跟踪器跟踪大量的跟踪,并且有很多事件(例如当SECCOMP
设置为RET_TRACE
时)。 waitpid(-1, ...)
可能不是等待任何痕迹的最好的事情,因为可能有很多痕迹改变状态,特别是在SMP系统中(还有其他人仍在运行UP系统),这意味着可能会有很多事件以非常短的数量到达时间,OP是对的,事件可能是乱序的:某些事件或信号甚至可能在PTRACE_EVENT_FORK
之前发生。
但是,当跟踪器调用waitpid(specific_pid_greater_than_zero,...)
时,情况并非如此(没有无序事件):我们只等待特定的tracee
。你认为程序模型可能看起来不那么优雅/简单,你甚至可能需要跟踪跟踪状态(阻止与否),并决定何时/哪个跟踪继续(PTRACE_CONT
),但奖励不要担心hacky方式处理无序事件(也很难做到正确)。