更多内容请访问blog.sina.com.cn/ifreecoding
接上一篇“从 ARM汇编指令机器码解释一些问题”
当程序的编译地址与运行地址不一致时,使用相对跳转指令和绝对跳转指令就需要注
意了,本文档将讲述其中原因。
另有一种 B指令是 BX,它的用法是
BX Rm
其中 Rm是通用寄存器,例如 BX R0,它的作用是跳转到 R0中所存储的地址,它的指
令格式如下
可以看到它最后的 4位所存储的就是 Rm寄存器的编号,共有 2^4=16种,对应 R0~R15。
R寄存器可以存放 32bits 的数据,可以表示全空间地址范围,因此 BX指令是一种绝对跳转
指令,并且可以跳转到 32位机的全部地址空间。
BL是相对跳转指令,BX是绝对跳转指令,它们虽然都是跳转指令,实现跳转功能,
除了可跳转的距离不一样之外,在某些场合还是有限制的。比如 linux内核程序编译的基址
是 0xC0000000,内核程序认为它们都是在 0xC0000000以上空间运行的,而内核在进入页
机制之前都是运行在以 0x00000000为基址的内存空间,编译空间与运行空间不一致这样不
会有问题么?
答案
八年级地理上册填图题岩土工程勘察试题省略号的作用及举例应急救援安全知识车间5s试题及答案
当然是不会有问题,否则 linux怎么会跑起来,这其中就涉及到程序运行
的相对寻址和绝对寻址的问题了。
我们知道程序分为顺序执行和跳转执行,顺序执行就是一条指令执行完再去执行它下面
的一条指令,而跳转又分为相对跳转和绝对跳转,正如上述的 BL和 BX。顺序执行和相对
跳转执行都是在当前指令的基础上加上相对的偏移量找到下条指令的,而绝对跳转则是需要
找到实实在在的地址。
我们先来看一个例子,然后再说明上述情况是怎么实现的。
比如说我坐火车从沈阳到大连,我在 2号车厢,餐车在 7号车厢,
� 如果火车停在沈阳站还没开的时候,我要去餐车,我只要走过 5节车厢就可以到了。
� 如果火车已经到达大连了,那么我还是只要经过 5节车厢就可以到餐车了。
� 如果火车已经到达大连了,但你告诉我说火车还停在沈阳,那么我还是只需要经过 5
节车厢就可以到餐车了。
上面这 3种情况都是使用了相对地址,无需知道火车所处的绝对地址。
其中第 3种情况就可以解释编译地址与运行地址不一致的问题。
� 火车已经到达大连——代码实际运行在 0x00000000地址。
� 你告诉我说火车还停在沈阳,我就认为火车停在沈阳——代码被编译到 0xC0000000,
代码认为它们运行在 0xC0000000。
� 从我所在位置经过 5节车厢到达餐车——从当前指令位置经过相对偏移量找到下条指
令。
只要将被编译到 0xC0000000地址的代码放到 0x00000000地址开始执行,如果它们只
使用顺序执行或者相对跳转执行方式就可以正常运行,但如果使用了绝对寻址,那么程序就
跑飞了。
我们参照下面这段伪代码来说明这个情况。
指令编号 指令功能
指令 1: 顺序执行
指令 2: 顺序执行
指令 3: 相对跳转到指令 5
指令 4: 顺序执行
指令 5: 顺序执行
指令 6: 绝对跳转到指令 8
指令 7: 顺序执行
指令 8: 顺序执行
在编译、链接的时候,这段程序被告知放在 0xC0000000地址空间,编译结果为(每条
指令以 4字节计算):
指令地址 指令编号 指令功能 下条指令地址
0x00000000 指令 1: 顺序执行 当前地址+4
0x00000004 指令 2: 顺序执行 当前地址+4
0x00000008 指令 3: 相对跳转到指令 5 当前地址+8
0x0000000C 指令 4: 顺序执行 当前地址+4
0x00000010 指令 5: 顺序执行 当前地址+4
0x00000014 指令 6: 绝对跳转到指令 8 0xC000001C
0x00000018 指令 7: 顺序执行 当前地址+4
0x0000001C 指令 8: 顺序执行 当前地址+4
当这段程序被放在 0xC0000000空间时,开始执行指令 1,然后采用相对寻址的方法就
可以运行到指令 6,在指令 6执行时也可以使用绝对寻址的方法从 0xC0000014正确跳转到
指令 8所在的 0xC000001C位置,这段代码运行正常。
当这段代码被放在 0x00000000空间时,开始执行指令 1,然后采用相对寻址的方法就
可以运行到指令 6,但在指令 6 执行时使用绝对寻址的方法从 0x00000014 跳转到了
0xC000001C,但 0xC000001C空间没有代码,这样程序就跑飞了。
因此我们在看 linux内核启动这部分的代码时只能看到顺序执行指令和相对跳转指令,
不会存在绝对跳转指令,就是这个原因。如果需要使用全局变量或者函数指针时,则需要将
这个地址减去 0xC0000000的偏移量才可以获取到此时运行的地址,因为全局变量和函数指
针在编译时都是按照 0xC0000000基址编译出的绝对地址,运行时既然将程序段和数据段都
偏移了 0xC0000000的距离,那么使用时只要减去这个值就可以找到正确的位置了。
当 linux 内核完成初始化时,就会开启页机制,0x00000000 地址空间就会转换为
0xC0000000,再使用一个绝对跳转指令跳转到 0xC0000000去执行,在此之后程序就运行在
了编译时指定的 0xC0000000地址空间,无论是采用相对寻址还是绝对寻址都不会有问题了。
……
……
……
指令 8
指令 7
指令 6
指令 5
指令 4
指令 3
指令 2
指令 1
0x00000010
0x0000000C
0x00000008
0x00000004
0x00000000
0x0000001C
0x00000018
0x00000014
0xC0000010
0xC000000C
0xC0000008
0xC0000004
0xC0000000
0xC000001C
0xC0000018
0xC0000014
指令 5
指令 4
指令 3
指令 2
指令 1
……
……
……
指令 8
指令 7
指令 6
没有指令
0x00000010
0x0000000C
0x00000008
0x00000004
0x00000000
0x0000001C
0x00000018
0x00000014
0xC0000010
0xC000000C
0xC0000008
0xC0000004
0xC0000000
0xC000001C
0xC0000018
0xC0000014
编译地址不等于运行
地址,使用绝对跳转指
令出错的情况
编译地址等于运行地
址,使用绝对跳转指令
正确的情况