GSI 通用系统镜像
GSI(Generic System Image,通用系统镜像)经常被概括为“一张可以刷到所有 Treble 设备上的 system.img”。这句话抓住了它的目标,却省略了最重要的条件:GSI 只替换 framework/system 一侧, 设备仍使用自己的 boot、kernel、vendor、odm 和硬件实现;两侧必须通过稳定接口、VINTF 元数据、内核 要求和启动链共同满足兼容条件。
本文承接 系统镜像结构。读者需要先能区分构建 staging 目录、 system.img、super 中的逻辑分区和运行时 /system。这里不把某一台设备的解锁、擦除和刷写步骤 写成通用处方,而是回答四个更基础的问题:Android 17 怎样构建 GSI,为什么它不应携带设备专属 vendor 内容,设备用什么证据判断 system/vendor 能否协作,以及静态刷写与 DSU(Dynamic System Updates,动态系统更新)分别改变了什么。
读完后,应能从 gsi_release.mk 追到 system.img 的组成,从 gsi_tool 追到 gsid 的安装状态, 并在启动失败时区分镜像格式、分区映射、VINTF、HAL、动态链接和 SELinux 等不同边界。
1. GSI 兼容边界
1.1 GSI与设备镜像
普通设备产品会同时选择 system、product、system_ext、vendor、odm、boot 等内容。它的 framework、 资源 overlay、HAL 实现和分区配置可以围绕同一设备一起演进。GSI 则故意拿走这种便利:system 一侧 按照通用产品配置构建,运行时与目标设备现有的 vendor 一侧组合。
| 对象 | 主要拥有者 | 包含什么 | 不应据此推断什么 |
|---|---|---|---|
设备专属 system.img | 设备产品配置 | framework、系统应用、产品允许进入 system 的配置 | 不能代表其他设备可启动 |
GSI system.img | AOSP 通用 system 产品 | 通用 framework/system 内容及兼容旧 vendor 所需组件 | 不是完整设备固件 |
| vendor/odm | OEM、SoC 与设备产品 | HAL 服务、驱动相关库、设备 manifest、资源和固件接口 | 不由 GSI 替换 |
| boot/vendor_boot/init_boot | 设备启动链 | kernel、通用或 vendor ramdisk、fstab 与早期启动配置 | GSI 本身不提供目标设备内核 |
| DSU backing image | gsid 与 ImageManager | 保存在 /data/gsi/... 的 system/userdata 映像 | 不等同于改写物理 system 分区 |
因此,“能执行 fastboot flash system”只证明传输和写入路径接受镜像;“能挂载 /system”也只证明 文件系统与块设备路径成立。真正启动到桌面还要经过 init、SELinux、动态链接器、servicemanager、HAL、 zygote 和 system_server 等消费者。
1.2 Treble 合约
Project Treble 的关键不是简单地增加一个 vendor 分区,而是让 framework 与设备实现通过可检查的稳定 边界协作。VINTF(Vendor Interface)元数据把其中一部分合约写成 XML:device manifest 声明设备 提供的 HAL,framework compatibility matrix 声明 framework 要求的 HAL;反方向则由 framework manifest 与 device compatibility matrix 约束。
图中双向检查很重要。兼容性不是“新 system 向下兼容几个 Android 版本”这样一个固定数字,而是由目标 设备的 FCM level、manifest、kernel level、接口版本、GSI 内实际提供的兼容组件和各项条件共同决定。 Android 17 的 gsi_release.mk 明确加入 VNDK 31、32、33、34 的额外快照,但这只能证明该构建配置 携带这些版本,不能推出任意 vendor 都一定可用,更不能用“最近三代”替代真实检查。
1.3 GSI适用边界
GSI 通常可以帮助验证:
- 设备的 vendor 实现是否依赖未声明的 framework 私有接口;
- device manifest 与 framework compatibility matrix 是否匹配;
- vendor 进程能否在通用 system 提供的动态链接环境中启动;
- HAL、SELinux、属性和文件路径是否符合 system/vendor 分离后的约束;
- 设备能否在不替换 vendor 和内核的前提下运行另一套 framework。
它不能消除硬件差异。CPU ABI、页大小、kernel 配置、boot image header、fstab、动态分区、AVB key、 图形栈能力、设备 overlay、厂商扩展和 bootloader 策略仍然属于目标设备。所谓“Treble 兼容设备”也 不是只看 ro.treble.enabled=true;该属性可以作为线索,但不能代替 VINTF、分区和启动证据。
2. Android 17 构建
2.1 aosp_arm64.mk
Android 17 的 GSI 构建不是由一条特殊编译命令临时删减普通镜像,而是由产品配置提前决定内容边界。 以 ARM64 为例,相关入口分为三层:
build/make/target/product/aosp_arm64.mk继承通用 system、product 和 generic device 配置;当TARGET_PRODUCT为aosp_arm64时,再继承gsi_release.mk。build/make/target/product/gsi_release.mk设置BUILDING_GSI := true,关闭 vendor、userdata、 super 等非 GSI 镜像,并加入 GSI 启动与兼容组件。build/make/target/board/BoardConfigGsiCommon.mk定义 system 文件系统、非 sparse 输出、动态容量、 AVB 测试 key、通用架构和 GSI 专用目录布局。
aosp_arm64.mk 中的关键选择如下:
源码文件:build/make/target/product/aosp_arm64.mk
ifeq (aosp_arm64,$(TARGET_PRODUCT))
MODULE_BUILD_FROM_SOURCE ?= true
$(call inherit-product, $(SRC_TARGET_DIR)/product/gsi_release.mk)
PRODUCT_SOONG_DEFINED_SYSTEM_IMAGE := aosp_system_image
USE_SOONG_DEFINED_SYSTEM_IMAGE := true
endif这里并不是说所有名为 aosp_arm64 的历史版本都具有相同实现,而是 Android 17 当前 tag 将 system 镜像的定义交给 Soong 中的 aosp_system_image。选择产品后,可用 lunch目标与编译变体 中的方法检查真实配置:
source build/envsetup.sh
lunch aosp_arm64-trunk_staging-userdebug
get_build_var TARGET_PRODUCT
get_build_var TARGET_ARCH
get_build_var PRODUCT_OUT
get_build_var BUILDING_GSIrelease 名称属于当前源码树的 lunch 三元组,不应从旧文章照抄。若本地分支没有同名 release,应先用 lunch 或产品查询命令读取当前候选,而不是退回旧式二段组合并假定语义相同。
2.2 gsi_release.mk
Android 17 的配置直接说明 GSI 发布构建不应产生哪些设备镜像:
源码文件:build/make/target/product/gsi_release.mk
BUILDING_GSI := true
PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_USE_DYNAMIC_PARTITION_SIZE := true
PRODUCT_BUILD_CACHE_IMAGE := false
PRODUCT_BUILD_DEBUG_BOOT_IMAGE := false
PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE := false
PRODUCT_BUILD_USERDATA_IMAGE := false
PRODUCT_BUILD_VENDOR_IMAGE := false
PRODUCT_BUILD_SUPER_PARTITION := false
PRODUCT_BUILD_SUPER_EMPTY_IMAGE := false
PRODUCT_BUILD_SYSTEM_DLKM_IMAGE := false这些变量建立了三个边界:
- GSI 构建关注 system 镜像,不声称拥有目标设备的 vendor、userdata 或完整 super 布局;
PRODUCT_USE_DYNAMIC_PARTITIONS允许它与支持动态分区的环境混合使用,但发布产物本身不因此变成 目标设备的super.img;- boot 相关产物即使因测试或 dist 需要出现,也不能被视为目标硬件可直接使用的设备 boot 镜像。
同一文件还设置 PRODUCT_SHIPPING_API_LEVEL := $(PLATFORM_SDK_VERSION),让 GSI 使用当前平台默认配置; 同时通过 PRODUCT_EXTRA_VNDK_VERSIONS 加入旧 vendor 兼容内容。后者是“为了混合不同 vendor 而携带 额外接口实现”的证据,不是“忽略 vendor 版本”的许可。
2.3 gsi_defaults
build/make/target/product/gsi/Android.bp 定义 android_gsi_defaults。它继承 system、system_ext、 product 三类 filesystem defaults,并用 symlink 和依赖把相应内容装入一张 GSI system image。
源码文件:build/make/target/product/gsi/Android.bp
android_filesystem_defaults {
name: "android_gsi_defaults",
defaults: [
"system_image_defaults",
"system_ext_image_defaults",
"product_image_defaults",
],
symlinks: gsi_symlinks,
dirs: ["cache"],
deps: [
"gsi_skip_mount.cfg",
"init.gsi.rc",
"init.vndk-nodef.rc",
"com.android.vndk.v31",
"com.android.vndk.v32",
"com.android.vndk.v33",
"com.android.vndk.v34",
],
type: "ext4",
}这解释了为什么 GSI 不是“删掉 vendor.img 后剩下的普通 system.img”。它有自己的依赖、overlay、 init 配置、VNDK 兼容包和目录合并策略。
2.4 system_ext
BoardConfigGsiCommon.mk 设置:
源码文件:build/make/target/product/gsi/BoardConfigGsiCommon.mk
相关函数/类型:copy-out layout
TARGET_COPY_OUT_PRODUCT := system/product
TARGET_COPY_OUT_SYSTEM_EXT := system/system_ext
BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE :=
BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE :=因此构建 staging 中仍然保留 product/system_ext 的逻辑归属,但它们不生成独立文件系统镜像,而是进入 GSI system image 下的 system/product 和 system/system_ext。对应 symlink 让运行时 /product、 /system_ext 指向这些目录。
GSI 还安装 skip_mount.cfg,要求 init 跳过设备 fstab 中会覆盖这些 system 内目录的 /product、 /system_ext 等 mount point。否则目标设备自己的逻辑 product 分区可能遮住 GSI 随附的内容,使 framework 运行在一组混合且不可预测的组件上。
GSI system.img
└── system/
├── bin、lib、framework、etc ...
├── product/ <- GSI product内容
└── system_ext/ <- GSI system_ext内容
运行时符号链接
/product -> /system/product
/system_ext -> /system/system_ext2.5 构建结果
构建主线可以概括为:
BoardConfigGsiCommon.mk 默认 GSI_FILE_SYSTEM_TYPE ?= ext4,并禁用 ext/EROFS sparse 输出。这里的 “非 sparse”对 DSU 很重要,因为 gsid 的写入路径按声明大小把镜像内容写入 backing device;静态 fastboot 是否接受 sparse/raw 则是另一层能力。检查产物时,至少应区分镜像逻辑大小、实际文件大小、 文件系统类型和 AVB footer:
product_out="$(get_build_var PRODUCT_OUT)"
ls -lh "${product_out}/system.img"
file "${product_out}/system.img"
simg_dump "${product_out}/system.img" 2>/dev/null || true
avbtool info_image --image "${product_out}/system.img"如果 simg_dump 报告它不是 sparse image,而 file 能识别 ext4,这与当前 GSI board 配置一致;不要 把该结果误判为构建失败。
3. 设备兼容性分层
3.1 ABI
ARM64 GSI 面向 TARGET_ARCH := arm64,并可能同时携带 32 位 ABI 支持。目标设备是否能运行它,至少要 查看:
adb shell getprop ro.product.cpu.abilist
adb shell getprop ro.product.cpu.abilist64
adb shell getprop ro.product.cpu.abilist32
adb shell getconf PAGE_SIZEAndroid 17 的 gsi_release.mk 明确设置 PRODUCT_MAX_PAGE_SIZE_SUPPORTED := 16384,同时关闭旧的固定 Bionic page-size macro。它表明该 GSI 构建考虑 16 KiB page size,但不能单凭这一个变量判断所有 native 模块、vendor blob 和目标 kernel 都满足 16 KiB 运行条件。
ABI 不匹配通常比 HAL 问题更早暴露:ELF loader 可能直接报告错误架构,32 位 vendor 进程也可能因 GSI 缺少相应运行时而无法启动。此时继续调整 framework 属性没有意义,应先确认镜像组合本身成立。
3.2 VINTF 检查
system/libvintf/VintfObject.cpp::checkCompatibility 的核心不是比较两个 Android 版本号,而是依次检查:
源码文件:system/libvintf/VintfObject.cpp
相关函数/类型:checkCompatibility
deviceManifest->checkCompatibility(frameworkCompatibilityMatrix, error);
frameworkManifest->checkCompatibility(deviceCompatibilityMatrix, error);
runtimeInfo->checkCompatibility(frameworkCompatibilityMatrix, error, ...);对应关系如下:
| 提供方 | 声明文件 | 约束方 | 典型内容 |
|---|---|---|---|
| vendor/odm | device HAL manifest | framework compatibility matrix | HAL 包、版本、实例、transport、FCM level |
| framework/system | framework HAL manifest | device compatibility matrix | framework 提供给 vendor 的接口 |
| kernel/runtime | runtime info | framework compatibility matrix | kernel version、level 与配置要求 |
目标设备上的只读检查可以从以下信息开始:
adb shell getprop ro.vendor.api_level
adb shell getprop ro.vndk.version
adb shell getprop ro.product.first_api_level
adb shell ls -R /vendor/etc/vintf /odm/etc/vintf 2>/dev/null
adb shell ls -R /system/etc/vintf /system_ext/etc/vintf 2>/dev/null
adb shell /system/bin/vintf不同构建可能不安装相同的诊断二进制或输出格式,因此命令失败时应回到 XML 文件与 host-side checkvintf,而不是据此认定设备不支持 Treble。
3.3 checkvintf
system/libvintf/check_vintf.cpp::checkAllFiles 使用目录映射、属性和可选 runtime info 构造 VintfObject,然后执行 compatibility、deprecation、unused HAL 等检查。INCOMPATIBLE 与读取文件失败 会进入不同返回路径,这使故障可以分成“合约确实不匹配”和“验证输入不完整”。
host 侧的基本形式是把解包后的 system/vendor 目录映射到运行时路径:
checkvintf \
--dirmap /system:<unpacked-system> \
--dirmap /vendor:<unpacked-vendor> \
--dirmap /odm:<unpacked-odm> \
--property ro.boot.product.vendor.sku=<vendor-sku> \
--property ro.boot.product.hardware.sku=<hardware-sku>SKU 属性会改变 manifest 的选择,漏掉它可能让 host 检查读取错误的 vendor/odm 组合。完整产品构建也会 在打包阶段调用 VINTF 检查;但把单独下载的 GSI 与某台设备组合时,需要提供那台设备的真实 vendor、 odm、属性和 kernel 信息,单独检查 GSI 自身不能证明组合兼容。
3.4 VINTF通过条件
VINTF 能覆盖声明式接口契约,却不会证明每个 HAL 的实现质量、设备节点权限、固件版本、图形能力和运行 时序都正确。常见的后续失败包括:
- vendor 可执行文件依赖了不允许或不存在的共享库;
- HAL 在 manifest 中声明正确,但服务进程崩溃或无法注册实例;
- SELinux policy 组合后拒绝访问 binder、property、设备节点或文件;
- framework 假定某项可选硬件能力存在,而设备 overlay/feature 声明与实际实现不一致;
- kernel config 或驱动行为满足静态级别要求,却在真实操作中失败。
所以兼容性验证应逐层推进,而不是把所有开机动画卡住都归为“VNDK 不匹配”。
4. 静态刷写与 DSU
4.1 GSI分区
静态路径通过 bootloader fastboot 或 userspace fastbootd,把 GSI 写入目标设备的 system 分区。动态分区 设备通常需要由 fastbootd 操作逻辑分区;bootloader fastboot 是否能处理某个逻辑分区取决于设备实现。
这条路径会改变设备持久分区,必须同时处理:
- bootloader 是否允许解锁和刷写;
- system 的 slot、逻辑分区名和可用容量;
- fastboot 当前处于 bootloader 还是 userspace fastbootd;
- AVB chain、rollback index 与签名 key;
- userdata 与原系统数据格式是否兼容;
- 失败后使用哪套原厂镜像恢复。
因此本文只给出读取和决策命令,不给出“所有设备都执行同一组 erase/resize/disable-verity”的脚本:
fastboot getvar is-userspace
fastboot getvar current-slot
fastboot getvar has-slot:system
fastboot getvar partition-type:system
fastboot getvar partition-size:system
fastboot flashing get_unlock_ability返回值由目标设备决定。若 system 位于 super 中,应先理解 系统镜像结构 中的 LP metadata、group 和 slot;随意删除 product/system_ext 来腾空间会破坏原产品布局,也可能让 OTA 和恢复流程失去前提。
4.2 fastbootd
Android 17 的 system/core/fastboot/fastboot.cpp 明确列出:
gsi wipe|disable|status
Wipe, disable or show status of a GSI installation (fastbootd only).设备侧 system/core/fastboot/device/commands.cpp::GsiHandler 再调用 libgsi 检查、禁用或标记清理 GSI。 这组命令针对 DSU 安装状态,不是“刷入另一张 system.img”的同义词。它的价值在于 Android userspace 无法正常启动时,仍可从 fastbootd 禁用或清理已安装的动态 GSI。
4.3 gsid.rc
DSU 不直接覆盖物理 system 分区。gsid.rc 创建 /metadata/gsi 和 /data/gsi 目录,gsid 注册 IGsiService Binder 服务。安装时,gsi_tool 作为命令入口,调用服务创建 userdata 和只读 system backing image,再把 GSI 字节写入映射设备。
gsi_tool.cpp::Install 的主线可以直接从 Android 17 实现看到:
源码文件:system/gsid/gsi_tool.cpp
相关函数/类型:Install
auto status = gsid->openInstall(installDir, &error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
return EX_SOFTWARE;
}
if (partition == kDefaultPartition) {
status = gsid->createPartition("userdata", userdataSize, false, &error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) return EX_SOFTWARE;
status = gsid->closePartition(&error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) return EX_SOFTWARE;
}
status = gsid->createPartition(partition, gsiSize, true, &error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) return EX_SOFTWARE;
bool ok = false;
status = gsid->commitGsiChunkFromStream(stream, gsiSize, &ok);
if (!ok) return EX_SOFTWARE;
status = gsid->closePartition(&error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) return EX_SOFTWARE;
status = gsid->closeInstall(&error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) return EX_SOFTWARE;
status = gsid->enableGsi(true, dsuSlot, &error);
if (!status.isOk() || error != IGsiService::INSTALL_OK) return EX_SOFTWARE;这里的 owner 分工很清楚:
| 层 | owner | 拥有什么状态 |
|---|---|---|
| CLI | gsi_tool | 参数、输入 fd、进度展示和命令退出码 |
| Binder 服务 | GsiService | 当前 install dir、installer、active slot、enable/disable 状态 |
| 单分区安装 | PartitionInstaller | 目标大小、已写字节、mapped device、完成状态 |
| backing image | ImageManager | 稀疏 backing file、device-mapper 映射和元数据 |
| 启动判断 | libgsi + init/fs_mgr | install status、boot attempts、active slot、映射与挂载 |
PartitionInstaller::CommitGsiChunk 检查写入长度不能超过声明 size,并在每次写入后增加 gsi_bytes_written_。CheckInstallState 还会确认字节数正好完成,并验证 image metadata。也就是说,CLI 显示传输结束不等于安装完成;close partition/install 的状态仍然是必要提交点。
这段真实调用链还暴露两个失败边界:安装正在 live GSI 中时,CLI 会在 openInstall 前拒绝;输入流写入 完成但 ok 为 false 时,不会继续执行 closeInstall 和 enableGsi。因此“文件传完”不是可启动状态, enableGsi 成功才把已完成 slot 交给后续启动消费者。
4.4 DSU 状态文件
关键持久状态位于 /metadata/gsi/dsu/:
| 文件或目录 | 含义 |
|---|---|
active | 当前选择的 DSU slot |
install_status | ok、disabled、wipe 或启动尝试计数 |
one_shot_boot | 下一次只启动一次 GSI |
booted | 当前启动已进入 live GSI 的标志 |
<slot>/complete | 安装是否完整提交 |
<slot>/lp_metadata | DSU 逻辑分区映射信息 |
libgsi.cpp::CanBootIntoGsi 在尝试启动前先删除旧的 booted 标志,然后检查安装状态和启动次数。超过 最大尝试次数会拒绝继续进入 GSI;one-shot 模式会提前将下一次状态置为 disabled,使设备在后续重启 回到原系统。
状态机解释了两个容易混淆的现象:disable 保留镜像和 userdata,只改变后续启动选择;wipe 请求完整 删除并回收空间,但如果当前正运行在 GSI 中,实际文件删除必须等回到原系统后完成。
4.5 DSU 安装门槛
PartitionInstaller::PerformSanityChecks 拒绝负数 size、在 live GSI 内再次安装,以及剩余空间不足。 对于使用 Virtual A/B、安装到 /data 且非虚拟设备的情况,GetMinimumFreeSpaceThreshold 还会读取 super block device 大小,并保留相应空间,避免 DSU 占满空间后破坏 Virtual A/B 更新前提。
GsiService::ValidateInstallParams 只允许 /data/gsi/ 或受支持的外部存储路径;外部路径还要检查 system fstab 的 verity check_at_most_once 条件。由此可见,“/data 空闲容量大于 system.img 大小” 仍不一定足够。
5. 兼容性测试
5.1 Boot验证
system/gsid/tests/boot_tests.cpp 包含三个有代表性的启动测试。
MetadataPartition.FirstStageMount 在首发 API level 达到 R 及以上时读取默认 fstab,要求 /metadata 存在且带 first_stage_mount。这证明 DSU 元数据不能等 Android framework 启动后才挂载,因为进入 GSI 的决定发生在更早阶段。
MetadataPartition.MinimumSize 进一步读取 metadata block device 大小并要求至少 16 MiB。它证明“有 /metadata 路径”不等于分区布局满足要求。
MetadataPartition.FsType 检查 /data/gsi 不是 symlink,且承载文件系统是 F2FS 或 ext4。不过该测试 对车载设备和较旧 VSR level 有 skip 条件,因此它证明的是受条件约束的合规要求,不应改写成所有 Android 设备的无条件事实。
5.2 VINTF验证
该 host test 把 system.img 打包为 zip,启动 DynamicSystem 安装 Activity,等待设备重启进入 DSU, 随后验证:
gsi_tool enable可以重新启用已安装 GSI;- DSU 安装实际消耗
/data空间; - GSI 与原系统维护独立的 adb remount overlay;
enable-verity会拆除 GSI 的 remount overlay;gsi_tool wipe后回到原系统,并回收大部分占用空间。
关键断言不是“某条命令退出 0”,而是跨重启观察 isDsuRunning()、文件是否存在、overlay 内容是否 持久,以及 wipe 前后的空间差值。这给实际验证提供了正确模板:状态必须跨启动边界检查。
5.3 DSU验证
DSUEndtoEndTest::extractSystemImageFromBuildInfo 先查找独立 system.img;找不到时读取 super.img, 必要时 unsparse,再用 lpunpack -p system_a 提取 system logical partition。随后测试执行 gsi_tool install,并按 normal -> running -> installed -> running -> normal 检查重启后的状态。
这说明测试基础设施可以从完整设备产物中提取 system image,但“从 super 中提取出来”不会自动把设备 system 变成 GSI。镜像是否通用仍由构建产品和内容边界决定。
6. 启动验证链
6.1 产物身份验证
一张名为 system.img 的文件可能来自 GSI 产品、设备产品、target files 解包或 super 提取。先记录:
sha256sum system.img
file system.img
avbtool info_image --image system.img
du -h system.img若有对应 AOSP output,还应读取:
get_build_var TARGET_PRODUCT
get_build_var TARGET_BUILD_VARIANT
get_build_var BUILDING_GSI
get_build_var PRODUCT_OUT哈希用于固定本次输入;AVB 信息用于识别 footer 和签名算法;产品变量用于证明镜像来自哪个配置。只看 文件名或下载包描述不足以支撑后续结论。
6.2 设备事实
至少记录以下类别,而不是只记录 Android release:
# ABI与页大小
adb shell getprop ro.product.cpu.abilist
adb shell getconf PAGE_SIZE
# 启动和分区
adb shell getprop ro.boot.slot_suffix
adb shell getprop ro.boot.dynamic_partitions
adb shell getprop ro.virtual_ab.enabled
adb shell ls -l /dev/block/by-name/super 2>/dev/null
# Treble/VINTF
adb shell getprop ro.treble.enabled
adb shell getprop ro.vendor.api_level
adb shell getprop ro.vndk.version
adb shell getprop ro.product.first_api_level
# 当前挂载与AVB状态
adb shell cat /proc/mounts
adb shell getprop ro.boot.verifiedbootstate
adb shell getprop ro.boot.vbmeta.device_state这些信息的作用是建立条件表,不是自动生成“可刷/不可刷”的单一答案。比如动态分区为 true 说明 system 大概率是逻辑分区,但仍需查看 fastboot getvar 与 LP metadata;bootloader unlocked 也不表示 AVB chain 可以忽略。
6.3 静态/DSU
| 条件 | 更适合先考虑的路径 | 原因 |
|---|---|---|
设备明确支持 DSU,gsid/DynamicSystem 可用,目标是验证 framework | DSU | 保留原 system,状态和回退由 DSU 管理 |
| 需要验证真实 system 逻辑分区、AVB 或量产刷写链 | 静态 fastboot | DSU backing image 无法代替真实分区测试 |
| bootloader 不允许解锁,但产品开放受控 DSU | DSU | 静态分区不可写,DSU 仍可能由系统授权入口安装 |
| 设备没有 metadata/动态分区/DSU 支持 | 依据产品文档评估静态路径 | gsid 源码存在不代表设备集成了完整能力 |
| 没有可靠的原厂恢复包或分区表 | 先停止写入操作 | 无法建立失败后的恢复路径 |
DSU 也不是零风险:安装会占用大量 /data 空间,重启进入另一套 framework,且不兼容的 GSI 仍可能 无法启动。它的优势是保留原 system 并提供状态化回退,而不是保证运行成功。
7. 启动失败定位
7.1 启动失败
GSI 启动涉及的关键阶段可按可观察信号分流:
如果设备始终停留在 bootloader 或 fastbootd,logcat 还不存在;如果 /system/bin/init 都没有执行, 搜索 system_server 异常同样没有意义。先用最后一个已知成功阶段缩小 owner。
7.2 启动状态
原系统仍可启动时:
adb shell gsi_tool status
adb shell ls -l /metadata/gsi/dsu 2>/dev/null
adb shell getprop ro.gsid.image_running
adb shell getprop ro.gsid.image_installed
adb shell dmesg | grep -i -E 'device-mapper|dm-|gsi|avb|verity'status 要结合 active、install_status 和 booted 理解。installed 只表示安装元数据存在;enabled 表示允许下次尝试;running 才表示当前启动被标记为 live GSI。若安装中断,RunStartupTasks 会扫描缺少 complete marker 的 slot,并尝试清理损坏安装。
进入 fastbootd 后还能使用:
fastboot gsi status
fastboot gsi disable
fastboot gsi wipe后两条会改变持久状态,应在确认目标确为待恢复的 DSU 后执行。disable 与 wipe 的数据后果不同。
7.3 VINTF
系统至少进入 early userspace 后,可收集:
adb logcat -b all -d | grep -i -E 'vintf|manifest|compatibility matrix|servicemanager|hwservicemanager'
adb shell /system/bin/vintf 2>&1
adb shell lshal 2>/dev/null
adb shell service list判断时要保留完整错误中的 package、interface、instance、required/provided version 和来源 XML。把 android.hardware.foo 缩写成“HAL 不兼容”会丢掉最关键的定位信息。
若错误提示 manifest 缺少 required instance,应检查设备侧 manifest 是否声明、SKU 是否选择正确、服务 二进制是否安装,以及 init rc 是否启动它。若 manifest 已声明但 servicemanager 中没有实例,则问题已从 静态合约转到服务进程启动或注册路径。
7.4 动态链接
典型日志包括 CANNOT LINK EXECUTABLE、library ... not found、symbol lookup failure。此时需要记录失败 进程的位宽、namespace 和依赖:
adb shell getprop ro.vndk.version
adb shell cat /proc/<pid>/maps 2>/dev/null
adb shell readelf -d /vendor/bin/hw/<service> 2>/dev/null
adb logcat -b all -d | grep -E 'CANNOT LINK EXECUTABLE|cannot locate symbol|library .* not found'不要用把 system 私有库复制到 vendor 的方式跳过 Treble 边界;即使进程暂时启动,也会隐藏 ABI、 namespace、更新和安全问题。应回到 vendor 依赖是否允许、GSI 是否携带对应 VNDK snapshot、进程是否选错 架构等根因。
7.5 SELinux
SELinux 拒绝要保留 source context、target context、class 和 permission:
adb logcat -b all -d | grep 'avc: *denied'
adb shell dmesg | grep 'avc: *denied'
adb shell ps -AZ
adb shell ls -lZ /dev/<device-node> /vendor/<path> 2>/dev/nullpermissive 只能帮助判断拒绝是否与现象相关,不能证明正确修复。GSI 的目的之一正是暴露 system/vendor policy 边界;将设备长期切为 permissive 会移除测试本来要验证的约束。
功能级问题应在基本启动完成后分别交给图形、音频、相机、传感器等专题。比如 UI 黑屏可能来自 HWC、 gralloc、display config 或 SurfaceFlinger,而不是 GSI 文件系统本身;能进入 shell 不代表显示栈通过。
8. 兼容性验证
从入口到消费者可以完整复述如下:
gsi_tool install通过IGsiService创建 system/userdata backing image,并在closeInstall后写入 complete、active 和 disabled 状态。enableGsi设置 one-shot 或持久模式、重置启动计数并写 active slot。- 下一次启动时,init/fs_mgr 调用
CanBootIntoGsi。它先清除旧 booted 标志,验证安装状态和尝试次数, 再允许创建 DSU 逻辑分区映射。 - one-shot 模式在进入本次 GSI 前就把后续状态改为 disabled;若启动没能重新创建 booted 标志,原 system 仍是下一次启动的默认路径。
- GSI 成功进入 userspace 后,
MarkSystemAsGsi写 booted;gsid RunStartupTasks再把可重试状态恢复 为ok。 - 超过启动尝试上限、显式 disable 或 wipe 都会阻止继续选择该 GSI。wipe 的文件删除由原系统启动任务 完成,因为运行中的 GSI backing image 不能被直接拆除。
这个设计没有让 GSI 获得 bootloader 的永久所有权。原 system 分区、boot 链和恢复入口仍然存在,DSU 只通过早期启动元数据临时改变 system 映射。这正是 DSU 与静态刷写最本质的区别。
9. 源码导航
继续研究 GSI 时,可以按问题选择入口,而不是从整个 AOSP 全局搜索同一个缩写:
| 想回答的问题 | 先读入口 | 继续追踪 |
|---|---|---|
| GSI 构建了哪些镜像 | build/make/target/product/gsi_release.mk | aosp_arm64.mk、generic_system*.mk |
| system/product/system_ext 怎样合并 | build/make/target/board/BoardConfigGsiCommon.mk | target/product/gsi/Android.bp |
| 为什么跳过设备 product/system_ext mount | target/product/gsi/gsi_skip_mount.cfg | init 的 skip mount 读取逻辑 |
| CLI 怎样安装 system.img | system/gsid/gsi_tool.cpp::Install | IGsiService.aidl、GsiService::createPartition |
| backing image 怎样写入和校验 | system/gsid/partition_installer.cpp | ImageManager、device-mapper、libfiemap |
| 启动和回退状态怎样决定 | system/gsid/libgsi.cpp::CanBootIntoGsi | init/fs_mgr 的 DSU 映射入口 |
| system/vendor 是否匹配 | system/libvintf/check_vintf.cpp::checkAllFiles | VintfObject::checkCompatibility |
| DSU 能否承载在目标设备 | system/gsid/tests/boot_tests.cpp | fstab、metadata、F2FS/ext4 条件 |
| 生命周期是否跨重启成立 | DsuGsiIntegrationTest、DSUEndtoEndTest | enable/disable/remount/wipe 断言 |
对一张待验证镜像,最终可以按以下顺序检查:构建产品证明它是 GSI,镜像工具证明其格式和 AVB 信息,设备事实证明 ABI/分区/metadata 条件,VINTF 检查证明声明式合约,启动日志再证明 HAL 与 framework 消费者实际工作。缺少其中一层时,应把结论停在那一层,而不是用“理论上支持 GSI”跨过未知条件。
可以任选一个失败现象做反向验证:如果 gsi_tool status 显示 installed 但不显示 running,先解释 install_status、active slot、boot attempts 和 booted marker 分别由谁写入;如果 VINTF 报某个 HAL instance 缺失,则指出对应 device manifest、framework matrix、服务 rc 和 servicemanager 注册点。能够 把现象沿这些状态追到真正消费者,才算把 GSI 从一条刷机命令还原成可分析的 Android 系统机制。
