为什么 systemTap 脚本在操作员错误附近报告读取错误?

Why systemTap script report a read fault near operator error?

我 运行在 CentOS Linux 版本 7.6.1810 上使用 SystemTap。 SystemTap 的版本是:

$ stap -V
Systemtap translator/driver (version 4.0/0.172/0.176, rpm 4.0-11.el7)
Copyright (C) 2005-2018 Red Hat, Inc. and others
This is free software; see the source for copying conditions.
tested kernel versions: 2.6.18 ... 4.19-rc7
enabled features: AVAHI BOOST_STRING_REF DYNINST BPF JAVA PYTHON2 LIBRPM LIBSQLITE3 LIBVIRT LIBXML2 NLS NSS READLINE


$ uname -rm
3.10.0-957.21.3.el7.x86_64 x86_64

$ rpm -qa | grep kernel-devel
kernel-devel-3.10.0-957.21.3.el7.x86_64

$ rpm -qa | grep kernel-debuginfo
kernel-debuginfo-3.10.0-957.21.3.el7.x86_64
kernel-debuginfo-common-x86_64-3.10.0-957.21.3.el7.x86_64

我有一个名为 sg.stp 的 systemTap 脚本,它用于监视为什么 rabbitmq 集群的 k8s pods 偶尔以退出代码 137 终止:

global target_pid = 32719
probe signal.send{
  if (sig_pid == target_pid) {
    printf("%s(%d) send %s to %s(%d)\n", execname(), pid(), sig_name, pid_name, sig_pid);
    printf("parent of sender: %s(%d)\n", pexecname(), ppid())
    printf("task_ancestry:%s\n", task_ancestry(pid2task(pid()), 1));
  }
}

我在运行脚本的时候,过了一会儿就报错了:

$  stap sg.stp
ERROR: read fault [man error::fault] at 0x4a8 near operator '@cast' at /usr/share/systemtap/tapset/linux/task.stpm:2:5
epmd(29073) send SIGCHLD to rabbitmq-server(32719)
parent of sender: rabbitmq-server(32719)
WARNING: Number of errors: 1, skipped probes: 0
WARNING: /usr/bin/staprun exited with status: 1
Pass 5: run failed.  [man error::pass5]

pid2task() 可以 return NULL

像这样检查 pid2task(pid())current_task() returning NULL:

task = pid2task(pid());
if (task) {
  printf("task_ancestry:%s\n", task_ancestry(task, 1));
} else {
  printf("task_ancestry more available\n");
}

请注意,我对以下解释并不完全确定:

可能会发生 task_struct 不再可用,即使您在 运行 pid() 的上下文中也是如此,因为进程已经终止并且 task_struct 已清理,因为不再需要它。

在那种情况下 pid2task() returns NULL。 AFAICS 在以下两种情况下(可能更多),pid() 可能会发生这种情况:

  • 您的探测与 运行 进程异步 - 在您使用信号探测的情况下,这似乎就是这种情况。

  • .return 探测器执行得太晚,可能是因为它在内核中停留的时间太长(例如阻塞调用)。

对于后者,似乎有一些简单的解决方法:

使用 @entry(task_ancestry(current_task())) 而不是 task_ancestry(current_task())。通过这种方式,数据会在系统调用的入口点收集,进程很可能仍然完好无损。

但是在您的 Signal 案例中,我没有看到如此简单的解决方法,因此您必须检查 NULL。


请注意,我不完全确定这是您的问题,并且在不锁定页面的情况下检查 NULL 是完美的解决方案。因为即使您获得了指向某个结构的指针,包含该结构的页面也可能会在探测过程中消失,这要归功于 SMP。也许 stap 以某种方式防止这种情况。但我怀疑。 像这样的竞争条件很难调试和避免。