根和堆写危害的静态分析

Treeherder 可以运行两种静态分析构建:完整浏览器 (linux64-haz) 和仅 JS shell (linux64-shell-haz)。它们在 Treeherder 上显示为 HSM(H)

诊断危害故障

第一步是查看报告了哪种类型的危害。有两种类型的危害会导致作业失败:垃圾回收的堆栈根危害和 stylo 的堆写线程安全危害。

摘要输出将包含字符串 <N> rooting hazards detected<N> heap write hazards detected out of <M> allowed。请参阅下面每个部分的相应内容。

诊断根危害故障

点击 H 构建链接,选择左下角的“工件”窗格,然后下载 public/build/hazards.txt.gzpublic/build/hazards.html.gz 文件。在本地运行分析时,HTML 文件最有用,因为它会链接到相关代码的确切部分,但在本文中讨论文本文件更容易。

hazards.txt 中的示例片段

Function 'jsopcode.cpp:uint8 DecompileExpressionFromStack(JSContext*, int32, int32, class JS::Handle<JS::Value>, int8**)' has unrooted 'ed' of type 'ExpressionDecompiler' live across GC call 'uint8 ExpressionDecompiler::decompilePC(uint8*)' at js/src/jsopcode.cpp:1866
    js/src/jsopcode.cpp:1866: Assume(74,75, !__temp_23*, true)
    js/src/jsopcode.cpp:1867: Assign(75,76, return := 0)
    js/src/jsopcode.cpp:1867: Call(76,77, ed.~ExpressionDecompiler())
GC Function: uint8 ExpressionDecompiler::decompilePC(uint8*)
    JSString* js::ValueToSource(JSContext*, class JS::Handle<JS::Value>)
    uint8 js::Invoke(JSContext*, JS::Value*, JS::Value*, uint32, JS::Value*, class JS::MutableHandle<JS::Value>)
    uint8 js::Invoke(JSContext*, JS::CallArgs, uint32)
    JSScript* JSFunction::getOrCreateScript(JSContext*)
    uint8 JSFunction::createScriptForLazilyInterpretedFunction(JSContext*, class JS::Handle<JSFunction*>)
    uint8 JSRuntime::cloneSelfHostedFunctionScript(JSContext*, class JS::Handle<js::PropertyName*>, class JS::Handle<JSFunction*>)
    JSScript* js::CloneScript(JSContext*, class JS::Handle<JSObject*>, class JS::Handle<JSFunction*>, const class JS::Handle<JSScript*>, uint32)
    JSObject* js::CloneStaticBlockObject(JSContext*, class JS::Handle<JSObject*>, class JS::Handle<js::StaticBlockObject*>)
    js::StaticBlockObject* js::StaticBlockObject::create(js::ExclusiveContext*)
    js::Shape* js::EmptyShape::getInitialShape(js::ExclusiveContext*, js::Class*, js::TaggedProto, JSObject*, JSObject*, uint32, uint32)
    js::Shape* js::EmptyShape::getInitialShape(js::ExclusiveContext*, js::Class*, js::TaggedProto, JSObject*, JSObject*, uint64, uint32)
    js::UnownedBaseShape* js::BaseShape::getUnowned(js::ExclusiveContext*, js::StackBaseShape*)
    js::BaseShape* js_NewGCBaseShape(js::ThreadSafeContext*) [with js::AllowGC allowGC = (js::AllowGC)1u]
    js::BaseShape* js::gc::NewGCThing(js::ThreadSafeContext*, uint32, uint64, uint32) [with T = js::BaseShape; js::AllowGC allowGC = (js::AllowGC)1u; size_t = long unsigned int]
    void js::gc::RunDebugGC(JSContext*)
    void js::MinorGC(JSRuntime*, uint32)
    GC

这意味着在 js/src/jsopcode.cpp 的第 1866 行,在函数 DecompileExpressionFromStack 中发现了根危害(它以文件名作为前缀,因为它是一个静态函数)。问题在于,有一个未根化的变量 ed 持有一个 ExpressionDecompiler,该变量在调用 decompilePC 期间保持活动状态。“活动”表示变量在调用 decompilePC 返回后被使用。decompilePC 可能会根据从以“GC Function:”开头的行开始给出的静态调用堆栈触发 GC。

危害本身有一些几乎无法理解的 Assume(...)Call(...) 乱码,描述了变量进入函数调用的确切数据流路径。这些内容很少有用——通常,只有在它抱怨临时变量并且想知道临时变量来自哪里时,才需要查看它。类型 ExpressionDecompiler 被认为持有指向某种 GC 控制对象的指针。当前分析不会描述它担心的确切字段。

为了稍微解释一下,分析表示以下情况可能会发生

  • ExpressionDecompiler 包含指向某个 GC 对象的指针。例如,它可能有一个类型为 JSObject* 的字段 obj。(在 hazardIntermediates.tar.xz 内部的 gcTypes.txt 文件中,将提供所有类型的详细说明。)

  • DecompileExpressionFromStack 被调用。

  • 一个指针存储在 ed 变量的该字段中。

  • decompilePC 被调用,它调用 ValueToSource,后者调用 Invoke,最终调用 js::MinorGC

  • 在由此产生的垃圾回收期间,ed.obj 指向的对象被移动到另一个位置。JS 堆中存储的所有指针都会自动更新,根指针也是如此。ed.obj 不会更新,因为 GC 不知道它。

  • decompilePC 返回后,某些内容访问 ed.obj。这现在是一个陈旧的指针,可能指向任何东西——错误的对象、无效的对象或任何其他东西。正如 TeX 所说,**糟糕程度 10000**。

诊断堆写危害故障

已过时:堆写危害分析已多年未更新,并且正在查找不再存在的事物,因此始终会报告零问题。

对于线程不安全的堆写分析,危害意味着某些 Gecko_* 函数直接或间接调用了写入堆上某些内容的代码,或者调用了可能写入堆上某些内容的未知函数。该分析需要相当多的注释来描述实际上安全的方面。随着我们获得更多分析经验,本节将进行扩展,但以下是一些常见问题

  • 添加新的 Gecko_* 函数:通常,需要在 js/src/devtools/rootAnalysis/analyzeHeapWrites.js 中的 treatAsSafeArgument 函数中注释任何 outparams 或拥有(线程本地)的参数。

  • 调用某些 libc 函数:如果添加对某些随机 libc 函数的调用(例如 sin()floor()ceil(),尽管后两个函数已被注释),分析将报告“外部函数”。将其添加到 checkExternalFunction 中,假设它没有可能写入共享堆内存。

  • 如果调用分析不知道的某些不返回(崩溃)的函数,则需要将其添加到 ignoreContents 中。

另一方面,您可能遇到了真正的线程安全问题。共享缓存是常见的问题。修复它。

分析实现

这些构建执行以下操作

  • 设置构建环境并在其中运行分析,然后上传结果文件

  • 编译优化的 JS shell 以供稍后运行分析

  • 使用 gcc 编译浏览器,使用六鳃鲨(http://svn.sixgill.org)gcc 插件的略微修改版本

  • 生成一组描述编译过程中遇到的所有内容的 .xdb 文件

  • 使用 js/src/devtools/rootAnalysis 中的脚本分析 .xdb 文件

存储在这些文件中的信息的格式已有一些文档记录

运行分析

推送到 Try

运行分析最简单的方法是使用 mach try fuzzy -q "'haz" 推送到 Try(或者,如果感兴趣的危害完全包含在 js/src 中,请使用 mach try fuzzy -q "'shell-haz" 以获得更快的结果)。linux64-haz 的预期周转时间略低于 1.5 小时(hazard-linux64-shell-haz 大约 20 分钟)。

输出将被上传,并且输出文件 hazards.txt.xz 将被放置到 Treeherder 上的“工件”信息窗格中。

在本地运行

危害分析可以使用 mach 运行

所以您通过添加危害破坏了分析。现在怎么办?

回退、修复危害,或(最后手段)更新 js/src/devtools/rootAnalysis/expect.browser.json 中的预期危害数量(但不要这样做)。

修复危害最常见的方法是将变量更改为 Rooted 类型,如 RootingAPI.h 中所述

对于更复杂的情况,请在 Matrix 频道上询问(有关联系信息,请参阅 spidermonkey.dev)。如果您没有收到回复,请 ping sfink 或 jonco(根危害),bholley 或 sfink(堆写危害)。