u-boot2020.04移植(5、u-boot重定位)

本文深入探讨U-Boot的重定位过程,包括重定位前的准备工作、代码和数据段的拷贝、动态符号表的调整及向量表的重定位。通过分析,揭示了U-Boot如何适应不同内存地址,确保正确访问函数和变量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果想了解重定位是怎么一回事,可以参考这位老哥的博客:uboot的relocation原理详细分析_做一个有技术追求的人-CSDN博客_uboot原理

第一阶段结束,便开始u-boot的重定位(这里有点小疑惑,在前面将u-boot从SD卡拷贝到DDR中时,我就是拷贝到链接地址的,也就是说那个时候u-boot的运行地址就和链接地址一致了,可以正常运行完整个u-boot,这里又再次重定位,只不过是为了将u-boot搬到内存的高地址去运行,网上搜了一下,有说是因为防止内核解压的时候覆盖了u-boot本身,那也就是说只要解压内核不会覆盖u-boot这部分内存,理论上就不需要再次重定位了,等之后有时间我测试一下,去掉这部分重定位代码,看u-boot是否还能正常运行,以验证自己的想法------经过后面的验证,u-boot运行在链接地址后,后面的重定位确实可以去掉了,这里的重定位主要就是进行内存规划),关于这部分新版u-boot和以前的不太一样,个人感觉新版的设计更加简单便于理解。

现在回到arch/arm/lib/crt0.S文件:

重定位前的准备工作

/*arch/arm/lib/crt0.S*/

/*GD_START_ADDR_SP定义在include/generated/generic-asm-offsets.h文件(这个文件是u-boot自动生成的,而且过程也是有点高端,
感兴趣的可以网上了解一下,贴一个我看过的链接:http://blog.chinaunix.net/uid-25000873-id-4134037.html),
	在这里表示在gd这个数据结构中start_addr_sp的偏移值,前面我们也知道r9这个寄存器绑定的就是gd全局变量这个指针,
	所以相当于将gd->start_addr_sp里面的值加载到r0中*/
	ldr	r0, [r9, #GD_START_ADDR_SP]	/* sp = gd->start_addr_sp */
	bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
	/*设置栈指针*/
	mov	sp, r0
	/*获取gd->bd的位置*/
	ldr	r9, [r9, #GD_BD]		/* r9 = gd->bd */
	/*在内存图上可以看出,新的gd结构在bd结构的下面紧挨着,所以减去gd的大小就是新的gd起始地址*/
	sub	r9, r9, #GD_SIZE		/* new GD is below bd */

	/*这里将here标号的地址值读取到lr中,注意这里是adr,关于adr与ldr的区别可自行网上搜索*/
	adr	lr, here
	/*将重定位偏移值加载到r0寄存器中*/
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off */
	/*链接寄存器加上偏移值后,lr的地址就变成重定位后的地址了*/
	add	lr, lr, r0
	/*未定义*/
#if defined(CONFIG_CPU_V7M)
	orr	lr, #1				/* As required by Thumb-only */
#endif
	/*将重定位地址加载到r0中,作为参数传给relocate_code*/
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */
	/*执行重定位*/
	b	relocate_code
	/*从relocate_code回来后,就直接运行在重定位后的u-boot中了,here标号已经是重定位后那个here标号了*/
here:

/*
省略
*/

上面的步骤结束后,sp栈指针就设置成gd->start_addr_sp指向的地方了,而r9之前绑定的那个gd也变成重定位后的新地址的gd结构了,内存图如下:

图1

执行重定位

/*arch/arm/lib/relocate.S*/

ENTRY(relocate_code)
	/*从链接脚本可知,主要是将代码段和数据段拷贝到新的地址*/
	ldr	r1, =__image_copy_start	/* r1 <- SRC &__image_copy_start */
	/*r0是重定位地址,r1是目前u-boot运行地址,这里也有个疑问,要是u-boot运行地址现在和链接地址不一样,
	它要拷贝的地方可能根本就是空的或者是错的,那这里不就有问题吗?这个我之后得验证一下*/
	subs	r4, r0, r1		/* r4 <- relocation offset */
	/*上一句subs指令会影响CPSR的状态,这里如果r4的值为0,表示r0和r1是一样的,不需要再重定位*/
	beq	relocate_done		/* skip relocation */
	/*将拷贝结束地址放到r2中*/
	ldr	r2, =__image_copy_end	/* r2 <- SRC &__image_copy_end */

copy_loop:
	/*以r1为起始地址(也就是目前u-boot的起始地址),加载两个字到r10,r11中*/
	ldmia	r1!, {r10-r11}		/* copy from source address [r1]    */
	/*以r0为起始地址(也就是重定位的新地址),加载r10,r11的值到r0,r0+4中*/
	stmia	r0!, {r10-r11}		/* copy to   target address [r0]    */
	/*比较是否读取到结束地址*/
	cmp	r1, r2			/* until source end address [r2]    */
	/*一直循环,直到拷贝结束*/
	blo	copy_loop

	/*
	 * fix .rel.dyn relocations
	 */
	 /*下面这些才是重定位最重要的,上面虽然将u-boot拷贝到新地址了,但是我们要使用的变量,
	 和一些数据这些的地址(这里说地址可能有些不太准确)还是没变的,重定位后的u-boot应该访问的是新地址的变量和数据等,
	 下面这段代码就是干这个事的,我们给新地址的u-boot每一个要重定位的地址加上一个偏移,这样就能正确访问了*/
	ldr	r2, =__rel_dyn_start	/* r2 <- SRC &__rel_dyn_start */
	ldr	r3, =__rel_dyn_end	/* r3 <- SRC &__rel_dyn_end */
fixloop:
	/*以r2为起始地址(也就是动态符号表的起始地址),加载两个字分别到r0,r1中
    r0为存放待重定位内容的地址,r1为类型*/
	ldmia	r2!, {r0-r1}		/* (r0,r1) <- (SRC location,fixup) */
	/*取出r1中数据的低8位*/
	and	r1, r1, #0xff
	/*R_ARM_RELATIVE用来检查这个符号是不是需要被重定位*/
	cmp	r1, #R_ARM_RELATIVE
	/*不需要的话就跳过*/
	bne	fixnext

	/* relative fix: increase location by offset */
	/*r4是重定位偏移,这里就是要加上这个偏移*/
	add	r0, r0, r4
	/*取出加上重定位偏移后的以r0为地址内的数据存到r1中,这里取出的也就是新的u-boot里面
对应位置的值*/
	ldr	r1, [r0]
	/*给新的u-boot里面对应的值加上重定位偏移*/
	add	r1, r1, r4
	/*将加了重定位偏移后的值写回,这样新的u-boot就能正确访问函数和变量了*/
	str	r1, [r0]
fixnext:
	cmp	r2, r3
	blo	fixloop

relocate_done:

/*未定义*/
#ifdef __XSCALE__
	/*
	 * On xscale, icache must be invalidated and write buffers drained,
	 * even with cache disabled - 4.2.7 of xscale core developer's manual
	 */
	mcr	p15, 0, r0, c7, c7, 0	/* invalidate icache */
	mcr	p15, 0, r0, c7, c10, 4	/* drain write buffer */
#endif

	/* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
	mov	pc, lr
#else
	/*重定位结束,前面lr保存的是重定位后的here标号的地址,这里返回后,就跳转到重定位后的u-boot代码中了*/
	bx	lr
#endif

ENDPROC(relocate_code)

接下来重定位向量表,这个很简单,就是操作协处理器

/*arch/arm/lib/relocate.S*/

	.section	.text.relocate_vectors,"ax",%progbits
	.weak		relocate_vectors

ENTRY(relocate_vectors)

/*未定义*/
#ifdef CONFIG_CPU_V7M
	/*
        省略
    */
#else
/*定义了,前面也讲过,这个就是向量表基地址寄存器*/
#ifdef CONFIG_HAS_VBAR
	/*
	 * If the ARM processor has the security extensions,
	 * use VBAR to relocate the exception vectors.
	 */
	 /*这个还是操作协处理器,和start.S里面设置重设向量表是一样的*/
	ldr	r0, [r9, #GD_RELOCADDR]	/* r0 = gd->relocaddr */
	mcr     p15, 0, r0, c12, c0, 0  /* Set VBAR */
#else
	/*
        省略
    */
#endif
#endif
	bx	lr

ENDPROC(relocate_vectors)
/*arch/arm/cpu/armv7/start.S*/

ENTRY(c_runtime_cpu_setup)
/*
 * If I-cache is enabled invalidate it
 */
 /*因为u-boot又已经重定位了,现在流水线和缓存里面的内容都是旧的,需要清掉重新填充*/
#if !CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
	mcr	p15, 0, r0, c7, c5, 0	@ invalidate icache
	mcr     p15, 0, r0, c7, c10, 4	@ DSB
	mcr     p15, 0, r0, c7, c5, 4	@ ISB
#endif

	bx	lr

ENDPROC(c_runtime_cpu_setup)

然后是清bss段,这是C语言运行环境需要的,然后执行board_init_r

/*arch/arm/lib/crt0.S*/

/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	/* we still call old routine here */
#endif
/*CONFIG_SPL_BUILD未定义,要执行*/
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)

/*CONFIG_SPL_EARLY_BSS未定义,要执行*/
#if !defined(CONFIG_SPL_EARLY_BSS)
	/*清BSS段,这是一个宏定义,执行了一个memset,就不追进去了*/
	SPL_CLEAR_BSS
#endif

/*未定义*/
# ifdef CONFIG_SPL_BUILD
	/* Use a DRAM stack for the rest of SPL, if requested */
	bl	spl_relocate_stack_gd
	cmp	r0, #0
	movne	sp, r0
	movne	r9, r0
# endif

/*未定义,要执行,就是点亮一颗led灯,执不执行都无所谓*/
#if ! defined(CONFIG_SPL_BUILD)
	bl coloured_LED_init
	bl red_led_on
#endif
	/*将gd_t这个数据结构放到r0中,将重定位地址放到r1中,作为参数传给board_init_r*/
	/* call board_init_r(gd_t *id, ulong dest_addr) */
	mov     r0, r9                  /* gd_t */
	ldr	r1, [r9, #GD_RELOCADDR]	/* dest_addr */
	/* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
	ldr	lr, =board_init_r	/* this is auto-relocated! */
	bx	lr
#else
	/*调用board_init_r,进入u-boot第二阶段*/
	ldr	pc, =board_init_r	/* this is auto-relocated! */
#endif
	/* we should not return here. */
#endif

到了这里,u-boot重定位就结束了,其实u-boot重定位的知识远不止代码中的这点儿,有很多的细节,建议读者有时间的话,多去网上查阅一下相关的资料,比如位置无关代码与位置有关代码,为什么需要重定位等问题,这样对重定位才有更加深入和深刻的理解。

fixloop过程的补充:

先来看一下重定位符号表的内容:

arm-linux-gnueabi-readelf -r u-boot | less

 左边第一列(偏移量)就是存放需要重定位的函数或全局变量的地址,以第一行为例,来找一下0x34800020这个地址的含义。

我们将编译生成的u-boot文件进行反汇编:

arm-linux-gnueabi-objdump -S u-boot | less

根据上图可以得知,0x34800020(_undefined_instruction)存放的是函数undefined_instruction的实现地址,而_undefined_instruction是异常向量地址,是个相对地址,要想新的u-boot能够正确的进行函数跳转,就需要修改新的u-boot中对应的undefined_instruction标号的地址,将其加上与新运行地址的偏移值即可:

 左边我叉掉的地址就是链接地址,u-boot被搬运前运行地址也在这里,而u-boot被搬运后,链接地址还是这个,但是运行地址已经变了,里面的全局变量和函数等使用绝对地址的就需要重新修正,即下图绿色的示例内容。

 再次对照代码来看一下:
    add    r0, r0, r4    ====> 4ff96020 = 34800020 + offset
    ldr    r1, [r0]         ====>  r1 = 34800060 = *((unsigned int *)4ff96020)
    add    r1, r1, r4    ====>  4ff96060 = 34800060 + offset
    str    r1, [r0]         ====>  *((unsigned int *)4ff96020) = 4ff96060

通过u-boot的md工具将内存读出,进行对比可以验证上面的结果:

欢迎扫码关注我的微信公众号

漫长当下
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值