uboot启动过程详解
来源:互联网 发布:java 静态方法 泛型 编辑:程序博客网 时间:2024/05/29 15:35
在android启动过程中,首先启动的便是uboot,uboot是负责引导内核装入内存启动或者是引导recovery模式的启动。现在在很多android的uboot的启动过程中,都需要对内核镜像和ramdisk进行验证,来保证android系统的安全性,如果在uboot引导过程中,如果内核镜像或ramdisk刷入的是第三方的未经过签名认证的相关镜像,则系统无法启动,这样便保证了android系统的安全性。
在uboot启动过程中,是从start.S
开始的,这里详细的细节不在赘述了,该篇文章主要学习uboot对内核镜像和ramdisk镜像的验证启动过程,同时学习一下里面的优秀巧妙的编码方式。
我们从arch/arm/lib/board.c
的函数board_init_r
函数开始,我们来看一下该代码:
void board_init_r(gd_t *id, ulong dest_addr) { gd = id; gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */ monitor_flash_len = _end_ofs; debug("monitor flash len: %08lX\n", monitor_flash_len); board_init(); /* Setup chipselects */ #if defined(CONFIG_MISC_INIT_R) /* miscellaneous platform dependent initialisations */ misc_init_r(); #endif #if defined(CONFIG_USE_IRQ) /* set up exceptions */ interrupt_init(); /* enable exceptions */ enable_interrupts(); printf("init interrupt done!\n"); #endif #if defined(CONFIG_COMIP_FASTBOOT) && defined(CONFIG_LCD_SUPPORT) if (gd->fastboot) { /*register lcd support*/ #if defined (CONFIG_LCD_AUO_OTM1285A_OTP) extern int lcd_auo_otm1285a_otp_init(void); lcd_auo_otm1285a_otp_init(); #endif #if defined (CONFIG_LCD_AUO_R61308OTP) extern int lcd_auo_r61308opt_init(void); lcd_auo_r61308opt_init(); #endif #if defined (CONFIG_LCD_AUO_NT35521) extern int lcd_auo_nt35521_init(void); lcd_auo_nt35521_init(); #endif #if defined (CONFIG_LCD_SHARP_R69431) extern int lcd_sharp_eR69431_init(void); lcd_sharp_eR69431_init(); #endif /*initialize lcdc & display logo*/ extern int comipfb_probe(void); comipfb_probe(); } #endif #if defined(CONFIG_COMIP_TARGETLOADER) extern int targetloader_init(void); targetloader_init(); #elif defined(CONFIG_COMIP_FASTBOOT) if (gd->fastboot) { extern int fastboot_init(void); fastboot_init(); while(1); } #endif #if defined(CONFIG_ENABLE_SECURE_VERIFY_FOR_BOOT) extern int secure_verify(void); extern void pmic_power_off(void); if(secure_verify()) { printf("Secure verify failed! Shutdown now!\n"); pmic_power_off(); } else { printf("Secure verify succeed!\n"); } #endif do_bootm_linux(); /* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { //main_loop(); } /* NOTREACHED - no way out of command loop except booting */ }
首先该函数做的是初始化board,调用board_init()
函数。该函数位于board/****/***.c
文件中,该文件由于属于板子厂家,所以暂时保密。我们来看一下这个函数:
int board_init(void) { gd->bd->bi_arch_number = MACH_TYPE_LC186X; gd->bd->bi_boot_params = CONFIG_BOOT_PARAMS_LOADADDR; #ifndef CONFIG_COMIP_TARGETLOADER tl420_init(); watchdog_init(); comip_lc186x_coresight_config(); comip_lc186x_sysclk_config(); comip_lc186x_sec_config(); comip_lc186x_bus_prior_config(); #endif #if defined(COMIP_LOW_POWER_MODE_ENABLE) comip_lp_regs_init(); #endif icache_enable(); //dcache_enable(); #if CONFIG_COMIP_EMMC_ENHANCE mmc_set_dma(1); #endif flash_init(); #ifndef CONFIG_COMIP_TARGETLOADER pmic_power_on_key_check(); boot_image(); pmic_power_on_key_check(); #endif #ifdef CONFIG_PMIC_VIBRATOR pmic_vibrator_enable_set(); #endif return 0; }
在该函数中,主要是用于初始化一些参数和硬件,包括arch版本号,boot加载地址,初始化watchdog,系统时钟,总线,flash,同时还需要做的就是,我们开机时的按钮监听,组合键按钮监听,启动镜像,开机震动等操作,在这里我们看一下boot_image()
函数的实现。
static void boot_image(void) { char *kernel_name = CONFIG_PARTITION_KERNEL; char *ramdisk_name = CONFIG_PARTITION_RAMDISK; int pu_reason; int key_code; int ret; pu_reason = pmic_power_up_reason_get(); if ((pu_reason == PU_REASON_REBOOT_RECOVERY) || (pu_reason == PU_REASON_REBOOT_FOTA) || check_recovery_misc() || check_recovery_fota()) { gd->boot_mode = BOOT_MODE_RECOVERY; ramdisk_name = CONFIG_PARTITION_RAMDISK_RECOVERY; #if defined(CONFIG_USE_KERNEL_RECOVERY) kernel_name = CONFIG_PARTITION_KERNEL_RECOVERY; #endif } else { ret = keypad_init(); if (ret) printf("keypad init failed!\n"); key_code = keypad_check(); printf("key code: %d\n", key_code); if(pu_reason == PU_REASON_USB_CHARGER #if defined(CONFIG_COMIP_FASTBOOT) && key_code != CONFIG_KEY_CODE_FASTBOOT #endif ) { gd->boot_mode = BOOT_MODE_NORMAL; ramdisk_name = CONFIG_PARTITION_RAMDISK_AMT1; } else { switch (key_code) { case KEY_CODE_RECOVERY: gd->boot_mode = BOOT_MODE_RECOVERY; ramdisk_name = CONFIG_PARTITION_RAMDISK_RECOVERY; #if defined(CONFIG_USE_KERNEL_RECOVERY) kernel_name = CONFIG_PARTITION_KERNEL_RECOVERY; #endif break; #if defined(CONFIG_USE_RAMDISK_AMT3) case KEY_CODE_AMT3: gd->boot_mode = BOOT_MODE_AMT3; ramdisk_name = CONFIG_PARTITION_RAMDISK_AMT3; break; #endif default: gd->boot_mode = BOOT_MODE_NORMAL; ramdisk_name = CONFIG_PARTITION_RAMDISK; break; } #if defined(CONFIG_COMIP_FASTBOOT) if (key_code == CONFIG_KEY_CODE_FASTBOOT) { printf("goto fastmode!\n"); gd->fastboot = 1; } #endif } } printf("kernel name: %s, ramdisk name: %s\n", kernel_name, ramdisk_name); flash_partition_read(kernel_name, (u8*)(CONFIG_KERNEL_LOADADDR - IMAGE_ADDR_OFFSET), 0xffffffff); flash_partition_read(ramdisk_name, (u8*)(CONFIG_RAMDISK_LOADADDR - IMAGE_ADDR_OFFSET), 0xffffffff); #if defined(CONFIG_COMIP_FASTBOOT) && defined(CONFIG_LCD_SUPPORT) if (unlikely(gd->fastboot)) flash_partition_read(CONFIG_PARTITION_FASTBOOT_LOGO, (u8*)(unsigned int)gd->fb_base, CONFIG_FB_MEMORY_SIZE); else flash_partition_read(CONFIG_PARTITION_LOGO, (u8*)(unsigned int)gd->fb_base, CONFIG_FB_MEMORY_SIZE); #else flash_partition_read(CONFIG_PARTITION_LOGO, (u8*)(unsigned int)gd->fb_base, CONFIG_FB_MEMORY_SIZE); #endif printf("boot image end\n"); } #endif /* !CONFIG_COMIP_TARGETLOADER */
在这里首先需要确定内核镜像和ramdisk镜像的地址,然后初始化按钮监听,根据不同的按钮组合按键启动不同的镜像,包括正常启动,也就是说启动内核,启动android;启动recovery镜像;启动工厂模式等。将这些镜像数据读取进入flash中引导启动。
接着我们回到board.c
,程序接着运行,接着初始化misc,初始化中断,使能中断;同时在这里判断是否进去fastboot
模式。接着,进入我们的重点,也就是安全启动验证阶段。
#if defined(CONFIG_ENABLE_SECURE_VERIFY_FOR_BOOT) extern int secure_verify(void); extern void pmic_power_off(void); if(secure_verify()) { printf("Secure verify failed! Shutdown now!\n"); pmic_power_off(); } else { printf("Secure verify succeed!\n"); } #endif
在这里,我们刚刚说了,已经把相应的内核镜像数据和ramdisk镜像数据读入到flash中了。
那么uboot又是如何验证内核镜像和ramdisk镜像的呢?我们接着看。
我们先来看函数secure_verify()
函数。
int secure_verify(void) { getverifyimage(VERIFY_KERNEL); if (image_rsa_verify()) { printf("kernel verify failed!\n"); return 1; } else { printf("kernel verify ok!\n"); getverifyimage(VERIFY_RAMDISK); if(image_rsa_verify()) { printf("ramdisk verify failed!\n"); return 1; } else { printf("ramdisk verify ok!\n"); } } return 0; }
在这段代码中,我们可以看出,首先是获取内核镜像数据,然后进行rsa签名验证,接着获取ramdisk镜像数据,接着进行签名验证。
我们来看一下如何获取内核镜像数据或者是ramdisk镜像数据,也就是getverifyimage()
函数。
void getverifyimage(int whichimage) { int i; unsigned int *cfgInfor = image_data_all; int ret; if(whichimage == VERIFY_KERNEL) { image_data_all = CONFIG_KERNEL_LOADADDR - HEADINFOLEN; } else if(whichimage == VERIFY_RAMDISK) { image_data_all = CONFIG_RAMDISK_LOADADDR - HEADINFOLEN; } cfgInfor = (unsigned int *)image_data_all; ORIGIN_IMAGE_LEN = cfgInfor[0]; for(i=0; i<(256/4); i++) { RSASIGNATURE[i] = cfgInfor[i + (RSASIGNEDLEN / 4)]; } for(i=0; i<(524/4); i++) { RSAPUBKEYSTRU[i] = cfgInfor[i + (RSAPUBKEYLEN / 4)]; } ORIGIN_IMAGE_BASEADDR = &image_data_all[HEADINFOLEN]; #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) printf("image len:0x%x(%d)\n", ORIGIN_IMAGE_LEN, ORIGIN_IMAGE_LEN); dumphex("rsa pub key", RSAPUBKEYSTRU, 524/4); dumpint("rsa pub key", RSAPUBKEYSTRU, 524/4); #endif }
我们以内核启动验证为例进行讲解,ramdisk是一样的。我先来画一下内核镜像数据在flash中的分布,这样分析起代码来便会更容易理解。
首先我们需要了解的是,我们刷入的内核镜像并不是可运行的内核镜像,因为我们在真正的内核镜像之前加入了一个小小的1.5K的头,该头里面包含了内核的大小,经过私钥对内核签名后的签名,以及需要使用的公钥生成的一些属性。所以在该获取镜像的函数中,我们获取了所有的内核镜像数据,内核镜像大小,签名数据以及公钥属性数据。
下面我们就需要对其进行rsa验证。
/****************************************************** Let image to do RSA verify. If verify OK, return 0. Otherwise, return 1. *******************************************************/ int image_rsa_verify(void) { unsigned int value, i; unsigned char *image_sha256; unsigned char *signature; RSAPublicKey *public_key; SHA256_CTX ctx; updateNum = 0; value = rsaPubKey_sha256_verify(); if(value == 1) return 1; updateNum = 0; image_sha256 = (unsigned char*)SHA256_hash(ORIGIN_IMAGE_BASEADDR, ORIGIN_IMAGE_LEN, image_sha256, &ctx); #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) dumphex("current image hash", uboot_sha256, 32); #endif for(i=0; i<32; i++) ORIGIN_IMAGE_SHA[i] = image_sha256[i]; updateNum = 0; signature = (unsigned char*)RSASIGNATURE; public_key = (RSAPublicKey *)RSAPUBKEYSTRU; value = RSA_verify(public_key, signature, 256, ORIGIN_IMAGE_SHA, 32); if(value == 0) return 1; return 0; }
通过这个函数可以看到,对内核进行了两次验证,一次是通过函数rsaPubKey_sha256_verify()
进行验证,另外一个是通过RSA_verify
进行验证,我们先来看第一个:
/**************************************************** Use SHA256 to generate digest of RSA pub-key, which is 524 BYTES. Then compare the digest with the original digest which is store in the EFUSE. If the new digest equals original digest, it means RSA pub-key is right. Otherwise, means the RSA pub-key is wrong. *****************************************************/ int rsaPubKey_sha256_verify(void) { unsigned int *digest, origDigest[8], i, result; unsigned char *newDigest; SHA256_CTX ctx; digest = (unsigned int *)SHA256_hash(RSAPUBKEYSTRU, 524, newDigest, &ctx); #if 1 origDigest[0] = *RSA_SIGNATURE0; origDigest[1] = *RSA_SIGNATURE1; origDigest[2] = *RSA_SIGNATURE2; origDigest[3] = *RSA_SIGNATURE3; origDigest[4] = *RSA_SIGNATURE4; origDigest[5] = *RSA_SIGNATURE5; origDigest[6] = *RSA_SIGNATURE6; origDigest[7] = *RSA_SIGNATURE7; #else origDigest[0] = rsahash[0]; origDigest[1] = rsahash[1]; origDigest[2] = rsahash[2]; origDigest[3] = rsahash[3]; origDigest[4] = rsahash[4]; origDigest[5] = rsahash[5]; origDigest[6] = rsahash[6]; origDigest[7] = rsahash[7]; #endif #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) dumphex("pubkey hash", digest, 32); dumphex("read pubkey hash", origDigest, 32); #endif //new key and old key xor #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) printf("read pubkey hash:\n"); #endif for(i=0; i<8; i++) { #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) printf("%08x ", origDigest[i]); #endif result = ((origDigest[i]) ^ (digest[i])); if(result != 0) { #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) printf("ERROR! %s %d\n", __func__,__LINE__); #endif return 1; } } #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG) dumphex("pubkey hash", digest, 32); #endif return 0;
这里是对公钥进行sha256签名来验证公钥是否是对的,具体的函数实现不再学习。
接着通过对内核镜像数据进行sha256获取哈希,然后,使用公钥和签名进行签名验证,验证内核镜像数据是否是正确的。这样,通过这两步,必须两步都对,才能进行内核的正常加载和运行。
ramdisk镜像的签名验证也是如何,对内核镜像和ramdisk镜像签名验证之后,接着执行下面的操作,也就是执行do_bootm_linux()
函数,该函数的实现如下:
void do_bootm_linux(void) { bd_t *bd = gd->bd; void (*theKernel) (int zero, int arch, uint params); theKernel = (void (*)(int, int, uint))CONFIG_KERNEL_LOADADDR; params = (struct tag *)bd->bi_boot_params; params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size(tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next(params); params = comip_set_boot_params(params); params->hdr.tag = ATAG_NONE; params->hdr.size = 0; /* we assume that the kernel is in place */ printf("\nStarting kernel ...\n"); cleanup_before_linux(); theKernel(0, bd->bi_arch_number, bd->bi_boot_params); }
在这里实际上就是通过一个theKernel
函数指针,加载内核启动运行,这样,便进行内核的启动运行了。
这样,我们便把uboot的启动流程以及对内核和ramdisk进行启动验证的过程进行了一个整体的学习,其内部的RSA算法实现不再赘述。
- Uboot启动过程详解
- Uboot启动过程详解
- Uboot启动过程详解
- uboot启动过程详解
- Uboot启动过程详解
- Uboot启动过程详解
- Uboot启动过程详解
- Uboot启动过程详解
- Uboot启动过程详解
- Uboot启动过程详解
- uboot启动过程详解
- 210板uboot启动过程详解
- U-BOOT移植过程详解: UBOOT启动过程
- uboot的启动过程
- UBOOT启动过程分析
- UBOOT 启动过程
- uboot+linux启动过程
- uboot启动过程
- iOS架构师之路:制定代码规范
- Unxi环境高级编程读书笔记(2.2)
- Pyspark连接数据库
- 安全学习
- JDK8新特性_接口中也可以有方法
- uboot启动过程详解
- 我的个人博客
- Dubbo学习(二)服务注册
- 操作系统分类
- 4816: [Sdoi2017]数字表格
- Android Studio插件bug终结者——findbugs精解
- redis3.2.8安装和配置,及常用命令简介
- Codeforces 796D Police Stations (bfs+思维)
- RHEL / CentOS 7 install MongoDB