高通(Qualcomm)LK源码深度分析(三)

来源:互联网 发布:80年代网络歌曲 编辑:程序博客网 时间:2024/05/27 19:26

本编文章的内容主要是分析 boot/recovery 的启动过程,其中的 boot 就是 android 的kernel, 是整个 android 系统的核心。本文的分析是紧接着 aboot_init 的分析内容的,只是因为其重要性才单独成为一章。

以下是前两篇文章回顾:

高通(Qualcomm)LK源码深度分析

高通(Qualcomm)LK源码深度分析(二)

recovery boot & normal boot

recoverynormal 使用的是同一套加载流程,所以放在一起分析。在开始分析加载过程之前,先从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_msgmisc 分区读取到 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 messageout:  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 缓存并验证镜像 解压释放 kernelramdisk 解压释放device tree 调用boot_linux 启动系统

启动模式 检测 

启动模式 检测这一部分代码的作用是检测当前的启动将要进入的模式,然后进行相应设置。msm8916 检测以下 3 种启动模式:

boot modeffbm(fast factory boot mode)normal moderecovery 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};

msm8916boot_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 检查是否进入 ffbm1 模式,get_ffbm 所完成的任务很简单,只是读取misc 分区,并判断内容是否为ffbm- 开头,如果是就将读取到的信息保存到全局变量ffbm_mode_string 中,并且设置全局变量boot_into_ffbm 为 true。 现在会检查内存固定位置0x8F6FF000 是否和 boot.img 的 MAGIC 值"ANDROID!" 相同,如果相同,则直接按照这个内存地址来启动系统,不再从emmc 中读取。

启动模式 检测完成后,除了直接从内存启动的方式以外,其他方式都需要将需要启动的 imageemmc 中读取并加载到内存中。

读取 boot_img_hdr 

normalrecoveryimage 在结构上是相同的,所以可以使用同一套流程加载并启动。这一部分的内容就是从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_ADDR0x90000000 。 imagesizeactual这个值是image 加载到内存所需要的内存大小,在msm8916 平台计算方法如下:imagesizeactual = 分页大小 + kernel 大小 + ramdisk 大小 + 设备树大小

image_addrimagesize_actual 确定后就可以进行缓存 image 并验证的步骤。

缓存并验证镜像 

这一部分代码的作用就是将 imageemmc 加载到内存中的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 进行对比,如果一致则签名验证通过。

解压释放 kernelramdisk

经过上面部分的加载和验证,需要 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_structfdt_header.off_dt_strings < 0xFFFFFFFF –fd_header.size_dt_stringsfdt_header.off_dt_struct +fdt_header.size_dt_struct <fdt_header.totalsizefdt_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 主要涉及到的命令,参数,和会使用的情况可以整理为下表:

cmdvalueconditionandroidboot.emmctruetarget is emmcandroidboot.serialno[serial number]no conditiongptno valuerecovery boot and gpt existsmdtpno valuemdtp activatedandroidboot.mode[ffbm string]ffbm bootandroidboot.alarmboottruealarm bootandroidboot.modechargerchargerandroidboot.authorizedkerneltruesigned kernel and kernel authorizedandroidboot.basebandapqbaseband is apqandroidboot.basebandmsmbaseband is msmandroidboot.basebandcsfbbaseband is csfbandroidboot.basebandsvlte2abaseband is svlte2aandroidboot.basebandmdmbaseband is mdmandroidboot.basebandmdm2baseband is mdm2androidboot.basebandsgltebaseband is sglteandroidboot.basebanddsdabaseband is dsdaandroidboot.basebanddsda2baseband is dsda2androidboot.basebandsglte2baseband is sglte2qpnp-power-on.warmboottruetarget warm bootmdssmdp[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 *)&param;    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 ) &param;    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 。

原创粉丝点击