FP8300 ColorOS 16 移植分享与复现指南
Aptx5497
编辑于 2026年07月13日 11:51

更新日期:2026-07-13

视频链接: 联想Y700四代刷近乎完美的coloros16.0.8系统,oppo pad mini UI适配​

本文面向两个读者群体:一是需要审计当前 FP8300 发布包来源的人,二是准备从官方固件开始做 Android 跨厂商移植的人。本文目标是能够让0基础的用户,在claude code或者codex在顶级模型的帮助下,至少能够修改功能重新打包,甚至从0开始移植

本文涉及的主输入为:

  • 联想 Y700 四代 1.1.11.202 官方救砖镜像(硬件底座) GPT、programmer、启动链、设备树、固件、内核模块和真实 HAL 必须匹配 TB322FU/TB322FC 硬件。

  • OPPO OPD2413 16.0.8.300 官方 OTA(ColorOS 上层) 提供同平台 ColorOS framework、应用、私有服务、资源和对应 ABI。

  • OPPO Pad Mini OPD2515 16.0.8.301 官方 OTA(单一辅助资源) 只提取已验证的平板 Launcher 资源覆盖,不引入其启动链、HAL 或固件。

  • 本仓库补丁和构建脚本(融合层) 记录设备身份、接口桥接、闭合修复、重打包和哈希门禁。

这里的“来源”按文件内容定义,不按文件名定义。重签名后的 boot.img 与联想 原文件 SHA-256 不同,但其 Android boot image 正文可以仍与联想逐字节一致;反过来, 同名 vendor.img 即使能挂载,也可能已经换了整个 HAL 栈。

本项目选择 OPD2413 的首要前提是它与 Y700 四代同属 SM8750 sun 平台。相同 SoC 使 CPU 架构、GKI/KMI 代际、QTI HAL、图形/媒体接口和很多 vendor ABI 有形成闭合的 可能,但“同 SoC”只是一道必要性很强的筛选条件,不是充分条件。不同板级仍可能有 不同的 panel、touch、sensor、PMIC、charging、camera、RF、DTBO 和分区布局,因此 硬件启动链和固件始终以 Y700 官方镜像为准。

一、当前发布包的镜像来源

当前发布目录为 FP8300_ColorOS_16.0.8.300_20260713。本地组装目录名保留 20260712,远程交付目录使用 20260713;两者最终文件内容一致。最终 super.img SHA-256 为:

代码块
PlainText
自动换行
复制代码
b12d7c604c372d24b63030803b84db90dbb01d2e2b65469cfcd25a71e0daad15
复制成功

1.1 物理分区镜像

除 super.img 内的动态分区外,硬件相关镜像原则上都从联想 202 官方镜像出发。 OPPO 的 modem、DSP、XBL、TZ、ABL、UEFI、蓝牙固件和设备树不能直接用于 Y700。

下表中的“正文逐字节保留”已经用 AVB Original image size 截断后做过 cmp, 不是根据文件名猜测。boot、dtbo、init_boot、pvmfw 的正文与联想 202 一致,差异只在重新生成的 footer、描述符、指纹和签名。

  • ALIGN_TO_128K_1.img → ALIGN_TO_128K_1:联想 202 官方镜像逐字节保留

  • abl.img → abl_a、abl_b:联想 202 官方镜像逐字节保留

  • aop.img → aop_a、aop_b:联想 202 官方镜像逐字节保留

  • aop_config.img → aop_config_a、aop_config_b:联想 202 官方镜像逐字节保留

  • apdp.img → apdp:联想 202 官方镜像逐字节保留

  • apdpb.img → apdpb:联想 202 官方镜像逐字节保留

  • bluetooth.img → bluetooth_a、bluetooth_b:联想 202 官方镜像逐字节保留

  • boot.img → boot_a、boot_b:联想 202 内核正文逐字节保留;重建 AVB footer 和描述符

  • connsec.img → connsec:联想 202 官方镜像逐字节保留

  • cpucp.img → cpucp_a、cpucp_b:联想 202 官方镜像逐字节保留

  • cpucp_dtb.img → cpucp_dtb_a、cpucp_dtb_b:联想 202 官方镜像逐字节保留

  • devcfg.img → devcfg_a、devcfg_b:联想 202 官方镜像逐字节保留

  • dpm.img → dpm:联想 202 官方镜像逐字节保留

  • dsp.img → dsp_a、dsp_b:联想 202 官方镜像逐字节保留

  • dtbo.img → dtbo_a、dtbo_b:联想 202 板级设备树正文逐字节保留;重建 AVB footer 和描述符

  • featenabler.img → featenabler_a、featenabler_b:联想 202 官方镜像逐字节保留

  • hyp.img → hyp_a、hyp_b:联想 202 官方镜像逐字节保留

  • imagefv.img → imagefv_a、imagefv_b:联想 202 官方镜像逐字节保留

  • init_boot.img → init_boot_a、init_boot_b:联想 202 ramdisk 正文逐字节保留;重建 AVB footer 和描述符

  • keymaster.img → keymaster_a、keymaster_b:联想 202 官方镜像逐字节保留

  • keystore.img → 默认 XML 不写入:联想 202 官方附件逐字节保留

  • mdcompress.img → mdcompress:联想 202 官方镜像逐字节保留

  • metadata.img → 默认 XML 不写入:联想 202 官方附件逐字节保留;清数据时由 QDL erase 处理目标分区

  • modem.img → modem_a、modem_b:联想 202 官方镜像逐字节保留

  • multiimgoem.img → multiimgoem_a、multiimgoem_b:联想 202 官方镜像逐字节保留

  • multiimgqti.img → multiimgqti_a、multiimgqti_b:联想 202 官方镜像逐字节保留

  • pdp.img → pdp_a、pdp_b:联想 202 官方镜像逐字节保留

  • pdp_cdb.img → pdp_cdb_a、pdp_cdb_b:联想 202 官方镜像逐字节保留

  • pvmfw.img → pvmfw_a、pvmfw_b:联想 202 正文逐字节保留;重建 AVB footer 和描述符

  • qmcs.img → qmcs:联想 202 官方镜像逐字节保留

  • qupfw.img → qupfw_a、qupfw_b:联想 202 官方镜像逐字节保留

  • qweslicstore.img → qweslicstore_a、qweslicstore_b:联想 202 官方镜像逐字节保留

  • recovery.img → recovery_a、recovery_b:联想 202 官方镜像逐字节保留

  • shrm.img → shrm_a、shrm_b:联想 202 官方镜像逐字节保留

  • soccp_dcd.img → soccp_dcd_a、soccp_dcd_b:联想 202 官方镜像逐字节保留

  • soccp_debug.img → soccp_debug_a、soccp_debug_b:联想 202 官方镜像逐字节保留

  • spunvm.img → spunvm:联想 202 官方镜像逐字节保留

  • spuservice.img → spuservice_a、spuservice_b:联想 202 官方镜像逐字节保留

  • storsec.img → storsec:联想 202 官方镜像逐字节保留

  • super.img → super:复用联想 202 的物理分区尺寸和 LP 几何;动态分区按 1.3 节融合后由 lpmake 重建

  • toolsfv.img → toolsfv:联想 202 官方镜像逐字节保留

  • tz.img → tz_a、tz_b:联想 202 官方镜像逐字节保留

  • tzsc.img → tzsc:联想 202 官方镜像逐字节保留

  • uefi.img → uefi_a、uefi_b:联想 202 官方镜像逐字节保留

  • uefisecapp.img → uefisecapp_a、uefisecapp_b:联想 202 官方镜像逐字节保留

  • uefivarstore.img → uefivarstore:联想 202 官方镜像逐字节保留

  • vbmeta.img → vbmeta_a、vbmeta_b:根据当前 boot、init_boot、vendor_boot、dtbo、recovery 和 vbmeta_system 重新生成并签名

  • vbmeta_system.img → vbmeta_system_a、vbmeta_system_b:根据当前 pvmfw 重新生成并签名;不保留已重建动态分区的旧 hashtree

  • vendor_boot.img → vendor_boot_a、vendor_boot_b:联想 202 DTB 和 ramdisk 为硬件基底;合入同平台 OPPO 模块子集、同步融合后的 fstab.qcom 和 Enforcing 启动参数后重建 footer

  • xbl.img → xbl_a、xbl_b:联想 202 官方镜像逐字节保留

  • xbl_config.img → xbl_config_a、xbl_config_b:联想 202 官方镜像逐字节保留

  • xbl_ramdump.img → xbl_ramdump_a、xbl_ramdump_b:联想 202 官方镜像逐字节保留

  • xbl_sc_logs.img → xbl_sc_logs:联想 202 官方镜像逐字节保留

  • xbl_sc_test_mode.img → xbl_sc_test_mode:联想 202 官方镜像逐字节保留

vendor_boot.img 不是把 OPPO ramdisk 整包覆盖到联想镜像。它保留联想 DTB、 first-stage 设备路径和硬件模块基底,只加入与上层 ABI 确有依赖的模块。曾经通过 rawdump 证明会和当前面板通知符号 ABI 冲突的 LinkPower 模块不进入 first-stage modules.load。这条边界很重要:同一 SoC 不代表内核模块可以跨产品直接加载。

AVB 的发布状态为 flags=0,使用项目测试密钥签名。flags=0 不是“保留官方 信任链”,它表示生成的描述符本身保持启用;刷写前提仍是设备接受本项目签名链。 不要把本发布的 vbmeta* 搭配其他 boot chain。

1.2 GPT、programmer 和 XML

  • gpt_main0.bin 至 gpt_main5.bin:从联想 202 的六个 UFS LUN 布局生成;分区名、起止 LBA、属性逐项相同,GUID 和 CRC 随重建变化

  • gpt_backup0.bin 至 gpt_backup5.bin:与对应主 GPT 同一布局生成

  • rawprogram1.xml、rawprogram2.xml、rawprogram5.xml:联想 202 官方 XML 逐字节保留

  • rawprogram0.xml:从联想 LUN0 XML 生成;super 改为单个非 sparse 镜像,保留用户/持久分区的空 filename

  • rawprogram3.xml:联想布局不变,显式补上 ALIGN_TO_128K_1.img 文件引用

  • rawprogram4.xml:联想布局不变,显式补上包内已提供的辅助分区镜像引用;logfs 保持空 filename

  • patch0.xml 至 patch5.xml:联想 202 官方 patch XML 逐字节保留

  • prog_firehose_ddr.elf:联想 202 官方 xbl_s_devprg_ns.melf 逐字节复制并按 QDL 约定改名

  • zeros_1sector.bin、zeros_5sectors.bin:本地生成的全零辅助块,当前 rawprogram 不引用

所有 GPT 的内容布局已经用 GPT header 和 entry table 解析验证,六个 LUN 均与 联想 202 的分区名、LBA 和 attributes 一致。只比较整个 GPT 文件 SHA-256 会把 GUID/CRC 变化误判成分区布局变化。

1.3 super.img 动态分区

当前 super.img 大小为 23,622,320,128 字节,LP metadata slots 为 3,使用 Virtual A/B 结构。有效内容都在 a 分区,所有 b 逻辑分区大小为 0;这不等于 物理启动链只刷 A 槽,QDL 仍把 A/B 启动镜像写入双槽。

  • my_bigball_a(LP 分区大小 335,872 B;基底 OPPO 16.0.8.300;来源定性:OPPO 原样) 未修改,镜像 SHA-256 与官方 OTA 提取物一致。

  • my_carrier_a(LP 分区大小 335,872 B;基底 OPPO 16.0.8.300;来源定性:OPPO 原样) 未修改,镜像 SHA-256 与官方 OTA 提取物一致。

  • my_engineering_a(LP 分区大小 335,872 B;基底 OPPO 16.0.8.300;来源定性:OPPO 原样) 未修改,镜像 SHA-256 与官方 OTA 提取物一致。

  • my_heytap_a(LP 分区大小 335,872 B;基底 OPPO 16.0.8.300;来源定性:OPPO 原样) 未修改,镜像 SHA-256 与官方 OTA 提取物一致。

  • my_manifest_a(LP 分区大小 335,872 B;基底 OPPO 16.0.8.300;来源定性:OPPO 原样) 未修改,保留 OPD2413 16.0.8.300 版本身份。

  • my_region_a(LP 分区大小 7,016,448 B;基底 OPPO 16.0.8.300;来源定性:OPPO 原样) 未修改,镜像 SHA-256 与官方 OTA 提取物一致。

  • my_stock_a(LP 分区大小 3,826,978,816 B;基底 OPPO 16.0.8.300;来源定性:OPPO 基底 + 集成保留项) 保留 ColorOS OCR 与安全键盘包,恢复人脸生物特征声明;其余文件树与官方上层一致。

  • my_product_a(LP 分区大小 2,160,902,144 B;基底 OPPO 16.0.8.300;来源定性:OPPO 基底 + 平板融合) 平板应用清单、刷新率/窗口配置、490 dpi 相关资源、Gboard、本地集成的 Aperture 相机、Pad Mini Launcher overlay、Y700 显示 overlay。

  • product_a(LP 分区大小 9,433,088 B;基底 OPPO 16.0.8.300;来源定性:OPPO 基底 + 单点设备适配) 文件树只对 init.oplus.nandswap.sh 做设备路径适配后重压缩。

  • system_a(LP 分区大小 1,044,496,384 B;基底 OPPO 16.0.8.300;来源定性:OPPO 基底 + framework 融合) framework、services.jar、oplus-services.jar、SurfaceFlinger、音频策、init 手势、本地集成的 Megvii 人脸模型/运行库和功能补丁。

  • system_ext_a(LP 分区大小 932,540,416 B;基底 OPPO 16.0.8.300;来源定性:OPPO 基底 + 服务桥接) Horae、充电桥、Launcher 防崩补丁及配套 rc/context。

  • system_dlkm_a(LP 分区大小 7,372,800 B;基底 联想 202;来源定性:联想硬件层) 内核模块内容保留,只有版本属性/重打包元数据对齐。

  • vendor_a(LP 分区大小 1,359,294,464 B;基底 联想 202;来源定性:联想基底融合 OPPO 接口) 保留 firmware、VINTF、SELinux、QTI/CamX 相机栈和硬件 HAL 主体;合入 ColorOS 所需 APEX/服务/context/属性、音频表、AON 导出和 sec_channel 配置。

  • vendor_dlkm_a(LP 分区大小 56,971,264 B;基底 联想 202;来源定性:联想基底融合 OPPO 模块子集) 保留联想模块集合,补入同平台 OPPO 网络/稳定性模块和相应 module metadata;不把已知 ABI 冲突模块放入 first-stage 自动加载。

  • odm_a(LP 分区大小 834,605,056 B;基底 OPPO 16.0.8.300;来源定性:OPPO 基底 + 联想硬件适配,融合最深) 从 1764 个官方文件裁到约 388 个运行文件,保留实际需要的私有 HAL,叠加 Y700 项目号、传感器、热控、充电、AON、无线和 SELinux 闭合;移除不匹配的 OPPO 相机 OEM 层和 UFF 人脸 HAL。

system、system_ext、vendor、odm、my_product 都使用 rsync -aHAX --numeric-ids 保留 owner、mode、symlink 和 xattr,再用 mkfs.erofs 重建。product、system_dlkm、vendor_dlkm 即使只改一个文件, 整镜像 SHA-256 也会因为 EROFS UUID、压缩布局和时间戳策略不同而变化,所以来源 判断以文件树和明确补丁为准。

my_product 使用的 Pad Mini Launcher overlay 是唯一来自第三套官方 OTA 的 文件。它只解决平板 Launcher 布局,不携带该设备的 framework、vendor、内核或 固件。Y700 显示 overlay 则在本仓库重建并签名,删掉错误持久化 2400x3392 的两个 stable-size resource,保留颜色、圆角、状态栏和光感资源。

1.4 可选 KernelSU 镜像

默认 images/ 刷机集不带 root。root/ 下另有一组定向载荷,其中也包含两个镜像:

  • root/*/init_boot.img:以本发布默认 init_boot 的联想 ramdisk 为基底注入 KernelSU 启动内容,重新生成 init_boot footer;不替换 boot kernel。

  • root/*/vbmeta.img:针对 KernelSU init_boot 与本发布其余启动链重新生成并签名,描述符中的 dtbo、vendor_boot、boot、recovery、vbmeta_system 均指向本发布版本。

定向 rawprogram_kernelsu_ab.xml 只写 init_boot_a/b 和 vbmeta_a/b。这两个镜像 不是默认固件来源,不能用于其他版本;重新执行完整 QDL 刷机后会被 images/ 中的 非 root 镜像覆盖。

1.5 组包数据流

代码块
PlainText
自动换行
复制代码
联想 202 官方镜像
  -> GPT / programmer / firmware / boot 正文 / DTBO / recovery
  -> vendor + system_dlkm + vendor_dlkm 硬件基底

OPD2413 16.0.8.300 官方 OTA
  -> payload_dumper 解出逻辑分区和启动镜像用于 ABI 对照
  -> system / system_ext / product / my_* 上层基底
  -> ODM 私有服务、库和配置的同版本来源

本仓库融合
  -> 文件级选择和功能补丁
  -> VINTF / rc / ELF / context / policy 闭合
  -> 五个 EROFS 分区 fsck
  -> lpmake 重建 super
  -> avbtool 重建 footer 和 vbmeta 链
  -> 联想 rawprogram/GPT 布局组成 QDL 发布集
复制成功

二、Y700 四代的具体移植修改

2.1 先建立最小可启动面

最初不能把完整 OPPO ODM 直接作为默认运行面。一个 HAL 不是一个 .so,而是一组 不可拆的合约:

代码块
PlainText
自动换行
复制代码
VINTF manifest
  + init rc
  + service binary
  + DT_NEEDED / dlopen 依赖
  + file_contexts 和 exec type
  + init -> domain transition
  + binder / hwservice / service contexts
  + framework feature/property 声明
复制成功

处理方式是先裁掉 Y700 没有硬件提供者或上层不必需的私有 HAL,再按功能恢复。 删除时必须整组删除,不能只删 binary 或只把 service 设为 disabled。带 interface aidl/hidl 的 disabled service 仍可能被懒启动;保留 manifest 也会让 framework 认为能力存在并等待一个永远起不来的 provider。

最小面验收条件:

  • SELinux 为 Enforcing,sys.boot_completed=1,boot animation 停止。

  • framework-facing 的 display、health、USB、audio、power、lights、vibrator、 sensors、thermal、camera provider、Bluetooth、Wi-Fi、DRM、identity HAL 已注册。

  • 任何被删除的 VINTF instance 都没有残留 rc、binary 或 permission XML。

  • 任何保留的 init service 都有可执行文件、递归 ELF 依赖和有效执行域。

2.2 产品身份、密度和窗口尺寸

ColorOS 的 UI、应用清单和私有 HAL 会同时读取项目号。只改 ro.product.model 不够。 本项目统一建立 25928 设备身份,生成 etc/25928、传感器、温控、无线笔和快充 配置别名,并把需要的 oplusVerX 身份通过受限数据路径提供给私有服务。

最终显示策略:

  • 逻辑 density 固定为 490。

  • 面板真实尺寸保持 1904x3040,不伪造为 2400x3392。

  • 只迁移旧版本精确写入的 2400x3392 stable display;其他用户自定义尺寸保留。

  • 跨屏互联和 PadConnect 使用真实稳定尺寸,避免远端应用窗口比例错误。

  • persist.sys.compactwindow.scalesurface=false,避免紧凑窗口二次缩放。

  • Launcher 使用已验证的平板资源覆盖,不把手机 Launcher 布局硬拉伸到 8.8 英寸。

验收条件:wm size 同时显示 physical/stable 1904x3040,wm density 为 490; 跨屏窗口纵横比、点击坐标和应用内容区一致;升级已有数据时旧错误尺寸自动迁移, 不要求清除 userdata。

2.3 微信横屏紧凑窗口

微信横屏曾出现界面和字体整体缩小。全局 density 已经是 490,问题不是 density 没有 写入,而是 ColorOS 平板紧凑窗口路径又对应用 surface 做了一次缩放。设备保留 ro.panel.pad_compact_scale=0.7 时,异常现场的微信 ResourcesKey 曾降到 347 dpi,正好接近 490 * 0.7;另一次异常约为 435 dpi。

修复是在 system/build.prop 中把:

代码块
properties
自动换行
复制代码
persist.sys.compactwindow.scalesurface=true
复制成功

改为:

代码块
properties
自动换行
复制代码
persist.sys.compactwindow.scalesurface=false
复制成功

这个修改只关闭紧凑窗口的 surface 二次缩放,不删除 ColorOS 的平板窗口能力,也不 修改全局 490 dpi 或 ro.panel.pad_compact_scale。因此不能用改 wm density、删除 compact scale 或伪造分辨率来替代它,否则会同时破坏 Launcher、系统 UI 和其他应用。

验收条件:

  • wm size=1904x3040、wm density=490,属性读取为 false。

  • 微信横屏 ResourcesKey 为 mCompatInfo={490dpi always-compat}。

  • 微信 DisplayMetrics.density=3.0625,即 490 / 160。

  • WindowManager 的 mIsDensityForced=false,微信 task 的 mOplusCompatMode=false。

  • 横屏字体和内容比例正常,回到竖屏以及使用其他紧凑窗口时没有尺寸回归。

该项已保存正常状态真机证据,用户实体观察确认微信显示恢复正常。

2.4 显示色彩

问题根因不是设置页没有保存值,而是 ColorOS 私有 mode、AOSP composition color space、SurfaceFlinger force mode 和联想 composer render intent 使用了不同编号。 只改白点或只写设置值,会出现“点下去瞬间生效,随后又回退”的现象。

最终映射为:

  • 自然(framework mode 0,composition 0) 面板 Native,保持自然模式更亮的原生白点。

  • 专业(framework mode 0,composition 9) Display P3 色度学输出,白底相对偏暗但还原更准确。

  • 鲜明(framework mode 3,composition 9) P3 + 联想 SDR enhanced intent,保持更高饱和度和较亮白底。

修改同时覆盖 oplus-services.jar、services.jar 和 SurfaceFlinger 的最终 profile 选择,并在 SCREEN_ON、USER_PRESENT 后重放当前模式,防止灭屏亮屏后被面板默认 状态覆盖。自然模式不再套 1.1 的非色度学增益,专业和鲜明也不共用同一个最终 intent。

验收条件:三个模式可肉眼区分;自然与专业的白底有差异;鲜明的饱和度明显更高且 白底不等同专业;切换后等待、锁屏再亮屏、解锁后均不回退。当前发布已完成上述 实体屏幕验收。

2.5 护眼模式

ColorOS 智能护眼默认依赖环境色温传感器,Y700 只有普通环境光路径,没有该私有 CCT sensor type。原逻辑注册失败后直接返回,所以打开“智能调节”当场不应用,切到 自定义才触发另一条更新路径。

修复只在私有 CCT sensor 确实不存在、护眼已开启且智能模式已开启时,读取已保存的 智能护眼基线 CCT,并用 2 秒动画立即应用。设备若存在传感器则仍走原算法。

验收条件:停留在智能调节时直接打开护眼即可看到色温变化;切换自定义不再是首次 生效的必要条件;普通亮度自动调节不受影响。

2.6 双击亮屏与抬起亮屏

双击亮屏使用联想 Novatek 驱动真实节点 /proc/gesture_control。ColorOS 设置值 虽然能保存,但没有可靠写入这个节点。system init 在 boot、late-init 和 sys.boot_completed=1 三个时点写入 1,SELinux 只增加:

代码块
PlainText
自动换行
复制代码
init -> proc_touch_wake:file { getattr open write }
复制成功

没有放开 shell,也没有给整个 /proc 通配权限。实体双击日志已观察到 KEYCODE_WAKEUP 和 WAKE_REASON_WAKE_KEY。

抬起亮屏不是写同一个节点。它通过可选 KernelSU 模块让 PowerManager 读取独立 secure setting fp8300_lift_to_wake,保留联想正常 wake gesture 路径,同时禁用 不兼容的 OPPO tilt 开关。模块安装后默认开启,KernelSU 模块的“操作”按钮用于切换。

验收条件:设备静置熄屏后,连续至少 5 次拿起测试都能唤醒;放下或普通移动不应 高频误亮;重启后设置保持。三个可选模块都挂载同一个 services.jar,只能启用一个。

2.7 注视时不息屏

注视功能需要 AON 推理库、QNN delegate、host library 和 CDSP skeleton 成套存在。 只恢复 AON service 不会形成推理闭合。当前 ODM 恢复同版本完整 QNN 运行栈,vendor 导出 libaiboost.so,并修复 AttentionManagerService 的冷绑定超时顺序:无论 attention service 是否已绑定,先安排取消超时,避免屏幕长期停在 dim。

验收条件包含两条相反路径:持续注视时 dim 后应恢复亮度;未检测到注视时应按系统 超时正常锁屏。不能只验证“没有锁屏”,因为一直 dim 且不锁同样是失败。

2.8 相机:硬件栈与应用分层

相机是板级差异最大的子系统之一,不能因为 donor 和目标是同一 SoC,就使用 donor 的整套相机栈。镜头模组、sensor ID、OTP/标定数据、OIS、驱动和 CamX plugin 都与具体产品绑定。

当前方案把相机拆成三层:

  1. 硬件层以联想官方镜像为准,保留 vendor.qti.camera.provider-service_64、camera.qcom.so、 camx.device-impl.so、camx.provider-impl.so、/vendor/lib64/camera 以及对应 sensor、标定和 firmware。成品中 provider 和上述 CamX 核心库 已与联想 202 原文件做 SHA-256 逐个相等性核对。

  2. framework 层保留 ColorOS Camera2/权限/隐私调用面,通过标准 android.hardware.camera.provider.ICameraProvider/vendor_qti/0 使用真实硬件。

  3. 应用层使用 my_product/del-app/Camera/Camera.apk 中的 Aperture (org.lineageos.aperture.dev)。OPPO 相机应用依赖私有 OEM 算法层和对应调校, 在这台设备上不作为可验证的默认相机。Aperture 也不是两套官方 镜像原件,而是本地应用层融合资产;发布时应保留 APK 哈希和许可证信息。

OPPO ODM 中的 camera.oemlayer.so、camera RFI 依赖和对应目录会尝试加载 不匹配的厂商算法 ABI,因此当前 ODM 明确不保留这一层。删除时要同时处理 VINTF、rc、binary、库和权限 XML,不能留下半套懒启动合约。

SELinux 还有一个容易漏的点:CamX 的若干 SPHAL 库必须在 EROFS inode 上真正 带有 same_process_hal_file xattr。只在 vendor_file_contexts 里写了规则、 重打包却丢了 xattr,仍会导致 SurfaceFlinger/cameraserver 执行或 mmap 拒绝。 当前成品已对相机 common/device/metadata、offlinecamera 和 libcamxexternalformatutils.so 的实际 inode label 做过核对。

验收分两级。静态/运行闭合要求 provider 在 hal_camera_default 域稳定运行, ICameraProvider/vendor_qti/0、IAONService/default 和 IOfflineCameraService/default 全部注册,无 dlopen failed、连续崩溃或相关 AVC。功能验收必须覆盖前后摄预览、切换、拍照、录像与录音、旋转、闪光灯、 第三方 Camera2 应用和隐私指示,不能用“相机 APK 能打开”代替。

2.9 人脸解锁:前摄软件识别路线

OPPO 官方人脸方案使用 UFF android.hardware.biometrics.face.IFace/default 和安全世界 trustlet。直接搬到联想启动链后,HAL 能走到 QSEE 加载,但会因 trustlet/安全固件不匹配而失败,最终没有可用 provider。因此当前包不保留这套 UFF manifest、rc 和 ODM HAL,也不用一个假的 VINTF 声明让 framework 无限等待。

最终使用已在 Y700 前摄实机验证的 Megvii 软件链路,再接入 ColorOS 生物 识别 framework。这批模型和运行库不属于联想 202 system 或 OPD2413 官方 OTA 的原样文件,它们是受哈希固定的本地功能融合资产。复现时必须单独记录来源哈希、 ABI 和授权,不能把它们误标为任一官方包原件:

  • system/etc/face/model_file 和 panorama_mgb 提供模型资源, libFaceDetectCA.so、libMegviiUnlock*.so 和 libmegface.so 提供识别运行时。

  • services.jar 中的 FaceService/FaceProvider 在硬件 IFace/default 不存在时注册软件-backed sensor,并把 enroll/authenticate/remove 调度到 FaceAuthBridge。

  • getEnrolledFaces() 和 hasEnrolledFaces() 返回软件 provider 的真实结果。 /data/system/face_unlock_data/feature 是录入实体;仅有 key_face_templates_enrolled 或 persist.sys.oplus.isFaceEnrolled 不能算已录入, 避免出现界面显示已录入、实际永远无法认证的幽灵状态。

  • 录入或删除完成后主动通知 BiometricStateCallback,让 SystemUI 实时更新 mFaceEnrolled,不需重启 SystemUI 或整机。

  • android.hardware.biometrics.face feature 仍在 vendor 和产品功能声明中对 Settings/SystemUI 可见,但它不代表存在 OPPO UFF HAL。

这是普通前摄上的 2D 软件人脸识别,不能宣称具有 3D 结构光、活体硬件或 TEE 内完整模板保护能力。重启后第一次仍必须输入 PIN/密码,这是 Android strong-auth 规则,不是人脸移植失败。

验收条件:能完成录入,产生 system:system 0600 的完整 feature 文件;Settings 和 SystemUI 立即显示已录入;重启后先用 PIN 完成首次解锁,再息屏亮屏能用人脸 解锁;删除 feature 或正常移除人脸后 hasEnrolledFaces=false;全过程无 UFF/QSEE 失败循环、system_server 崩溃或相关 AVC。这条路径直接绕过了原生 BiometricScheduler 的部分计数,因此 dumpsys face 的 Total Attempts 不是可靠成功信号;应以 reportSuccessfulBiometricUnlock、锁屏消失和用户可见结果为准。

2.10 充电、快充、限充、智能优化和旁路

ColorOS charger、power stats、Horae 和 framework 客户端写死访问 /sys/class/oplus_chg,而 Y700 暴露的是联想/QCOM power_supply 和联想 Battery AIDL。修复没有伪造内核节点,而是把经过 SHA-256 门禁的客户端重定向到专用 /data/local/opluschg 树,由受限 bridge 每 2 秒同步真实节点,并把控制写回联想 接口。

功能边界如下:

  • 基础充电状态

    • 实现:转换 online、电压、电流、容量、SOH、cycle、charger type。

    • 不做的事:不伪造不存在的传感器读数。

  • 协议显示

    • 实现:只按真实 real_type/PPS 映射 PD、QC、PPS。

    • 不做的事:不把联想私有协议数字当成 OPPO VOOC/SuperVOOC ABI。

  • 固定上限

    • 实现:调联想 Battery AIDL 的 max/recharge/maintenance 事务,并持久化 latch。

    • 不做的事:不每 2 秒重放同一事务。

  • 智能优化

    • 实现:映射 ColorOS 设置状态和 mmi_charging_enable 到真实 charging_enable。

    • 不做的事:不声称复刻服务端学习模型。

  • 旁路

    • 实现:外部电源在线时暂停电池充电。

    • 不做的事:不宣称硬件已切成完全绕过电池的独立供电拓扑。

所有固定上限档位使用 1% 回差:设置 N% 时到 N% 停止,降到 N-1% 恢复。 例如 90% 档为 90% 停、89% 恢复。即使设备已到 100% 后才打开 90% 上限,也要 立即锁存停止意图,不能因为 Settings 在 early boot 暂时不可读而重新启充。

快充是否激活由充电器、线材、联想内核和 Battery HAL 的协商共同决定。OPPO 与联想 私有协议不相同,软件只能正确呈现已协商的 PD/QC/PPS,不能把 5V2A 变成私有快充。 验证必须读取真实 voltage_now/current_max/input_current_limit/real_type/pps_status, 不能只看设置页图标。

验收条件:插电 10 分钟容量和电流方向正确;每个上限档都按 N/N-1 工作;100% 后开启 90% 能停止;智能优化开关能改变真实 charging_enable;旁路开启时外部电源 仍在线但电池电流接近停止充电;协议 UI 与内核实际协商一致。

2.11 USB-C 有线耳机音量

插耳机后音量 UI 会变化但听感一直最大,根因是 ColorOS 音频策略和保留的 QTI AIDL HAL 对 USB device category 的 sentinel 约定不同,并且 vendor 实际读取的 volume table 没有完整 USB headset 曲线。

修复包括两部分:音频策略接受保留 HAL 的 -1 sentinel 并落入 legacy USB headset category;vendor 表为 14 个 stream 提供 USB 曲线,同时删除该 AIDL 路径不支持的 A2DP 扩展 category。媒体曲线从低音量 1,-7000 到高音量 80,-150。

验收条件:音量键和滑块每档都有可听差异,最低档不是最大声,静音有效,扬声器和 蓝牙音量曲线不回归。实体耳机听感已经完成验收。

2.12 外接键盘 Command/Meta

ColorOS 会在应用之前把 Meta 键作为 AI 按键和系统快捷键入口,导致远程桌面中的 Command+C/V 也被拦截。这个改动不进入主固件,只在可选 KernelSU 模块中完成。

模块把 Meta event 在 ColorOS 和 Android 系统快捷键处理之前交给前台应用,非 Meta 系统快捷键保持原行为。三种模块为 Command-only、Lift-only、Command+Lift,版本均为 2.0.2。模块对整个 jar 和三个 dex entry 做哈希门禁;目标固件不匹配时自动禁用。

验收条件:远程桌面能收到左右 Meta key;Command+C/V、Command+Tab 和应用自定义 组合键由前台应用处理;Android 音量、电源和非 Meta 系统按键不受影响。

2.13 Horae 热控和安全通道

Horae 固定读取 OPPO 的 /proc/shell-temp、/proc/oplusVersion 和 /proc/chip, Y700 没有这些路径。当前实现把等长字符串重定向到专用 /data 类型,由 system bridge 按 thermal zone 的 type 名称动态发现温度源,不依赖易变化的 zone 编号, 也不加载曾导致早期 panic 的内核模块。

ssgtzd 的配置解析器实际要求 key=value。设备没有对应 modem QMI 通道,所以 保留 TrustZone 通路 tz_enable=1,设置 modem_enable=0。这不是停止安全守护进程, 而是关闭硬件上不存在的分支。

验收条件:120 秒内 Horae、bridge、ssgtzd、ssgqmigd PID 稳定;无 /proc/shell-temp 错误、QMI 重试洪泛、fatal signal 或相关 AVC。

2.14 私有 HAL 运行依赖

URCC、Subsys、AON 和 crypto service 的教训相同:链接依赖和运行时 dlopen 依赖 必须作为同一 build set 恢复。URCC 除 service/core/Algo 外还需要 libuahcore.so; Subsys 需要 libvasapi.so;AON 需要完整 QNN host + CDSP 集合。缺少这类文件时, VINTF 可能通过、进程也可能短暂出现,但随后会反复 crash 或让 framework 等待。

验收条件:lddtree/readelf -d 的静态依赖全部存在;运行时 logcat 没有 dlopen failed;目标 service PID 稳定;对应 AIDL/HIDL instance 已注册。

2.15 “查找”的边界

当前只保留关闭开关、读取状态和本地协议返回的稳定性修复。关闭后状态能够持久化, 不会出现设置页开关关不掉。开启仍提示“未获取到设备信息”,原因是设备没有完成 对应服务器侧注册和认证。

验收条件只包含“可以关闭、重启后仍关闭、读取状态一致”。不把服务器激活列为已 支持能力,也不应反复尝试未知服务器接口。

2.16 Launcher 稳定性

移植早期出现 Launcher 壁纸 blur 后台预计算崩溃。补丁跳过不安全的后台 precompute,保留按需主线程 blur,不影响壁纸功能本体。任何 native/odex 指令补丁 都用原文件 SHA-256、固定 offset、原字节和目标字节四重门禁,版本不匹配直接失败。

验收条件:开机、解锁、换壁纸和 Launcher 重启时无 native crash;壁纸模糊仍能在 实际需要时生成。

三、SELinux Enforcing 闭合

3.1 为什么“能编译”仍然不能开机

跨厂商组合后,发布分区里的 CIL 不一定等于厂内构建时的完整输入。常见缺口包括:

  • vendor CIL 引用了 system/system_ext public type,但对应 mapping 或 public declaration 没有进入本次编译。

  • init rc 指定的 seclabel/domain 在另一个分区声明,当前输入顺序或文件集合缺失。

  • 已知可启动二进制策略包含少量引导 allow,发布 CIL 没有保留其源语句。

  • file_contexts 有 exec type,但策略没有 type_transition;或者反过来。

  • policy 编译成功,但 precompiled_sepolicy.*.sha256 仍对应另一组输入,设备实际 没有加载刚生成的策略。

  • EROFS 重打包丢失 security.selinux xattr,导致正确策略也匹配不到正确对象。

本项目的关键闭合不是“收集 AVC 然后 audit2allow”,而是从实际分区输入重编,并 建立结构化门禁:1,787 个 vendor type 全部存在,12 个关键 init transition 全部 存在,34 组当前必需 allow 全部存在,permissive domain 为 0。另有两条启动引导 规则只能从可启动策略二进制和失败日志交叉证明,已作为显式最小 delta 固化。

3.2 secilc 成功但漏掉两条启动规则

本项目踩到的关键坑是:分区里的 split CIL 可以被主机和 Android 两套 secilc 正常编译,返回码都是 0,但编出的 policy 仍比真机已验证可启动的二进制策略少两条 crypto service 启动规则。也就是说,“策略成功编译”只证明语法和声明闭合,不证明 生成结果与原先可启动策略语义等价。

发布 CIL 没有记录、但启动实际需要的规则为:

代码块
cil
自动换行
复制代码
(allow init_202404 hal_cryptoeng_oplus (process (noatsecure)))
(allow hal_cryptoeng_oplus default_prop_202404 (file (read getattr map open)))
复制成功

第一条允许 init 子进程完成目标 domain 的安全属性处理;第二条允许 crypto service 读取和 mmap 默认属性区。缺少它们时,policy 本身能编译和加载,但 class hal 启动 crypto service 时,init 子进程会在真正 exec 之前 SIGABRT,设备随即回 fastboot。 因此现场看起来像 AVB、boot chain 或 init binary 问题,编译日志却完全正常。

定位过程应按以下顺序复现:

  1. 用相同 CIL 输入分别在主机和设备侧编译,确认两者结果一致,排除 secilc 版本或 policy binary 格式差异。

  2. 用 seinfo/sesearch 比较真机已验证策略和本次重编策略,不比较整个二进制 SHA-256,因为策略语句排序和编译布局会制造噪声。

  3. 先按启动 service/domain 收窄差异,只提取能解释 init SIGABRT 的规则,不复制 整份未知策略。

  4. 把两条规则放入单独的 bootstrap delta,加入固定编译输入,再用 sesearch 对 最终 policy 做强制门禁。

  5. 真机冷启动确认 init SIGABRT 为 0、crypto service 注册、SELinux 仍为 Enforcing。

对应的最小检查可以写成:

代码块
Shell
自动换行
复制代码
sesearch --allow -s init -t hal_cryptoeng_oplus \
  -c process -p noatsecure precompiled_sepolicy
sesearch --allow -s hal_cryptoeng_oplus -t default_prop \
  -c file -p read -p getattr -p map -p open precompiled_sepolicy
复制成功

CIL 输入使用带 vendor API 后缀的 type 名;编译后的 binary policy 中通过 mapping 归一成 init 和 default_prop,因此 sesearch 检查最终二进制时使用无后缀名称。

构建脚本现在把这两项纳入 REQUIRED_COMPAT_ALLOWS。任何一项缺失,即使 secilc 返回 0,也必须让构建失败。这个案例说明 SELinux 闭合需要同时验证四层:CIL 能编译、 最终类型/transition/allow 存在、对象 label 正确、真机启动调用链通过。

3.3 编译输入和顺序

实际输入顺序为:

代码块
PlainText
自动换行
复制代码
plat_sepolicy.cil
plat mapping/<vendor-api>.cil
system_ext_sepolicy.cil
system_ext mapping/<vendor-api>.cil
product_sepolicy.cil
product mapping/<vendor-api>.cil
vendor_sepolicy.cil
plat_pub_versioned.cil
设备 public-type 兼容声明
经过证据确认的最小 runtime/bridge delta
复制成功

对应的编译形式为:

代码块
Shell
自动换行
复制代码
secilc -m -M true -G -N -c 30 \
  -o precompiled_sepolicy \
  -f compiled_file_contexts \
  plat_sepolicy.cil plat_mapping.cil \
  system_ext_sepolicy.cil system_ext_mapping.cil \
  product_sepolicy.cil product_mapping.cil \
  vendor_sepolicy.cil plat_pub_versioned.cil \
  device_public_compat.cil runtime_compat.cil
复制成功

-N 只处理发布 CIL 缺少构建期 neverallow 环境的问题,不会补 type、transition、 context 或 allow。它不是“放宽运行时策略”的替代品。编译后仍必须用 seinfo、 sesearch 和自定义闭合审计逐项验证。

3.4 服务闭合检查

对每个默认启动或可能懒启动的服务,至少检查:

  1. rc 中的绝对可执行路径真实存在。

  2. shell script 的 shebang/interpreter 存在。

  3. ELF 的 DT_NEEDED 和运行时 dlopen 目标递归存在。

  4. executable 能被 file_contexts 命中且实际 xattr 正确。

  5. init 对该 exec type 有目标 domain transition,或 rc 的显式 seclabel 有效。

  6. VINTF instance、service context 和实现版本一致。

  7. 启动触发器确实会执行;不能因 service 标了 disabled 就当作死代码。

当前发布的静态结果为:HAL/VINTF PASS 150 / FAIL 0,init 无条件 exec PASS 14 / FAIL 0,递归 init ELF PASS 18,797 / FAIL 0。静态通过不替代真机 运行,但能在刷机前挡住大部分可确定错误。

3.5 不应放开的权限

以下做法能快速减少日志,但会破坏安全边界,也常常掩盖真正缺文件或缺 provider:

  • 给 generic sysfs、proc、block_device、system_data_root_file 整类读写。

  • 给应用域 dac_read_search、任意 property area 枚举或任意 socket。

  • 把 init、vendor_init、HAL 或 system_server 整域改 permissive。

  • 在 secilc -D 暴露 dontaudit 后把所有新增 AVC 全部转换成 allow。

正确做法是从功能调用链反推最小对象类型。例如双击亮屏只允许 init 写 proc_touch_wake,充电 bridge 只访问专用 data type,诊断读取只给 rawdump 或 pstoredump 专用 block type,不给 generic block device。

四、从官方包开始的通用移植教程

4.1 第零阶段:先证明打包基础设施可用

没有这一步,不应开始移植。先选一套能在目标设备正常启动的官方救砖包,完成一次 “全解包、零功能修改、重新打包、刷回、正常开机”。成功标准不是脚本退出 0,而是 重打包后的镜像真机启动。

需要具备的工具能力:

代码块
Shell
自动换行
复制代码
# OTA
unzip -p official_ota.zip payload.bin > work/payload.bin
payload_dumper --out work/donor_images --workers 8 work/payload.bin

# Dynamic partitions
lpunpack base/super.img work/base_parts
lpdump base/super.img

# Filesystem inspection
file work/base_parts/*.img
fsck.erofs work/rebuilt/system.img
e2fsck -fn work/base_parts/vendor_dlkm.img

# Metadata-preserving tree copy
rsync -aHAX --numeric-ids mounted_source/ stage/

# Repack
mkfs.erofs --ignore-mtime --workers=12 -z lz4hc,9 -b 4096 \
  work/system.img work/stage_system
lpmake ... --output work/super.img

# AVB
avbtool info_image --image images/boot.img
avbtool info_image --image images/vbmeta.img

# QDL reference validation
qdl --dry-run images/prog_firehose_ddr.elf \
  images/rawprogram0.xml images/rawprogram1.xml \
  images/rawprogram2.xml images/rawprogram3.xml \
  images/rawprogram4.xml images/rawprogram5.xml \
  images/patch0.xml images/patch1.xml images/patch2.xml \
  images/patch3.xml images/patch4.xml images/patch5.xml
复制成功

基础设施验收清单:

  • lpunpack -> lpmake 后 LP geometry、group、metadata slots、alignment 和总容量合法。

  • EROFS/ext4 能保留 uid/gid/mode、symlink、capability 和 security.selinux xattr。

  • 每个重建文件系统先过 fsck,再进入 lpmake。

  • AVB footer 的 partition name、original size、rollback location、key 和上层 chain 一致,不能简单把 flags 改成关闭校验。

  • rawprogram 引用的每个非空 filename 都存在,programmer 与目标硬件匹配。

  • 零功能修改包真机至少完成冷启动、ADB、解锁、重启和基本硬件检查。

如果零修改重包都不能启动,先修打包基础设施。此时修改 framework 或 SELinux 只会 把两个问题叠在一起。

4.2 建立分区责任矩阵

先选 donor,再谈分区组合。对 ColorOS 这类厂商系统,推荐优先级为:

  • 同 SoC、同 Android 大版本、同 vendor API/KMI 代际(可行性:最合适) 可尝试 framework 上层和经过闭合审计的同平台私有组件。

  • 同 SoC、不同 Android/KMI 代际(可行性:高风险) 以上层应用和资源为主;kernel/vendor 接口需要逐项验证。

  • 不同 SoC、同 Android 大版本(可行性:不适合作为完整移植 donor) 通常只借应用、framework 资源或通用 system 逻辑;不能搬硬件栈。

  • 不同 SoC、不同 Android 大版本(可行性:不建议作为主 donor) 只适合提取独立应用或研究功能实现。

Android Treble/GSI 说明通用 system 理论上可以跨 SoC 运行,但厂商 ColorOS 上层会 调用大量私有 AIDL/HIDL、property、sysfs 和 vendor library。工程上应把“同 SoC + 同代接口”作为完整移植 donor 的起点。即使 SoC 相同,也不能直接复制 donor 的 XBL、ABL、TZ、modem、DSP、DTBO、vendor_boot DTB、camera firmware 或触控固件。

选择 donor 前至少核对:

  • SoC/platform codename 和 CPU ABI。

  • Android 大版本、vendor API level、VNDK 和 SELinux policy version。

  • boot header、GKI kernel version、KMI、DLKM vermagic 和实际导出符号。

  • 图形 composer、media codec、audio、radio 和 camera provider 的接口版本。

  • Virtual A/B、dynamic partition group、filesystem 类型和 AVB chain 结构。

  • 板级 panel、touch、sensor、PMIC、charger、camera、RF 和 storage 差异。

本项目中,同属 SM8750 sun 让 OPD2413 成为合理的 ColorOS framework donor; Y700 的具体板级仍由联想 DTBO、vendor、DLKM、firmware 和设备配置承担。这个边界 解释了为什么上层可以组合,而 OPPO 的 modem、DTBO 或整套 vendor boot 不能直接刷。

把目标官方包称为 hardware base,把 ColorOS 官方包称为 framework donor。先按职责 选源,不按“哪个版本新”选源:

  • XBL/ABL/TZ/HYP/UEFI/modem/DSP/Bluetooth/firmware → hardware base 原因:与板级、射频、安全世界和外设固件强绑定。

  • dtbo、vendor_boot DTB、fstab → hardware base 为主 原因:决定板级设备、分区路径和 first-stage mount。

  • boot kernel、vendor_boot modules、vendor_dlkm → 必须做 ABI 整体 原因:模块 vermagic 相同也不保证私有符号 ABI 相同。

  • system、system_ext、product、my_* → framework donor 为主 原因:提供 ColorOS framework、应用和资源。

  • vendor → hardware base 为主,按上层需求合入接口 原因:保留真实 HAL/firmware,同时满足上层 client。

  • odm → donor 为基底时必须大幅裁剪并补硬件配置 原因:私有 HAL 最集中,也是半闭合风险最大的分区。

  • vbmeta* → 按最终镜像重新生成 原因:任何受保护镜像变化都会使旧描述符失效。

先生成一个机器可读 TSV:partition/path -> selected source -> reason -> source hash -> output hash。任何“先从能开机的包复制,之后再说”的临时动作都必须在进入主线前 还原成官方源选择或明确补丁,否则发布来源无法复现。

4.3 组合底层和上层

推荐顺序:

  1. 固定 GPT、programmer 和全部硬件固件为 hardware base。

  2. 固定 kernel、DTBO 和 vendor_boot DTB,先不注入 donor 模块。

  3. 用 donor 的 system/system_ext/product/my_* 作为上层。

  4. vendor 从 hardware base 开始,只合入上层明确缺失的 context、APEX、service 或库;firmware、VINTF、SELinux 和 lib/modules 默认不替换。

  5. odm 从 donor 树开始时,先删除没有真实 provider 的 HAL 合约,再补目标项目号、 sensor、thermal、touch、charge、camera 和 radio 配置。

  6. 合并 fstab,保留目标设备专有 mount;动态分区同时提供实际文件系统类型需要的 row,移除目标 GPT 不存在的 AVB chain 和保留分区。

  7. 重建所有受影响逻辑分区、super、footer 和 vbmeta。

每次只引入一个可判决变量。例如先验证 donor 上层 + hardware vendor,随后才加入 一个私有 HAL 集合。一次改几十个服务,即使开机也不知道哪项必要,失败更无法归因。

4.4 Enforcing 下启动 minimal 版本

第一目标是 Enforcing 开机,不是先恢复全部厂商功能。没有 UART 时,10 到 50 轮 刷写属于正常成本。每一轮都要有:唯一构建 ID、输入 hash、假设、唯一变量、刷写 日志、启动结果和证据目录。

minimal 版本只保留 Android framework 启动必需的 HAL:display allocator/composer、 health、USB、audio、power stats、memtrack、power、light、vibrator、sensors、thermal、 camera provider、Bluetooth、Wi-Fi、DRM 和 identity。私有 GameOpt、OLC、性能、 扩展触控、风险检测、虚拟音频等在有明确功能缺口前不要默认启动。

全程保持 androidboot.selinux=enforcing。只在需要证明“失败是否由 SELinux 拒绝 这一类问题导致”时制作同 policy 的临时 permissive 对照包。对照包只能改变模式, 不能同时换 super、service 或 policy;成功后仍必须回到 Enforcing 修复。

4.5 按启动症状选择取证方法

  • 上电后很快回 fastboot

    • 高优先级检查:AVB chain、footer size/partition name、rollback、fstab、SELinux 编译/加载、init fatal。

    • 取证方法:avbtool info_image、offline secilc、rawdump/pstore、bootloader reason。

  • 卡联想 Logo 不动

    • 高优先级检查:kernel 已起但 userspace 阶段未知。

    • 取证方法:带阶段 marker 的 timed panic,生成 rawdump。

  • 联想 Logo 一会儿循环重启且没有 rawdump

    • 高优先级检查:userspace 主动 reboot、watchdog 或 panic 路径未生效。

    • 取证方法:在 init 不同阶段放 panic 做二分,或写原始 pstoredump trace。

  • 到 ColorOS 开机 Logo

    • 高优先级检查:framework 已走得较远,通常 ADB 已可用。

    • 取证方法:adb wait-for-device、getprop、logcat、dmesg、service/HAL 状态。

  • 桌面出现后“重启”

    • 高优先级检查:先区分整机 reboot、system_server Watchdog、SystemUI/Launcher 重启。

    • 取证方法:比较 uptime、surfaceflinger/system_server PID、dropbox、ANR 和 tombstone。

先检查 AVB:

代码块
Shell
自动换行
复制代码
avbtool info_image --image images/boot.img
avbtool info_image --image images/vendor_boot.img
avbtool info_image --image images/vbmeta.img
avbtool info_image --image images/vbmeta_system.img
fastboot getvar current-slot
fastboot getvar all 2>&1 | tee fastboot-vars.txt
复制成功

重点核对 Original image size、footer、partition name、public key、flags、rollback index/location 和所有 chain/hash descriptors。旧 vbmeta_system 内的 dynamic hashtree 不能继续描述已经重建的 system/vendor/odm。

4.6 rawdump、pstore 和 pstoredump

没有 UART 时,timed panic 的作用是把“卡 Logo”变成可读取的 crashdump。脚本应先 向 /dev/kmsg 写唯一 marker,设置 ramdump collection,再在预定秒数执行:

代码块
Shell
自动换行
复制代码
echo fp8300_diag:timed_panic_trigger > /dev/kmsg
echo c > /proc/sysrq-trigger
复制成功

CrashDump 界面出现约 3 秒时长按音量加,打断自动重启循环;随后进入 EDL 读取 rawdump。进入循环前应先清旧分区,防止把上一次 dump 当作本轮证据:

代码块
Shell
自动换行
复制代码
qdl -s ufs -R images/prog_firehose_ddr.elf \
  erase 0/rawdump erase 0/pstoredump

# 刷入诊断包并复现后重新进 EDL
qdl -s ufs -R images/prog_firehose_ddr.elf \
  read 0/rawdump rawdump.bin
qdl -s ufs -R images/prog_firehose_ddr.elf \
  read 0/pstoredump pstoredump.bin
复制成功

-R 表示读写完成后不自动 reset,使两个分区能连续读取。所有 QDL 命令退出后再 断线或重新进启动模式。有效 Qualcomm rawdump 头通常为 Raw_Dmp!,优先解析 md_lastkmsg.txt、kernel console、ramoops、logcat 和 minidump segment。先确认 dump 里的 build ID/marker 属于本轮,再做归因。

如果 kernel 没有产生新 rawdump,可临时让 init/root 诊断服务每秒把关键属性、当前 init action、进程、最近 logcat 和节点标签写入 pstoredump 的固定 slot。这个服务只 应获得专用 pstoredump block type 的写权限,正式包必须移除。

设备能进 ADB 时优先读取标准证据:

代码块
Shell
自动换行
复制代码
adb wait-for-device
adb shell getenforce
adb shell getprop sys.boot_completed
adb shell getprop init.svc.bootanim
adb shell getprop sys.powerctl
adb shell cat /proc/uptime
adb shell logcat -b all -d > logcat-all.txt
adb shell cat /sys/fs/pstore/console-ramoops-0 > console-ramoops-0.txt
adb shell dmesg > dmesg.txt
复制成功

production build 可能禁止 dmesg、/data/anr 和 tombstone 读取。此时用与当前 kernel ABI 完全匹配的 debug/root 启动载荷取证;不要为了 root 换一套不同 kernel, 否则会先引入模块 ABI panic。

4.7 用阶段二分代替猜测

启动 Logo 循环但没有可用 dump 时,把 Android init 事件当成有序阶段:

代码块
PlainText
自动换行
复制代码
early-init
init
late-init
early-fs / fs
post-fs / post-fs-data
zygote-start
early-boot
boot
sys.boot_completed=1
复制成功

先在中点阶段放唯一 marker + 延时 panic。能看到 panic 说明执行已越过该阶段;看不到 则向前移动。重复二分,而不是每次随机删一批 service。若设备自然回 fastboot 早于 timed panic,改用 pstoredump slot 记录“最后完成的 init action”。

ColorOS Logo 一旦出现,通常 ADB 已经能上线。用循环探测,不要等桌面:

代码块
Shell
自动换行
复制代码
while ! adb get-state >/dev/null 2>&1; do sleep 1; done
adb shell getprop init.action
adb shell getprop init.command
adb shell ps -A
adb shell service list
复制成功

framework 卡住时,看 system_server 主线程和 waitForDeclaredService()。如果依次 卡在 power stats、memtrack、power、lights、vibrator、sensors,说明 minimal HAL 集合缺 provider;逐个恢复实际共同 HAL,比重新打开整个 class_start hal 更容易 判决。

4.8 功能逐项恢复

开机稳定后,每个功能按同一模板处理:

  1. 写清用户可见症状和成功标准。

  2. 从 framework 调用点追到 AIDL/HIDL、property、sysfs/proc 或应用资源。

  3. 在目标设备确认真实 provider 和节点,不按 donor 路径猜硬件能力。

  4. 只补完整调用链需要的文件、配置、context 和 policy。

  5. 离线过 hash gate、ELF、VINTF、init、SELinux 和 filesystem 检查。

  6. 真机同时测正路径、失败路径、锁屏/亮屏、重启和 120 秒稳定日志。

  7. 一个功能通过后立即提交代码;下一功能从这个已知状态继续。

建议顺序为:显示/触摸、音频、充电、传感器、相机、人脸解锁、网络、热控、外设、私有应用。 这些功能的硬件影响和用户可见性从高到低,且前项常是后项的依赖。

4.9 每轮提交和证据

每个提交只回答一个问题,提交说明应包含:

代码块
PlainText
自动换行
复制代码
症状:用户看到什么
根因:哪个接口契约不一致
改动:修改了哪些分区/文件/策略
门禁:输入 hash、目标 hash、匹配次数
验证:离线检查、刷写包 hash、真机结果、剩余边界
复制成功

不要在验证功能 A 时顺便整理无关属性或升级工具。移植需要几十轮刷写时,Git 提交 就是实验日志;如果一次提交包含多个假设,之后无法可靠回滚和二分。

五、发布验收清单

5.1 构建

  • 官方输入 SHA-256 固定,payload 和 super 能重新提取。

  • 所有修改都有输入 hash、精确匹配次数或结构化 parser 门禁。

  • 五个重建 EROFS 分区通过 fsck.erofs。

  • 每个 logical image 小于 LP partition size,总和不超过 group/device size。

  • lpmake 命令和 LP metadata 存档。

  • avbtool info_image 对全部受保护镜像通过,vbmeta 描述符与当前文件一致。

  • rawprogram XML 所有非空 filename 存在,QDL --dry-run 状态码为 0。

5.2 静态闭合

  • VINTF manifest 与 rc/binary/instance 形成闭合。

  • init 无条件 exec 和脚本解释器 0 缺失。

  • init ELF 递归依赖 0 缺失,dlopen 运行时依赖有专项清单。

  • file_contexts 命中、实际 EROFS xattr、exec type 和 domain transition 一致。

  • vendor type、关键 transition、必需 allow 0 缺失,permissive domain 为 0。

  • platform/system_ext/product policy mapping hash 与预编译策略输入一致。

5.3 真机

  • 至少三次冷启动均为 Enforcing,ADB 和 sys.boot_completed=1 正常。

  • 120 秒清日志采样无 init SIGABRT、fatal signal、关键 service crash loop、相关 AVC。

  • 锁屏、解锁、重启、USB 插拔、横竖屏和息屏亮屏无 SystemUI/Launcher 循环重启。

  • 显示、触摸、双击、色彩、护眼、注意力、音频、充电、网络、蓝牙、相机、人脸解锁逐项验收。

  • 人脸验收覆盖录入、首次强认证后解锁、删除、重启和旧数据幽灵录入;不只看设置页入口。

  • 限充测试覆盖“低于上限开始充”“到上限停止”“下降 1% 恢复”“100% 后开启低上限”。

  • 可选 KernelSU 模块分别安装、重启、动作切换、卸载恢复;互斥关系明确。

5.4 交付

  • SHA256SUMS.txt 覆盖发布目录中的每个文件。

  • MANIFEST.tsv 记录相对路径、大小和用途,SHA256SUMS.txt 记录对应哈希。

  • 外层压缩包另附 SHA-256,解压后再次校验内部清单。

  • 发布文档明确进入 fastboot/EDL、保留数据刷写、清数据 -R 顺序、恢复和 root 可选路径。

  • 交付目录不包含历史临时包、空占位 zip、旧 hash 或不匹配的模块。

六、核心原则

跨厂商移植不是“把 system 换掉”。它是让 hardware base 的真实能力与 framework donor 的调用契约重新闭合。判断一个改动是否正确,只看三个问题:

  1. 用户需要的功能是否真实工作,而不是 UI 显示成功。

  2. 调用链是否从 framework 到真实硬件形成最小、可解释的闭合。

  3. 构建、策略和验证证据是否足以让下一位工程师从官方镜像复现。

能 Enforcing 开机只是第一道门;能逐项解释来源、失败模式和验收条件,才是可发布的 移植。