本编文章的内容主要是分析 boot/recovery 的启动过程,其中的 boot 就是 android 的kernel, 是整个 android 系统的核心。本文的分析是紧接着 aboot_init 的分析内容的,只是因为其重要性才单独成为一章。
recovery boot & normal boot
recovery
和 normal
使用的是同一套加载流程,所以放在一起分析。在开始分析加载过程之前,先从aboot_init
进入加载过程前的代码入手:
if (target_is_emmc_boot())
{
if(emmc_recovery_init())
dprintf(ALWAYS,"error in emmc_recovery_init\n");
if(target_use_signed_kernel())
{
if((device.is_unlocked) || (device.is_tampered))
{
#ifdef TZ_TAMPER_FUSE
set_tamper_fuse_cmd();
#endif
#if USE_PCOM_SECBOOT
set_tamper_flag(device.is_tampered);
#endif
}
}
boot_linux_from_mmc();
}
由于 msm8916
并没有定义 TZ_TAMPER_FUSE
宏和 USE_PCOM_SECBOOT
宏,所以进入boot_linux_from_mmc
前只有emmc_recovery_init
步骤需要进行。emmc_recovery_init
函数位于target/msm8916/init.c
文件中,只是_emmc_recovery_init
函数的转接层,本身没有任何功能。_emmc_recovery_init
函数位于app/aboot/recovery.c
文件中。_emmc_recovery_init
的代码逻辑并不复杂,但是具有明显的分析,所以依据代码逻辑分块分为以下 2 个部分来分析:
加载 recovery 命令这部分的主要功能是从 emmc
中获取指定的 recovery
预处理命令,为后面的解析和处理提供条件。
int _emmc_recovery_init(void)
{
int update_status = 0;
struct recovery_message *msg;
uint32_t block_size = 0;
block_size = mmc_get_device_blocksize();
// get recovery message
msg = (struct recovery_message *)memalign(CACHE_LINE, block_size);
ASSERT(msg);
if(emmc_get_recovery_msg(msg))
{
if(msg)
free(msg);
return -1;
}
msg->command[sizeof(msg->command)-1] = '\0'; //Ensure termination
if (msg->command[0] != 0 && msg->command[0] != 255) {
dprintf(INFO,"Recovery command: %d %s\n",
sizeof(msg->command), msg->command);
}
//...
return 0;
}
整个加载 recovery 命令的过程比较重要的结构是 recovery_message
, 用作存储读取到的 recovery
命令,它的结构如下:
/* Recovery Message */
struct recovery_message {
char command[32];
char status[32];
char recovery[1024];
};
通过 emmc_get_recovery_msg
从 misc
分区读取到 recovery
命令就通过这个结构体向后传递。
处理 recovery 命令这个部分主要的功能是处理一些需要在启动前处理的 recovery
命令:
int _emmc_recovery_init(void)
{
//..
if (!strcmp(msg->command, "boot-recovery")) {
boot_into_recovery = 1;
}
if (!strcmp("update-radio",msg->command))
{
/* We're now here due to radio update, so check for update status */
int ret = get_boot_info_apps(UPDATE_STATUS, (unsigned int *) &update_status);
if(!ret && (update_status & 0x01))
{
dprintf(INFO,"radio update success\n");
strlcpy(msg->status, "OKAY", sizeof(msg->status));
}
else
{
dprintf(INFO,"radio update failed\n");
strlcpy(msg->status, "failed-update", sizeof(msg->status));
}
boot_into_recovery = 1; // Boot in recovery mode
}
if (!strcmp("reset-device-info",msg->command))
{
reset_device_info();
}
if (!strcmp("root-detect",msg->command))
{
set_device_root();
}
else
goto out;// do nothing
strlcpy(msg->command, "", sizeof(msg->command)); // clearing recovery command
emmc_set_recovery_msg(msg); // send recovery message
out:
if(msg)
free(msg);
return 0;
}
boot-recovery
这条命令处理逻辑是最简单的,只是将全局变量 boot_into_recovery
设置为 1, 这个变量在后面的加载部分会用到。update-readio
这条命令是检查基带升级是否成功,根据状态设置recovery_message.status
, 然后设置boot_into_recovery
为 1。reset-device-info
根据 abootinit init 部分 的分析,我们知道 deviceinfo 的数据结构,这里是重设device_info.is_tampered
为 0, 并写入emmc
中。root-detect
这条命令正好和reset-device-info
相反,这里会设置device_info.is_tampered
为 1, 也就是说device_info.is_tampered
是手机是否root
的标志位。
当以上 4 条命令任意一条执行后,就会清理掉 recovery_message
并重新协会 misc
分区。
需要预处理的 recovery_message
处理完成后,就到了使用 boot_linux_from_mmc
加载系统的部分。boot_linux_from_mmc
函数位于app/aboot/aboot.c
文件中,代码流程较长,同样使用分块的方法来分析:
启动模式
检测 读取 boot_img_hdr
缓存并验证镜像 解压释放 kernel
和ramdisk
解压释放device tree
调用boot_linux
启动系统
启动模式
检测
启动模式
检测这一部分代码的作用是检测当前的启动将要进入的模式,然后进行相应设置。msm8916
检测以下 3 种启动模式:
boot mode |
ffbm(fast factory boot mode) |
normal mode |
recovery mode |
检测部分的代码如下:
int boot_linux_from_mmc(void)
{
struct boot_img_hdr *hdr = (void*) buf;
struct boot_img_hdr *uhdr;
unsigned offset = 0;
int rcode;
unsigned long long ptn = 0;
int index = INVALID_PTN;
unsigned char *image_addr = 0;
unsigned kernel_actual;
unsigned ramdisk_actual;
unsigned imagesize_actual;
unsigned second_actual = 0;
unsigned int dtb_size = 0;
unsigned int out_len = 0;
unsigned int out_avai_len = 0;
unsigned char *out_addr = NULL;
uint32_t dtb_offset = 0;
unsigned char *kernel_start_addr = NULL;
unsigned int kernel_size = 0;
int rc;
#if DEVICE_TREE
struct dt_table *table;
struct dt_entry dt_entry;
unsigned dt_table_offset;
uint32_t dt_actual;
uint32_t dt_hdr_size;
unsigned char *best_match_dt_addr = NULL;
#endif
struct kernel64_hdr *kptr = NULL;
if (check_format_bit())
boot_into_recovery = 1;
if (!boot_into_recovery) {
memset(ffbm_mode_string, '\0', sizeof(ffbm_mode_string));
rcode = get_ffbm(ffbm_mode_string, sizeof(ffbm_mode_string));
if (rcode <= 0) {
boot_into_ffbm = false;
if (rcode < 0)
dprintf(CRITICAL,"failed to get ffbm cookie");
} else
boot_into_ffbm = true;
} else
boot_into_ffbm = false;
uhdr = (struct boot_img_hdr *)EMMC_BOOT_IMG_HEADER_ADDR;
if (!memcmp(uhdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
dprintf(INFO, "Unified boot method!\n");
hdr = uhdr;
goto unified_boot;
}
//...
}
通过 check_format_bit
检查是否进入 recovery
, check_format_bit
的代码比较简单,其代码如下:
static bool check_format_bit()
{
bool ret = false;
int index;
uint64_t offset;
struct boot_selection_info *in = NULL;
char *buf = NULL;
index = partition_get_index("bootselect");
if (index == INVALID_PTN)
{
dprintf(INFO, "Unable to locate /bootselect partition\n");
return ret;
}
offset = partition_get_offset(index);
if(!offset)
{
dprintf(INFO, "partition /bootselect doesn't exist\n");
return ret;
}
buf = (char *) memalign(CACHE_LINE, ROUNDUP(page_size, CACHE_LINE));
ASSERT(buf);
if (mmc_read(offset, (uint32_t *)buf, page_size))
{
dprintf(INFO, "mmc read failure /bootselect %d\n", page_size);
free(buf);
return ret;
}
in = (struct boot_selection_info *) buf;
if ((in->signature == BOOTSELECT_SIGNATURE) &&
(in->version == BOOTSELECT_VERSION)) {
if ((in->state_info & BOOTSELECT_FORMAT) &&
!(in->state_info & BOOTSELECT_FACTORY))
ret = true;
} else {
dprintf(CRITICAL, "Signature: 0x%08x or version: 0x%08x mismatched of /bootselect\n",
in->signature, in->version);
ASSERT(0);
}
free(buf);
return ret;
}
check_format_bit
唯一的作用就是读取 bootselect
分区的信息,然后存放到 boot_selection_info
结构体,其结构如下:
/* bootselect partition format structure */
struct boot_selection_info {
uint32_t signature; // Contains value BOOTSELECT_SIGNATURE defined above
uint32_t version;
uint32_t boot_partition_selection; // Decodes which partitions to boot: 0-Windows,1-Android
uint32_t state_info; // Contains factory and format bit as definded above
};
msm8916
中 boot_selection_info
满足以下条件则或进入 recovery
:
if (in->signature == ('B' | ('S' << 8) | ('e' << 16) | ('l' << 24)) &&
(in->version == 0x00010001)) {
if ((in->state_info & (1 << 31)) &&
!(in->state_info & (1 << 30)))
boot_in_recovery = true;
}
如果满足条件,则设置全局标志位 boot_into_recovery
为 true。
通过 get_ffbm
检查是否进入 ffbm
1 模式,get_ffbm
所完成的任务很简单,只是读取misc
分区,并判断内容是否为ffbm-
开头,如果是就将读取到的信息保存到全局变量ffbm_mode_string
中,并且设置全局变量boot_into_ffbm
为 true。 现在会检查内存固定位置0x8F6FF000
是否和 boot.img 的 MAGIC 值"ANDROID!"
相同,如果相同,则直接按照这个内存地址来启动系统,不再从emmc
中读取。
启动模式
检测完成后,除了直接从内存启动的方式以外,其他方式都需要将需要启动的 image
从 emmc
中读取并加载到内存中。
读取 boot_img_hdr
normal
和 recovery
的 image
在结构上是相同的,所以可以使用同一套流程加载并启动。这一部分的内容就是从emmc
读取boot_img_hdr
结构,这个结构是image
头部结构,包含基础的加载信息。
int boot_linux_from_mmc(void)
{
//...
if (!boot_into_recovery) {
index = partition_get_index("boot");
ptn = partition_get_offset(index);
if(ptn == 0) {
dprintf(CRITICAL, "ERROR: No boot partition found\n");
return -1;
}
}
else {
index = partition_get_index("recovery");
ptn = partition_get_offset(index);
if(ptn == 0) {
dprintf(CRITICAL, "ERROR: No recovery partition found\n");
return -1;
}
}
/* Set Lun for boot & recovery partitions */
mmc_set_lun(partition_get_lun(index));
if (mmc_read(ptn + offset, (uint32_t *) buf, page_size)) {
dprintf(CRITICAL, "ERROR: Cannot read boot image header\n");
return -1;
}
if (memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
dprintf(CRITICAL, "ERROR: Invalid boot image header\n");
return -1;
}
if (hdr->page_size && (hdr->page_size != page_size)) {
if (hdr->page_size > BOOT_IMG_MAX_PAGE_SIZE) {
dprintf(CRITICAL, "ERROR: Invalid page size\n");
return -1;
}
page_size = hdr->page_size;
page_mask = page_size - 1;
}
/* ensure commandline is terminated */
hdr->cmdline[BOOT_ARGS_SIZE-1] = 0;
kernel_actual = ROUND_TO_PAGE(hdr->kernel_size, page_mask);
ramdisk_actual = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask);
image_addr = (unsigned char *)target_get_scratch_address();
#if DEVICE_TREE
dt_actual = ROUND_TO_PAGE(hdr->dt_size, page_mask);
imagesize_actual = (page_size + kernel_actual + ramdisk_actual + dt_actual);
#else
imagesize_actual = (page_size + kernel_actual + ramdisk_actual);
#endif
//...
}
根据 启动模式
获取需要读取的分区偏移。其中 normal
存储在 boot
分区,recovery
存储在recovery
分区。
读取 boot_img_hdr
, 其结构如下:
struct boot_img_hdr
{
unsigned char magic[BOOT_MAGIC_SIZE];
unsigned kernel_size; /* size in bytes */
unsigned kernel_addr; /* physical load addr */
unsigned ramdisk_size; /* size in bytes */
unsigned ramdisk_addr; /* physical load addr */
unsigned second_size; /* size in bytes */
unsigned second_addr; /* physical load addr */
unsigned tags_addr; /* physical addr for kernel tags */
unsigned page_size; /* flash page size we assume */
unsigned dt_size; /* device_tree in bytes */
unsigned unused; /* future expansion: should be 0 */
unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */
unsigned char cmdline[BOOT_ARGS_SIZE];
unsigned id[8]; /* timestamp / checksum / sha1 / etc */
};
进行基础的 boot_img_hdr
合法性检查:
bootimghdr.magic 是否等于 “ANDROID!” bootimghdr.pagesize 是否大于 4096
根据 boot_img_hdr
初始化两个重要的变量:
imageaddr这个值是
image
在内存的缓存地址,缓存的地址由SCRATCH_ADDR
宏指定,这个宏定义在target/msm8916/rules.mk
文件中。在msm8916
平台SCRATCH_ADDR
为0x90000000
。 imagesizeactual这个值是image
加载到内存所需要的内存大小,在msm8916
平台计算方法如下:imagesizeactual = 分页大小 + kernel 大小 + ramdisk 大小 + 设备树大小
当 image_addr
和 imagesize_actual
确定后就可以进行缓存 image
并验证的步骤。
缓存并验证镜像
这一部分代码的作用就是将 image
从 emmc
加载到内存中的image_addr
位置,并且验证image
是否合法。
int boot_linux_from_mmc(void)
{
struct boot_img_hdr *hdr = (void*) buf;
struct boot_img_hdr *uhdr;
unsigned offset = 0;
int rcode;
unsigned long long ptn = 0;
int index = INVALID_PTN;
unsigned char *image_addr = 0;
unsigned kernel_actual;
unsigned ramdisk_actual;
unsigned imagesize_actual;
unsigned second_actual = 0;
unsigned int dtb_size = 0;
unsigned int out_len = 0;
unsigned int out_avai_len = 0;
unsigned char *out_addr = NULL;
uint32_t dtb_offset = 0;
unsigned char *kernel_start_addr = NULL;
unsigned int kernel_size = 0;
int rc;
#if DEVICE_TREE
struct dt_table *table;
struct dt_entry dt_entry;
unsigned dt_table_offset;
uint32_t dt_actual;
uint32_t dt_hdr_size;
unsigned char *best_match_dt_addr = NULL;
#endif
struct kernel64_hdr *kptr = NULL;
//...
#if VERIFIED_BOOT
boot_verifier_init();
#endif
if (check_aboot_addr_range_overlap((uint32_t) image_addr, imagesize_actual))
{
dprintf(CRITICAL, "Boot image buffer address overlaps with aboot addresses.\n");
return -1;
}
/*
* Update loading flow of bootimage to support compressed/uncompressed
* bootimage on both 64bit and 32bit platform.
* 1. Load bootimage from emmc partition onto DDR.
* 2. Check if bootimage is gzip format. If yes, decompress compressed kernel
* 3. Check kernel header and update kernel load addr for 64bit and 32bit
* platform accordingly.
* 4. Sanity Check on kernel_addr and ramdisk_addr and copy data.
*/
dprintf(INFO, "Loading (%s) image (%d): start\n",
(!boot_into_recovery ? "boot" : "recovery"),imagesize_actual);
bs_set_timestamp(BS_KERNEL_LOAD_START);
/* Read image without signature */
if (mmc_read(ptn + offset, (void *)image_addr, imagesize_actual))
{
dprintf(CRITICAL, "ERROR: Cannot read boot image\n");
return -1;
}
dprintf(INFO, "Loading (%s) image (%d): done\n",
(!boot_into_recovery ? "boot" : "recovery"),imagesize_actual);
bs_set_timestamp(BS_KERNEL_LOAD_DONE);
/* Authenticate Kernel */
dprintf(INFO, "use_signed_kernel=%d, is_unlocked=%d, is_tampered=%d.\n",
(int) target_use_signed_kernel(),
device.is_unlocked,
device.is_tampered);
/* Change the condition a little bit to include the test framework support.
* We would never reach this point if device is in fastboot mode, even if we did
* that means we are in test mode, so execute kernel authentication part for the
* tests */
if((target_use_signed_kernel() && (!device.is_unlocked)) || boot_into_fastboot)
{
offset = imagesize_actual;
if (check_aboot_addr_range_overlap((uint32_t)image_addr + offset, page_size))
{
dprintf(CRITICAL, "Signature read buffer address overlaps with aboot addresses.\n");
return -1;
}
/* Read signature */
if(mmc_read(ptn + offset, (void *)(image_addr + offset), page_size))
{
dprintf(CRITICAL, "ERROR: Cannot read boot image signature\n");
return -1;
}
verify_signed_bootimg((uint32_t)image_addr, imagesize_actual);
/* The purpose of our test is done here */
if (boot_into_fastboot && auth_kernel_img)
return 0;
} else {
second_actual = ROUND_TO_PAGE(hdr->second_size, page_mask);
#ifdef TZ_SAVE_KERNEL_HASH
aboot_save_boot_hash_mmc((uint32_t) image_addr, imagesize_actual);
#endif /* TZ_SAVE_KERNEL_HASH */
#ifdef MDTP_SUPPORT
{
/* Verify MDTP lock.
* For boot & recovery partitions, MDTP will use boot_verifier APIs,
* since verification was skipped in aboot. The signature is not part of the loaded image.
*/
mdtp_ext_partition_verification_t ext_partition;
ext_partition.partition = boot_into_recovery ? MDTP_PARTITION_RECOVERY : MDTP_PARTITION_BOOT;
ext_partition.integrity_state = MDTP_PARTITION_STATE_UNSET;
ext_partition.page_size = page_size;
ext_partition.image_addr = (uint32)image_addr;
ext_partition.image_size = imagesize_actual;
ext_partition.sig_avail = FALSE;
mdtp_fwlock_verify_lock(&ext_partition);
}
#endif /* MDTP_SUPPORT */
}
//...
}
初始化对 boot/recovery 的验证, boot_verifier_init
的代码如下:
void boot_verifier_init()
{
uint32_t boot_state;
/* Check if device unlock */
if(device.is_unlocked)
{
boot_verify_send_event(DEV_UNLOCK);
boot_verify_print_state();
dprintf(CRITICAL, "Device is unlocked! Skipping verification...\n");
return;
}
else
{
boot_verify_send_event(BOOT_INIT);
}
/* Initialize keystore */
boot_state = boot_verify_keystore_init();
if(boot_state == YELLOW)
{
boot_verify_print_state();
dprintf(CRITICAL, "Keystore verification failed! Continuing anyways...\n");
}
}
如果手机已经解锁 bootloader 则不会进行验证,而是将 boot_state
设置为 ORANGE 状态,在 android 中存在以下几种启动状态2:
green yellow orange red
然后会在 boot_verify_keystore_init
函数中读取两个 key, oem key 和 user key:
oem key 会编译到 lk 代码中,其位置在
platform/msm_shared/include/oem_keystore.h
文件中,作用是为了验证 user key。 user key 存储在 keystore 分区中,作用验证 boot.img。
使用 emmc_read
读取 boot/recovery 到指定的内存地址,由于 boot/recovery 的地址在 bootloader 的高地址处,数据往低地址写可能覆盖 bootloader,所以在读取 boot/recovery 之前,会使用check_aboot_addr_range_overlap
检查将加载到内存的 boot/recovery 是否会覆盖到 aboot 的地址。
读取位于 boot/recovery 尾部的签名,并通过 verify_signed_bootimg
来验证签名是否能够匹配,通过这里可以检查出 boot/recovery 是否被修改,和 APK 签名的作用类似。verify_signed_bootimg
中会调用boot_verify_image
来进行签名验证,但是会根据启动模式的不同传入不同的参数:
if(boot_into_recovery)
{
ret = boot_verify_image((unsigned char *)bootimg_addr,
bootimg_size, "/recovery");
}
else
{
ret = boot_verify_image((unsigned char *)bootimg_addr,
bootimg_size, "/boot");
}
boot_verify_image
函数位于 platform/msm_shared/boot_verifier.c
文件中,这个函数的主要作用是是将 boot/recovery 的签名数据转化为VERIFIED_BOOT_SIG *
的结构。
/**
* AndroidVerifiedBootSignature DEFINITIONS ::=
* BEGIN
* FormatVersion ::= INTEGER
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
* AuthenticatedAttributes ::= SEQUENCE {
* target CHARACTER STRING,
* length INTEGER
* }
* Signature ::= OCTET STRING
* END
*/
typedef struct auth_attr_st
{
ASN1_PRINTABLESTRING *target;
ASN1_INTEGER *len;
}AUTH_ATTR;
typedef struct verif_boot_sig_st
{
ASN1_INTEGER *version;
X509 *certificate;
X509_ALGOR *algor;
AUTH_ATTR *auth_attr;
ASN1_OCTET_STRING *sig;
}VERIFIED_BOOT_SIG;
其中的大多数成员都是 openssl 中的类型,在这里不详细叙述。签名转换完成后就通过 verify_image_with_sig
来验证签名,其中比较重要的参数如下:
char* pname, 即将要验证的分区名称 VERIFIEDBOOTSIG *sig, 分区所带的签名 KEYSTORE *ks, 验证所使用的密钥
static bool verify_image_with_sig(unsigned char* img_addr, uint32_t img_size,
char *pname, VERIFIED_BOOT_SIG *sig, KEYSTORE *ks)
{
bool ret = false;
uint32_t len;
int shift_bytes;
RSA *rsa = NULL;
bool keystore_verification = false;
int attr = 0;
if(!strcmp(pname, "keystore"))
keystore_verification = true;
/* Verify target name */
if(strncmp((char*)(sig->auth_attr->target->data), pname,
sig->auth_attr->target->length) ||
(strlen(pname) != (unsigned long) sig->auth_attr->target->length))
{
dprintf(CRITICAL,
"boot_verifier: verification failure due to target name mismatch\n");
goto verify_image_with_sig_error;
}
/* Read image size from signature */
/* A len = 0xAABBCC (represented by 3 octets) would be stored in
len->data as 0X00CCBBAA and len->length as 3(octets).
To read len we need to left shift data to number of missing octets and
then change it to host long
*/
len = *((uint32_t*)sig->auth_attr->len->data);
shift_bytes = sizeof(uint32_t) - sig->auth_attr->len->length;
if(shift_bytes > 0) {
len = len << (shift_bytes*8);
}
len = ntohl(len);
/* Verify image size*/
if(len != img_size)
{
dprintf(CRITICAL,
"boot_verifier: image length is different. (%d vs %d)\n",
len, img_size);
goto verify_image_with_sig_error;
}
/* append attribute to image */
if(!keystore_verification)
{
// verifying a non keystore partition
attr = add_attribute_to_img((unsigned char*)(img_addr + img_size),
sig->auth_attr);
if (img_size > (UINT_MAX - attr))
{
dprintf(CRITICAL,"Interger overflow detected\n");
ASSERT(0);
}
else img_size += attr;
}
/* compare SHA256SUM of image with value in signature */
if(ks != NULL)
rsa = ks->mykeybag->mykey->key_material;
ret = boot_verify_compare_sha256(img_addr, img_size,
(unsigned char*)sig->sig->data, rsa);
if(!ret)
{
dprintf(CRITICAL,
"boot_verifier: Image verification failed.\n");
}
verify_image_with_sig_error:
return ret;
}
整个验证过程分为以下几个部分:
签名对应的分区是否正确,签名中携带的分区信息为以下两个成员:
分区名称:sig->authattr->target->data 名称长度:sig->authattr->target->length
检查 boot/recovery 的大小是否和签名中存储的大小信息相等,大小信息存储在以下两个成员中:
大小信息:sig->authattr->len->data 数据长度:sig->authattr->len->length
这里需要注意的是 data 是按网络字节的顺序存储,例如 len 原值为 0xAABBCC 则 data 中实际存储的值为 0x00CCBBAA。而 len->length 的作用就是指明这个 data 占了多少个字节,所以转换为 unsigned int 的算法如下。
len = *((uint32_t*)sig->auth_attr->len->data);
shift_bytes = sizeof(uint32_t) - sig->auth_attr->len->length;
if(shift_bytes > 0) {
len = len << (shift_bytes*8);
}
len = ntohl(len);
最后一步就是比对 SHA256 的值是否正确,整个过程如下:
从 keystore 中获取 rsa 公钥,ks->mykeybag->mykey->keymaterial。 使用 rsa 解密签名中携带的 SHA256 值,sig->sig->data。 计算传入的 boot/recovery 的 SHA256 hash 值。 将解密后的 hash 和解密前的 hash 进行对比,如果一致则签名验证通过。
解压释放 kernel
和 ramdisk
经过上面部分的加载和验证,需要 lk 启动的 boot/recovery 镜像已经加载到了内存的缓冲区中,但是现在还是完整的一个整体,并没有分开加载。下面的代码就是对每一个部分的代码和数据进行分开加载,然后才能进行系统启动的操作。
int boot_linux_from_mmc(void)
{
//...
/*
* Check if the kernel image is a gzip package. If yes, need to decompress it.
* If not, continue booting.
*/
if (is_gzip_package((unsigned char *)(image_addr + page_size), hdr->kernel_size))
{
out_addr = (unsigned char *)(image_addr + imagesize_actual + page_size);
out_avai_len = target_get_max_flash_size() - imagesize_actual - page_size;
dprintf(INFO, "decompressing kernel image: start\n");
rc = decompress((unsigned char *)(image_addr + page_size),
hdr->kernel_size, out_addr, out_avai_len,
&dtb_offset, &out_len);
if (rc)
{
dprintf(CRITICAL, "decompressing kernel image failed!!!\n");
ASSERT(0);
}
dprintf(INFO, "decompressing kernel image: done\n");
kptr = (struct kernel64_hdr *)out_addr;
kernel_start_addr = out_addr;
kernel_size = out_len;
} else {
kptr = (struct kernel64_hdr *)(image_addr + page_size);
kernel_start_addr = (unsigned char *)(image_addr + page_size);
kernel_size = hdr->kernel_size;
}
/*
* Update the kernel/ramdisk/tags address if the boot image header
* has default values, these default values come from mkbootimg when
* the boot image is flashed using fastboot flash:raw
*/
update_ker_tags_rdisk_addr(hdr, IS_ARM64(kptr));
/* Get virtual addresses since the hdr saves physical addresses. */
hdr->kernel_addr = VA((addr_t)(hdr->kernel_addr));
hdr->ramdisk_addr = VA((addr_t)(hdr->ramdisk_addr));
hdr->tags_addr = VA((addr_t)(hdr->tags_addr));
kernel_size = ROUND_TO_PAGE(kernel_size, page_mask);
/* Check if the addresses in the header are valid. */
if (check_aboot_addr_range_overlap(hdr->kernel_addr, kernel_size) ||
check_aboot_addr_range_overlap(hdr->ramdisk_addr, ramdisk_actual))
{
dprintf(CRITICAL, "kernel/ramdisk addresses overlap with aboot addresses.\n");
return -1;
}
#ifndef DEVICE_TREE
if (check_aboot_addr_range_overlap(hdr->tags_addr, MAX_TAGS_SIZE))
{
dprintf(CRITICAL, "Tags addresses overlap with aboot addresses.\n");
return -1;
}
#endif
/* Move kernel, ramdisk and device tree to correct address */
memmove((void*) hdr->kernel_addr, kernel_start_addr, kernel_size);
memmove((void*) hdr->ramdisk_addr, (char *)(image_addr + page_size + kernel_actual), hdr->ramdisk_size);
//...
}
整个加载过程如下:
由于 emmc 存储空间有限,所以有的时候 kernel 是压缩保存在 emmc 中的,所以需要先判断是否需要解压,由于需要解压的情况较多,所以先分析此过程。调用is_gzip_package
检查 kernel block 是否压缩,这个函数在lib/zlib_inflate/decompress.c
文件中,它的实现如下:
/* check if the input "buf" file was a gzip package.
* Return true if the input "buf" is a gzip package.
*/
int is_gzip_package(unsigned char *buf, unsigned int len)
{
if (len < 10 || !buf || buf[0] != 0x1f ||
buf[1] != 0x8b || buf[2] != 0x08)
{
return false;
}
return true;
}
为压缩包的条件非常简单,只有以下两个:
长度不小于 10 MAGIC 为 0x1F8B08
只要满足这两个条件即判定为压缩包
设置解压后数据的存储位置和大小,分别为以下两个值:address = imgbufferaddress + imgsize + pagesizesize = 0×10000000 – (imgsize + pagesize)
调用 decompress
函数解压 kernel, 这个函数实现在 lib/zlib_inflate/decompress.c
文件中:
/* decompress gzip file "in_buf", return 0 if decompressed successful,
* return -1 if decompressed failed.
* in_buf - input gzip file
* in_len - input the length file
* out_buf - output the decompressed data
* out_buf_len - the available length of out_buf
* pos - position of the end of gzip file
* out_len - the length of decompressed data
*/
int decompress(unsigned char *in_buf, unsigned int in_len,
unsigned char *out_buf,
unsigned int out_buf_len,
unsigned int *pos,
unsigned int *out_len) {
struct z_stream_s *stream;
int rc = -1;
int i;
if (in_len < GZIP_HEADER_LEN) {
dprintf(INFO, "the input data is not a gzip package.\n");
return rc;
}
if (out_buf_len < in_len) {
dprintf(INFO, "the avaiable length of out_buf is not enough.\n");
return rc;
}
stream = malloc(sizeof(*stream));
if (stream == NULL) {
dprintf(INFO, "allocating z_stream failed.\n");
return rc;
}
stream->zalloc = zlib_alloc;
stream->zfree = zlib_free;
stream->next_out = out_buf;
stream->avail_out = out_buf_len;
/* skip over gzip header */
stream->next_in = in_buf + GZIP_HEADER_LEN;
stream->avail_in = out_buf_len - GZIP_HEADER_LEN;
/* skip over asciz filename */
if (in_buf[3] & 0x8) {
for (i = 0; i < GZIP_FILENAME_LIMIT && *stream->next_in++; i++) {
if (stream->avail_in == 0) {
dprintf(INFO, "header error\n");
goto gunzip_end;
}
--stream->avail_in;
}
}
rc = inflateInit2(stream, -MAX_WBITS);
if (rc != Z_OK) {
dprintf(INFO, "inflateInit2 failed!\n");
goto gunzip_end;
}
rc = inflate(stream, 0);
/* Z_STREAM_END is "we unpacked it all" */
if (rc == Z_STREAM_END) {
rc = 0;
} else if (rc != Z_OK) {
dprintf(INFO, "uncompression error \n");
rc = -1;
}
inflateEnd(stream);
if (pos)
/* alculation the length of the compressed package */
*pos = stream->next_in - in_buf + 8;
if (out_len)
*out_len = stream->total_out;
gunzip_end:
free(stream);
return rc; /* returns 0 if decompressed successful */
}
这个过程是标准的 gzip 解压,这里就详细分析,都是直接调用 zlib 的接口实现。
保存解压后的 kernel 头地址和大小,这个涉及到一个比较重要的结构体 kernel64_hdr
:
struct kernel64_hdr
{
uint32_t insn;
uint32_t res1;
uint64_t text_offset;
uint64_t res2;
uint64_t res3;
uint64_t res4;
uint64_t res5;
uint64_t res6;
uint32_t magic_64;
uint32_t res7;
};
这个结构就是 kernel block 的头部结构,定义在 app/aboot/bootimg.h
文件中。
检查 kernel 和 ramdisk 是否会越界覆盖到 bootloader, 同样是通过 check_aboot_addr_range_overlap
完成。 将 kernel 和 ramdisk 拷贝到boot_img_hdr
指定的加载地址中。
解压释放 device tree
接下来就需要加载 device tree 到内存,由于存在两种情况:
在 boot_img_hdr
中指定了 dtsize 没有指定 dtsize
两种情况分开分析。
指定了 dtsize 如何加载 device tree。
首先需要明确 device tree 在 image 中的位置,其位置计算如下:
dt_table_offset = ((uint32_t)image_addr + page_size + kernel_actual + ramdisk_actual + second_actual);
table = (struct dt_table*) dt_table_offset;
这里涉及到 dttable 结构体,其定义在 platform/msm_shared/include/dev_tree.h
文件中,结构如下:
struct dt_table
{
uint32_t magic;
uint32_t version;
uint32_t num_entries;
};
验证 device tree block 的数据是否合法,调用 dev_tree_validate
函数来确定,其定义在 platform/msm_shared/dev_tree.c
文件中,实现如下:
/* Returns 0 if the device tree is valid. */
int dev_tree_validate(struct dt_table *table, unsigned int page_size, uint32_t *dt_hdr_size)
{
int dt_entry_size;
uint64_t hdr_size;
/* Validate the device tree table header */
if(table->magic != DEV_TREE_MAGIC) {
dprintf(CRITICAL, "ERROR: Bad magic in device tree table \n");
return -1;
}
if (table->version == DEV_TREE_VERSION_V1) {
dt_entry_size = sizeof(struct dt_entry_v1);
} else if (table->version == DEV_TREE_VERSION_V2) {
dt_entry_size = sizeof(struct dt_entry_v2);
} else if (table->version == DEV_TREE_VERSION_V3) {
dt_entry_size = sizeof(struct dt_entry);
} else {
dprintf(CRITICAL, "ERROR: Unsupported version (%d) in DT table \n",
table->version);
return -1;
}
hdr_size = (uint64_t)table->num_entries * dt_entry_size + DEV_TREE_HEADER_SIZE;
/* Roundup to page_size. */
hdr_size = ROUNDUP(hdr_size, page_size);
if (hdr_size > UINT_MAX)
return -1;
else
*dt_hdr_size = hdr_size & UINT_MAX;
return 0;
}
第一点需要验证的就是 MAGIC 是否为正确,正确的 device tree magic 如下:
#define DEV_TREE_MAGIC 0x54444351 /* "QCDT" */
第二步是检查 device tree 格式的版本是否支持,目前的 lk 支持以下 3 个版本:
#define DEV_TREE_VERSION_V1 1
#define DEV_TREE_VERSION_V2 2
#define DEV_TREE_VERSION_V3 3
每个版本对应不同的 dt_entry
结构体,按照上面的版本顺序,分别是以下 3 个结构体:
struct dt_entry_v1
{
uint32_t platform_id;
uint32_t variant_id;
uint32_t soc_rev;
uint32_t offset;
uint32_t size;
};
struct dt_entry_v2
{
uint32_t platform_id;
uint32_t variant_id;
uint32_t board_hw_subtype;
uint32_t soc_rev;
uint32_t offset;
uint32_t size;
};
struct dt_entry
{
uint32_t platform_id;
uint32_t variant_id;
uint32_t board_hw_subtype;
uint32_t soc_rev;
uint32_t pmic_rev[4];
uint32_t offset;
uint32_t size;
};
计算并验证所需要内存大小是否正确,计算过程如下:
hdr_size = (uint64_t)table->num_entries * dt_entry_size + DEV_TREE_HEADER_SIZE;
/* Roundup to page_size. */
hdr_size = ROUNDUP(hdr_size, page_size);
if (hdr_size > UINT_MAX)
return -1;
else
*dt_hdr_size = hdr_size & UINT_MAX;
ROUNDUP 实际上就是按照分页对齐,宏定义为 #define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
。
从 dt_table
中的 numentries 字段可以知道 device tree 在存储中实际上是数组结构,这里就是遍临构造出这个 device tree 数组。这里调用dev_tree_get_entry_info
来实现,其定义在platform/msm_shared/dev_tree.c
文件中,由于有多个版本的 device tree,通过对比可以发现dt_entry
的字段是不断增加的,所以我们只分析 version 3 这一种情况,其实现如下:
/* Function to obtain the index information for the correct device tree
* based on the platform data.
* If a matching device tree is found, the information is returned in the
* "dt_entry_info" out parameter and a function value of 0 is returned, otherwise
* a non-zero function value is returned.
*/
int dev_tree_get_entry_info(struct dt_table *table, struct dt_entry *dt_entry_info)
{
uint32_t i;
unsigned char *table_ptr = NULL;
struct dt_entry dt_entry_buf_1;
struct dt_entry *cur_dt_entry = NULL;
struct dt_entry *best_match_dt_entry = NULL;
struct dt_entry_v1 *dt_entry_v1 = NULL;
struct dt_entry_v2 *dt_entry_v2 = NULL;
struct dt_entry_node *dt_entry_queue = NULL;
struct dt_entry_node *dt_node_tmp1 = NULL;
struct dt_entry_node *dt_node_tmp2 = NULL;
uint32_t found = 0;
if (!dt_entry_info) {
dprintf(CRITICAL, "ERROR: Bad parameter passed to %s \n",
__func__);
return -1;
}
table_ptr = (unsigned char *)table + DEV_TREE_HEADER_SIZE;
cur_dt_entry = &dt_entry_buf_1;
best_match_dt_entry = NULL;
dt_entry_queue = (struct dt_entry_node *)
malloc(sizeof(struct dt_entry_node));
if (!dt_entry_queue) {
dprintf(CRITICAL, "Out of memory\n");
return -1;
}
list_initialize(&dt_entry_queue->node);
dprintf(INFO, "DTB Total entry: %d, DTB version: %d\n", table->num_entries, table->version);
for(i = 0; found == 0 && i < table->num_entries; i++)
{
memset(cur_dt_entry, 0, sizeof(struct dt_entry));
switch(table->version) {
case DEV_TREE_VERSION_V1:
//...
break;
case DEV_TREE_VERSION_V2:
//...
break;
case DEV_TREE_VERSION_V3:
memcpy(cur_dt_entry, (struct dt_entry *)table_ptr,
sizeof(struct dt_entry));
/* For V3 version of DTBs we have platform version field as part
* of variant ID, in such case the subtype will be mentioned as 0x0
* As the qcom, board-id = <0xSSPMPmPH, 0x0>
* SS -- Subtype
* PM -- Platform major version
* Pm -- Platform minor version
* PH -- Platform hardware CDP/MTP
* In such case to make it compatible with LK algorithm move the subtype
* from variant_id to subtype field
*/
if (cur_dt_entry->board_hw_subtype == 0)
cur_dt_entry->board_hw_subtype = (cur_dt_entry->variant_id >> 0x18);
table_ptr += sizeof(struct dt_entry);
break;
default:
dprintf(CRITICAL, "ERROR: Unsupported version (%d) in DT table \n",
table->version);
free(dt_entry_queue);
return -1;
}
/* DTBs must match the platform_id, platform_hw_id, platform_subtype and DDR size.
* The satisfactory DTBs are stored in dt_entry_queue
*/
platform_dt_absolute_match(cur_dt_entry, dt_entry_queue);
}
best_match_dt_entry = platform_dt_match_best(dt_entry_queue);
if (best_match_dt_entry) {
*dt_entry_info = *best_match_dt_entry;
found = 1;
}
if (found != 0) {
dprintf(INFO, "Using DTB entry 0x%08x/%08x/0x%08x/%u for device 0x%08x/%08x/0x%08x/%u\n",
dt_entry_info->platform_id, dt_entry_info->soc_rev,
dt_entry_info->variant_id, dt_entry_info->board_hw_subtype,
board_platform_id(), board_soc_version(),
board_target_id(), board_hardware_subtype());
if (dt_entry_info->pmic_rev[0] == 0 && dt_entry_info->pmic_rev[0] == 0 &&
dt_entry_info->pmic_rev[0] == 0 && dt_entry_info->pmic_rev[0] == 0) {
dprintf(SPEW, "No maintain pmic info in DTB, device pmic info is 0x%0x/0x%x/0x%x/0x%0x\n",
board_pmic_target(0), board_pmic_target(1),
board_pmic_target(2), board_pmic_target(3));
} else {
dprintf(INFO, "Using pmic info 0x%0x/0x%x/0x%x/0x%0x for device 0x%0x/0x%x/0x%x/0x%0x\n",
dt_entry_info->pmic_rev[0], dt_entry_info->pmic_rev[1],
dt_entry_info->pmic_rev[2], dt_entry_info->pmic_rev[3],
board_pmic_target(0), board_pmic_target(1),
board_pmic_target(2), board_pmic_target(3));
}
return 0;
}
dprintf(CRITICAL, "ERROR: Unable to find suitable device tree for device (%u/0x%08x/0x%08x/%u)\n",
board_platform_id(), board_soc_version(),
board_target_id(), board_hardware_subtype());
list_for_every_entry(&dt_entry_queue->node, dt_node_tmp1, dt_node, node) {
/* free node memory */
dt_node_tmp2 = (struct dt_entry_node *) dt_node_tmp1->node.prev;
dt_entry_list_delete(dt_node_tmp1);
dt_node_tmp1 = dt_node_tmp2;
}
free(dt_entry_queue);
return -1;
}
首先开始遍历整个数组,每个数组成员是一个 dt_entry
结构体,获取到一个 dt_entry
都保存到cur_dt_entry
变量中,然后调用platform_dt_absolute_match
存储到dt_entry_queue
中,dt_entry_queue
是一个链表结构,node 结构如下:
typedef struct dt_entry_node {
struct list_node node;
struct dt_entry * dt_entry_m;
}dt_node;
而 platform_dt_absolute_match
的实现如下:
static int platform_dt_absolute_match(struct dt_entry *cur_dt_entry, struct dt_entry_node *dt_list)
{
uint32_t cur_dt_hlos_ddr;
uint32_t cur_dt_hw_platform;
uint32_t cur_dt_hw_subtype;
uint32_t cur_dt_msm_id;
dt_node *dt_node_tmp = NULL;
/* Platform-id
* bit no |31 24|23 16|15 0|
* |reserved|foundry-id|msm-id|
*/
cur_dt_msm_id = (cur_dt_entry->platform_id & 0x0000ffff);
cur_dt_hw_platform = (cur_dt_entry->variant_id & 0x000000ff);
cur_dt_hw_subtype = (cur_dt_entry->board_hw_subtype & 0xff);
/* Determine the bits 10:8 to check the DT with the DDR Size */
cur_dt_hlos_ddr = (cur_dt_entry->board_hw_subtype & 0x700);
/* 1. must match the msm_id, platform_hw_id, platform_subtype and DDR size
* soc, board major/minor, pmic major/minor must less than board info
* 2. find the matched DTB then return 1
* 3. otherwise return 0
*/
if((cur_dt_msm_id == (board_platform_id() & 0x0000ffff)) &&
(cur_dt_hw_platform == board_hardware_id()) &&
(cur_dt_hw_subtype == board_hardware_subtype()) &&
(cur_dt_hlos_ddr == (target_get_hlos_subtype() & 0x700)) &&
(cur_dt_entry->soc_rev <= board_soc_version()) &&
((cur_dt_entry->variant_id & 0x00ffff00) <= (board_target_id() & 0x00ffff00)) &&
((cur_dt_entry->pmic_rev[0] & 0x00ffff00) <= (board_pmic_target(0) & 0x00ffff00)) &&
((cur_dt_entry->pmic_rev[1] & 0x00ffff00) <= (board_pmic_target(1) & 0x00ffff00)) &&
((cur_dt_entry->pmic_rev[2] & 0x00ffff00) <= (board_pmic_target(2) & 0x00ffff00)) &&
((cur_dt_entry->pmic_rev[3] & 0x00ffff00) <= (board_pmic_target(3) & 0x00ffff00))) {
dt_node_tmp = dt_entry_list_init();
memcpy((char*)dt_node_tmp->dt_entry_m,(char*)cur_dt_entry, sizeof(struct dt_entry));
dprintf(SPEW, "Add DTB entry %u/%08x/0x%08x/%x/%x/%x/%x/%x/%x/%x\n",
dt_node_tmp->dt_entry_m->platform_id, dt_node_tmp->dt_entry_m->variant_id,
dt_node_tmp->dt_entry_m->board_hw_subtype, dt_node_tmp->dt_entry_m->soc_rev,
dt_node_tmp->dt_entry_m->pmic_rev[0], dt_node_tmp->dt_entry_m->pmic_rev[1],
dt_node_tmp->dt_entry_m->pmic_rev[2], dt_node_tmp->dt_entry_m->pmic_rev[3],
dt_node_tmp->dt_entry_m->offset, dt_node_tmp->dt_entry_m->size);
insert_dt_entry_in_queue(dt_list, dt_node_tmp);
return 1;
}
return 0;
}
这个函数最主要的作用就是将 dt_entry
添加到 dt_entry_queue
链表中,但是需要满足以下所有条件才能加入:
msmid 匹配 platformhwid 匹配 platformsubtype 匹配 ddr size 匹配 soc 版本匹配 board 版本匹配 pmic 版本匹配
通过 platform_dt_match_best
来获取最佳匹配,并且赋值给输出参数 dt_entry_info
中,其实现如下:
static struct dt_entry *platform_dt_match_best(struct dt_entry_node *dt_list)
{
struct dt_entry_node *dt_node_tmp1 = NULL;
/* check Foundry id
* the foundry id must exact match board founddry id, this is compatibility check,
* if couldn't find the exact match from DTB, will exact match 0x0.
*/
if (!platform_dt_absolute_compat_match(dt_list, DTB_FOUNDRY))
return NULL;
/* check PMIC model
* the PMIC model must exact match board PMIC model, this is compatibility check,
* if couldn't find the exact match from DTB, will exact match 0x0.
*/
if (!platform_dt_absolute_compat_match(dt_list, DTB_PMIC_MODEL))
return NULL;
/* check panel type
* the panel type must exact match board panel type, this is compatibility check,
* if couldn't find the exact match from DTB, will exact match 0x0.
*/
if (!platform_dt_absolute_compat_match(dt_list, DTB_PANEL_TYPE))
return NULL;
/* check boot device subtype
* the boot device subtype must exact match board boot device subtype, this is compatibility check,
* if couldn't find the exact match from DTB, will exact match 0x0.
*/
if (!platform_dt_absolute_compat_match(dt_list, DTB_BOOT_DEVICE))
return NULL;
/* check soc version
* the suitable soc version must less than or equal to board soc version
*/
if (!update_dtb_entry_node(dt_list, DTB_SOC))
return NULL;
/*check major and minor version
* the suitable major&minor version must less than or equal to board major&minor version
*/
if (!update_dtb_entry_node(dt_list, DTB_MAJOR_MINOR))
return NULL;
/*check pmic info
* the suitable pmic major&minor info must less than or equal to board pmic major&minor version
*/
if (!update_dtb_entry_node(dt_list, DTB_PMIC0))
return NULL;
if (!update_dtb_entry_node(dt_list, DTB_PMIC1))
return NULL;
if (!update_dtb_entry_node(dt_list, DTB_PMIC2))
return NULL;
if (!update_dtb_entry_node(dt_list, DTB_PMIC3))
return NULL;
list_for_every_entry(&dt_list->node, dt_node_tmp1, dt_node, node) {
if (!dt_node_tmp1) {
dprintf(CRITICAL, "ERROR: Couldn't find the suitable DTB!\n");
return NULL;
}
if (dt_node_tmp1->dt_entry_m)
return dt_node_tmp1->dt_entry_m;
}
return NULL;
}
整个过程和刚才添加验证的过程类似,比对与硬件的匹配度,找到最佳匹配的 dt_entry
然后返回。
如果获取到了最佳匹配的 dt_entry
则按照加载 kernel 的步骤来加载到内存地址,即按照如下步骤:根据标志位来解压数据。 拷贝到boot_img_hdr
指定的内存地址。
这样 device tree 的加载就完成了。
没有指定 dtsize 如何加载 device tree。如果没有专门的 device tree block, 则需要判断 kernel block 是否附加了 device tree 信息。整个过程都是通过调用函数dev_tree_appended
函数实现,其实现如下:
/*
* Will relocate the DTB to the tags addr if the device tree is found and return
* its address
*
* Arguments: kernel - Start address of the kernel loaded in RAM
* tags - Start address of the tags loaded in RAM
* kernel_size - Size of the kernel in bytes
*
* Return Value: DTB address : If appended device tree is found
* 'NULL' : Otherwise
*/
void *dev_tree_appended(void *kernel, uint32_t kernel_size, uint32_t dtb_offset, void *tags)
{
void *kernel_end = kernel + kernel_size;
uint32_t app_dtb_offset = 0;
void *dtb = NULL;
void *bestmatch_tag = NULL;
struct dt_entry *best_match_dt_entry = NULL;
uint32_t bestmatch_tag_size;
struct dt_entry_node *dt_entry_queue = NULL;
struct dt_entry_node *dt_node_tmp1 = NULL;
struct dt_entry_node *dt_node_tmp2 = NULL;
/* Initialize the dtb entry node*/
dt_entry_queue = (struct dt_entry_node *)
malloc(sizeof(struct dt_entry_node));
if (!dt_entry_queue) {
dprintf(CRITICAL, "Out of memory\n");
return NULL;
}
list_initialize(&dt_entry_queue->node);
if (dtb_offset)
app_dtb_offset = dtb_offset;
else
memcpy((void*) &app_dtb_offset, (void*) (kernel + DTB_OFFSET), sizeof(uint32_t));
if (((uintptr_t)kernel + (uintptr_t)app_dtb_offset) < (uintptr_t)kernel) {
return NULL;
}
dtb = kernel + app_dtb_offset;
while (((uintptr_t)dtb + sizeof(struct fdt_header)) < (uintptr_t)kernel_end) {
struct fdt_header dtb_hdr;
uint32_t dtb_size;
/* the DTB could be unaligned, so extract the header,
* and operate on it separately */
memcpy(&dtb_hdr, dtb, sizeof(struct fdt_header));
if (fdt_check_header((const void *)&dtb_hdr) != 0 ||
((uintptr_t)dtb + (uintptr_t)fdt_totalsize((const void *)&dtb_hdr) < (uintptr_t)dtb) ||
((uintptr_t)dtb + (uintptr_t)fdt_totalsize((const void *)&dtb_hdr) > (uintptr_t)kernel_end))
break;
dtb_size = fdt_totalsize(&dtb_hdr);
if (check_aboot_addr_range_overlap((uint32_t)tags, dtb_size)) {
dprintf(CRITICAL, "Tags addresses overlap with aboot addresses.\n");
return NULL;
}
dev_tree_compatible(dtb, dtb_size, dt_entry_queue);
/* goto the next device tree if any */
dtb += dtb_size;
}
best_match_dt_entry = platform_dt_match_best(dt_entry_queue);
if (best_match_dt_entry){
bestmatch_tag = (void *)best_match_dt_entry->offset;
bestmatch_tag_size = best_match_dt_entry->size;
dprintf(INFO, "Best match DTB tags %u/%08x/0x%08x/%x/%x/%x/%x/%x/%x/%x\n",
best_match_dt_entry->platform_id, best_match_dt_entry->variant_id,
best_match_dt_entry->board_hw_subtype, best_match_dt_entry->soc_rev,
best_match_dt_entry->pmic_rev[0], best_match_dt_entry->pmic_rev[1],
best_match_dt_entry->pmic_rev[2], best_match_dt_entry->pmic_rev[3],
best_match_dt_entry->offset, best_match_dt_entry->size);
dprintf(INFO, "Using pmic info 0x%0x/0x%x/0x%x/0x%0x for device 0x%0x/0x%x/0x%x/0x%0x\n",
best_match_dt_entry->pmic_rev[0], best_match_dt_entry->pmic_rev[1],
best_match_dt_entry->pmic_rev[2], best_match_dt_entry->pmic_rev[3],
board_pmic_target(0), board_pmic_target(1),
board_pmic_target(2), board_pmic_target(3));
}
/* free queue's memory */
list_for_every_entry(&dt_entry_queue->node, dt_node_tmp1, dt_node, node) {
dt_node_tmp2 = (struct dt_entry_node *) dt_node_tmp1->node.prev;
dt_entry_list_delete(dt_node_tmp1);
dt_node_tmp1 = dt_node_tmp2;
}
if(bestmatch_tag) {
memcpy(tags, bestmatch_tag, bestmatch_tag_size);
/* clear out the old DTB magic so kernel doesn't find it */
*((uint32_t *)(kernel + app_dtb_offset)) = 0;
return tags;
}
dprintf(CRITICAL, "DTB offset is incorrect, kernel image does not have appended DTB\n");
dprintf(INFO, "Device info 0x%08x/%08x/0x%08x/%u, pmic 0x%0x/0x%x/0x%x/0x%0x\n",
board_platform_id(), board_soc_version(),
board_target_id(), board_hardware_subtype(),
board_pmic_target(0), board_pmic_target(1),
board_pmic_target(2), board_pmic_target(3));
return NULL;
}
首先需要获取 device tree table 的偏移,偏移有以下两种情况: kernel 是经过解压的,则指定的位置在解压 kernel 时确定。 kernel 没有经过压缩,则偏移在kernel + 0x2C
的位置上获取。
从 device tree 偏移位置开始到 kernel 尾部的范围内遍历 device tree 数据。这里相当于遍历一个数组,数组的成员为 struct fdt_header
, 这个结构定义在 lib/libfdt/fdt.h
文件中,它的结构如下:
struct fdt_header {
uint32_t magic; /* magic word FDT_MAGIC */
uint32_t totalsize; /* total size of DT block */
uint32_t off_dt_struct; /* offset to structure */
uint32_t off_dt_strings; /* offset to strings */
uint32_t off_mem_rsvmap; /* offset to memory reserve map */
uint32_t version; /* format version */
uint32_t last_comp_version; /* last compatible version */
/* version 2 fields below */
uint32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
uint32_t size_dt_strings; /* size of the strings block */
/* version 17 fields below */
uint32_t size_dt_struct; /* size of the structure block */
};
检查遍历到的 device tree 的 fdt_header
是否符合以下条件:
是否能通过 fdt_check_header
检查。fdt_check_header
的代码在 lib/libfdt/fdt.c
文件中,其实现如下:
int fdt_check_header(const void *fdt)
{
if (fdt_magic(fdt) == FDT_MAGIC) {
/* Complete tree */
if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
return -FDT_ERR_BADVERSION;
if (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION)
return -FDT_ERR_BADVERSION;
} else if (fdt_magic(fdt) == FDT_SW_MAGIC) {
/* Unfinished sequential-write blob */
if (fdt_size_dt_struct(fdt) == 0)
return -FDT_ERR_BADSTATE;
} else {
return -FDT_ERR_BADMAGIC;
}
if (fdt_off_dt_struct(fdt) > (UINT_MAX - fdt_size_dt_struct(fdt)))
return FDT_ERR_BADOFFSET;
if (fdt_off_dt_strings(fdt) > (UINT_MAX - fdt_size_dt_strings(fdt)))
return FDT_ERR_BADOFFSET;
if ((fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt)) > fdt_totalsize(fdt))
return FDT_ERR_BADOFFSET;
if ((fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt)) > fdt_totalsize(fdt))
return FDT_ERR_BADOFFSET;
return 0;
}
如果符合符合以下条件都是正常的 fdt_header
。
如果 MAGIC = 0xd00dfeed。 fdt_header.version
>= 0×10。 fdt_header.last_comp_version
<= 0×11。 如果 MAGIC = 0x2ff20112。fdt_header.size_dt_struct
不等于 0。fdt_header.off_dt_struct
< 0xFFFFFFFF –fd_header.size_dt_struct
。fdt_header.off_dt_strings
< 0xFFFFFFFF –fd_header.size_dt_strings
。fdt_header.off_dt_struct
+fdt_header.size_dt_struct
<fdt_header.totalsize
。 fdt_header.off_dt_strings
+fdt_header.size_dt_strings
<fdt_header.totalsize
。 device tree 偏移加fdt_header.totalsize
是否小于 device tree 的偏移,即fdt_header.totalsize
是否为负数。 device tree 偏移加fdt_header.totalsize
是否大于 kernel end 的偏移,即是否越界。
通过检查的 device tree 调用 dev_tree_compatible
函数检查兼容性,符合条件的添加到链表中,甚于的步骤和指定了 dtsize 的步骤就基本相同了。
调用 boot_linux
启动系统
到这一步 boot/recovery 基本的初始化工作,加载工作就基本完成了,下一步就可以通过 boot_linux
函数来进行启动了。启动完成后就会将控制权移交给 linux kernel,android 系统就开始正式运行了。boot_linux
的代码位于app/aboot/aboot.c
文件中,其实现如下:
void boot_linux(void *kernel, unsigned *tags,
const char *cmdline, unsigned machtype,
void *ramdisk, unsigned ramdisk_size)
{
unsigned char *final_cmdline;
#if DEVICE_TREE
int ret = 0;
#endif
void (*entry)(unsigned, unsigned, unsigned*) = (entry_func_ptr*)(PA((addr_t)kernel));
uint32_t tags_phys = PA((addr_t)tags);
struct kernel64_hdr *kptr = ((struct kernel64_hdr*)(PA((addr_t)kernel)));
ramdisk = (void *)PA((addr_t)ramdisk);
final_cmdline = update_cmdline((const char*)cmdline);
#if DEVICE_TREE
dprintf(INFO, "Updating device tree: start\n");
/* Update the Device Tree */
ret = update_device_tree((void *)tags,(const char *)final_cmdline, ramdisk, ramdisk_size);
if(ret)
{
dprintf(CRITICAL, "ERROR: Updating Device Tree Failed \n");
ASSERT(0);
}
dprintf(INFO, "Updating device tree: done\n");
#else
/* Generating the Atags */
generate_atags(tags, final_cmdline, ramdisk, ramdisk_size);
#endif
free(final_cmdline);
#if VERIFIED_BOOT
/* Write protect the device info */
if (!boot_into_recovery && target_build_variant_user() && devinfo_present && mmc_write_protect("devinfo", 1))
{
dprintf(INFO, "Failed to write protect dev info\n");
ASSERT(0);
}
#endif
/* Turn off splash screen if enabled */
#if DISPLAY_SPLASH_SCREEN
target_display_shutdown();
#endif
/* Perform target specific cleanup */
target_uninit();
dprintf(INFO, "booting linux @ %p, ramdisk @ %p (%d), tags/device tree @ %p\n",
entry, ramdisk, ramdisk_size, (void *)tags_phys);
enter_critical_section();
/* Initialise wdog to catch early kernel crashes */
#if WDOG_SUPPORT
msm_wdog_init();
#endif
/* do any platform specific cleanup before kernel entry */
platform_uninit();
arch_disable_cache(UCACHE);
#if ARM_WITH_MMU
arch_disable_mmu();
#endif
bs_set_timestamp(BS_KERNEL_ENTRY);
if (IS_ARM64(kptr))
/* Jump to a 64bit kernel */
scm_elexec_call((paddr_t)kernel, tags_phys);
else
/* Jump to a 32bit kernel */
entry(0, machtype, (unsigned*)tags_phys);
}
首先进行地址转换,不过在目前的实现中 PA
宏是直接返回传入的地址,所以地址不会被转换,相当于一个预留的扩展接口。
#define PA(x) platform_get_virt_to_phys_mapping(x)
#define VA(x) platform_get_phys_to_virt_mapping(x)
addr_t platform_get_virt_to_phys_mapping(addr_t virt_addr)
{
/* Using 1-1 mapping on this platform. */
return virt_addr;
}
addr_t platform_get_phys_to_virt_mapping(addr_t phys_addr)
{
/* Using 1-1 mapping on this platform. */
return phys_addr;
}
这个只需要注意的是 kernel 的首地址即是整个 kernel 的入口,入口的函数类型为 entry_func_ptr
, 其定义如下:
typedef void entry_func_ptr(unsigned, unsigned, unsigned*);
*本文原创作者:SetRet,本文属FreeBuf原创奖励计划,未经许可禁止转载调用update_cmdline
更新boot_img_hdr.cmdline
字段中启动命令。
unsigned char *update_cmdline(const char * cmdline)
{
int cmdline_len = 0;
int have_cmdline = 0;
unsigned char *cmdline_final = NULL;
int pause_at_bootup = 0;
bool warm_boot = false;
bool gpt_exists = partition_gpt_exists();
int have_target_boot_params = 0;
char *boot_dev_buf = NULL;
bool is_mdtp_activated = 0;
#ifdef MDTP_SUPPORT
mdtp_activated(&is_mdtp_activated);
#endif /* MDTP_SUPPORT */
if (cmdline && cmdline[0]) {
cmdline_len = strlen(cmdline);
have_cmdline = 1;
}
else {
dprintf(CRITICAL,"cmdline is NULL\n");
ASSERT(0);
}
if (target_is_emmc_boot()) {
cmdline_len += strlen(emmc_cmdline);
#if USE_BOOTDEV_CMDLINE
boot_dev_buf = (char *) malloc(sizeof(char) * BOOT_DEV_MAX_LEN);
ASSERT(boot_dev_buf);
memset((void *)boot_dev_buf, 0, sizeof(*boot_dev_buf));
platform_boot_dev_cmdline(boot_dev_buf);
cmdline_len += strlen(boot_dev_buf);
#endif
}
cmdline_len += strlen(usb_sn_cmdline);
cmdline_len += strlen(sn_buf);
if (boot_into_recovery && gpt_exists)
cmdline_len += strlen(secondary_gpt_enable);
if(is_mdtp_activated)
cmdline_len += strlen(mdtp_activated_flag);
if (boot_into_ffbm) {
cmdline_len += strlen(androidboot_mode);
cmdline_len += strlen(ffbm_mode_string);
/* reduce kernel console messages to speed-up boot */
cmdline_len += strlen(loglevel);
} else if (boot_reason_alarm) {
cmdline_len += strlen(alarmboot_cmdline);
} else if ((target_build_variant_user() || device.charger_screen_enabled)
&& target_pause_for_battery_charge()) {
pause_at_bootup = 1;
cmdline_len += strlen(battchg_pause);
}
if(target_use_signed_kernel() && auth_kernel_img) {
cmdline_len += strlen(auth_kernel);
}
if (get_target_boot_params(cmdline, boot_into_recovery ? "recoveryfs" :
"system",
&target_boot_params) == 0) {
have_target_boot_params = 1;
cmdline_len += strlen(target_boot_params);
}
/* Determine correct androidboot.baseband to use */
switch(target_baseband())
{
case BASEBAND_APQ:
cmdline_len += strlen(baseband_apq);
break;
case BASEBAND_MSM:
cmdline_len += strlen(baseband_msm);
break;
case BASEBAND_CSFB:
cmdline_len += strlen(baseband_csfb);
break;
case BASEBAND_SVLTE2A:
cmdline_len += strlen(baseband_svlte2a);
break;
case BASEBAND_MDM:
cmdline_len += strlen(baseband_mdm);
break;
case BASEBAND_MDM2:
cmdline_len += strlen(baseband_mdm2);
break;
case BASEBAND_SGLTE:
cmdline_len += strlen(baseband_sglte);
break;
case BASEBAND_SGLTE2:
cmdline_len += strlen(baseband_sglte2);
break;
case BASEBAND_DSDA:
cmdline_len += strlen(baseband_dsda);
break;
case BASEBAND_DSDA2:
cmdline_len += strlen(baseband_dsda2);
break;
}
if (cmdline) {
if ((strstr(cmdline, DISPLAY_DEFAULT_PREFIX) == NULL) &&
target_display_panel_node(display_panel_buf,
MAX_PANEL_BUF_SIZE) &&
strlen(display_panel_buf)) {
cmdline_len += strlen(display_panel_buf);
}
}
if (target_warm_boot()) {
warm_boot = true;
cmdline_len += strlen(warmboot_cmdline);
}
if (cmdline_len > 0) {
const char *src;
unsigned char *dst;
cmdline_final = (unsigned char*) malloc((cmdline_len + 4) & (~3));
ASSERT(cmdline_final != NULL);
memset((void *)cmdline_final, 0, sizeof(*cmdline_final));
dst = cmdline_final;
/* Save start ptr for debug print */
if (have_cmdline) {
src = cmdline;
while ((*dst++ = *src++));
}
if (target_is_emmc_boot()) {
src = emmc_cmdline;
if (have_cmdline) --dst;
have_cmdline = 1;
while ((*dst++ = *src++));
#if USE_BOOTDEV_CMDLINE
src = boot_dev_buf;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
#endif
}
src = usb_sn_cmdline;
if (have_cmdline) --dst;
have_cmdline = 1;
while ((*dst++ = *src++));
src = sn_buf;
if (have_cmdline) --dst;
have_cmdline = 1;
while ((*dst++ = *src++));
if (warm_boot) {
if (have_cmdline) --dst;
src = warmboot_cmdline;
while ((*dst++ = *src++));
}
if (boot_into_recovery && gpt_exists) {
src = secondary_gpt_enable;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
}
if (is_mdtp_activated) {
src = mdtp_activated_flag;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
}
if (boot_into_ffbm) {
src = androidboot_mode;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
src = ffbm_mode_string;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
src = loglevel;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
} else if (boot_reason_alarm) {
src = alarmboot_cmdline;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
} else if (pause_at_bootup) {
src = battchg_pause;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
}
if(target_use_signed_kernel() && auth_kernel_img) {
src = auth_kernel;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
}
switch(target_baseband())
{
case BASEBAND_APQ:
src = baseband_apq;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_MSM:
src = baseband_msm;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_CSFB:
src = baseband_csfb;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_SVLTE2A:
src = baseband_svlte2a;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_MDM:
src = baseband_mdm;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_MDM2:
src = baseband_mdm2;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_SGLTE:
src = baseband_sglte;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_SGLTE2:
src = baseband_sglte2;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_DSDA:
src = baseband_dsda;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
case BASEBAND_DSDA2:
src = baseband_dsda2;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
break;
}
if (strlen(display_panel_buf)) {
src = display_panel_buf;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
}
if (have_target_boot_params) {
if (have_cmdline) --dst;
src = target_boot_params;
while ((*dst++ = *src++));
free(target_boot_params);
}
}
if (boot_dev_buf)
free(boot_dev_buf);
if (cmdline_final)
dprintf(INFO, "cmdline: %s\n", cmdline_final);
else
dprintf(INFO, "cmdline is NULL\n");
return cmdline_final;
}
更新 cmdline 可以分为以下几个步骤:
通过已有的命令和需要添加的命令计算出 final_cmdline
需要的长度。 申请存储 final_cmdline
的 buffer, 并且清零。 拷贝需要的 cmd 命令到final_cmdline
中。
update 主要涉及到的命令,参数,和会使用的情况可以整理为下表:
cmd | value | condition |
androidboot.emmc | true | target is emmc |
androidboot.serialno | [serial number] | no condition |
gpt | no value | recovery boot and gpt exists |
mdtp | no value | mdtp activated |
androidboot.mode | [ffbm string] | ffbm boot |
androidboot.alarmboot | true | alarm boot |
androidboot.mode | charger | charger |
androidboot.authorizedkernel | true | signed kernel and kernel authorized |
androidboot.baseband | apq | baseband is apq |
androidboot.baseband | msm | baseband is msm |
androidboot.baseband | csfb | baseband is csfb |
androidboot.baseband | svlte2a | baseband is svlte2a |
androidboot.baseband | mdm | baseband is mdm |
androidboot.baseband | mdm2 | baseband is mdm2 |
androidboot.baseband | sglte | baseband is sglte |
androidboot.baseband | dsda | baseband is dsda |
androidboot.baseband | dsda2 | baseband is dsda2 |
androidboot.baseband | sglte2 | baseband is sglte2 |
qpnp-power-on.warmboot | true | target warm boot |
mdssmdp | [display panel buffer] | have dssmdp cmd in boot image header cmdline |
调用 update_device_tree
更新 device tree 信息。这一步主要是从 device tree 中解析出地址信息,然后在启动 kernel 之时传递给 kernel 方便加载。整个过程由于需要对 device tree 整体结构的详解,这里暂不赘述,有兴趣的读者可以参考高通平台 Android 源码分析之 Linux 内核设备树(DT – Device Tree)。 在未解锁的情况下对 devinfo
分区进行写保护,这个分区存储的是 bootloader 的解锁信息和验证信息,进行写保护避免误操作。 关闭一些针对 lk 开启的特性,清理预设的数据,如:MMU, CACHE 等。
调用 kernel 入口点,进入 kernel 的代码区域。这里需要注意的是 32 位和 64 位的进入方法不同。32 位是直接 call kernel 的入口点,将入口点作为函数调用。而 64 位 kernel 则是通过scm_elexec_call
来进入内核空间。scm_elexec_call
的实现在platform/msm_shared/scm.c
文件中,其实现如下:
/* Execption Level exec secure-os call
* Jumps to kernel via secure-os and does not return
* on successful jump. System parameters are setup &
* passed on to secure-os and are utilized to boot the
* kernel.
*
@ kernel_entry : kernel entry point passed in as link register.
@ dtb_offset : dt blob address passed in as w0.
@ svc_id : indicates direction of switch 32->64 or 64->32
*
* Assumes all sanity checks have been performed on arguments.
*/
void scm_elexec_call(paddr_t kernel_entry, paddr_t dtb_offset)
{
uint32_t svc_id = SCM_SVC_MILESTONE_32_64_ID;
uint32_t cmd_id = SCM_SVC_MILESTONE_CMD_ID;
void *cmd_buf;
size_t cmd_len;
static el1_system_param param __attribute__((aligned(0x1000)));
scmcall_arg scm_arg = {0};
param.el1_x0 = dtb_offset;
param.el1_elr = kernel_entry;
/* Response Buffer = Null as no response expected */
dprintf(INFO, "Jumping to kernel via monitor\n");
if (!is_scm_armv8_support())
{
/* Command Buffer */
cmd_buf = (void *)¶m;
cmd_len = sizeof(el1_system_param);
scm_call(svc_id, cmd_id, cmd_buf, cmd_len, NULL, 0);
}
else
{
scm_arg.x0 = MAKE_SIP_SCM_CMD(SCM_SVC_MILESTONE_32_64_ID, SCM_SVC_MILESTONE_CMD_ID);
scm_arg.x1 = MAKE_SCM_ARGS(0x2, SMC_PARAM_TYPE_BUFFER_READ);
scm_arg.x2 = (uint32_t ) ¶m;
scm_arg.x3 = sizeof(el1_system_param);
scm_call2(&scm_arg, NULL);
}
/* Assert if execution ever reaches here */
dprintf(CRITICAL, "Failed to jump to kernel\n");
ASSERT(0);
}
而我们知道 scm(Secure Channel Manager) 相关的函数是 TrustZone 提供给普通个世界的接口,函数流程比较简单,根据是否支持 armv8 进行不同的跳转,由于 TrustZone 实现是黑盒,所以这里暂不研究。只需要知道 64 位 kernel 是通过 TrustZone 来启动即可。
1 参考资料
Verifying Boot | Android Open Source ProjectVerified Boot | Android Open Source Projectandroid-cdd.pdf高通平台 Android 源码分析之 Linux 内核设备树(DT – Device Tree) | Andy.Lee’s Blog
Footnotes:
1 ffbm (fast factory boot mode) 是高通开发的一套半开机模式下的测试界面,用于工厂测试,提高生产效率。
2 具体的 boot state 模式可以参考 android 官方文档Verifying Boot | Android Open Source Project
lk 源码分析的系列文章到这里就告一段落了。请期待后续其他文章,如果有任何疑问和交流意向可以和我们联系 SecRet201611@gmail.com 。