引导加载程序概览
引导加载程序是供应商专有的映像,负责在设备上启动内核。引导加载程序会监护设备状态,负责初始化可信执行环境 (TEE) 以及绑定其信任根。引导加载程序还会在将执行工作移到内核之前先验证 boot
和 recovery
分区的完整性。
引导加载程序流程示例
下面是引导加载程序流程的示例:
-
加载并初始化内存。
-
根据启动时验证流程验证设备。
-
根据启动时验证流程验证启动分区,包括
boot
、dtbo
、init_boot
和recovery
。在执行此步骤的过程中,请检查启动映像头文件版本,并相应地解析该头文件。 -
如果使用 A/B 更新,则确定要启动的当前槽位。
-
确定是否应启动恢复模式。如需了解详情,请参阅支持 OTA 更新。
-
加载启动映像,例如
boot.img
、vendor_boot.img
、init_boot.img
及其他专有供应商启动映像。这些启动映像包含内核和 ramdisk 映像。-
将内核作为可自行执行的压缩二进制文件加载到内存中。内核将自身解压缩并开始执行到内存中。
-
将 ramdisk 和 bootconfig 部分加载到内存中以创建
initramfs
。
-
与引导加载程序相关的其他功能
下面列出了您可以实现的其他与引导加载程序相关的功能:
-
设备树叠加层 (DTO)。 借助设备树叠加层,引导加载程序可以支持不同的硬件配置。DTO 会被编译到引导加载程序所使用的设备树 blob (DTB) 中。
-
内核映像虚拟地址随机化。引导加载程序支持对加载内核映像的虚拟地址进行随机化。如需对地址进行随机化,请在内核配置中将
RANDOMIZE_BASE
设置为true
。 引导加载程序必须通过在/chosen/kaslr-seed
设备树节点中传递一个随机的 u64 值来提供熵。 -
启动时验证。借助启动时验证,引导加载程序可以确保所有已执行代码均来自可信来源。
-
启动配置。 启动配置在 Android 12 及更高版本中可用,是一种将配置详细信息从 build 和引导加载程序传递至操作系统的机制。 在 Android 12 之前,系统使用带有
androidboot
前缀的内核命令行参数。 -
无线下载 (OTA) 更新。正常使用的 Android 设备可以接收和安装系统、应用软件和时区规则的 OTA 更新。此功能会对引导加载程序实现产生影响。如需了解有关 OTA 的一般信息,请参阅 OTA 更新。如需详细了解引导加载程序的相关 OTA 实现,请参阅支持 OTA 更新。
-
版本绑定。 版本绑定将安全密钥绑定至操作系统和补丁级别版本。版本绑定可确保在旧版系统或 TEE 软件中发现漏洞的攻击者无法将设备回滚到易受攻击的版本,也无法使用在较新版本中创建的密钥。引导加载程序必须提供某些信息才能支持版本绑定。如需了解详情,请参阅 AVB 属性中的版本信息。
内核命令行
从以下位置串联内核命令行:
-
引导加载程序命令行:由引导加载程序确定的一组静态和动态参数
-
设备树:从
chosen/bootargs
节点 -
defconfig
:从CONFIG_CMDLINE
-
boot.img
:从命令行(如需了解偏移量和大小,请参阅 system/core/mkbootimg/bootimg.h)
从 Android 12 开始,对于需要传递给 Android 用户空间的 androidboot.*
参数,我们可以使用 bootconfig 而非内核命令行。
规范化启动原因
Android 9 对引导加载程序的启动原因规范进行了以下更改。
启动原因
引导加载程序使用专用的硬件和内存资源来确定设备重新启动的原因,然后将 androidboot.bootreason=<reason>
添加到用于启动设备的 Android 内核命令行中,以传达这一判定。然后,init
会转换此命令行,使其传播到 Android 属性 bootloader_boot_reason_prop
(ro.boot.bootreason
) 中。对于发布时搭载 Android 12 或更高版本(内核版本为 5.10 或更高版本)的设备中,androidboot.bootreason=<reason>
添加到了 bootconfig,而非内核命令行中。
启动原因规范
之前的 Android 版本中指定的启动原因格式如下:不使用空格,全部为小写字母,只有非常少的要求(例如报告 kernel_panic
、watchdog
、cold
/warm
/hard
),并且允许其他特殊原因。这种宽松的规范导致出现了成百上千种自定义启动原因字符串(有时毫无意义),进而造成了难以管理的情况。到目前最新的 Android 版本发布之前,引导加载程序提交的近乎无法解析或毫无意义的内容急剧增加已经为 bootloader_boot_reason_prop
造成了合规性问题。
在开发 Android 9 版本中,Android 团队发现旧的 bootloader_boot_reason_prop
中内容会急剧增加,并且无法在系统运行时重写。因此,如需对启动原因规范进行任何改进,都必须与引导加载程序开发者进行互动交流,并对现有系统进行调整。为此,Android 团队采取了以下措施:
- 与引导加载程序开发者互动交流,鼓励他们:
- 向
bootloader_boot_reason_prop
提供规范、可解析且可识别的原因。 - 向
system/core/bootstat/bootstat.cpp
kBootReasonMap
列表添加内容。
- 向
- 添加受控且可在系统运行时重写的
system_boot_reason_prop
(sys.boot.reason
) 源代码。只有少量的系统应用(如bootstat
和init
)可重写此属性,不过,所有应用都可以通过获得 sepolicy 权限来读取它。 - 将启动原因告知用户,让他们等到 userdata 分区装载完毕后再信任系统启动原因属性
system_boot_reason_prop
中的内容。
为什么要等这么久?虽然 bootloader_boot_reason_prop
在启动过程的早期阶段就已可用,但 Android 安全政策根据需要对其进行了屏蔽,因为它表示不准确、不可解析且不合规范的信息。大多数情况下,只有对启动系统有深入了解的开发者才需要访问这些信息。只有在 userdata 分区装载完毕之后,才可以通过 system_boot_reason_prop
准确可靠地提取经过优化、可解析且合乎规范的启动原因 API。具体而言:
- 在 userdata 分区装载完毕之前,
system_boot_reason_prop
将包含bootloader_boot_reason_prop
中的值。 - 在 userdata 分区装载完毕之后,可以更新
system_boot_reason_prop
,以使其符合要求或报告更准确的信息。
出于上述原因,Android 9 延长了可以正式获取启动原因之前需要等待的时间段,将其从启动时立即准确无误的状态(使用 bootloader_boot_reason_prop
)更改为仅在 userdata 分区装载完毕之后才可用(使用 system_boot_reason_prop
)。
Bootstat 逻辑依赖于信息更丰富且合规的 bootloader_boot_reason_prop
。当该属性使用可预测的格式时,能够提高所有受控重新启动和关机情况的准确性,从而优化和扩展 system_boot_reason_prop
的准确性和含义。
规范化启动原因格式
在 Android 9 中,bootloader_boot_reason_prop
的规范化启动原因格式使用以下语法:
<reason>,<subreason>,<detail>…
格式设置规则如下:
- 小写
- 无空格(可使用下划线)
- 全部为可打印字符
- 以英文逗号分隔的
reason
、subreason
,以及一个或多个detail
。- 必需的
reason
,表示设备为什么必须重新启动或关机且优先级最高的原因。 - 选用的
subreason
,表示设备为什么必须重新启动或关机的简短摘要(或重新启动设备/将设备关机的人员)。 - 一个或多个选用的
detail
值。detail
可以指向某个子系统,以协助确定是哪个具体系统导致了subreason
。您可以指定多个detail
值,这些值通常应按照重要程度排序。不过,也可以报告多个具有同等重要性的detail
值。
- 必需的
如果 bootloader_boot_reason_prop
为空值,则会被视为非法(因为这会允许其他代理在事后添加启动原因)。
原因要求
为 reason
(第一个跨度,位于终止符或英文逗号之前)指定的值必须是以下集合(分为内核原因、强原因和弱原因)之一:
- 内核集:
- "
watchdog"
"kernel_panic"
- "
- 强集:
"recovery"
"bootloader"
- 弱集:
"cold"
:通常表示完全重置所有设备,包括内存。"hard"
:通常表示硬件重置了状态,并且ramoops
应保留持久性内容。"warm"
:通常表示内存和设备保持某种状态,并且ramoops
(请参阅内核中的pstore
驱动程序)后备存储空间包含持久性内容。"shutdown"
"reboot"
:通常意味着ramoops
状态和硬件状态未知。该值是与cold
、hard
和warm
一样的通用值,可提供关于设备重置深度的提示。
引导加载程序必须提供内核集或弱集 reason
,强烈建议引导加载程序提供 subreason
(如果可以确定的话)。例如,电源键长按(无论是否有 ramoops
备份)的启动原因为 "reboot,longkey"
。
第一个跨度 reason
不能是任何 subreason
或 detail
的组成部分。不过,由于用户空间无法产生内核集原因,因此可能会在弱集原因之后重复使用 "watchdog"
以及源代码的详细信息(例如 "reboot,watchdog,service_manager_unresponsive"
或 "reboot,software,watchdog"
)。
启动原因应该无需专家级内部知识即可解读,并且(或者)应该能让人看懂并提供直观报告。示例:"shutdown,vbxd"
(糟糕)、"shutdown,uv"
(较好)、"shutdown,undervoltage"
(首选)。
“原因-子原因”组合
Android 保留了一组 reason
-subreason
组合,在正常使用情况下不应过量使用这些组合;不过,如果组合能准确反映相关状况,则可根据具体情况加以使用。保留组合的示例包括:
"reboot,userrequested"
"shutdown,userrequested"
"shutdown,thermal"
(来自thermald
)"shutdown,battery"
"shutdown,battery,thermal"
(来自BatteryStatsService
)"reboot,adb"
"reboot,shell"
"reboot,bootloader"
"reboot,recovery"
如需了解详情,请参阅 system/core/bootstat/bootstat.cpp
中的 kBootReasonMap
以及 Android 源代码库中的关联 git 变更日志记录。
报告启动原因
所有启动原因(无论是来自引导加载程序还是记录在规范化启动原因中)都必须记录在 system/core/bootstat/bootstat.cpp
的 kBootReasonMap
部分中。kBootReasonMap
列表包含各种合规原因和不合规的旧版原因。引导加载程序开发者应在此处仅登记新的合规原因(除非产品已发货且无法更改,否则不应登记不合规的原因)。
注意:虽然 system/core/bootstat/bootstat.cpp
包含一个 kBootReasonMap
部分,其中列出了大量旧版原因,但这些原因的存在并不意味着 reason
字符串已获准使用。该列表的一个子集内列出了合规原因;随着引导加载程序开发者不断登记更多合规原因并加以说明,这个子集预计将不断增大。
强烈建议使用 system/core/bootstat/bootstat.cpp
中的现有合规条目,如要使用不合规字符串,应先对其加以限制。请参阅以下指导原则:
- 允许从引导加载程序中报告
"kernel_panic"
,因为bootstat
或许能检查kernel_panic signatures
的ramoops
,以便将子原因优化为规范的system_boot_reason_prop
。 - 不允许从引导加载程序中以
kBootReasonMap
(如"panic")
)的形式报告不合规的字符串,因为这最终将导致无法优化reason
。
例如,如果 kBootReasonMap
包含 "wdog_bark"
,则引导加载程序开发者应采取以下措施:
- 更改为
"watchdog,bark"
,并将其添加到kBootReasonMap
中的列表内。 - 考虑
"bark"
对于不熟悉该技术的人来说意味着什么,并确定是否存在更有意义的subreason
。
验证启动原因合规性
目前,对于引导加载程序可能提供的所有启动原因,Android 没有提供能够准确触发或检查这些原因的主动 CTS 测试;合作伙伴仍然可以尝试运行被动测试来确定兼容性。
因此,要实现引导加载程序合规性,引导加载程序开发者需要自愿遵循上述规则和准则的精神。我们会敦促此类开发者为 AOSP(特别是 system/core/bootstat/bootstat.cpp
)做贡献,并将这个机会作为一个讨论启动原因问题的论坛。
启动映像标头
Android 9 在启动映像头文件中引入了一个版本字段,更新头文件的同时还可保持向后兼容性。引导加载程序必须检查该头文件版本字段并对头文件进行解析。如果设备:
- 发布时搭载 Android 13,则可以使用启动头文件版本 3 或 4。对于支持通用内核映像 (GKI) 架构的设备,版本 4 是主要启动映像,并且启动头文件中的 os_version 字段必须为零。设备引导加载程序应从 Android 启动时验证 (AVB) 属性中获取版本信息。
- 发布时搭载 Android 12,则可以使用启动头文件版本 3 或 4。对于支持通用内核映像 (GKI) 架构的设备,版本 4 是主要启动映像。
- 发布时搭载 Android 11,则可以使用启动头文件版本 3。对于支持通用内核映像 (GKI) 架构的设备,必须将此版本用于主要启动映像。
- 搭载的是 Android 10,则必须使用启动头文件版本 2。
- 搭载的是 Android 9,则必须使用启动头文件版本 1。
- 搭载的是 Android 8 及更低版本,则将其视为使用启动映像头文件版本 0。
对于所有搭载 Android 9 或更高版本的设备,供应商测试套件 (VTS) 会检查 boot/recovery
映像的格式,以确保启动映像头文件使用的版本正确无误。如需查看有关当前支持的所有启动映像头文件和供应商启动映像头文件的 AOSP 详细信息,请参阅 system/tools/mkbootimg/include/bootimg/bootimg.h。
实现启动映像头文件版本编号
mkbootimg
工具接受以下参数。
参数 | 说明 |
---|---|
header_version | 设置启动映像头文件版本。如果头文件版本为:
|
recovery_dtbo | 适用于使用 DTB 的架构。指定 DTBO 恢复映像的路径。对于无需恢复映像的 A/B 设备,此参数为可选参数。如果非 A/B 设备使用的 header_version 为:
|
recovery_acpio | 适用于使用 ACPI(而非 DTB)的架构。指定 ACPIO 恢复映像的路径。对于不需要恢复映像的 A/B 设备,此参数为可选参数。如果非 A/B 设备使用的 header_version 为:
|
dtb | 启动/恢复映像中 DTB 映像的路径。 |
dtb_offset | 在添加到 base 参数时,系统会提供最终设备树的实际加载地址。例如,如果 base 参数为 0x10000000 且 dtb_offset 参数为 0x01000000 ,启动映像头文件中的 dtb_addr_field 将填充为 0x11000000 。 |
设备 BoardConfig.mk
使用 BOARD_MKBOOTIMG_ARGS
配置将 header version
添加到 mkbootimg
特定于主板的其他参数。例如:
BOARD_MKBOOTIMG_ARGS := --ramdisk_offset $(BOARD_RAMDISK_OFFSET) --tags_offset $(BOARD_KERNEL_TAGS_OFFSET) --header_version $(BOARD_BOOTIMG_HEADER_VERSION)
Android 构建系统使用 BoardConfig
变量 BOARD_PREBUILT_DTBOIMAGE
在创建恢复映像期间设置 mkbootimg
工具的 recovery_dtbo
参数。如需详细了解 Android 开源项目 (AOSP) 的变化,请查看与启动映像头文件版本编号相关的更改列表。
启动映像头文件版本 4
Android 12 在启动映像头文件版本 4 中提供了一个 boot_signature
,可用于检查内核和 ramdisk 的完整性。该检查在 VtsSecurityAvbTest 中完成,对于使用 GKI 架构的设备来说是必须执行的。不过,特定于设备的启动时验证流程中不涉及 boot_signature
,它仅用于 VTS 中。如需了解详情,请参阅 GKI boot.img 开发板配置和 GKI 启动时验证设置。
供应商启动映像头文件版本 4 支持多个供应商 ramdisk fragment。
启动映像头文件版本 4 采用以下格式。
struct boot_img_hdr
{
#define BOOT_MAGIC_SIZE 8
uint8_t magic[BOOT_MAGIC_SIZE];
uint32_t kernel_size; /* size in bytes */
uint32_t ramdisk_size; /* size in bytes */
uint32_t os_version;
uint32_t header_size; /* size of boot image header in bytes */
uint32_t reserved[4];
uint32_t header_version; /* offset remains constant for version check */
#define BOOT_ARGS_SIZE 512
#define BOOT_EXTRA_ARGS_SIZE 1024
uint8_t cmdline[BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE];
uint32_t signature_size; /* size in bytes */
};
启动映像头文件版本 3
Android 11 将启动映像头文件更新为版本 3,该版本移除了以下数据:
-
第二阶段引导加载程序。启动映像头文件中不再显示
second_size
和second_addr
字段。具有第二阶段引导加载程序的设备必须将相应引导加载程序存储在自己的分区中。 -
恢复映像。指定恢复映像时需满足的要求已被废弃,且启动映像头文件中不再显示
recovery_dtbo_size
、recovery_dtbo_offset
、recovery_acpio_size
和recovery_acpio_offset
字段。-
A/B 设备使用了更新和恢复方案,因此无需指定 DTBO 或 ACPIO 恢复映像。
-
如果非 A/B 设备需要指定恢复映像(DTBO 或 ACPIO),则应使用启动映像头文件版本 1 或 2。
-
-
设备树 blob (DTB)。DTB 存储在供应商启动分区中,因此启动映像头文件中不再显示
dtb_size
和dtb_addr
字段(但供应商启动映像头文件中会显示)。
设备可以通过启动映像头文件版本 3 来满足通用内核映像 (GKI) 架构的要求,该架构统一了核心内核,并将启动所需的供应商模块移至 vendor_boot
分区(这意味着启动映像仅包含 GKI 组件)。如果设备:
-
使用 GKI(需要 android-4.19 或 android-5.4 内核)但不使用 A/B 更新机制,则可以使用启动映像版本 3(适用于启动映像)和启动映像版本 2(适用于恢复映像)指定恢复映像。
-
既不使用 GKI 也不使用 A/B 更新机制,则可以使用启动映像版本 1 指定恢复映像或将启动映像版本 2 同时用于启动映像和恢复映像。
启动映像头文件版本 3 采用以下格式。
struct boot_img_hdr
{
#define BOOT_MAGIC_SIZE 8
uint8_t magic[BOOT_MAGIC_SIZE];
uint32_t kernel_size; /* size in bytes */
uint32_t ramdisk_size; /* size in bytes */
uint32_t os_version;
uint32_t header_size; /* size of boot image header in bytes */
uint32_t reserved[4];
uint32_t header_version; /* offset remains constant for version check */
#define BOOT_ARGS_SIZE 512
#define BOOT_EXTRA_ARGS_SIZE 1024
uint8_t cmdline[BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE];
};
启动映像头文件版本 2
Android 10 已将启动映像头文件更新为版本 2,且添加了 DTB 恢复映像的信息部分(映像大小和实际加载地址)。
启动映像头文件版本 2 采用以下格式。
struct boot_img_hdr
{
uint8_t magic[BOOT_MAGIC_SIZE];
uint32_t kernel_size; /* size in bytes */
uint32_t kernel_addr; /* physical load addr */
uint32_t ramdisk_size; /* size in bytes */
uint32_t ramdisk_addr; /* physical load addr */
uint32_t second_size; /* size in bytes */
uint32_t second_addr; /* physical load addr */
uint32_t tags_addr; /* physical addr for kernel tags */
uint32_t page_size; /* flash page size we assume */
uint32_t header_version;
uint32_t os_version;
uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
uint8_t cmdline[BOOT_ARGS_SIZE];
uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
uint32_t recovery_[dtbo|acpio]_size; /* size of recovery image */
uint64_t recovery_[dtbo|acpio]_offset; /* offset in boot image */
uint32_t header_size; /* size of boot image header in bytes */
uint32_t dtb_size; /* size of dtb image */
uint64_t dtb_addr; /* physical load address */
};
启动映像头文件版本 1
Android 9 会将启动映像头文件的 unused
字段转换为头文件版本字段。搭载 Android 9 的设备必须使用头文件版本为 1 或更高(由 VTS 进行验证)的启动映像头文件。
启动映像头文件版本 1 采用以下格式。
struct boot_img_hdr
{
uint8_t magic[BOOT_MAGIC_SIZE];
uint32_t kernel_size; /* size in bytes */
uint32_t kernel_addr; /* physical load addr */
uint32_t ramdisk_size; /* size in bytes */
uint32_t ramdisk_addr; /* physical load addr */
uint32_t second_size; /* size in bytes */
uint32_t second_addr; /* physical load addr */
uint32_t tags_addr; /* physical addr for kernel tags */
uint32_t page_size; /* flash page size we assume */
uint32_t header_version;
uint32_t os_version;
uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
uint8_t cmdline[BOOT_ARGS_SIZE];
uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
uint32_t recovery_[dtbo|acpio]_size; /* size of recovery image */
uint64_t recovery_[dtbo|acpio]_offset; /* offset in boot image */
uint32_t header_size; /* size of boot image header in bytes */
};
非 A/B 设备可以指定 DTB/ACPI 叠加层恢复映像,以减少无线下载 (OTA) 更新失败的次数。(A/B 设备不会出现这种问题,且无需指定叠加层映像。)您可以指定 DTBO 映像或 ACPIO 映像,但不能同时指定二者(因为它们所适用的架构有所不同)。要正确配置启动映像头文件:
-
使用 DTBO 恢复映像时,请添加
recovery_dtbo_size
和recovery_dtbo_offset
字段(不要添加recovery_acpio_size
和recovery_acpio_offset
字段)。 -
使用 ACPIO 恢复映像时,请添加
recovery_acpio_size
和recovery_acpio_offset
字段(不要添加recovery_dtbo_size
和recovery_dtbo_offset
字段)。
header_size
字段包含启动映像头文件大小。如果启动映像头文件版本为 1,那么除了 kernel
、ramdisk
和 second sections
之外,id
字段还包含启动映像 recovery_[dtbo|acpio]
部分的 SHA-1 摘要。如需详细了解 recovery_[dtbo|acpio]_size
和 recovery_[dtbo|acpio]_offset
字段,请参阅恢复映像。
旧版启动映像头文件版本 0
如果设备搭载 Android 9 之前的版本且使用旧版启动映像头文件,则将其视为使用启动映像头文件版本 0。
struct boot_img_hdr
{
uint8_t magic[BOOT_MAGIC_SIZE];
uint32_t kernel_size; /* size in bytes */
uint32_t kernel_addr; /* physical load addr */
uint32_t ramdisk_size; /* size in bytes */
uint32_t ramdisk_addr; /* physical load addr */
uint32_t second_size; /* size in bytes */
uint32_t second_addr; /* physical load addr */
uint32_t tags_addr; /* physical addr for kernel tags */
uint32_t page_size; /* flash page size we assume */
uint32_t unused;
uint32_t os_version;
uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
uint8_t cmdline[BOOT_ARGS_SIZE];
uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
};
在 Android 12 中实现 Bootconfig
在 Android 12 中,bootconfig 功能取代了 Android 11 及更低版本中使用的androidboot.*
内核命令行选项。 bootconfig 功能是一种将配置详细信息从构建和引导加载程序传递到 Android 12 的机制。
此功能提供了一种将 Android 用户空间的配置参数与内核的配置参数分开的方法。将冗长的androidboot.*
内核参数移动到 bootconfig 文件会在内核 cmdline 上创建空间,并使其可用于将来的扩展。
内核和 Android 用户空间都必须支持bootconfig
。
- 具有此支持的第一个版本:Android 12
- 具有此支持的第一个内核版本:12-5.4.xx 内核
为使用 12-5.10.xx 内核版本启动的新设备实施 bootconfig 功能。如果您正在升级设备,则无需实施它。
示例和来源
当您查看本节中的示例和源代码时,请注意bootconfig
代码的格式与 Android 11 及更低版本中使用的内核 cmdline 的格式略有不同。但是,以下区别对您的使用很重要:
- 参数必须由换行符转义序列
\n
分隔,而不是空格。
引导加载程序示例
有关引导加载程序示例,请参阅 Cuttlefish U-boot 参考引导加载程序实现。下面列出了参考中的两个提交。第一个将引导头版本支持升级到最新版本。在示例中,第一次提交更新(或升级)版本支持到下一个版本,v4。第二个做两件事;它添加了 bootconfig 处理,并演示了在运行时添加参数:
- 将引导头版本支持升级到最新的 v4 版本。
- 添加 bootconfig 处理。
构建示例
有关显示mkbootimg
更改以使用供应商引导标头 v4 构建vendor_boot.img
的构建示例,请参阅mkbootimg changes for bootconfig 。请参阅 Cuttlefish 更改以执行以下操作:
- 使用(或升级到)供应商引导头版本 v4 。
- 将bootconfig to the kernel cmdline and move selected parameters to bootconfig 。
执行
合作伙伴必须为其引导加载程序添加支持,并将其构建时androidboot.*
参数从内核 cmdline 移至 bootconfig 文件。实施此更改的最佳方法是逐步进行;有关遵循增量过程的信息,请参阅增量实施和验证部分。
如果您有在 /proc/cmdline 文件中搜索androidboot.*
参数的更改,请将它们指向 /proc/bootconfig 文件。 ro.boot.*
属性使用新的bootconfig
值设置,因此您无需使用这些属性对代码进行更改。
构建更改
首先,将您的引导头版本升级到版本 4:
- BOARD_BOOT_HEADER_VERSION := 3
+ BOARD_BOOT_HEADER_VERSION := 4
添加bootconfig
内核命令行参数。这使得内核寻找 bootconfig 部分:
BOARD_KERNEL_CMDLINE += bootconfig
bootconfig 参数是从BOARD_BOOTCONFIG
变量中的参数创建的,就像内核 cmdline 是从BOARD\_KERNEL\_CMDLINE
创建的。
任何androidboot.*
参数都可以按原样移动,类似于以下内容:
- BOARD_KERNEL_CMDLINE += androidboot..selinux=enforcing
+ BOARD_BOOTCONFIG += androidboot..selinux=enforcing
引导加载程序更改
引导加载程序在跳转到内核之前设置initramfs
。内核引导配置搜索 bootconfig 部分,并寻找它位于initramfs,
以及预期的预告片。
引导加载程序从供应商引导映像头中获取vendor_boot.img
布局信息。
图 1. Android 12 bootconfig 内存分配
引导加载程序在内存中创建 bootconfig 部分。 bootconfig 部分包含以下内容的内存分配:
- 参数
- 4 B尺寸
parameters size
- 4 B大小
parameters checksum
- 12 B bootconfig 魔法字符串 (
#BOOTCONFIG\n
)
参数来自两个来源:构建时已知的参数和构建时未知的参数。必须添加未知参数。
构建时已知的参数被打包到 bootconfig 部分中vendor_boot
映像的末尾。该部分的大小(以字节为单位)存储在供应商引导标头字段vendor_bootconfig_size
中。
在构建时不知道的参数仅在引导加载程序的运行时才知道。在应用 bootconfig 预告片之前,这些必须添加到 bootconfig 参数部分的末尾。
如果在应用 bootconfig 预告片后需要添加任何参数,请覆盖预告片并重新应用它。
增量实施和验证
按照本节中给出的过程逐步实施 bootconfig 功能。在添加 bootconfig 参数时,保持内核 cmdline 参数不变。
重要提示:如果 bootconfig 函数加载失败,并且androidboot.*
参数不在内核 cmdline 中,则设备可能无法启动。其中许多参数对 Android 启动非常重要。因此,Google建议您逐步实现此功能,以确保引导加载程序和构建更改在删除内核 cmdline 参数之前有效。
这些是带有验证的增量实施的步骤:
- 进行引导加载程序和构建更改,然后执行以下操作:
- 使用
BOARD_BOOTCONFIG
变量添加新的 bootconfig 参数。 - 保持内核 cmdline 参数原样,以便设备可以继续正确启动。这使得调试和验证更加容易。
- 使用
- 通过检查
/proc/bootconfig
的内容来验证您的工作。验证您是否在设备启动后看到了新添加的参数。 - 使用
BOARD_BOOTCONFIG
变量和引导加载程序将androidboot.*
参数从内核 cmdline 移动到 bootconfig。 - 验证每个参数是否存在于
/proc/bootconfig
中并且它们不在/proc/cmdline
中。如果您可以验证这一点,那么您的实施是成功的。
OTA升级降级注意事项
当您管理不同版本的 Android 或不同内核版本之间的 OTA 升级和降级时,应特别小心。
Android 12 是第一个支持 bootconfig 的版本。如果降级到之前的任何版本,必须使用内核命令行参数而不是 bootconfig。
内核版本 12-5.4 及更高版本支持 bootconfig。如果降级到之前的任何版本(包括 11-5.4),必须使用内核命令行参数。
从 Android 11 及更早版本升级到 Android 12 及更高版本可以继续使用内核 cmdline 参数。升级内核版本也是如此。
故障排除
执行验证步骤时,如果在/proc/bootconfig
中没有看到预期的参数,请检查logcat
中的内核日志。如果内核支持,则 bootconfig 始终存在一个日志条目。
示例日志输出
$ adb logcat | grep bootconfig
02-24 17:00:07.610 0 0 I Load bootconfig: 128 bytes 9 nodes
如果您看到返回的错误日志,则说明加载 bootconfig 时出现问题。要查看不同的错误类型,请查看init/main.c 。
DTB 图像
Android 实现可以包含供引导加载程序使用的设备树 blob (DTB) 映像。 DTB 图像的位置(以及用于指定 DTB 图像参数的选项)因 Android 版本而异。
-
在 Android 11 中,使用通用内核映像 (GKI)的设备必须支持供应商引导分区,其中包括从引导分区重定位的所有供应商特定信息。因为 DTB 映像包含特定于供应商的数据,所以它现在是供应商引导分区的一部分。要指定 DTB 映像参数,请参阅供应商引导标头。
-
在 Android 10 中,设备可以在启动分区中包含 DTB 映像。要指定 DTB 映像参数,请参阅在启动映像中包含 DTB 映像。
-
在 Android 9 及更低版本中,DTB 映像可以存在于自己的分区中,也可以附加到内核
image.gz
以创建内核 + DTB 映像(然后将其传递给mkbootimg
以创建boot.img
)。
DTB 图像格式
在 Android 10 及更高版本中,DTB 图像必须使用以下格式之一:
-
DT blob 一个接一个地连接起来。引导加载程序使用每个 FDT 标头中的
totalsize
字段来读取和解析相应的 blob。 -
DTB/DTBO 分区。引导加载程序通过检查可以保存条目的硬件识别信息的
dt_table_entry
结构(包含id
、rev
和custom
字段)来选择正确的 DT blob 有一种有效的方法。有关详细信息,请参阅DTB/DTBO 分区。
在启动映像中包含 DTB 映像
运行 Android 10 的设备可以在启动映像中包含 DTB 映像。这消除了 Android 支持将 DTB 图像附加到内核中的image.gz
的脚本的需要,并允许使用供应商测试套件 (VTS)测试来验证(和标准化)DTB 放置。
此外,对于非 A/B 设备,将 DTB 作为恢复映像的一部分而不是在单独的分区中更安全,以防止因 OTA 中断而导致的问题。在 OTA 期间,如果在 DTB 分区更新后(但在完成完整更新之前)出现问题,设备会尝试启动进入恢复模式以完成 OTA;但是,由于 DTB 分区已更新,因此恢复映像(尚未更新)可能会出现不匹配。将 DTB 映像作为引导映像格式的一部分通过使恢复映像自给自足(即,它不依赖于另一个分区)来防止此类问题。
引导映像结构
运行 Android 10 的设备可以包含使用以下启动映像结构的 DTB 映像。
引导映像部分 | 页数 |
---|---|
引导标题(1 页) | 1 |
内核(l 页) | l = ( kernel_size + page_size - 1) / page_size |
Ramdisk (m 页) | m = ( ramdisk_size + page_size - 1) / page_size |
第二阶段引导加载程序(n 页) | n = ( second_size + page_size - 1) / page_size |
恢复 DTBO(o 页) | o = ( recovery_dtbo_size + page_size - 1) / page_size |
DTB(p 页) | p = ( dtb_size + page_size - 1) / page_size |
DTB 图像路径
对于运行 Android 10 的设备,您可以使用mkbootimg.py
工具和以下参数来指定 DTB 映像的路径。
争论 | 描述 |
---|---|
dtb | 要包含在引导/恢复映像中的 DTB 映像的路径。 |
dtb_offset | 添加到base 参数时,提供最终设备树的物理加载地址。例如,如果base 参数是0x10000000 并且dtb_offset 参数是0x01000000 ,则引导映像头中的dtb_addr_field 填充为0x11000000 。 |
必须使用板配置变量BOARD_PREBUILT_DTBIMAGE_DIR
来指定 DTB 图像的路径。如果目录BOARD_PREBUILT_DTBIMAGE_DIR
中存在多个扩展名为*.dtb
的文件,Android 构建系统将连接这些文件以创建用于创建启动映像的最终 DTB 映像。
要使用BOARD_PREBUILT_DTBIMAGE_DIR
指定的目录中的 DTB 图像将参数dtb
传递给mkbootimg.py
,必须将板配置变量BOARD_INCLUDE_DTB_IN_BOOTIMG
设置为true
。例如:
BOARD_INCLUDE_DTB_IN_BOOTIMG := true
您可以将dtb_offset
参数附加到BOARD_MKBOOTIMG_ARGS
板配置变量以及其他偏移量和标头版本。例如:
BOARD_MKBOOTIMG_ARGS := --ramdisk_offset $(BOARD_RAMDISK_OFFSET) --dtb_offset $(BOARD_DTB_OFFSET) --tags_offset $(BOARD_KERNEL_TAGS_OFFSET) --header_version $(BOARD_BOOTIMG_HEADER_VERSION)
引导加载程序支持
要使 VTS 在运行 Android 10 的设备上成功运行,引导加载程序必须支持更新的引导映像,并且必须添加androidboot.dtb_idx
内核命令行参数以指示所选设备树 (DT) 的索引。您只能指定一 (1) 个索引。例如,参数androidboot.dtb_idx=N
将N
报告为引导加载程序从引导映像中存在的 DTB 集合中选择的设备树的从零开始的索引。
锁定/解锁引导加载程序
默认情况下,大多数 Android 设备都附带锁定的引导加载程序,这意味着用户无法刷新引导加载程序或设备分区。如果需要,您(和启用了开发人员选项的设备用户)可以解锁引导加载程序以刷新新映像。
解锁引导加载程序
要解锁引导加载程序并启用要刷新的分区,请在设备上运行fastboot flashing unlock
命令。设置后,解锁模式在重新启动后仍然存在。
除非get_unlock_ability
设置为1
,否则设备应拒绝fastboot flashing unlock
命令。如果设置为0
,用户需要启动到主屏幕,打开设置 > 系统 > 开发人员选项菜单并启用OEM 解锁选项(将get_unlock_ability
设置为1
)。设置后,此模式会在重启和恢复出厂设置后持续存在。
当发送fastboot flashing unlock
命令时,设备应提示用户警告他们可能会遇到非官方图像的问题。用户确认警告后,设备应执行出厂数据重置以防止未经授权的数据访问。即使无法正确重新格式化,引导加载程序也应重置设备。只有在重置后才能设置持久标志,以便重新刷新设备。
所有尚未被覆盖的 RAM 都应在快速启动fastboot flashing unlock
过程中重置。此措施可防止读取上次启动时剩余的 RAM 内容的攻击。同样,未锁定的设备应在每次启动时清除 RAM(除非这会造成不可接受的延迟),但应保留用于内核ramoops的区域。
锁定引导加载程序
要锁定引导加载程序并重置设备,请在设备上运行fastboot flashing lock
命令。用于零售的设备应在锁定状态下发货( get_unlock_ability
返回0
),以确保攻击者无法通过安装新系统或启动映像来破坏设备。
设置锁定/解锁属性
ro.oem_unlock_supported
属性应在构建时根据设备是否支持闪烁解锁进行设置。
- 如果设备支持刷机解锁,将
ro.oem_unlock_supported
设置为1
。 - 如果设备不支持刷机解锁,将
ro.oem_unlock_supported
设置为0
。
如果设备支持闪存解锁,则引导加载程序应通过将内核命令行变量androidboot.flash.locked
设置为1
(如果锁定)或设置为0
(如果解锁)来指示锁定状态。此变量必须在 bootconfig 中设置,而不是在 Android 12 中的内核命令行中设置。
对于支持dm-verity 的设备,使用ro.boot.verifiedbootstate
将ro.boot.flash.locked
的值设置为0
;如果已验证的引导状态为橙色,这将解锁引导加载程序。
保护关键部分
设备应该支持关键部分的锁定和解锁,关键部分被定义为将设备引导到引导加载程序所需的任何内容。这些部分可能包括保险丝、传感器集线器的虚拟分区、第一阶段引导加载程序等。要锁定关键部分,您必须使用一种机制来防止在设备上运行的代码(内核、恢复映像、OTA 代码等)故意修改任何关键部分。如果设备处于锁定临界状态,OTA 应该无法更新临界区。
从锁定状态转换到解锁状态应该需要与设备进行物理交互。这种交互类似于运行fastboot flashing unlock
命令的效果,但需要用户按下设备上的物理按钮。设备不应允许在没有物理交互的情况下以编程方式从lock critical
状态转换为unlock critical
关键状态,并且设备不应以unlock critical
状态运送。