//////////////////////////////////////////////////////
LLVM 链路时间优化:设计与实现
描述¶
LLVM具备强大的模块化优化功能,可在链路处使用 时间。链路时间优化(LTO)是互模化的另一种名称 在链路阶段进行优化。本文档描述了 LTO 优化器与链接器之间的接口与设计。
设计理念
LLVM 链路时间优化器在执行 在编译器工具链中。其主要目标是让 开发者利用了互模块优化,而无需做出任何优化 对开发者的 makefile 或 build 系统进行了重大更改。这是 通过与联络器的紧密集成实现。在该模型中,链接器 将LLVM位码文件视为本地目标文件,并允许混合和 他们之间有匹配。链接器使用 libLTO(共享对象)来处理 LLVM 位码文件。链接器与LLVM优化器之间的紧密集成 有助于实现其他模型无法实现的优化。链接器 输入使优化器避免依赖保守逃逸分析。
链路时间优化示例
以下示例展示了LTO综合方法的优势 以及干净的界面。此示例需要一个支持 LTO 的系统链接器 通过本文档中描述的界面。这里,清脆地叮当 调用系统链接器。
-
输入源文件被编译成LLVM比特码形式。
a.c -
输入源文件被编译成本地目标代码。
main.c
--- a.h ---
extern int foo1(void);
extern void foo2(void);
extern void foo4(void);
--- a.c ---
#include "a.h"
static signed int i = 0;
void foo2(void) {
i = -1;
}
static int foo3() {
foo4();
return 10;
}
int foo1(void) {
int data = 0;
if (i < 0)
data = foo3();
data = data + 42;
return data;
}
--- main.c ---
#include <stdio.h>
#include "a.h"
void foo4(void) {
printf("Hi\n");
}
int main() {
return foo1();
}
编译时,运行:
clang -flto -c a.c -o a.o # <-- a.o is LLVM bitcode file
clang -c main.c -o main.o # <-- main.o is native object file
clang -flto a.o main.o -o main # <-- standard link command with -flto
-
在这个例子中,链接器识别 是外部的 LLVM位码文件中定义的可见符号。链接器完成其惯常的 符号解析通过并找到未被使用的符号 任何地方。LLVM优化器会利用这些信息,并且 去除 。
foo2()foo2()foo2() -
一旦移除 ,优化器就会识别该条件总是为假,这意味着永远不会使用 。因此, 优化器还会移除 。
foo2()i < 0foo3()foo3() -
这反过来使 linker 能够去除 。
foo4()
这个例子说明了与 链接器。在这里,优化器无法在没有链接器的情况下移除 输入。foo3()
替代方法
编译器驱动单独调用链路时间优化器。
在该模型中,链路时间优化器无法利用 在链接器正常符号解析阶段收集的信息。 在上述例子中,优化器无法在没有 链接器的输入是因为它在外部可见。这反过来又禁止了 优化器从移除 。foo2()foo3()
使用独立工具收集所有对象文件中的符号信息。
在该模型中,一个新的独立工具或库复制链接器的 能够收集信息以优化链路时间。不仅如此 这种代码的重复难以证明,但它还有其他几个 缺点。例如,链接语义和所提供的特征 各平台的链接器并不独特。这意味着,这个新工具 需要在一个超级工具中支持所有这些功能和平台 每个平台需要独立工具。这增加了维护成本 链接时间优化器要大幅提升,但其实不是必需的。这种方法 同时也需要与各种链接器的发展保持同步 平台,这并不是链路时间优化器的主要关注点。最后, 这种方法由于重复工作,增加了终端用户的构建时间 由这个独立工具和链接器本身完成。
与链接器之间的多阶段通信
链接器收集关于符号定义和在各种用途中的信息 链接对象,比其他任何信息都更准确 工具在典型构建周期中。链接器通过以下方式收集这些信息 查看原生.o文件中符号的定义和用法,并使用 符号可见性信息。链接器还使用用户提供的信息, 例如导出符号列表。LLVM 优化器收集控制流 信息、数据流信息,并且对程序结构有更多了解 从优化者的角度来看。我们的目标是充分利用紧密的 通过共享这些信息,链接器与优化器之间的集成 在各个连接阶段。
第一阶段:读取LLVM比特码文件
链接器首先按自然顺序读取所有对象文件并收集符号 信息。这包括本地目标文件以及LLVM位码文件。 为了在所有.o文件都是原生文件的情况下,最小化链接器的成本 目标文件,链接器仅在 提供 结果发现对象文件不是原生目标文件。如果返回文件是LLVM位码文件,链接器随后会对 模块使用 和 来定义和引用所有符号。 这些信息会被添加到链接器的全局符号表中。lto_module_create()lto_module_create()lto_module_get_symbol_name()lto_module_get_symbol_attribute()
lto*函数都以共享对象libLTO实现。这允许 LLVM LTO 代码则独立于链接器工具进行更新。站台 支持它的共享对象加载得很懒散。
第二阶段:符号解析
在此阶段,链接器使用全局符号表解析符号。可能 报告未定义的符号错误,读取归档成员,替换弱符号等。 链接器能够无缝完成这一点,尽管它不知道具体操作方式 输入LLVM位码文件的内容。如果启用了死码剥离,则 林克收集了活符号列表。
第三阶段:优化比特码文件
符号解析后,链接器会告诉LTO共享对象哪些符号 是本地对象文件所必需的。在上述示例中,链接器报告 仅用于原生目标文件使用。接着链接器调用LLVM 优化器和代码生成器,使用后者返回 通过合并LLVM的位码文件并应用生成的本地对象文件 经过多次优化。foo1()lto_codegen_add_must_preserve_symbol()lto_codegen_compile()
第四阶段:优化后的符号解析
在此阶段,链接器读取优化的本地对象文件并更新 内部全局符号表以反映任何变化。链接器还收集 关于LLVM位码外部符号使用变化的信息 文件。在上述示例中,链接器注明了未被使用 更多。如果启用了死代码剥离,链接器会刷新实时代码 符号信息被适当处理,并执行死码剥离。foo4()
在此阶段之后,链接器继续链接,仿佛从未见过LLVM位码 文件。
libLTO
libLTO是LLVM工具中共享的对象,且意图如此 供链接器使用。 提供一个抽象的C接口以使用LLVM 过程间优化器,但不暴露LLVM内部细节。该 目的是保持接口尽可能稳定,即使LLVM发生 优化器不断发展。这甚至应该是完全可能的 不同的编译技术以提供不同的 libLTO,使其能够使用 他们的对象文件以及标准链接工具。libLTO
lto_module_t
非本地对象文件通过 .如下 函数允许链接器检查文件(无论是在磁盘上还是内存缓冲区中)是否是 一个 libLTO 可以处理的文件:lto_module_t
lto_module_is_object_file(const char*)
lto_module_is_object_file_for_target(const char*, const char*)
lto_module_is_object_file_in_memory(const void*, size_t)
lto_module_is_object_file_in_memory_for_target(const void*, size_t, const char*)
如果目标文件可以被 处理,链接器通过以下方式创建 :libLTOlto_module_t
lto_module_create(const char*)
lto_module_create_from_memory(const void*, size_t)
完成后,手柄通过以下方式释放
lto_module_dispose(lto_module_t)
链接器可以通过获取 的编号来内省非本地对象文件 符号以及通过以下方式获取每个符号的名称和属性:
lto_module_get_num_symbols(lto_module_t)
lto_module_get_symbol_name(lto_module_t, unsigned int)
lto_module_get_symbol_attribute(lto_module_t, unsigned int)
符号的属性包括对齐、可见性和类型。
在Darwin上处理对象文件的工具(例如Lipo)可能需要了解诸如CPU类型等属性:
lto_module_get_macho_cputype(lto_module_t mod, unsigned int *out_cputype, unsigned int *out_cpusubtype)
lto_code_gen_t
一旦链接器将每个非本地对象文件加载到一个 中,它可以请求处理全部并生成 原生对象文件。这需要几个步骤来完成。首先,代码生成器 由以下方式创建:lto_module_tlibLTO
lto_codegen_create()
然后,每个非本地对象文件被添加到代码生成器中,包含:
lto_codegen_add_module(lto_code_gen_t, lto_module_t)
链接器可以选择设置一些代码生成选项。无论如何 生成DWARF调试信息的设置为:
lto_codegen_set_debug_model(lto_code_gen_t)
哪种位置独立性由以下方式设定:
lto_codegen_set_pic_model(lto_code_gen_t)
而且每个被本地对象文件引用的符号都不能 Be 优化了,设为:
lto_codegen_add_must_preserve_symbol(lto_code_gen_t, const char*)
完成所有设置后,链接器请求本地对象文件 可从模块中创建,设置如下:
lto_codegen_compile(lto_code_gen_t, size*)
该指针返回包含生成的本地对象文件的缓冲区。 链接器随后解析该数据,并将其与本地对象的其他部分关联起来 文件。
///////////////////////////////////////////////////////
原生Android应用的配置文件引导优化
配置文件引导优化(PGO)是一种著名的编译器优化技术。在 PGO 中,编译器利用程序执行时的配置文件来做出内联和代码布局的优化选择。这带来了性能的提升和代码大小的缩小。开发者现在可以利用谷歌的工具包轻松部署 PGO 工具并改进原生 Android 应用。
在部分安卓系统组件上,启用 PGO 性能提升了 6–8%。PGO 还在一个组件中提供了代码大小改进,同时略微增加另外两个组件的代码大小。

PGO 对 Android 系统组件的优势
通过以下步骤,PGO 可以部署到您的应用程序或库中:
- 确定一个具有代表性的工作量。
- 收集个人资料。
- 在发布版本中使用配置文件。
第一步:确定具有代表性的工作量
首先,确定一个代表性的基准或工作负载。这是一个关键步骤,因为从工作负载中收集的配置文件可以识别代码中的热区和冷区。使用配置文件时,编译器会在热区进行积极优化和内联。编译器还可以选择在性能的同时减少冷区的代码大小。
识别良好的工作量对于跟踪整体表现也很有帮助。
第二步:收集资料
这些配置文件是通过在应用的带仪器构建中运行第一步的工作负载收集的。要生成带乐器构建,可以在编译器和链接器中添加标志。这个标志应该由一个独立的构建变量控制,因为默认构建时不需要该标志。-fprofile-generate
在你的邮箱里收到Android开发者的故事
免费加入Medium,获取作者的最新动态。
记得我,快速登录
当运行带工具的二进制文件时,配置文件会被收集,并在退出时写入文件。然而,注册的功能不会在安卓应用中调用——应用会被直接关闭。应用程序/工作负载必须通过调用函数显式触发配置文件写入。atexit__llvm_profile_write_file
在工作负载结束时触发配置文件写入的示例
如果工作负载是独立的二进制文件,写配置文件会更简单——只需在运行二进制文件前设置LLVM_PROFILE_FILE环境变量即可。
配置文件格式为 。使用 NDK 中的工具将 从 转换为 ,然后传递给编译器。.profrawllvm-profdata.profraw.profdata
将 .profraw 文件转换为 .profdata 的命令
使用同一NDK版本的和,以避免配置文件文件格式的版本不匹配。llvm-profdataclang
步骤3:使用配置文件构建应用
Use the profile from the previous step during a release build of your application by passing to the compiler and linker. The profiles can be used even as the code evolves — the Clang compiler can tolerate slight mismatch between the source and the profiles.-fprofile-use=<>.profdata
Case Study
“” is Android’s on-device AOT compiler. To get a representative workload for , we randomly selected 25 of the top 100 most-installed apps in the Play store. We also randomly generated dex2oat’s compilation options.dex2oatdex2oat
To generate PGO profiles, we built a PGO-instrumented binary and used it to compile the workload. We then generated a release-build of that uses these PGO profiles and evaluated performance gains on the remaining 75 of the 100 most-installed apps.dex2oatdex2oat
我们利用Android团队可用的测试基础设施,自动化收集这些PGO配置文件,使其能够轻松保持最新。
结论
PGO 是一种非常有用的性能优化技术。在构建过程中初步设置工作负载并集成后,它能以极少的维护带来令人印象深刻的性能提升。
以下是一些其他话题,可以帮助提升Android应用的性能:
- 链路时间优化:LTO + PGO 单独比每个都更好。
- Java 应用的云配置文件
/////////////////////////////////////////////////////////////
适用于 Android 13 及更低版本的 Android 构建系统支持在具有 blueprint 构建规则的原生 Android 模块上使用 Clang 的配置文件引导型优化 (PGO)。本页面介绍了 Clang PGO、如何持续生成和更新用于 PGO 的配置文件,以及如何将 PGO 集成到构建系统(包含用例)。
注意:本文档介绍了如何在 Android 平台中使用 PGO。如需了解如何在 Android 应用中使用 PGO,请访问此页面。
Clang PGO 简介
Clang 可以使用两类配置文件来执行配置文件引导的优化:
- 基于插桩的配置文件是从经过插桩的目标程序生成。这些配置文件很详细,且会产生很高的运行时开销。
- 基于采样的配置文件通常通过对硬件计数器进行采样生成。此类配置文件产生的运行时开销较低,并且无需对二进制文件进行任何插桩或修改即可收集。详细程度不如基于插桩的配置文件。
所有配置文件都应该从符合应用典型行为方式的代表性工作负载生成。虽然 Clang 同时支持基于 AST (-fprofile-instr-generate) 和基于 LLVM IR (-fprofile-generate)) 的配置文件,但对于基于插桩的 PGO,Android 仅支持基于 LLVM IR 的配置文件。
为了收集配置文件,您需要在构建时采用以下标志:
-fprofile-generate,适用于基于 IR 的插桩。借助此选项,后端可使用加权最小生成树方法来减少插桩点的数量,并优化它们在低权重边缘的放置(对于链接步骤也使用此选项)。Clang 驱动程序会自动将性能分析运行时 (libclang_rt.profile-arch-android.a) 传递给链接器。该库包含可在程序退出时将配置文件写入磁盘的例程。-gline-tables-only,适用于基于采样的配置文件收集,可生成精简的调试信息。
配置文件可用于 PGO(分别针对基于插桩的配置文件和基于采样的配置文件使用 -fprofile-use=pathname 或 -fprofile-sample-use=pathname)。
注意:对代码进行更改后,如果 Clang 无法再使用配置文件数据,它会生成一条 -Wprofile-instr-out-of-date 警告。
使用 PGO
如需使用 PGO,请按以下步骤操作:
- 通过将
-fprofile-generate传递给编译器和链接器,在构建库/可执行文件时执行插桩。 - 使用经过插桩的二进制文件来运行具有代表性的工作负载,以此收集配置文件。
- 使用
llvm-profdata实用程序对配置文件进行后处理(如需了解详情,请参阅处理 LLVM 配置文件)。 - 通过将
-fprofile-use=<>.profdata传递给编译器和链接器,使用配置文件进行 PGO。
对于 Android 中的 PGO,应离线收集配置文件并随代码签入,以确保 build 可重现。无论代码如何变化,配置文件都可一直使用,但必须定期(或在 Clang 发出配置文件过时的警告时)重新生成。
收集配置文件
Clang 可以使用通过以下方式收集的配置文件:使用库的插桩 build 来生成基准,或在生成基准时对硬件计数器进行采样。目前,Android 不支持使用基于采样的配置文件收集,因此您必须使用插桩 build 收集配置文件:
- 确定一个基准以及由该基准集体执行的一组库。
- 将
pgo属性添加到该基准和各个库(详情请见下文)。 - 使用以下命令生成包含这些库的插桩副本的 Android build:
make ANDROID_PGO_INSTRUMENT=benchmark
benchmark 是占位符,用于标识在构建时插桩的库集合。实际的代表性输入(也可能是链接到进行基准化的库的其他可执行文件)并非专用于 PGO,不在本文档的讨论范围内。
- 在设备上刷写或同步插桩 build。
- 运行基准以收集配置文件。
- 使用下文中介绍的
llvm-profdata工具对配置文件进行后处理,并使其做好签入源代码树的准备。
在构建时使用配置文件
将配置文件签入 Android 树中的 toolchain/pgo-profiles。名称应与库的 pgo 属性的 profile_file 子属性中指定的名称一致。构建库时,构建系统会自动将配置文件传递给 Clang。您可以将 ANDROID_PGO_DISABLE_PROFILE_USE 环境变量设置为 true,以暂时停用 PGO 并衡量其性能优势。
如需指定额外的产品专用配置文件目录,请将其附加到 BoardConfig.mk 中的 PGO_ADDITIONAL_PROFILE_DIRECTORIES make 变量。如果指定了其他路径,这些路径中的配置文件将覆盖 toolchain/pgo-profiles 中的配置文件。
当使用 make 的 dist 目标生成版本映像时,构建系统会将缺失的配置文件的名称写入 $DIST_DIR/pgo_profile_file_missing.txt。您可以检查此文件,看看哪些配置文件遭到了意外删除(这会以静默方式停用 PGO)。
在 Android.bp 文件中启用 PGO
如需在 Android.bp 文件中为原生模块启用 PGO,只需指定 pgo 属性即可。此属性具有以下子属性:
| 属性 | 说明 |
|---|---|
instrumentation | 对于使用插桩方法的 PGO,设置为 true。默认值为 false。 |
sampling | 对于使用采样方法的 PGO,设置为 true。默认值为 false。 |
benchmarks | 字符串列表。如果在 ANDROID_PGO_INSTRUMENT 构建选项中指定了该列表中的任何基准,则会构建此模块用于性能分析。 |
profile_file | 要与 PGO 结合使用的配置文件(相对于 toolchain/pgo-profile)。除非将 enable_profile_use 属性设置为 false,或者将 ANDROID_PGO_NO_PROFILE_USE 构建变体设置为 true,否则构建将通过将此文件添加到 $DIST_DIR/pgo_profile_file_missing.txt 来警告此文件不存在。 |
enable_profile_use | 如果在构建期间不应使用配置文件,则设置为 false。可在引导期间用来启用配置文件收集或暂时停用 PGO。默认值为 true。 |
cflags | 在插桩构建期间使用的其他标志的列表。 |
包含 PGO 的模块示例:
cc_library {
name: "libexample",
srcs: [
"src1.cpp",
"src2.cpp",
],
static: [
"libstatic1",
"libstatic2",
],
shared: [
"libshared1",
]
pgo: {
instrumentation: true,
benchmarks: [
"benchmark1",
"benchmark2",
],
profile_file: "example.profdata",
}
}
如果基准 benchmark1 和 benchmark2 为 libstatic1、libstatic2 或 libshared1 库执行代表性行为,这些库的 pgo 属性也可以包括这些基准。Android.bp 中的 defaults 模块可以包含一组库的通用 pgo 规范,以避免针对多个模块重复相同的构建规则。
如需为某个架构选择不同的配置文件或有选择性地停用 PGO,请按架构指定 profile_file、enable_profile_use 和 cflags 属性。示例(架构 target 以粗体显示)如下所示:
cc_library {
name: "libexample",
srcs: [
"src1.cpp",
"src2.cpp",
],
static: [
"libstatic1",
"libstatic2",
],
shared: [
"libshared1",
],
pgo: {
instrumentation: true,
benchmarks: [
"benchmark1",
"benchmark2",
],
}
target: {
android_arm: {
pgo: {
profile_file: "example_arm.profdata",
}
},
android_arm64: {
pgo: {
profile_file: "example_arm64.profdata",
}
}
}
}
如需在执行基于插桩的性能分析期间解析对性能分析运行时库的引用,请将构建标志 -fprofile-generate 传递给链接器。此外,使用 PGO 插桩的静态库、所有共享库以及任何直接依赖于静态库的二进制文件也必须针对 PGO 进行插桩。不过,此类共享库或可执行文件不需要使用 PGO 配置文件,而且它们的 enable_profile_use 属性可以设置为 false。除此限制外,您可以将 PGO 应用于任何静态库、共享库或可执行文件。
处理 LLVM 配置文件
执行插桩库或可执行文件会在 /data/local/tmp 中生成一个名为 default_unique_id_0.profraw 的配置文件(其中 unique_id 是一个数字哈希值,唯一对应于此库)。如果此文件已存在,性能分析运行时会在写入配置文件时将新老配置文件合并。请注意,应用开发者无法访问 /data/local/tmp;而是应该改用类似 /storage/emulated/0/Android/data/packagename/files 的路径。 如需更改配置文件的位置,请在运行时设置 LLVM_PROFILE_FILE 环境变量。
然后使用 llvm-profdata 实用程序将 .profraw 文件转换(可能会合并多个 .profraw 文件)为 .profdata 文件:
llvm-profdata merge -output=profile.profdata <.profraw and/or .profdata files>
然后,可以将 profile.profdata 签入源代码树,以在构建时使用。
如果某个基准运行期间加载了多个插桩二进制文件/库,则每个库都会生成一个单独的 .profraw 文件(具有一个独一无二的 ID)。通常,所有这些文件可以合并为一个 .profdata 文件来用于 PGO 构建。如果某个库由另一个基准执行,则必须使用来自两个基准的配置文件优化该库。在这种情况下,llvm-profdata 的 show 选项非常有用:
llvm-profdata merge -output=default_unique_id.profdata default_unique_id_0.profraw llvm-profdata show -all-functions default_unique_id.profdata
如需将 unique_id 映射到各个库,请搜索每个 unique_id 的 show 输出,以查找相应库独有的函数名称。unique_idunique_id
案例研究:适用于 ART 的 PGO
本案例将 ART 作为一个相关的示例;不过,它并没有准确描述为 ART 或其相互依赖关系性能分析的一系列实际库。
ART 中的 dex2oat 预编译器依赖于 libart-compiler.so,后者又依赖于 libart.so。ART 运行时主要在 libart.so 中实现。编译器和运行时的基准将不同:
| 基准 | 接受性能分析的库 |
|---|---|
dex2oat | dex2oat(可执行文件)、libart-compiler.so、libart.so |
art_runtime | libart.so |
或者,使用以下命令创建一个包含所有插桩库的插桩 build:
第二个命令会构建所有启用 PGO 的模块来进行性能分析。
- 将以下
pgo属性添加到dex2oat、libart-compiler.so:pgo: { instrumentation: true, benchmarks: ["dex2oat",], profile_file: "dex2oat.profdata", } - 将以下
pgo属性添加到libart.so:pgo: { instrumentation: true, benchmarks: ["art_runtime", "dex2oat",], profile_file: "libart.profdata", } - 使用以下命令为
dex2oat和art_runtime基准创建插桩 build:make ANDROID_PGO_INSTRUMENT=dex2oat make ANDROID_PGO_INSTRUMENT=art_runtime
make ANDROID_PGO_INSTRUMENT=dex2oat,art_runtime
(or)
make ANDROID_PGO_INSTRUMENT=ALL
- 运行执行
dex2oat和art_runtime的基准以获得:- 三个来自
dex2oat的.profraw文件(dex2oat_exe.profdata、dex2oat_libart-compiler.profdata和dexeoat_libart.profdata),这三个文件均使用处理 LLVM 配置文件中说明的方法标识。 - 一个
art_runtime_libart.profdata。
- 三个来自
- 使用以下命令为
dex2oat可执行文件和libart-compiler.so生成一个通用的 profdata 文件:llvm-profdata merge -output=dex2oat.profdata \ dex2oat_exe.profdata dex2oat_libart-compiler.profdata - 通过合并来自两个基准的配置文件获得
libart.so的配置文件:llvm-profdata merge -output=libart.profdata \ dex2oat_libart.profdata art_runtime_libart.profdata来自两个配置文件的
libart.so的原始计数可能是不同的,因为这两个基准的测试用例数和运行时长不同。在这种情况下,您可以使用以下加权合并命令:llvm-profdata merge -output=libart.profdata \ -weighted-input=2,dex2oat_libart.profdata \ -weighted-input=1,art_runtime_libart.profdata以上命令为来自
dex2oat的配置文件分配了两倍的权重。实际权重应根据领域知识或实验来确定。 - 将配置文件
dex2oat.profdata和libart.profdata签入toolchain/pgo-profiles,以在构建期间使用。
///////////////////////////////////////////////////////
获取系统健康状况信息
搭载 Android 16(或更高版本)的设备在启动时会进入以旧换新模式。在这种模式下,您可以使用 adb 连接到设备,并可以使用命令获取设备相关信息。设备必须满足以下前提条件,才能进入以旧换新模式:
- 设备必须恢复出厂设置。
- 设备不得提供移动网络服务。
- 设备必须未连接到网络或未建立账号。
- 设备必须运行不可调试的 build。
在以旧换新模式下,您可以查询基本诊断信息,也可以进入评估模式。在这种模式下,您可以在设备上执行各种 adb 命令并运行其他诊断。
收集常规运行状况信息
若要收集设备的常规运行状况信息,例如关于电池、存储空间状况和国际移动设备识别码 (IMEI) 的信息,请按以下步骤操作:
-
确保设备符合以旧换新模式的前提条件。
-
将设备插入工作站。
-
在工作站上,运行以下命令:
adb shell tradeinmode getstatus此命令会返回一个 JSON 对象,其中包含设备的相关信息。以下是 Pixel 7 的输出示例:
{ "battery": { "cycle_count": 16, "health": 100, "state": 2, "manufacturing_date": 1653004800, "first_usage_date": 0 }, "storage": { "useful_lifetime_remaining": 99, "capacity_bytes": "128000000000" }, "launch_level": 33, "locks": { "factory_reset_protection": false }, "product": { "brand": "google", "device": "panther", "manufacturer": "Google", "model": "Pixel 7", "name": "panther" }, "imeis": [ "353644930127905", "353644930127913" ], "serial": "26061FDH2000AP" }如果
factory_reset_protection设置为true,则设备处于安全状态,无法重置。如果设备无法重置,则无法对其进行评估。
识别 Android OS 状态
若要收集关于 Android 操作系统状态的信息(例如是否为已获批准的 build),请按以下步骤操作:
- 将设备插入工作站。
- 确保设备已连接到网络。
-
在工作站上,运行以下命令:
adb shell tradeinmode getstatus --challenge CHALLENGECHALLENGE 是随机生成的字母数字字符串,例如
p4tRsuHjWB。此命令会返回 JSON,其中包含一个带有 base64 认证记录的认证字段。此命令会将认证信息附加到
getstatus命令返回的信息。认证信息如下所示:"attestation": { "certificates": "AAAC\/DCCAvgwggKeoAMCAQICAQEwCgYIKoZIzj0EAwIwOTEMMAoGA1UEDAwDVEVFMSkwJwYDVQQF\n EyBmOTIyZTZhOWFkZmRjNjU0NmZiOWU1YmNlNzhiMDUzMzAeFw03MDAxMDEwMDAwMDBaFw00ODAx\n MDEwMDAwMDBaMB8xHTAbBgNVBAMTFEFuZHJvaWQgS2V5c3RvcmUgS2V5MFkwEwYHKoZIzj0CAQYI\n KoZIzj0DAQcDQgAEz9un3HpDJQy\/j7l0bWzw6WnRRMjFjvu6rg7+dCzFW93u+otCPK4VjmSjyYw ... }发布时搭载 Android 16 或更高版本的设备需要连接到网络才能创建认证记录。应在配置连接后,在评估模式下执行认证,因为如果在设置向导中执行,认证会失败。
-
使用以下方法之一解析认证信息:
- 使用在构建 AOSP 时构建的
parse_tim_attestation工具。 - 使用 Android 密钥认证库。
例如,若要使用
parse_tim_attestation工具,请运行以下命令:parse_tim_attestation --challenge CHALLENGE output_fileCHALLENGE 必须与您用于获取认证信息的验证问题相同。
如果您将第 2 步的输出保存到 output_file,则可以提供该文件名。否则,系统会从 stdin 读取认证信息。
系统会返回一个包含 Android OS 信息的 JSON 对象:
"record": { "keymaster_version": "400", "keymaster_security_level": "TRUSTED_ENVIRONMENT", "attributes": { "imeis": [ "353644930125669", "353644930125677" ], "vendor_patch_level": 20250305, "serial": "26161FDH2000NV", "os_version": 160000, "source": "hardware", "boot_patch_level": 20250305 }, "bootloader_locked": false, "verified_boot": false, "security_level": "TRUSTED_ENVIRONMENT" }, "certificate": "verified", "trustworthy": "verified boot disabled"如果
trustworthy等于yes,则操作系统会被视为可信;build 已签名且 IMEI 不是伪造的。请注意,在设备和主机上都需要网络连接才能执行认证。
- 使用在构建 AOSP 时构建的
测试以旧换新模式
在开发期间,您应测试设备,以确保设备能够正确进入和退出以旧换新模式。您可以按照以下步骤测试您的设备是否能够进入和退出以旧换新模式:
注意:这些命令仅适用于搭载 userdebug、eng 或 user build 的设备。user build 必须设置 ro=debuggable=1。
-
将设备插入工作站。
-
在工作站上重启设备,使其进入以旧换新模式:
adb shell tradeinmode testing start设备会重启并进入以旧换新模式。在进入以旧换新模式后,您可以使用任何
adb shell tradein命令。 -
确保以旧换新模式处于启用状态:
adb shell tradeinmode testing status设备会识别是否正在进行以旧换新模式测试。
-
退出以旧换新模式并恢复完整 adb 访问权限:
adb shell tradeinmode testing stop
自定义设置向导集成
除非添加了广播接收器,否则 evaluate 命令无法在具有自定义设置向导的设备上运行。如需向自定义设置向导应用添加广播接收器,请执行以下操作:
-
在应用清单中声明接收器:
<receiver android:name=".EnterEvaluationModeReceiver" android:exported="true" android:permission="android.permission.ENTER_TRADE_IN_MODE"> <intent-filter> <action android:name="com.google.android.setupwizard.ENTER_TRADE_IN_MODE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> -
创建类似于以下内容的广播接收器:
public class EnterEvaluationModeReceiver extends BroadcastReceiver { private static final String TRADE_IN_MODE_PROPERTY = "persist.adb.tradeinmode"; private static final int TIM_EVALUATION_MODE = 2; @Override public void onReceive(Context context, Intent intent) { if (SystemProperties.getInt(TRADE_IN_MODE_PROPERTY, 0) != TIM_EVALUATION_MODE) { return; } // Check if any factory reset protection is enabled. // Provision the device. // End the setup wizard activity. } }
接收方必须按顺序执行以下操作。
- 检查
persist.adb.tradeinmode是否为2。 - 检查是否没有恢复出厂设置保护机制或防盗锁。
- 配置设备,确保
Settings.Secure.USER_SETUP_COMPLETE和Settings.Global.DEVICE_PROVISIONED为1。 - 关闭设置向导 activity,使设备处于主屏幕。
以旧换新模式参考
本部分介绍了所有以旧换新模式命令。
evaluate
adb shell tradeinmode evaluate
跳过设置向导以进入评估模式,就像用户手动跳过每个设置界面一样。
跳过设置向导后,您可以在设备上运行其他 ADB 命令或功能测试。
退出评估模式后,设备会恢复出厂设置,以确保不会将测试中的任何制品意外传输给任何客户。
getstatus
adb shell tradeinmode getstatus [--challenge CHALLENGE]
返回一个包含设备系统信息(包括电池和存储空间状况信息)的 JSON 字符串。
添加 --challenge 参数,后跟随机生成的字母数字验证密钥,以返回额外的认证字段。解析此响应以识别关键的操作系统信息,例如引导加载程序的状态(已锁定或未锁定)和 IMEI 序列号的有效性。
poweroff
adb shell tradeinmode poweroff
关闭设备。使用此命令可防止在设备未接受测试或评估时消耗电池电量。
重新启动
adb shell tradeinmode reboot
重新启动设备。
testing start
adb shell tradeinmode testing start
重启设备,使其进入以旧换新模式。此命令只能在设置向导中使用。发出此命令后,系统会绕过授权,并且只有以旧换新模式命令有效。
此命令仅适用于搭载 userdebug、eng 或 user build 的设备。user build 必须设置 ro=debuggable=1。
testing status
adb shell tradeinmode testing status
识别是否正在进行以旧换新模式测试。
此命令仅适用于搭载 userdebug、eng 或 user build 的设备。user build 必须设置 ro=debuggable=1。
testing stop
adb shell tradeinmode testing stop
将设备恢复到您发出 adb shell tradeinmode testing start 命令之前的模式。
此命令仅适用于搭载 userdebug、eng 或 user build 的设备。user build 必须设置 ro=debuggable=1。
testing wipe
adb shell tradeinmode testing wipe
将设备恢复出厂设置。
此命令仅适用于搭载 userdebug、eng 或 user build 的设备。user build 必须设置 ro=debuggable=1。
wait-until-ready
adb shell tradeinmode wait-until-ready
等待系统服务准备就绪,以便以旧换新模式能够全面发挥作用。在自动化流程中使用此命令,以确保以旧换新模式命令成功。
您可以在其他以旧换新模式命令之前添加 wait-until-ready。例如,下面是链接到 getstatus 的 wait-until-ready:
adb shell tradeinmode wait-until-ready getstatus
/////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////

6125

被折叠的 条评论
为什么被折叠?



