对U-BOOT的第1阶段代码的分析
都知道U-BOOT分为两个阶段。 第一阶段是(~/cpu/arm920t/start.S中)在FLASH上运行(一般情况下),完成对硬件的初始化,包括看门狗,中断缓存等,并且负责把代码搬移到SDRAM中(在搬移的时候检查自身代码是否在SDRAM中),然后完成C程序运行所需要环境的建立,包括堆栈的初始化等,最后执行一句跳转指令: ??????? ldr?pc,_start_armboot ??????? _start_armboot:?.word start_armboot, 进入到/lib_arm/board.c中的函数void start_armboot (void)。从此就进入了第二阶段。这是在很多资料上都有讲述的,所以勿需多言了。 ? ??? 现在对于第一阶段有几个问题,以前我一直是没有搞明白的,既然在FLASH中的代码是把自己拷贝到SDRAM中,那么在S3C2410的内存地址空间,就有两份的启动代码,第一份就是在FLASH中,第二份就是在SDRAM中。 根据链接脚本文件(~/board/smdk2410/u-boot.lds) OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm") ?. = ALIGN(4); ?. = ALIGN(4); ?. = ALIGN(4); ?. = ALIGN(4); ?. = .; ?. = ALIGN(4); }
??????? _start_armboot:?.word start_armboot 此条语句后,并没有跳转到SDRAM中的void start_armboot (void),而是跳转到了FLASH中的void start_armboot (void)中。 所以就出现了这样的矛盾,在FLASH中有一段代码把自己拷贝到SDRAM中,产生了两份UBOOT可执行的指令流,但是最后却没有跳转到SDRAM中去运行以提高指令执行的速度。 ? 产生以上的认识是基于以下几个认识(肯定是错误的): ? 1. *.text中的所有地址标号(在链接时确定)是从0地址开始生成的。 ????? 实际上在arm-linux-ld 执行时,原来定义的0x0地址被更新为TEXT_BASE定义的地址。 ? 2. relocate:????/* relocate U-Boot to RAM???? */ ?? ldr?r2,_armboot_start 如果不是出于调试阶段,这段搬移代码中的r0和r1肯定不相等的,r0=#0,r1=#TEXT_BASE: 0x33F80000(在./board/smdk2410/config.mk中),所以执行代码的自身拷贝与搬移。 ? 注意:在GNU中: adr?r0,_start?作用是获得 _start?的实际运行所在的地址值,而ldr?r1,_TEXT_BASE?为获得地址_TEXT_BASE中所存放的数据, 其中adr?r0,_start翻译成 add r0,(PC+#offset),offset 就是 adr?r0,_start?指令到_start?的偏移量,在链接时确定,这个偏移量是地址无关的。 而 ldr?r1,_TEXT_BASE 指令表示以程序相对偏移的方式加载数据,是索引偏移加载的另外一种形式,等同于ldr?r1,[PC+#offset],offset 是 ldr?r1,_TEXT_BASE 到 _TEXT_BASE 的偏移量。注意这种用法并不是伪指令,伪指令的特征是 ldr?r1,=expr/lable_expr。对于LDR伪指令,ADS的情况有些不一样(细微差别),在ADS中的情况可以参考杜春雷<ARM体系结构与编程>144页。 ? ? 比较一下: ? add r0,(PC+#offset):(PC+#offset)是相对地址,表示把本指令上溯或下溯offset处的地址加载到 r0; ldr?r1,[PC+#offset]:[PC+#offset]也是相对地址,表示把偏移offset处的地址上的数据加载到 r1; 现在继续: ??? 刚才分析所得到的矛盾,肯定是在认识上存在的偏差,经过把U-BOOT进行make后,从所生成的两个.map文件来看(~/u-boot.map和Systen.map),所有的地址标号都是从0x33f80000开始的,就是从SDRAM的高地址开始,等于TEXT_BASE的值,也就是说,链接器是从0x33F80000开始来链接所编译生成的目标文件的,而不是从0地址开始,经过查看,start_armboot=0x33f80d9c,就是说void start_armboot (void)函数的入口地址在SDRAM中(链接器决定),所以执行 ??????? ldr?pc,_start_armboot ????????_start_armboot:?.word start_armboot, PC指针肯定就指向了SDRAM中,换句话就是说进入到SDRAM中了,对于ldr?pc,_start_armboot,其仍然是GNU中使用程序相对偏移的方式加载数据,翻译一下就是ldr?pc,[pc+pc到_start_armboot的偏移值],结果就把_start_armboot地址中的数start_armboot放入pc中完成了跳转,而 start_armboot 的值(函数地址)是在链接时就确定了,是相对于 TEXT_BASE 的。因为在整个UBOOT的阶段1中所有的寻址都是相对位置的寻址(虽然链接器认为是阶段1的代码是从地址0x3ff80000中开始链接的),把阶段1的代码放在0地址开始的FLASH中也是可以正确的运行的,如果ARM的复位向量是在0x00000001(假设),那么把代码烧写到从0x00000004处开始的地方,上电时也可以正确的运行(假设ARM的复位向量是在0x00000004成立),当然ARM的复位向量不在这里,只是以此假设来说明以上的对于阶段1的分析。 ??? 现在最后一个矛盾就是链接脚本(~/board/smdk2410/u-boot.lds)所描述的链接地址与实际的链接地址不相同的问题,因为根据链接脚本,所有的地址标号应该从0地址开始计数的,然而不是。经过查找Makefile文件, 在顶层的Makefile文件中,在166行中链接是的链接命令: $(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) /, 其中的LDFLAGS在定义在顶层的config.mk中的145行:LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS), 最关键的就是 -Ttext $(TEXT_BASE)命令了,他的含义就是说,起始地址在TEXT_BASE,而TEXT_BASE在~/board/smdk2410/config.mk中TEXT_BASE = 0x3FF80000; ? 到此就弄清楚为什么链接从0x3ff80000开始的了,至于链接脚本,其主要作用是用来指明各个*.o文件的顺序,如入口地址标号(_start)等,以及使两个地址标号得到当前的地址 ????__u_boot_cmd_start = .;??? *.u_boot_cmd段的起始地址 ??? .u_boot_cmd : { *(.u_boot_cmd) } 以供C程序使用。 __u_boot_cmd_start和__u_boot_cmd_end可以作为全局的一个常数使用 。 ? ? 总结: ??? 因为-Ttext $(TEXT_BASE)命令的使用,链接器把UBOOT从地址0x3ff80000开始连接,在第一阶段中,所有使用的目标地址寻址都是使用当前PC值加减偏移量的方法,所以把UBOOT烧写到0地址开始的FLASH中,不影响第一阶段的正确执行。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |