2016年6月27日

通过在Simics上运行Wind River Simics找到一个内核1-2-3 Bug !

由雅各布Engblom

jakob-for-wr-jive

我必须承认,我喜欢bug和奇怪的、意想不到的软件行为。它们为本应枯燥的软件开发世界提供了喜剧和戏剧。有些bug一旦解决了,谈论起来就显得微不足道,令人尴尬。有些虫子就像神话中的雪人一样难以捉摸,永远也找不到。有些bug给了我们英雄般的叙述,一个勇敢的程序员通过聪明的灵感和绝对的决心杀死了龙。这就是一个bug的故事——我们在Linux内核中发现的一个非常严重的bug。我们通过使用找到了它风河系统公司西米奇* (Simics)运行Simics本身。然而,只是触发和重现所需的bug一个网络,两个单独的机器,至少三个处理器核心(标题中的1-2-3)。这是一种需要真正并发才能触发的错误,而不仅仅是在单个处理器上进行多任务处理。找到错误需要在用户空间和Linux内核代码中进行长时间的跟踪。

背景

为了在基于IA的主机上快速运行Intel®Architecture (IA)目标,Simics使用了Intel®Virtualization技术(Intel®VT-x)。这在Simics中称为VMP,它要求您在主机上安装一个称为VMXMON的Linux内核模块。VMXMON提供了使用Intel VT-x来加速用户级Simics进程中代码执行所需的内核级硬件访问。

这个bug的故事始于对VMXMON的一行更改。引入这个更改是为了修复以前的一个问题。这个修正是经过仔细审查的、正确的、必要的。在部署前的测试中,我们发现VMXMON的新版本不仅修复了以前的问题,而且绝对可靠。

错误点击

然而,当我们在Simics与另一个模拟器集成的设置中部署新版本的VMXMON时,我们开始看到失败。新的、修改过的VMXMON、与以前相同的主机内核以及与以前相同版本的相同外部模拟器的组合——这导致了一个以前没有发生过的错误。在新的VMXMON中有些东西揭示了一个Bug -考虑到改变的性质,这被认为是非常令人惊讶的。导致这个问题的设置是这样的(但不是完全,我们很快就发现了):

sim1

Simics和外部模拟器通过主机上的一个文件进行通信。在两个模拟器中使用mmap打开这个文件。当我们使用较新的VMXMON时,我们开始看到某些类型的模拟器工作负载经常发生崩溃。每次的错误都是相同的:主机Linux操作系统在网络文件系统(NFS)驱动程序中的assert上停止。断言声明了一个虚拟内存页同时被认为是干净的和脏的,因此,文件系统的状态从根本上被破坏了,除了断言之外没有其他事情可做。

因此,在SIMICS上运行的测试和另一个模拟器最终崩溃了主机,这不是所需的行为。断言来自NFS使得设置比我们最初想到的更复杂。实际上,该文件存储在NFS文件服务器上而不是本地。当整个设置为本地时,如上所示,错误实际上没有击中。我们需要此设置触发它,添加网络文件服务器:

sim2

内核assert提供了一个堆栈跟踪,显示了检测到不一致状态的位置,并且断言触发。然而,这显然与问题的根本原因很少。真正的问题是找到首先创建不一致状态的代码。显然,这是一个完美的模拟反向调试案例,因为它涉及从检测到错误的点回来,到状态改变的点。能够从错误向后工作是识别和解决复杂错误的巨大优势。

复制错误

为了让bug在Simics上运行并进行分析,我们首先简化了设置。这有助于我们避免在设置中包含相当复杂的外部模拟器。幸运的是,复制这个bug是相当容易的。关键是让Simics和外部程序共享位于NFS服务器上的文件。对简化的复制设置的工作提供了一些关于bug的必要先决条件的额外见解:

  • NFS服务器必须位于远程计算机上。
  • 必须对共享文件进行更改,同时还要进行同步请求,以确保将更改写入磁盘并告知其他进程。
  • 主机Linux必须是某个版本。事实证明,该BUG在最近的Linux内核版本上没有触发,但是在SUSE Linux Enterprise Server 11 *(SUSE 11)上运行时,它确实基于2.6系列的Linux内核在测试服务器上使用。

此设置可靠地将与原始错误相同的内核堆栈跟踪。同样的断言在相同的条件下触发,因此我们将其带入了调试的SIMICS。SIMICS设置看起来如下所示,在单个SIMICS会话中,在网络上有两台机器。请注意,在SIMICS内部存在一个SIMICS,以及强制出现错误所需的测试程序。请注意,NFS服务器正在第二台计算机上运行。

sim3

当我们在Simics中重新运行设置时,我们发现还需要另一个重要的因素来触发这个bug:运行Simics和外部模拟器的机器必须至少有两个处理器核心。如果所涉及的软件线程之间没有真正的并发性,就不会触发该bug。这是一个错误的例子,它不会触发,并且可以使用在单个核上序列化多线程程序执行的通用策略进行(反向)调试。这种序列化是大多数反向调试器所做的,因为除非您有一个完整的系统模拟器,否则很难确定地重播并发工作负载的执行。有关反向调试方法的更多信息,请转到http://jakob.engbloms.se/archives/1564

在《Simics》中触发漏洞很容易,只需要运行几次。在每次运行中,过程是:

  • 在运行开始之前进行Simics检查点。
  • 设置书签以启用反向执行。
  • 运行测试直到内核崩溃——这发生得非常快。

分析

此时,连接SIMICS调试器以允许分析崩溃的系统。在物理机器上调试这将几乎不可能,因为内核因错误而崩溃。如果计算机支持连接此类设备,则连接到服务器的硬件调试器可能一直很有用。但是,即使使用调试端口,那种安装程序也无法反向调试问题的根本原因。

在附加Simics调试器并检查机器状态之后,与崩溃有关的内核数据原来是NFS文件系统和内核维护的内存页的元数据。每个页面有两组标志。Linux内核本身为所有页维护一组标志,用于跟踪页是脏的还是被写回磁盘。此外,在使用NFS时,NFS维护自己的标志,包括一个单独的NFS“clean/dirty”标志。在出现问题的情况下,NFS标志被设置为clean,但内核标志指示为dirty页。当内核检测到这种干净/脏不一致时,会触发断言。

要找到改变的根本原因,很简单:

  • 在页面的NFS标志上设置一个断点。
  • 反转执行以找到最后一个作者。

这揭示了Linux内核中线程之间的竞争。

作为完成页面的NFS写回的一部分,将从一个内核线程设置为NFS清洁标志。线程将清除页面上的内核的脏标志,并设置重写标志。一旦回写完成,线程将写回标志设置为NFS清洁并清除回写标志。回写完成左侧窗口的时间打开,其中不同的线程可以修改元数据并创建不一致。这就是在这里发生的事情。

与此同时,在用户线程的ioctl调用的上下文中运行时,VMXMON驱动程序将页面标记为dirty,并且页面也被锁定——这导致内核代码采取了不寻常的路径。最后,另一个内核线程正在寻找要回写的脏页。这个线程发现了一个被NFS标记为干净的页面,等待回写完成,但是VMXMON同时标记为脏的页面。找到这样的页面后,它触发了导致内核崩溃的断言。

如果我们退后一步来考虑这里发生的事情,我们会看到这个bug是涉及三个不同的并发线程的竞赛,以及一些非常不幸的计时,再加上mmap和页面锁定的不寻常组合。不幸的是,这就是在软件中发生的情况:健壮的、经过验证的软件组件仍然可能由于不可预见的或新奇的情况而失败。

通知SUSE有关该问题的通知,并为正在使用的特定旧版本的Linux创建了补丁。在较新的Linux内核中,不会发生此错误。较新的Linux内核代码经历了广泛的变化,似乎避免了比赛。

结论

如果我们将这个错误视为标本,那显然不是一个简单的“漫画救济”错误。相反,这显然是我们想要做的事情的困扰,是时候了一些严重调试的时候了。这是一位英雄的追求,跟踪龙怪的谷仓和网络的长足之行。最后,发现了龙,面对和杀死。

这样做时,有正确的工具很重要,我们很幸运能够利用SIMICS。SIMICS的两个属性是必不可少的:1)允许调试崩溃的操作系统的调试器。2)能够重复并反转包含多个机器的整个系统的执行,在网络和完整的软件堆栈中用多个处理器内核。

发现并修复这个bug是Simics全系统虚拟平台和工具独特的调试功能的一个很好的例子。这也是为了揭示bug而模拟实际硬件并发性是多么重要的一个例子。如果任务被迫在单个处理器上执行多任务,则有些bug不会触发。

*其他名称和品牌可能被认为是他人的财产。

以前的中国电信开放基础设施挑战和钛云
下一个风河螺旋底盘、物联网和互联汽车