0 bx r0;bx r0 08007e24 6c69 08007e26 0800 08007e28 7d8d 08007e2a 0800 细心的观众会发现地址是0x0800'7e1c,比我们查到的0x0800'7e1d差了1,这是arm家族的遗留问题,因为的指令至少是半字对齐的(16位thumb指令集 or 32位),所以pc指针的l**是常为0的,为了充分利用寄存器,给pc的l**了一个重要的使命,那就是在执行分支跳转时,pc的l**=1,表示使用thumb模式,l**=0,表示使用arm模式,但在最新的cortex-m3内核上,只使用了thumb-2指令集挑大梁,所以这一位要常保持1,所以我们查到的地址是0x0800'7e1d(c=1100,d=1101),放心,我们的cm3内核会忽略掉l**(除非为0,那么会引起一个fault),从而正确跳转到0x0800'7e1c。 从0x0800'7e20处的加载指令,我们可以算出__iar_program_start所处的位置,就是当前pc指针(0x0800'7e24),再加上4,即0x0800'7e28处的所指向的地址——0x0800'7d8d(0x0800'7d8c),我们跟紧着跳转,__iar_program_start果然在这里: __iar_program_start: 08007d8c f000f88c bl __low_level_init 08007d90 2800 cmp r0, #0x0 08007d92 d001 beq __iar_init$$done 08007d94 f7ffffde bl __iar_data_init2 08007d98 2000 movs r0, #0x0 08007d9a f7fdfc49 bl main 我们看到iar提供了__low_level_init这个函数进行了“底层”的初始化,进一步跟踪,我们可以查到__low_level_init这个函数做了些什么,不是不是我们想象中的不可告人。 __low_level_init: 08007ea8 2001 movs r0, #0x1 08007eaa 4770 bx lr __low_level_init出乎想象的简单,只是往r0寄存器写入了1,就立即执行"bx lr"回到调用处了,接下来,__iar_program_start检查了r0是否为0,为0,则执行__iar_init$$done,若不是0,就执行__iar_data_init2。__iar_init$$done这个函数很简单,只有2句话,第一句是把r0清零,第二句就直接"bl main",跳转到main()函数了。不过既然__low_level_init已经往r0写入了1,那么我们还是得走下远路——看看__iar_data_init2做了些什么,虽然距离main只有一步之遥,不过这中间隐藏了编译器的思想,我们得耐心看下去。 __iar_data_init2: 08007d54 b510 push {r4,lr} 08007d56 4804 ldr r0, [pc, #0x10] 08007d58 4c04 ldr r4, [pc, #0x10] 08007d5a e002 b 0x8007d62 08007d5c f8501b04 ldr r1, [r0], #0x4 08007d60 4788 blx r1 08007d62 42a0 cmp r0, r4 08007d64 d1fa bne 0x8007d5c 08007d66 bd10 pop {r4,pc} 08007d68 7c78 08007d6a 0800 08007d6c 7c9c 08007d6e 0800 看来iar迟迟不执行main()函数,就是为了执行__iar_data_init2,我们来分析分析iar都干了些什么坏事~ 首先压r4,lr入栈,然后加载0x0800'7c78至r0,0x0800'7c9c至r4,马上跳转到0x0800'7d62执行r0,r4的比较,结果若是相等,则弹出r4,pc,然后立即进入main()。不过iar请君入瓮是自不会那么快放我们出来的——结果不相等,跳转到0x0800'7d5c执行,在这里,把r0指向的地址——0x0800'7c78中的值——0x0800'7d71加载到r1,并且r0中的值自加4,更新为0x0800'7c7c,并跳转到r1指向的地址处执行,这里是另一个iar函数:__iar_zero_init2: __iar_zero_init2: 08007d70 2300 movs r3, #0x0 08007d72 e005 b 0x8007d80 08007d74 f8501b04 ldr r1, [r0], #0x4 08007d78 f8413b04 str r3, [r1], #0x4 08007d7c 1f12 subs r2, r2, #0x4 08007d7e d1fb bne 0x8007d78 08007d80 f8502b04 ldr r2, [r0], #0x4 08007d84 2a00 cmp r2, #0x0 08007d86 d1f5 bne 0x8007d74 08007d88 4770 bx lr 08007d8a 0000 movs r0, r0 __iar_data_init2还没执行完毕,就跳转到了这个__iar_zero_inti2,且看我们慢慢分析这个帮凶——__iar_zero_inti2做了什么。 __iar_zero_inti2将r3寄存器清零,立即跳转到0x0800'7d80执行'ldr r2, [r0], #0x4',这句指令与刚才在__iar_data_init2见到的'ldr r1, [r0], #0x4'很类似,都为“后索引”。这回,将r0指向的地址——0x0800'7c7c中的值——0x0000'02f4加载到r2寄存器,然后r0中的值自加4,更新为0x0800'7c80。接下来的指令检查了r2是否为0,显然这个函数想放我我们,r2的值为2f4,我们又被带到了0x0800'7d74处,随后4条指令做了如下的事情: 1、将r0指向的地址——0x0800'7c80中的值——0x2000'27d4加载到r1寄存器,然后r0中的值自加4,更新为0x0800'7c84。 2、将r1指向的地址——0x2000'27d4中的值——改写为r3寄存器的值——0,然后r1中的值自加4,更新为0x2000'27d8。 3、r2自减4 4、检查r2是否为0,不为0,跳转到第二条执行。不为,则执行下一条。 这简直就是一个循环!——c语言的循环for(r2=0x2f4;r2-=4;r!=0){...},我们看看循环中做了什么。 第一条指令把一个地址加载到了r1——0x2000'27d4 是一个ram地址,以这个为起点,在循环中,对长度为2f4的ram空间进行了清零的操作。那为什么iar要做这个事情呢?消除什么记录么?用jlink查看这片内存区域,可以发现这片区域是我们定义的全局变量的所在地。也就是说,iar在每次系统复位后,都会自动将我们定义的全局变量清零0。 清零完毕后,接下来的指令"ldr r2, [r0], #0x4"将r0指向的地址——0x0800'7c84中的值——0加载到r2寄存器,然后r0中的值自加4,更新为0x0800'7c88。随后检查r2是否为0,这里r2为0,执行'bx lr'返回到__iar_data_init2函数,若是不为0,我们可以发现又会跳转至“4指令”处进行一个循环清零的操作。 读到这里,我们应该可以猜到iar的意图了:__iar_data_init2一开始加载了0x0800'7c78至r0,0x0800'7c9c至r4,[r0,r4]就是一段启动代码区,在这个区域内保存了要“处理”的所有地址与信息——执行的函数地址或者参数,实际上,这片区域也有一个名字,叫做:region$$table$$。在这个区域内,程序以r0为索引,r4为上限,当r0=r4,__iar_data_init2执行完毕,跳转至main()函数。 好了,保持我们这个猜想,继续跟踪我们的pc指针——我们回到了__iar_data_init2函数中,第一件事就是比较r0,r4的值,可惜的是,仍然不相等,我们又被带到了0x0800'7d5c,至此,我们应该能看出这是一个__iar_data_init2的“主循环”,这也验证了我们对iar意图的猜想~ __iar_data_init2中的“主循环”: 08007d5c f8501b04 ldr r1, [r0], #0x4 08007d60 4788 blx r1 08007d62 42a0 cmp r0, r4 我们可以等价写为:for(r0=0x0800'7c78,r4=0x0800'7c9c;r0!=r4;r0 =4){...} 此时,我们的r0为0x0800'7c88,经过“指令1”,r0变为0x0800'7c8c,r1为0x0800'7c55。我们来看看,7c55处,iar又要执行何种操作。 __iar_copy_init2: 08007c54 b418 push {r3,r4} 08007c56 e009 b 0x8007c6c 08007c58 f8501b04 ldr r1, [r0], #0x4 08007c5c f8502b04 ldr r2, [r0], #0x4 08007c60 f8514b04 ldr r4, [r1], #0x4 08007c64 f8424b04 str r4, [r2], #0x4 08007c68 1f1b subs r3, r3, #0x4 08007c6a d1f9 bne 0x8007c60 08007c6c f8503b04 ldr r3, [r0], #0x4 08007c70 2b00 cmp r3, #0x0 08007c72 d1f1 bne 0x8007c58 08007c74 bc12 pop {r1,r4} 08007c76 4770 bx lr 这是一个名为__iar_copy_init2的函数,他执行了什么"copy"操作呢? 首先压r3,r4入栈,然后跳转到0x0800'7c6c,从r0——region$$table$$base中取出参数0x238放入r3,接下来的指令大家应该都熟悉了,0x238不为0,所以我们被带至7c58处,再次从region$$table$$base中取出参数0x0800'7f14放入r1,从region$$table$$base取出参数0x2000'2ac8放入r2处。细心的观众应该能察觉这和__iar_zero_init2中取参数的几乎一样:先取出大小,随后取出了地址——只不过这里多出了1个地址,没错这就是"copy",随后的指令 08007c60 f8514b04 ldr r4, [r1], #0x4 08007c64 f8424b04 str r4, [r2], #0x4 08007c68 1f1b subs r3, r3, #0x4 08007c6a d1f9 bne 0x8007c60 则是另一个“4指令”,指令1将r1指向地址的数据读到r4,指令2将r2指向地址的数据改写为r4的数据,指令3、4是完成一个循环。 20210311