指向分析报告

指向分析(points-to analysis)会生成两种报告:分析调用树和对象树。这些信息是在构建过程的中间步骤中生成的,代表了调用图和堆对象图的静态分析视图。这些图在构建过程中会进一步转换,然后分别被预编译到二进制文件和写入二进制堆中。

除了整个分析域的综合报告外,指向分析还可以生成可达性报告,说明为什么某些类型/方法/字段是可达的。

调用树 #

调用树是由指向分析所观察到的调用图的广度优先树状简化。指向分析会根据所分析的接收者类型,消除它判断在运行时不可达的方法调用。它还会完全消除不可达代码块中的调用,例如那些被总是失败的类型检查所保护的代码块。调用树报告可以通过-H:+PrintAnalysisCallTree命令行选项启用,并可以格式化为TXT文件(默认)或使用-H:PrintAnalysisCallTreeType=CSV选项格式化为一组CSV文件。

TXT格式 #

当使用TXT格式时,会生成一个具有以下结构的文件

VM Entry Points
├── entry <entry-method> id=<entry-method-id>
│   ├── directly calls <callee> id=<callee-id> @bci=<invoke-bci>
│   │   └── <callee-sub-tree>
│   ├── virtually calls <callee> @bci=<invoke-bci>
│   │   ├── is overridden by <override-method-i> id=<override-method-i-id>
│   │   │   └── <callee-sub-tree>
│   │   └── is overridden by <override-method-j> id-ref=<override-method-j-id>
│   └── interfacially calls <callee> @bci=<invoke-bci>
│       ├── is implemented by <implementation-method-x> id=<implementation-method-x-id>
│       │   └── <callee-sub-tree>
│       └── is implemented by <implementation-method-y> id-ref=<implementation-method-y-id>
├── entry <entry-method> id=<entry-method-id>
│   └── <callee-sub-tree>
└── ...

介于<>之间的标签会展开为具体值,其余部分按所示打印。方法使用<qualified-holder>.<method-name>(<qualified-parameters>):<qualified-return-type>格式化,并展开直到没有更多被调用者可达。

由于这是调用图的树状简化,每个具体方法只展开一次。树状表示本身会省略对已在不同分支或同一分支中探索过的方法的调用。此限制隐式解决了递归问题。为了传达通过树状简化丢失的信息,每个具体方法都被赋予一个唯一ID。因此,当方法首次被访问时,它会声明一个标识符,例如id=<method-id>。随后对同一方法的发现会使用标识符引用来指向先前的展开位置:id-ref=<method-id>。每个id=<method-id>id-ref=<method-id>后面都跟着一个空格,以便于搜索。

每个调用都标记有调用bci:@bci=<invoke-bci>。对于内联方法的调用,<invoke-bci>是一个bci值列表,用->分隔,枚举了内联位置,反向追溯到原始调用位置。

CSV格式 #

当使用CSV格式时,会生成一组包含方法及其关系的原始数据文件。具体来说,会生成三个文件——它们分别代表方法、方法调用和调用目标。call_tree_methods_*.csv具有以下列:

  • Id:此方法的唯一标识符。
  • Name:方法的名称。
  • Type:声明类型。
  • Parameters:参数类型的空格分隔列表。
  • Return:返回类型。
  • Display:方法限定名称的缩短版本,对可视化很有用。
  • Flags:其他元数据,例如可见性修饰符、同步等。
  • IsEntryPoint:如果为true,则该方法是调用图中的一个入口点(根方法),否则为false

call_tree_invokes_*.csv具有以下列:

  • Id:调用的唯一标识符。
  • MethodId:调用所在方法的标识符。
  • BytecodeIndexes:调用的字节码索引。如果方法被内联,它可能是一个通过->连接的字节码索引链。
  • TargetId:目标方法的ID。
  • IsDirect:如果为true,则该调用是直接调用,否则为false

call_tree_targets_*.csv有两列:InvokeIdTargetId,用于连接调用及其调用目标。

这些文件的目的是使原始数据能够被自定义脚本轻松处理或导入到图数据库中。图数据库可以提供以下功能:

  • 调用树图的精密图形可视化,与基于文本的格式相比提供了不同的视角。
  • 能够执行复杂查询,例如显示树中导致特定代码路径包含在调用树分析中的子集。此查询功能对于管理大型分析调用树至关重要。

将文件导入图数据库的过程因数据库而异。请遵循图数据库提供商提供的说明。

对象树 #

对象树是对原生二进制堆中包含的对象的详尽展开。该树通过深度优先遍历原生二进制堆对象图获得。它可以通过-H:+PrintImageObjectTree选项启用。根可以是静态字段,也可以是包含嵌入常数的方法图。打印的值是添加到原生二进制堆的具体常量对象。生成的文件结构如下:

Heap roots
├── root <root-field> value:
│   └── <value-type> id=<value-id> toString=<value-as-string> fields:
│       ├── <field-1> value=null
│       ├── <field-2> toString=<field-2-value-as-string> (expansion suppressed)
│       ├── <field-3> value:
│       │   └── <field-3-value-type> id=<field-3-value-id> toString=<field-3-value-as-string> fields:
│       │       └── <object-tree-rooted-at-field-3>
│       ├── <array-field-4> value:
│       │   └── <array-field-4-value-type> id=<array-field-4-value-id> toString=<array-field-4-value-as-string> elements (excluding null):
│       │       ├── [<index-i>] <element-index-i-value-type> id=<element-index-i-value-id> toString=<element-index-i-value-as-string> fields:
│       │       │   └── <object-tree-rooted-at-index-i>
│       │       └── [<index-j>] <element-index-j-value-type> id=<element-index-j-value-id> toString=<element-index-j-value-as-string> elements (excluding null):
│       │           └── <object-tree-rooted-at-index-j>
│       ├── <field-5> value:
│       │   └── <field-5-value-type> id-ref=<field-5-value-id> toString=<field-5-value-as-string>
│       ├── <field-6> value:
│       │   └── <field-6-value-type> id=<field-6-value-id> toString=<field-6-value-as-string> (no fields)
│       └── <array-field-7> value:
│           └── <array-field-7-value-type> id=<array-field-7-id> toString=<array-field-7-as-string> (no elements)
├── root <root-field> id-ref=<value-id> toString=<value-as-string>
├── root <root-method> value:
│   └── <object-tree-rooted-at-constant-embeded-in-the-method-graph>
└── ...

介于<>之间的标签会展开为具体值,其余部分按所示打印。根字段使用<qualified-holder>.<field-name>:<qualified-declared-type>格式化。非根字段使用<field-name>:<qualified-declared-type>格式化。值类型使用<qualified-type>格式化。根方法使用<qualified-holder>.<method-name>(<unqualified-parameters>):<qualified-return-type>格式化。非数组对象会对其所有字段(包括空字段)进行展开。没有字段的非数组对象会标记为(no fields)。数组对象会对其所有非空索引进行展开:[<element-index>] <object-tree-rooted-at-array-element>。空数组对象或所有元素都为空的数组对象会标记为(no elements)

每个常数值只展开一次以压缩格式。当一个值从多个分支到达时,它只在第一次被展开并给定一个标识符:id=<value-id>。随后对同一值的发现会使用标识符引用来指向先前的展开位置:id-ref=<value-id>

抑制值的展开 #

某些值,例如StringBigInteger和原始类型数组,默认不展开并标记为(expansion suppressed)。所有其他类型默认展开。要强制抑制默认展开的类型,可以使用-H:ImageObjectTreeSuppressTypes=<comma-separated-patterns>。要强制展开默认抑制或通过选项抑制的类型,可以使用-H:ImageObjectTreeExpandTypes=<comma-separated-patterns>。当同时指定-H:ImageObjectTreeSuppressTypes-H:ImageObjectTreeExpandTypes时,-H:ImageObjectTreeExpandTypes具有优先权。

同样,某些根,例如java.lang.Character$UnicodeBlock.map"(它打印大量字符串),完全不展开,也标记为(expansion suppressed)。所有其他根默认展开。要强制抑制默认展开的根,可以使用-H:ImageObjectTreeSuppressRoots=<comma-separated-patterns>。要强制展开默认抑制或通过选项抑制的根,可以使用-H:ImageObjectTreeExpandRoots=<comma-separated-patterns>。当同时指定-H:ImageObjectTreeSuppressRoots-H:ImageObjectTreeExpandRoots时,-H:ImageObjectTreeExpandRoots具有优先权。

以上所有抑制/展开选项都接受逗号分隔的模式列表。对于类型,模式基于类型的完全限定名,并指代常量的具体类型。(对于数组类型,只需指定元素类型;它将匹配该类型的所有维度数组。)对于根,模式基于上述根的字符串格式。模式接受*修饰符:

  • 结尾匹配:*<str> - 该模式精确匹配所有以<str>结尾的条目
  • 开头匹配:<str>* - 该模式精确匹配所有以<str>开头的条目
  • 包含:*<str>* - 该模式精确匹配所有包含<str>的条目
  • 等于:<str> - 该模式精确匹配所有等于<str>的条目
  • 所有:* - 该模式匹配所有条目

示例

类型抑制/展开

  • -H:ImageObjectTreeSuppressTypes=java.io.BufferedWriter - 抑制java.io.BufferedWriter对象的展开
  • -H:ImageObjectTreeSuppressTypes=java.io.BufferedWriter,java.io.BufferedOutputStream - 抑制java.io.BufferedWriterjava.io.BufferedOutputStream对象的展开
  • -H:ImageObjectTreeSuppressTypes=java.io.* - 抑制所有java.io.*对象的展开
  • -H:ImageObjectTreeExpandTypes=java.lang.String - 强制展开java.lang.String对象
  • -H:ImageObjectTreeExpandTypes=java.lang.String,java.math.BigInteger - 强制展开java.lang.Stringjava.math.BigInteger对象
  • -H:ImageObjectTreeExpandTypes=java.lang.* - 强制展开所有java.lang.*对象
  • -H:ImageObjectTreeSuppressTypes=java.io.* -H:ImageObjectTreeExpandTypes=java.io.PrintStream - 抑制所有java.io.*对象的展开,但不对java.io.PrintStream对象进行抑制
  • -H:ImageObjectTreeExpandTypes=* - 强制展开所有类型的对象,包括默认抑制的对象

根抑制/展开

  • -H:ImageObjectTreeSuppressRoots="java.nio.charset.Charset.lookup(String)" - 抑制根java.nio.charset.Charset.lookup(String)的展开
  • -H:ImageObjectTreeSuppressRoots=java.util.* - 抑制所有以java.util.开头的根的展开
  • -H:ImageObjectTreeExpandRoots=java.lang.Character$UnicodeBlock.map - 强制展开java.lang.Character$UnicodeBlock.map静态字段根
  • -H:ImageObjectTreeSuppressRoots=java.util.* -H:ImageObjectTreeExpandRoots=java.util.Locale - 抑制所有以java.util.开头的根的展开,但不对java.util.Locale进行抑制
  • -H:ImageObjectTreeExpandRoots=* - 强制展开所有根,包括默认抑制的根

可达性报告 #

在诊断代码大小或安全问题时,开发者通常需要知道为什么特定的代码元素(类型/方法/字段)是可达的。可达性报告就是为此目的而设计的。有三个选项分别用于诊断类型、方法和字段的可达性原因:

  • -H:AbortOnTypeReachable=<pattern>
  • -H:AbortOnMethodReachable=<pattern>
  • -H:AbortOnFieldReachable=<pattern>

对于每个选项,右侧指定了待诊断的代码元素的模式。

  • 指定类型和字段的语法与抑制/展开的语法相同(参见上文-H:ImageObjectTreeSuppressTypes的文档)。
  • 指定方法的语法与方法过滤器的语法相同(参见-Djdk.graal.MethodFilter的文档)。

当其中一个选项被启用且对应的代码元素可达时,可达性跟踪将被转储到TXT文件,并且Native Image将停止。以下是-H:AbortOnTypeReachable=java.io.File的可达性报告示例:

Type java.io.File is marked as allocated
at virtual method com.oracle.svm.core.jdk.NativeLibrarySupport.loadLibraryRelative(NativeLibrarySupport.java:105), implementation invoked
├── at virtual method com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport.loadJavaLibrary(JNIPlatformNativeLibrarySupport.java:44), implementation invoked
│       ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.loadJavaLibrary(PosixNativeLibraryFeature.java:117), implementation invoked
│       │       ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.initializeBuiltinLibraries(PosixNativeLibraryFeature.java:98), implementation invoked
│       │       │       ├── at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.initializeIsolate(CEntryPointSnippets.java:346), implementation invoked
│       │       │       │       str: static root method
│       │       │       └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
│       │       │               scanning root com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d: com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d embedded in
│       │       │                   org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
│       │       │                   at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
│       │       │                       at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.createIsolate(CEntryPointSnippets.java:209), implementation invoked
│       │       └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
│       └── type com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport is reachable
└── type com.oracle.svm.core.jdk.NativeLibrarySupport is marked as in-heap
        scanning root com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea: com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea embedded in
            org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
            at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified

报告文件 #

报告生成在构建目录的reports子目录中。执行native-image可执行文件时,构建目录默认为工作目录,可以使用-H:Path=<dir>选项进行修改。

当使用TXT格式时,调用树报告名称的结构为call_tree_<binary_name>_<date_time>.txt;当使用CSV格式时,调用树报告的名称结构为call_tree_*_<binary_name>_<date_time>.csv。生成CSV格式的调用树报告时,还会生成遵循call_tree_*.csv结构的符号链接,指向最新的调用树CSV报告。对象树报告名称的结构为:object_tree_<binary_name>_<date_time>.txt。二进制名称是生成的二进制文件的名称,可以使用-H:Name=<name>选项设置。<date_time>采用yyyyMMdd_HHmmss格式。

可达性报告也位于reports目录中。它们遵循相同的命名约定:

  • 类型可达性报告:trace_types_<binary_name>_<date_time>.txt
  • 方法可达性报告:trace_methods_<binary_name>_<date_time>.txt
  • 字段可达性报告:trace_fields_<binary_name>_<date_time>.txt

延伸阅读 #

联系我们