ARM中通过ldr pc, xxx地址,来跳转到对应的绝对地址去执行

先来了解一下ldr指令:

LDR 是ARM中的指令,也是伪指令。
当用 LDR r0, =num
编译时,编译器会根据num是否能表示为立即数,决定用 ldr 指令或者是mov或mvn指令。
当num能表示为立即数,就将它翻译成一条mov或mvn指令。不能,编译器会将num存在一个内存单元中,然后再用一条ldr指令加载这个内存单元的的值到r0中。
LDR r0, label  和 LDR r0, =label的区别:
LDR r0, =label 会把label表示的值加载到r0中,相当于直接赋值。
LDR r0, label  会把label当做地址,把label指向的地址中的值加载到r0中,相当于指针操作.
譬如 label的值是 0x8000, LDR r0, =label会将 0x8000加载到r0中,而LDR r0, label则会将内存0x8000处的值加载到r0中。
ADR 和 ADRL 伪指令:用于将一个地址加载到寄存器中。
ADR为小范围的地址读取伪指令。ADR指令将基于PC相对偏移的地址值读取到寄存器中。
在汇编编译源程序时,ADR伪指令被编译器替换在一条合适的指令,通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,
若不能使用一条指令实现,则产生错误。其能加载的地址范围,当为字节对齐时,是-1020~1020,当为非字对齐时在-255~255之间。
ADRL是中等范围的地址读取指令。会被编译器翻译成两条指令。如果不能用两条指令表示,则产生错误。
ADRL能加载的地址范围当为非字节对齐时是-64K~64K之间;当为字节对齐时是-256K~256K之间。 

在学习ARM裸机实时,对于程序的链接地址和程序的运行地址是很重要的,通常在程序的前几k的代码中使用的都是相对跳转指令b,bl,前面几k的代码的主要任务就是将整个程序复制到对应的链接地址上去的。这个过程就是代码的重定位,在重定位之前的代码与位置无关(是位置无关码);重定位完成以后就可以跳转到新的地址中去运行了,通常使用 ldr pc, xxx,来实现跳转。这里先来看看三段汇编代码,来理解uboot中重定位后,跳转的实现。und_addr,do_und都是标记,也就是地址。

  • ldr pc, und_addr(这是uboot中的用法)
  • ldr pc, do_und
  • ldr pc, =do_und(这是一条伪指令)
.text
.global _start

_start:
	b reset

	ldr pc, und_addr        //1
	//ldr pc, do_und        //2
	//ldr pc, =do_und       //3
	
und_addr:
	.word do_und
	
do_und:
	//设置未定义指令异常是的栈
	ldr sp, =0x34000000
	//保存现场
	stmdb sp!, {r0-r12, lr}
	//0x600000DB
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	//输出sprs 0x600000D3
	mrs r0, spsr
	bl putHex
	
	ldmia sp!, {r0-r12, pc}^

分别编译这三种情况,然后反汇编查看汇编代码。

 ldr pc, und_addr(这是uboot中的用法)

30000000 <_start>:
30000000:	ea00000e 	b	30000040 <reset>
30000004:	e51ff004 	ldr	pc, [pc, #-4]	; 30000008 <und_addr>

30000008 <und_addr>:
30000008:	3000000c 	.word	0x3000000c

3000000c <do_und>:
3000000c:	e3a0d30d 	mov	sp, #872415232	; 0x34000000
30000010:	e92d5fff 	push	{r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
30000014:	e10f0000 	mrs	r0, CPSR
30000018:	e59f109c 	ldr	r1, [pc, #156]	; 300000bc <halt+0x4>
3000001c:	eb0000dd 	bl	30000398 <printException>

 ldr pc, do_und

30000000 <_start>:
30000000:	ea00000e 	b	30000040 <reset>
30000004:	e59ff000 	ldr	pc, [pc, #0]	; 3000000c <do_und>

30000008 <und_addr>:
30000008:	3000000c 	.word	0x3000000c

3000000c <do_und>:
3000000c:	e3a0d30d 	mov	sp, #872415232	; 0x34000000
30000010:	e92d5fff 	push	{r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
30000014:	e10f0000 	mrs	r0, CPSR
30000018:	e59f109c 	ldr	r1, [pc, #156]	; 300000bc <halt+0x4>

 ldr pc, =do_und(这是一条伪指令)

30000000 <_start>:
30000000:	ea00000e 	b	30000040 <reset>
30000004:	e59ff0b0 	ldr	pc, [pc, #176]	; 300000bc <halt+0x4>

30000008 <und_addr>:
30000008:	3000000c 	.word	0x3000000c

3000000c <do_und>:
3000000c:	e3a0d30d 	mov	sp, #872415232	; 0x34000000
30000010:	e92d5fff 	push	{r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
30000014:	e10f0000 	mrs	r0, CPSR
30000018:	e59f10a0 	ldr	r1, [pc, #160]	; 300000c0 <halt+0x8>
3000001c:	eb0000dd 	bl	30000398 <printException>
30000020:	e14f0000 	mrs	r0, SPSR
...
...
...
300000b8 <halt>:
300000b8:	eafffffe 	b	300000b8 <halt>
300000bc:	3000000c 	.word	0x3000000c
300000c0:	3000002c 	.word	0x3000002c
300000c4:	4c000014 	.word	0x4c000014
300000c8:	4c000004 	.word	0x4c000004

代码中为什么要在定义下面这段

und_addr:
    .word do_und

主要是为了占领一个位置,不让这个地址放大CPU的片内srom以外了,不然就找不到他了。

在ldr pc, und_addr中可以将pc=3000000c,实现跳转

ldr pc, do_und,实现的是pc=e3a0d30d,是错误的。

ldr pc, =do_und,可能可以实现pc=3000000c,这还要取决于代码是超过了片内srom的大小ldr    pc, [pc, #176]    ; 300000bc <halt+0x4>,不可靠,也无法预测。

猜你喜欢

转载自blog.csdn.net/qq1113231395/article/details/83650069
PC:
今日推荐