讲解ARM汇编指令的书籍中,很多在讲到B指令的时候会说这条指令时一条绝对跳转指令,这是不负责任的说法。这条指令的迷惑之处在于,B指令的汇编形式为:
b label : :label: mov r1,r2 mov r2,r3
从汇编指令的书写形式上,确实是一条绝对跳转指令,但是实际上汇编在将“B label”这条指令翻译为机器指令的时候,会计算label相对于当前PC的偏移,将这个偏移值放到机器码中,所以实际上,B跳转指令为相对跳转指令。用相对跳转指令就可以编写位置无关的代码。
什么是位置无关的代码,其实位置无关的意思就是说编译好的一段代码可以放到内存的任何位置运行而结果不会变。了解arm-linux内核中中断向量表建立过程的同学大概都熟悉以下的一段代码:
.globl __vectors_start__vectors_start: swi SYS_ERROR0 b vector_und + stubs_offset ldr pc, .LCvswi + stubs_offset b vector_pabt + stubs_offset b vector_dabt + stubs_offset b vector_addrexcptn + stubs_offset b vector_irq + stubs_offset b vector_fiq + stubs_offset.globl __vectors_end__vectors_end:
上面的代码是arm-linux中中断向量表中的内容,比如:中断发生后悔就会跳转到标红的那条指令开始去执行中断处理过程。在我们编译内核的时候,上面的代码有一个链接地址,但是大家都知道,在内核中中断向量表位于0xFFFF000开始的位置,内核在early_trap_init函数中会将上面的代码“搬移”到0xFFFF000开始的位置。问题就来了,b指令是怎么找到目的地址的?
下面的图很详细的解释了这个问题:
上图下面的线显示了代码被“搬移”之前相对位置,上面的线显示了“搬移”后代码的相对位置。假设现在发生了一个数据预取异常,PC会先跳转到t1的位置执行b vector_irq + stubs_offset 然后跳转到E(vector_dabt)去执行对应的处理语句。我们怎样在写代码的时候确定将“b vector_irq + stubs_offset ”这样的代码放到中断向量表中,然后在进行代码“搬移”工作之后,发生异常事件的时候,在固定的位置执行一条指令,这条指令中包含了相对于当前PC值得偏移,进而跳转到一个新的位置开始执行后面的处理。要记住,B指令实际上是一条相对跳转指令,而且代码在编译的时候与“搬移”之后的代码在相对位置上是确定的,这里就有了位置无关的代码的用武之地。
这样解释之后,大家可以静下心来,看着linux中的源码(arch/arm/kernel/entry-armv.S),读读上面的英文(很简单),至于上面的计算公式,我相信一个初中能够正常毕业的人就能看懂。
当你看到offset=vector_dabt+stub_offset-t2的时候,你要这样理解,内核编译到t2这条指令的时候(b vector_dabt + stubs_offset )其实需要确定的是代码“搬移”之后的一个偏移,这个偏移是固定的(上图中的L1+L2,而且这个偏移在执行的时候,也就是代码“搬移”之后才有意义),偏移加上当前的PC(t2)的值是vector_dabt + stubs_offset,所以中断向量表中的这一项就写成了“b vector_dabt + stubs_offset ”