加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

相对跳转指令和绝对跳转指令的特殊用法

(2012-02-22 22:56:08)
标签:

编译地址

运行地址

相对跳转

绝对跳转

it

分类: 经验案例

接上一篇“ARM汇编指令机器码解释一些问题

 

当程序的编译地址与运行地址不一致时,使用相对跳转指令和绝对跳转指令就需要注意了,本文档将讲述其中原因。

 

另有一种B指令是BX,它的用法是

BX Rm

其中Rm是通用寄存器,例如BX R0,它的作用是跳转到R0中所存储的地址,它的指令格式如下

http://s8/middle/908da746gb98ebfa32a57&690

可以看到它最后的4位所存储的就是Rm寄存器的编号,共有2^4=16种,对应R0~R15R寄存器可以存放32bits的数据,可以表示全空间地址范围,因此BX指令是一种绝对跳转指令,并且可以跳转到32位机的全部地址空间。

 

BL是相对跳转指令,BX是绝对跳转指令,它们虽然都是跳转指令,实现跳转功能,除了可跳转的距离不一样之外,在某些场合还是有限制的比如linux内核程序编译的基址是0xC0000000内核程序认为它们都是在0xC0000000以上空间运行的,而内核在进入页机制之前都是运行在以0x00000000为基址的内存空间,编译空间与运行空间不一致这样不会有问题么?答案当然是不会有问题,否则linux怎么会跑起来,这其中就涉及到程序运行的相对寻址和绝对寻址的问题了。

 

我们知道程序分为顺序执行和跳转执行,顺序执行就是一条指令执行完再去执行它下面的一条指令,而跳转又分为相对跳转和绝对跳转,正如上述的BLBX。顺序执行和相对跳转执行都是在当前指令的基础上加上相对的偏移量找到下条指令的,而绝对跳转则是需要找到实实在在的地址。

 

我们先来看一个例子,然后再说明上述情况是怎么实现的。

比如说我坐火车从沈阳到大连,我在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空间没有代码,这样程序就跑飞了。

http://s14/middle/908da746gb98ec5aafa8d&690

因此我们在看linux内核启动这部分的代码时只能看到顺序执行指令和相对跳转指令,不会存在绝对跳转指令,就是这个原因。如果需要使用全局变量或者函数指针时,则需要将这个地址减去0xC0000000的偏移量才可以获取到此时运行的地址,因为全局变量和函数指针在编译时都是按照0xC0000000基址编译出的绝对地址,运行时既然将程序段和数据段都偏移了0xC0000000的距离,那么使用时只要减去这个值就可以找到正确的位置了。

linux内核完成初始化时,就会开启页机制,0x00000000地址空间就会转换为0xC0000000,再使用一个绝对跳转指令跳转到0xC0000000去执行,在此之后程序就运行在了编译时指定的0xC0000000地址空间,无论是采用相对寻址还是绝对寻址都不会有问题了。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有