ftrace: 当通过echo命令将current_tracer从function_graph更改时,系统会崩溃。

28

最近我一直在使用ftrace监控系统的某些行为特征。我通过一个小脚本来处理开关跟踪功能。运行脚本后,我的系统会崩溃并重新启动。最初,我认为脚本本身可能存在错误,但后来确定造成崩溃和重启的原因是在current_tracer设置为function_graph时向/sys/kernel/debug/tracing/current_tracer中echo了一些跟踪器。

也就是说,下面这个命令序列将导致系统崩溃/重启:

echo "function_graph" > /sys/kernel/debug/tracing/current_tracer
echo "function" > /sys/kernel/debug/tracing/current_tracer

在上述echo语句导致的崩溃重启期间,我看到大量输出,内容如下:

清理孤立的inode <inode>

我尝试通过在C程序中将current_tracer值从function_graph替换为其他值来复现此问题:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int openCurrentTracer()
{
        int fd = open("/sys/kernel/debug/tracing/current_tracer", O_WRONLY);
        if(fd < 0)
                exit(1);

        return fd;
}

int writeTracer(int fd, char* tracer)
{
        if(write(fd, tracer, strlen(tracer)) != strlen(tracer)) {
                printf("Failure writing %s\n", tracer);
                return 0;
        }

        return 1;
}

int main(int argc, char* argv[])
{
        int fd = openCurrentTracer();

        char* blockTracer = "blk";
        if(!writeTracer(fd, blockTracer))
                return 1;
        close(fd);

        fd = openCurrentTracer();
        char* graphTracer = "function_graph";
        if(!writeTracer(fd, graphTracer))
                return 1;
        close(fd);

        printf("Preparing to fail!\n");

        fd = openCurrentTracer();
        if(!writeTracer(fd, blockTracer))
                return 1;
        close(fd);

        return 0;
}

奇怪的是,这个C程序并没有崩溃我的系统。

我最初在使用Ubuntu(Unity环境)16.04 LTS时遇到了这个问题,并确认了它在4.4.0和4.5.5内核上确实存在问题。我还在一台运行Ubuntu(Mate环境)15.10的机器上测试了这个问题,在4.2.0和4.5.5内核上进行了测试,但无法复现该问题。这让我更加困惑了。

有人能给我解释发生了什么吗?具体来说,为什么我可以write(),但不能echo到/sys/kernel/debug/tracing/current_tracer?

更新

正如vilemetti所指出的那样,其他人也遇到了类似的问题(参见此处)。

ftrace_disable_ftrace_graph_caller()修改了在ftrace_graph_call的jmp指令, 假设它是一个5字节的near jmp(e9)。 然而,它只是由2个字节组成的短跳(eb)。 ftrace_stub()位于ftrace_graph_caller的下面,所以上面的修改破坏了该指令,导致内核 oops 在ftrace_stub()处出现无效操作码,如下所示:

修补程序(如下所示)解决了echo问题,但我仍然不明白为什么以前write()没有问题而echo有问题。

diff --git a/arch/x86/kernel/mcount_64.S b/arch/x86/kernel/mcount_64.S
index ed48a9f465f8..e13a695c3084 100644
--- a/arch/x86/kernel/mcount_64.S
+++ b/arch/x86/kernel/mcount_64.S
@@ -182,7 +182,8 @@ GLOBAL(ftrace_graph_call)
    jmp ftrace_stub
  #endif

 -GLOBAL(ftrace_stub)
 +/* This is weak to keep gas from relaxing the jumps */
 +WEAK(ftrace_stub)
    retq
  END(ftrace_caller)

通过https://lkml.org/lkml/2016/5/16/493获得。


1
你尝试过使用单个C程序(除了可能调用“dd”之外没有“exec”调用)来重现吗?有时候shell会做一些奇怪的事情。 - o11c
2
你是否考虑在Linux Stack Exchange网站上提出你的问题? - ashes999
你能否在你的笔记本电脑上重现这个崩溃?否则提到类似的事情不会崩溃就没有什么意义了。 - hkBst
你可以尝试在切换跟踪器之间,在你的C程序中或从命令行中添加dd活动。这就是你的shell脚本中发生的事情。 - hkBst
@hkBst 我很快就会更新帖子,并尝试让它更清晰,但实际上PC并不需要运行dd就会失败。重启后,我只需通过echo'ing即可导致崩溃。我认为是时候删除脚本了,C程序将仅反映有问题的行为--将跟踪器从function_graph更改为其他内容。 - buratino
显示剩余3条评论
1个回答

3

这个补丁解决了问题,但是我仍然不清楚具体发生了什么。如果你在回答中更加具体说明正在发生什么(即使涉及引用你提供的文章),并提供为什么echo函数图跟踪器会导致系统崩溃而write()不会的见解,那么我将很乐意将这个答案标记为已解决并授予悬赏金。 - buratino
1
文章中相关的文本如下。我不知道这是否完全适用于您的问题,但应该会有所帮助。"ftrace_disable_ftrace_graph_caller() 修改了 ftrace_graph_call 的 jmp 指令,假设它是一个 5 字节的 near jmp (e9 <offset>)。然而,它只由 2 个字节组成的 short jmp (eb <offset>)。ftrace_stub() 就位于 ftrace_graph_caller 的下方,因此上述修改会破坏指令,导致在 ftrace_stub() 上出现内核 oops 和无效操作码。" - vielmetti
这确实适用于我的问题,但我不清楚为什么只对echo而不是write()有效。 - buratino
1
我也不清楚为什么内核在write()上做得对,但在echo上却不行。我假设您正在使用shell的内置echo;使用/bin/echo时行为是否不同?如果您cat文件而不是echo它,它是否会做同样的事情?您使用的是哪个shell,并且在您可能使用的每个shell中它是否都会做同样的事情?我还注意到您正在以O_WRONLY打开文件;根据您的“echo”实现的细节,可能设置了其他标志,您需要跟踪一下。 - vielmetti
我使用了shell内置的echo命令,与/bin/echo的行为没有区别。将文件cat出来也没问题,因为我们只是读取内容而不是写入。 - buratino
另外,我已经认真考虑过这个问题。你的答案解决了我的问题,但它并没有回答我的问题(这可能部分是我的错,因为我现在意识到问题有点模糊)。因此,我认为你应该得到赏金(为解决问题),但你的答案不应该被接受(因为它没有回答问题)。对于混淆造成的困扰,我会更新问题以减少歧义。 - buratino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接