ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Android AVB 2.0 校验链解析与 vbmeta.img 生成实战

Android AVB 2.0 校验链解析与 vbmeta.img 生成实战 1. AVB 2.0 到底在解决什么问题1.1 从一个真实场景说起手里有一台 Android 设备可能是开发板也可能是自己编译了 AOSP 之后想刷进去的手机。系统编译完成out/target/product/xxx/目录下躺着一堆.img文件boot.img、system.img、vendor.img还有一个看起来不起眼的vbmeta.img。你把这些镜像一股脑刷进去重启结果设备卡在开机第一屏或者直接跳进 fastboot 报一句dm-verity corruption或者AVB verification failed。这个场景我遇到过不止一次。问题往往不在镜像本身而在于AVB 2.0 的校验链没有闭合。vbmeta.img是整个链条的“信任根容器”它里面存着各个分区镜像的哈希值和签名信息。设备启动时bootloader 会先校验vbmeta.img的签名再用vbmeta.img里记录的哈希去校验boot、system等分区。任何一环对不上启动就会被拦截。所以理解 AVB 2.0核心不是背概念而是搞清楚三件事谁签谁、谁校验谁、校验失败之后会发生什么。把这三件事理顺vbmeta.img的生成过程就变成了一个顺理成章的推导而不是一堆命令的堆砌。1.2 AVB 2.0 相比 1.0 的关键变化Android Verified Boot 不是新东西1.0 时代就已经存在但 1.0 的校验模型比较粗糙主要依赖 dm-verity 对system分区做块级别的哈希树校验vbmeta结构也相对简单。到了 AVB 2.0整个模型被重新设计几个变化值得注意。第一引入了统一的vbmeta结构。1.0 时代每个分区各自为政2.0 把所有分区的校验元数据集中到vbmeta.img里通过AvbVBMetaImageHeader加一系列 descriptor 来描述。这样 bootloader 只需要信任一个入口校验逻辑大大简化。第二支持链式分区chained partitions。比如vbmeta_system.img可以作为一个独立的 vbmeta 被vbmeta.img引用形成两级校验。这在动态分区super partition场景下特别有用因为system、vendor、product都在 super 里不可能每个都单独放一个顶层 vbmeta。第三哈希描述符和 hashtree 描述符分离。对于boot这种小分区直接用 hash descriptor 存整个镜像的哈希对于system这种大分区用 hashtree descriptor 存 dm-verity 的根哈希和盐值。这个区分直接决定了avbtool生成 vbmeta 时要不要加--do_not_use_ab之类的参数也决定了刷机时哪些分区必须一起刷。第四回滚保护rollback protection。AVB 2.0 在 vbmeta 里带了 rollback indexbootloader 可以把它存到 tamper-evident storage 里防止攻击者把系统降级到有漏洞的旧版本。这个特性在量产设备上默认开启但在开发板上经常被关掉因为频繁刷机时 rollback index 对不上会导致设备变砖。理解这四点后面看avbtool的命令行参数就不会觉得莫名其妙了。1.3 谁需要关心 AVB 2.0不是所有 Android 开发者都需要碰 AVB。做应用层开发的人可能一辈子都不会直接和vbmeta.img打交道。但以下几类人绕不开AOSP 系统开发者编译完系统要刷机刷机脚本里必然涉及 vbmeta 的生成和刷写。ROM 制作者修改了system或boot之后必须重新生成 vbmeta否则校验失败。设备厂商的 BSP 工程师需要配置设备的 AVB 策略决定哪些分区参与校验、用哪种签名算法。安全研究人员分析设备的启动链安全性vbmeta 是绕不过去的一环。刷机爱好者解锁 bootloader、刷第三方 recovery 之后经常会遇到 AVB 相关的报错。如果你属于以上任何一类那这篇文章的内容应该能帮你省下不少查文档和试错的时间。2. vbmeta.img 的生成avbtool 命令背后的逻辑2.1 avbtool 是什么从哪里来avbtool是 AOSP 里自带的一个 Python 脚本源码位置在external/avb/avbtool。它不依赖任何第三方库只要有 Python 环境就能跑。编译 AOSP 的时候这个工具会被打包到out/host/linux-x86/bin/avbtool可以直接调用。它的核心功能就三个生成 vbmeta 镜像、向镜像追加 AVB 元数据、验证镜像的 AVB 信息。日常用得最多的就是第一个和第三个。我习惯把它单独拷出来放到/usr/local/bin/下这样在任何目录都能直接敲avbtool不用每次都写完整路径。拷贝的时候注意给它加执行权限cp out/host/linux-x86/bin/avbtool /usr/local/bin/ chmod x /usr/local/bin/avbtool验证一下是否可用avbtool version正常会输出类似avbtool 1.2.0的版本号。版本号很重要不同 Android 版本自带的 avbtool 行为有差异比如 Android 12 之后对--flags的处理就和之前不一样。用错版本可能导致生成的 vbmeta 在设备上校验失败。2.2 生成 vbmeta 的完整命令拆解一个典型的生成命令长这样avbtool make_vbmeta_image \ --output vbmeta.img \ --key rsa4096_vbmeta.pem \ --algorithm SHA256_RSA4096 \ --chain_partition boot:1:boot.pem \ --chain_partition system:2:system.pem \ --include_descriptors_from_image boot.img \ --include_descriptors_from_image system.img \ --rollback_index 0 \ --flags 0这条命令看起来参数很多但拆开看每一块都有明确目的。--output指定输出文件名这个没什么好说的。--key和--algorithm是一对指定签名用的私钥和算法。RSA4096 是常见选择也有用 RSA2048 的但 4096 更稳妥。私钥文件通常是.pem格式由avbtool generate_rsa_key或者 openssl 生成。--chain_partition是链式分区的关键。格式是分区名:rollback_index:公钥路径。比如boot:1:boot.pem表示 boot 分区有自己的签名密钥rollback index 是 1。这意味着 boot 分区不是直接用 vbmeta 的密钥签的而是用 boot.pem 签的vbmeta 里只存了 boot.pem 的公钥。校验时 bootloader 先用 vbmeta 的密钥验证 vbmeta再从 vbmeta 里取出 boot.pem 的公钥去验证 boot 分区。--include_descriptors_from_image这个参数最容易被忽略但它是把分区哈希写进 vbmeta 的关键。执行这条命令时avbtool 会去读boot.img和system.img计算它们的哈希或 hashtree 根哈希然后生成对应的 descriptor 塞进 vbmeta。如果不加这个参数vbmeta 里就没有分区的校验信息bootloader 校验时找不到对应条目要么跳过校验要么直接报错。--rollback_index和--flags是两个策略性参数。rollback_index 设为 0 表示不启用回滚保护开发阶段常用。flags 控制一些行为比如是否允许 vbmeta 被覆盖、是否禁用 hashtree 校验等。量产时这两个参数需要根据安全策略仔细设置。2.3 签名密钥的生成与管理私钥的安全性是整个 AVB 链条的根基。开发阶段可以用测试密钥但量产必须用正式密钥而且私钥绝对不能泄露。生成 RSA 密钥对avbtool generate_rsa_key \ --key_size 4096 \ --output rsa4096_vbmeta.pem \ --output_pubkey rsa4096_vbmeta_pub.pem这条命令会生成私钥rsa4096_vbmeta.pem和公钥rsa4096_vbmeta_pub.pem。私钥用来签名公钥需要烧录到设备的 bootloader 里或者放在vbmeta的 chain partition 描述里。注意私钥文件权限一定要收紧建议chmod 600并且不要提交到代码仓库。我见过有人把私钥直接 commit 到 git 里结果整个项目的签名体系形同虚设。公钥的格式也有讲究。bootloader 里通常期望的是 DER 格式的公钥而 avbtool 默认输出的是 PEM 格式。转换方法openssl rsa -in rsa4096_vbmeta_pub.pem -pubin -outform DER -out rsa4096_vbmeta_pub.der这个 DER 文件才是最终要烧录到设备里的东西。2.4 不同分区的 descriptor 差异前面提到 hash descriptor 和 hashtree descriptor 的区别这里展开说一下因为搞混了会导致 vbmeta 生成失败或者校验不通过。hash descriptor适用于小分区比如boot、init_boot、dtbo。它直接存整个镜像的 SHA256 哈希值。生成方式avbtool add_hash_footer \ --image boot.img \ --partition_name boot \ --partition_size 67108864 \ --key rsa4096_vbmeta.pem \ --algorithm SHA256_RSA4096这条命令会在boot.img末尾追加 AVB 元数据包括哈希值和签名。之后再用--include_descriptors_from_image boot.img把 descriptor 提取到 vbmeta 里。hashtree descriptor适用于大分区比如system、vendor、product。它存的是 dm-verity 的根哈希、盐值和哈希树参数。生成方式avbtool add_hashtree_footer \ --image system.img \ --partition_name system \ --partition_size 2147483648 \ --key rsa4096_vbmeta.pem \ --algorithm SHA256_RSA4096 \ --salt 0x1234567890abcdef注意--salt参数如果不指定avbtool 会随机生成一个。但量产时通常要固定盐值否则每次编译生成的 hashtree 都不一样增量更新会出问题。--partition_size必须和实际分区大小一致否则刷机时会出现“镜像大小超出分区”的错误。这个值可以从分区表里查或者从BoardConfig.mk里的BOARD_XXXIMAGE_PARTITION_SIZE变量获取。3. Android 安全启动的完整校验链3.1 从上电到系统挂载的校验流程设备上电之后启动流程大致是这样的第一阶段BootROM执行它固化在芯片里不可修改。BootROM 会验证第一阶段 bootloader通常是 SPL 或 XBL的签名验证通过后跳转执行。第二阶段第一阶段 bootloader初始化基本硬件然后加载并验证第二阶段 bootloader通常是 ABL 或 LK。验证用的公钥通常烧录在芯片的 efuse 或 RPMB 里。第三阶段第二阶段 bootloader加载vbmeta.img用内置的公钥验证 vbmeta 的签名。验证通过后解析 vbmeta 里的 descriptor得到各个分区的校验信息。第四阶段bootloader 根据 descriptor 逐个校验分区。对于 hash descriptor直接计算分区哈希并比对对于 hashtree descriptor设置 dm-verity 的根哈希和盐值由内核在挂载时做块级别校验。第五阶段内核启动挂载system等分区时dm-verity 开始工作。如果某个块的数据被篡改哈希树校验会失败内核会返回 I/O 错误而不是直接 panic。这样系统可能还能启动到 recovery 或者提示用户数据损坏。整个链条里vbmeta.img是承上启下的关键。它上面的签名由 bootloader 验证它下面的分区校验信息由它提供。任何一环断裂启动都会被拦截或者降级处理。3.2 校验失败后的几种行为AVB 校验失败不是只有“变砖”一种结果具体行为取决于设备的 AVB 配置和 bootloader 实现。常见的有以下几种红色状态Red State设备完全不可信bootloader 拒绝启动直接黑屏或者显示警告后关机。这种情况通常发生在 bootloader 本身的签名验证失败或者 vbmeta 签名验证失败。橙色状态Orange Statebootloader 已解锁AVB 校验被跳过或者失败后继续启动但会显示警告信息比如“设备已解锁无法验证系统完整性”。这是开发阶段最常见的情况。黄色状态Yellow State设备使用自定义密钥签名bootloader 验证通过但公钥不是官方密钥显示警告后继续启动。绿色状态Green State一切正常官方密钥签名校验通过无警告启动。开发阶段通常把设备设为橙色状态方便刷机。量产设备必须是绿色状态否则用户会看到警告影响体验。实操心得如果你在开发板上刷了自编译的系统启动时看到橙色警告不要慌这是正常的。只要系统能起来说明 AVB 校验被跳过了不是校验失败。真正需要担心的是红色状态那说明 bootloader 根本不信任你的镜像。3.3 dm-verity 在 AVB 2.0 里的角色dm-verity 是 Linux 内核的一个设备映射目标它在块设备之上构建一棵哈希树每个数据块的哈希存在上一层最终汇聚到一个根哈希。读取数据时内核会逐层校验确保数据没有被篡改。在 AVB 2.0 里dm-verity 的根哈希和盐值被存在 vbmeta 的 hashtree descriptor 里。bootloader 校验 vbmeta 之后把这些参数传给内核内核在挂载分区时启用 dm-verity。这里有一个容易混淆的点vbmeta 校验和 dm-verity 校验是两回事。vbmeta 校验发生在启动早期由 bootloader 完成校验的是分区的元数据哈希值本身。dm-verity 校验发生在系统运行时由内核完成校验的是分区的实际数据块。两者配合才能实现从启动到运行时的完整信任链。如果只做了 vbmeta 校验而没有启用 dm-verity攻击者可以在系统启动后篡改分区数据而不被发现。如果只启用 dm-verity 而没有 vbmeta 校验攻击者可以替换整个分区包括哈希树dm-verity 也拦不住。所以两者缺一不可。3.4 动态分区对 AVB 的影响Android 10 之后引入了动态分区system、vendor、product等分区不再在分区表里固定存在而是放在一个大的super分区里由liblp在运行时动态映射。这对 AVB 的影响是不能直接对super分区做 hashtree因为 super 里的逻辑分区布局是动态的哈希树没法预先计算。解决方案是使用链式 vbmeta。具体做法是为system、vendor等每个逻辑分区单独生成一个 vbmeta 镜像比如vbmeta_system.img。然后在顶层vbmeta.img里用--chain_partition引用这些子 vbmeta。子 vbmeta 的镜像本身放在对应的逻辑分区里或者作为独立分区存在。生成子 vbmeta 的命令avbtool make_vbmeta_image \ --output vbmeta_system.img \ --key rsa4096_vbmeta.pem \ --algorithm SHA256_RSA4096 \ --include_descriptors_from_image system.img \ --include_descriptors_from_image system_ext.img \ --include_descriptors_from_image product.img然后在顶层 vbmeta 里引用avbtool make_vbmeta_image \ --output vbmeta.img \ --key rsa4096_vbmeta.pem \ --algorithm SHA256_RSA4096 \ --chain_partition vbmeta_system:3:vbmeta_system.pem \ --include_descriptors_from_image boot.img \ --include_descriptors_from_image dtbo.img这样 bootloader 先校验顶层 vbmeta再通过链式引用去校验vbmeta_system最后校验各个逻辑分区。层级清晰扩展性好。4. 实操中踩过的坑与排查方法4.1 常见报错与对应原因下面这张表是我在实际操作中整理出来的覆盖了大部分 AVB 相关的报错报错信息可能原因排查方法AVB verification failedvbmeta 签名不匹配或分区哈希对不上检查签名密钥是否一致重新生成 vbmetadm-verity corruption分区数据被篡改或 hashtree 参数不对检查盐值是否一致重新生成 hashtreeRollback index mismatchrollback index 比设备存储的小增大 rollback index或清除设备存储Partition size mismatch镜像大小和分区大小不一致检查--partition_size参数Public key not found链式分区的公钥路径错误检查--chain_partition的公钥路径Invalid vbmeta headervbmeta 镜像损坏或格式不对用avbtool info_image检查avbtool info_image是排查问题的利器它可以打印出镜像里所有的 AVB 信息avbtool info_image --image vbmeta.img输出会包含签名算法、公钥、descriptor 列表、rollback index 等。对比设备期望的值基本能定位问题。4.2 刷机顺序的重要性AVB 校验是链式的刷机顺序不对会导致校验失败。正确的顺序是先刷vbmeta.img如果它单独存在再刷各个分区镜像boot、system、vendor等最后刷vbmeta_system.img如果有链式分区但实际操作中fastboot 刷机通常是并行的顺序不好控制。更稳妥的做法是使用fastboot flashall或者厂商提供的刷机脚本它们会处理好依赖关系。如果手动刷建议先刷所有分区镜像最后刷 vbmeta。因为 vbmeta 里存的是分区的哈希如果先刷 vbmeta 再刷分区分区内容变了vbmeta 里的哈希就对不上了。注意有些设备的 vbmeta 是内嵌在 bootloader 里的不需要单独刷。这种情况下修改分区后需要重新编译 bootloader 或者用特殊工具更新 vbmeta。具体要看设备的实现。4.3 解锁 bootloader 之后的 AVB 行为解锁 bootloader 会触发设备进入橙色状态AVB 校验被跳过或者失败后继续启动。但这并不意味着 AVB 完全失效。有些设备在解锁后仍然会校验 vbmeta 的签名只是校验失败后不阻止启动而是显示警告。有些设备则完全禁用 AVB连警告都不显示。这取决于 bootloader 的实现和设备的 AVB 配置。如果你在解锁后刷了自编译的系统启动时看到警告但系统能起来说明 AVB 校验被跳过了。这时候你可以选择保持现状每次启动都看警告用自定义密钥重新签名所有镜像让设备进入黄色状态重新锁定 bootloader但前提是所有镜像都用官方密钥签名第三种做法风险最大因为一旦锁定后校验失败设备可能变砖。除非你确定所有镜像都正确签名否则不要轻易锁定。4.4 量产配置的注意事项开发阶段和量产阶段的 AVB 配置差异很大。开发阶段追求方便量产阶段追求安全。几个关键差异密钥管理开发用测试密钥量产用正式密钥。正式密钥应该存放在硬件安全模块HSM里签名操作在 HSM 内完成私钥不出模块。rollback index开发设为 0量产必须设置并递增。每次 OTA 更新都要增大 rollback index防止降级攻击。flags 配置开发可以设置--flags 1允许 vbmeta 被覆盖量产必须设为 0。dm-verity 模式开发可以用--verity_mode logging只记录不阻止量产必须用--verity_mode enforcing。分区大小量产的分区大小是固定的生成镜像时必须严格匹配。开发阶段可以用--partition_size指定一个较大的值但量产不行。这些配置通常写在BoardConfig.mk或avb_config.mk里编译时自动应用到 avbtool 命令中。理解这些配置的含义才能在出问题时快速定位。4.5 一个真实的排查案例有一次我编译了一个 Android 13 的镜像刷进设备后卡在开机动画logcat 里看到大量dm-verity错误。检查 vbmeta 的生成日志发现system.img的 hashtree 是用随机盐值生成的但设备期望的是固定盐值。原因是BoardConfig.mk里的BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS没有指定--saltavbtool 就用了随机值。修复方法是在这个变量里加上--salt 0x1234567890abcdef重新编译。这个问题的隐蔽之处在于vbmeta 本身校验是通过的因为 vbmeta 里存的根哈希和盐值是匹配的。但设备 bootloader 里可能内置了一个期望的盐值或者 OTA 更新时期望盐值不变。盐值不一致导致 dm-verity 校验失败系统无法挂载system分区。从那以后我在任何量产配置里都会显式指定盐值并且把它记录在文档里避免后续维护时忘记。5. 工具链与自动化实践5.1 用脚本封装 avbtool 命令手动敲 avbtool 命令容易出错尤其是参数多的时候。我习惯写一个 Python 脚本把常用操作封装起来。比如#!/usr/bin/env python3 import subprocess import os AVBTOOL /usr/local/bin/avbtool KEY rsa4096_vbmeta.pem ALGORITHM SHA256_RSA4096 def make_vbmeta(output, images, chain_partitionsNone): cmd [ AVBTOOL, make_vbmeta_image, --output, output, --key, KEY, --algorithm, ALGORITHM, ] for img in images: cmd.extend([--include_descriptors_from_image, img]) if chain_partitions: for part in chain_partitions: cmd.extend([--chain_partition, part]) subprocess.run(cmd, checkTrue) if __name__ __main__: make_vbmeta( vbmeta.img, [boot.img, dtbo.img], [vbmeta_system:3:vbmeta_system.pem] )这个脚本的好处是参数集中管理修改密钥或算法只需要改一个地方。而且可以集成到编译系统里在Android.mk或Android.bp里调用。5.2 与编译系统的集成AOSP 的编译系统已经内置了 AVB 支持通过BOARD_AVB_*系列变量配置。比如BOARD_AVB_ENABLE : true BOARD_AVB_ALGORITHM : SHA256_RSA4096 BOARD_AVB_KEY_PATH : device/xxx/security/rsa4096_vbmeta.pem BOARD_AVB_ROLLBACK_INDEX : 0 BOARD_AVB_BOOT_KEY_PATH : device/xxx/security/rsa4096_boot.pem BOARD_AVB_BOOT_ALGORITHM : SHA256_RSA4096 BOARD_AVB_BOOT_ROLLBACK_INDEX : 1这些变量会在编译时被build/make/core/avb.mk读取自动生成对应的 avbtool 命令。理解这些变量的含义比手动敲命令更高效。需要注意的是不同 Android 版本的变量名可能有差异。比如 Android 12 引入了BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGSAndroid 13 又增加了BOARD_AVB_INIT_BOOT_*系列。升级 Android 版本时要检查这些变量是否需要调整。5.3 验证生成的 vbmeta 是否正确生成 vbmeta 之后不要急着刷机先用avbtool verify_image验证一下avbtool verify_image --image vbmeta.img --key rsa4096_vbmeta_pub.pem这条命令会检查 vbmeta 的签名是否有效descriptor 是否完整。如果输出Verification succeeded说明 vbmeta 本身没问题。然后验证各个分区avbtool verify_image --image boot.img --key rsa4096_vbmeta_pub.pem如果 boot 分区是用链式密钥签的需要用对应的公钥验证。最后可以在设备上用fastboot getvar avb或者类似命令查看设备的 AVB 状态。有些设备支持fastboot oem avb verify之类的命令可以直接在 bootloader 里触发校验。5.4 自动化测试的思路在 CI/CD 流程里AVB 校验应该作为一个必过项。我的做法是在编译完成后自动执行以下检查检查vbmeta.img是否存在且大小合理用avbtool info_image确认 descriptor 数量和预期一致用avbtool verify_image确认签名有效对比vbmeta.img里的分区哈希和实际分区镜像的哈希如果任何一项失败编译流程直接中断避免有问题的镜像流入测试环节。这个检查脚本可以用 Python 写集成到 Jenkins 或 GitLab CI 里。关键是第 4 步因为 vbmeta 生成之后分区镜像可能被修改导致哈希对不上。自动检查可以及早发现这种问题。6. 几个容易被忽略的细节6.1 vbmeta 的 padding 和对齐vbmeta.img本身也有大小限制通常是 64KB 或 128KB。如果 descriptor 太多vbmeta 可能超出这个大小。avbtool 会在生成时自动 padding 到指定大小但需要确保--padding_size参数正确。如果 vbmeta 超出分区大小刷机时会报错。解决方法要么是增大 vbmeta 分区要么是减少 descriptor 数量比如把多个分区合并到一个链式 vbmeta 里。6.2 公钥格式的兼容性不同 bootloader 对公钥格式的要求不同。有些期望 PEM有些期望 DER有些期望特定长度的二进制。生成公钥后最好用avbtool extract_public_key提取一下确认格式正确avbtool extract_public_key --key rsa4096_vbmeta.pem --output vbmeta_pub.bin这个vbmeta_pub.bin是 avbtool 内部使用的格式可以直接烧录到设备里。6.3 多设备共用一套密钥的风险有些团队为了省事所有设备共用一套 AVB 密钥。这在开发阶段没问题但量产时风险很大。一旦某个设备的密钥泄露所有设备都受影响。正确做法是每个设备型号用独立的密钥甚至每个生产批次用不同的密钥。密钥轮换也是个麻烦事。如果设备已经出货更换密钥需要 OTA 更新 bootloader风险很高。所以密钥策略要在项目初期就规划好。6.4 Android 版本升级时的 AVB 变化从 Android 12 到 Android 13AVB 有几个变化值得注意init_boot分区引入需要单独生成 hash footervendor_boot分区的 AVB 配置更加复杂--flags参数的含义有调整对--rollback_index的检查更严格升级 Android 版本时建议先在一个开发板上完整走一遍 AVB 流程确认所有命令和配置都正确再应用到正式项目。6.5 调试 AVB 问题的实用命令最后整理几个我常用的调试命令# 查看镜像的 AVB 信息 avbtool info_image --image vbmeta.img # 验证镜像签名 avbtool verify_image --image vbmeta.img --key rsa4096_vbmeta_pub.pem # 提取公钥 avbtool extract_public_key --key rsa4096_vbmeta.pem --output pub.bin # 计算镜像哈希 avbtool calculate_vbmeta_digest --image vbmeta.img # 查看分区表 fastboot getvar partition-size:vbmeta这些命令覆盖了大部分调试场景。遇到问题时先用info_image看 vbmeta 里到底存了什么再用verify_image确认签名最后对比设备期望的值。大部分问题都能通过这三步定位。我个人在实际操作中的体会是AVB 2.0 的复杂性不在于单个命令而在于整个链条的配置一致性。密钥、盐值、rollback index、分区大小任何一个参数不一致都会导致校验失败。所以最好的做法是把所有配置集中管理用脚本生成命令用 CI 自动检查。这样即使项目换了人也不会因为配置散落各处而踩坑。
返回列表