uboot启动过程详解

uboot启动过程详解

大家好,又见面了,我是全栈君。

在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算法实现不再赘述。

转载于:https://www.cnblogs.com/bobo1223/p/7287452.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/108567.html原文链接:https://javaforall.net

(0)
上一篇 2022年3月2日 下午2:00
下一篇 2022年3月2日 下午3:00


相关推荐

  • 5 tips for using Google Buzz on your phone

    5 tips for using Google Buzz on your phonehttp://googlemobile.blogspot.com/2010/03/5-tips-for-using-google-buzz-on-your.html 

    2022年10月16日
    6
  • Java中CharSet字符集

    Java中CharSet字符集java nio charset 包中提供了 Charset 类 它继承了 Comparable 接口 还有 CharsetDecod CharsetEncod 编码和解码的类 它们都是继承 Object 类 Java 中的字符使用 Unicode 编码 每个字符占用两个字节 16 个二进制位 向 ByteBuffer 中存放数据的时候需要考虑字符的编码 从中读取的时候也需要考虑字符的编码方式 也就是编码和解码

    2026年3月18日
    2
  • StarUML入门教程

    StarUML入门教程声明 原文链接 StarUML 使用简明教程 作者 栾小邑 StarUML 入门教程 StarUML 简称 SU 是一种创建 UML 类图 生成类图和其他类型的统一建模语言 UML 图表的工具 StarUML 是一个开源项目之一发展快 灵活 可扩展性强 zj StarUML 官方下载地址 http staruml io downloadStar 主界面创建工程在启动 starUML 时 系统会默认帮我们创建一个工程 如果这个工程不是你想要的工程 你可以点击 File gt

    2025年6月30日
    5
  • 【AI】教你如何为 OpenClaw 安装 Skills

    【AI】教你如何为 OpenClaw 安装 Skills

    2026年3月13日
    2
  • MATLAB(2)–MATLAB矩阵的表示

    MATLAB(2)–MATLAB矩阵的表示MATLAB–MATLAB矩阵的表示矩阵的建立冒号表达式linspace结构矩阵单元矩阵最后矩阵的建立利用直接输入法建立矩阵:将矩阵的元素用中括号括起来,按矩阵的顺序输入各元素,同一行的各元素之间用逗号或者空格分隔,不同的元素之间用分号分隔。利用已建好的矩阵建立更大的矩阵:一个大矩阵可以由已经建立好的小矩阵拼接而成。可以用实部矩阵和虚部矩阵构成复数矩阵。冒号表达式冒号是一个重要的运算符,利用它可以产生行向量。冒号表达式的一般格式为:e1:e2:e3其中,e1为初始值,e2为步长,e3为终

    2022年6月25日
    33
  • nginx配置url重定向_Nginx 跳转到www二级域名,域名重定向配置方法。[通俗易懂]

    nginx配置url重定向_Nginx 跳转到www二级域名,域名重定向配置方法。[通俗易懂]阿里(万网)支持显性URL配置,也就是:将域名指向一个http(s)协议地址,访问域名时,自动跳转至目标地址(例如:将sojson.com显性转发到www.sojson.com后,访问sojson.com时,地址栏显示的地址为:www.sojson.com)。其实就是重定向,我以前觉得阿里的挺好用的,今天阿里给我说服务器给攻击了,不提供服务。下面是我和阿里的客服对话,我对阿里的服务表示有点失望。我…

    2022年5月21日
    122

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号