运行阶段:静态链接---都说重定位必须保证链接地址与运行地址相同,真的吗?

静态链接---都说重定位必须保证链接地址与运行地址相同,真的吗?

讨论问题:

  1. 重定位发生在哪个阶段。
  2. 实现重定位是否一定是位置相关代码
  3. 重定位是否一定要保证链接地址与运行地址一致,否则无法正常执行

3.3 静态链接–指令修正

上一小节回答了重定位发生在链接阶段,本小节来进一步的分析重定位的过程。程序的链接也有静态静态链接和动态链接,我们结合静态链接继续分析前面静态重定位的例子。

/* 代码清单1 */
/* 反汇编程序: arm-none-linux-gnueabi-objdump -d main.o*/
00000000 :
   0:   e92d4800        push    {fp, lr}
   4:   e28db004        add     fp, sp, #4
   8:   ebfffffe        bl      0
   c:   ebfffffe        bl      0
  10:   e3a03000        mov     r3, #0
  14:   e1a00003        mov     r0, r3
  18:   e8bd8800        pop     {fp, pc}

在未进行链接前,0x00000008和0x0000000c地址处的指令是‘bl 0’指令。为什么是bl指令呢,bl指令后的地址怎么确定,这就不得不来看main.o二进制目标文件的重定位表:

/* 重定位表: arm-none-linux-gnueabi-objdump -r main.o*/
RELOCATION RECORDS FOR [.text]:
OFFSET        TYPE              VALUE 
00000008      R_ARM_CALL        hello
0000000c      R_ARM_CALL        world

在它的重定位表中注明了这两个重定位的类型是R_ARM_CALL,这个类型的作用是什么,我们通过ARM架构elf文件中的说明来分析:

这是一种静态重定位
指令类型是ARM指令
它的指令修正操作是:((S + A) | T) – P
对应的重定位指令是BL/BLX 。

这也就回答了上述的第一个问题为什么是bl指令,而回答第二个问题就得来进一步的了解指令修正操作,指令修正操作中的几个字母的解释如下表:
在这里插入图片描述
在链接器对main.o和test.o文件完成链接后,hello、world这两个符号在程序中的位置也就确定了,我们来查看它们的地址。根据代码清单2,hello、world的地址分别为0x00008340和0x00008354,也就是S的值。

/* 代码清单2 */
/* arm-none-linux-gnueabi-readelf  -s  main */
Symbol table '.symtab' contains 100 entries:
Num:  Value      Size  Type  Bind   Vis       Ndx    Name
80:   00008354   20    FUNC  GLOBAL DEFAULT   12     world
85:   00008340   20    FUNC  GLOBAL DEFAULT   12     hello

接着分析A的值,代码清单1中的反汇编程序的bl伪指令对应的指令码为ebfffffe,bl伪指令的结构如下图所示:
在这里插入图片描述

通过低24为([23:0])的值来确定跳转目标的地址。而A就是根据将低24左移2位,再将左移后的结果扩展到32位(符号位/高位扩展为1),得到补齐量A,具体计算如下:

0xfffffe:是指代码清单1中,重定入口地址中的指令的后24位
A = sign_extend(imm24 << 2) = sign_extend(0xfffffe << 2 )
= sign_extend (0xfffff8)
= 0xfffffff8
= -0x08

最后,我们需要来确定P值,通过反汇编链接后生成的可执行文件来确定原重定位入口的指令的位置:

/* 代码清单3 */
/* hello() */
00008340 :
	8340:       e52db004        push    {fp}
	
/* world() */
00008354:
	8354:       e52db004        push    {fp}      
	
/* main() */
00008368:
	8368:       e92d4800        push    {fp, lr}
	836c:       e28db004        add     fp, sp, #4
	8370:       ebfffff2        bl      8340
	8374:       ebfffff6        bl      8354

根据代码清单3,这两个需要进行指令修正的位置分别是0x00008370和0x00008374,故P的值也确定了。

那接下来就可以确定bl指令低24位的值,也就是指令修正操作:
0x00008370位置指令修正

( ( (S+A) | T ) - P
= ((0x00008340 - 0x08) - 0x00008370) >> 2
= 0xfffffff2,故imm24[23:0]
= fffff2
0x00008370位置指令修正后的指令为:ebfffff2

0x00008374位置指令修正

( ( (S+A) | T ) - P
= ((0x00008340 - 0x08) - 0x00008374) >> 2
= 0xfffffff2,故imm24[23:0]
= fffff6
0x00008374位置指令修正后的指令为:ebfffff6

我们通过修正后的指令就可以逆推出目标符号所在的位置。仔细看这个指令修正操作,是在做减法运算,而且还是目标符号地址和需要重定位入口地址之间的减法运算,减法运算的结果是得到一个距离,通常称之为偏移量。

也就是说R_ARM_CALL类型的指令修正的目的就是为了算出这个偏移量。在这个偏移量确定后,无论程序被加载到内存的哪个位置,都可以根据偏移量和当前指令的位置来确定目标符号所在的地址,从而实现跳转到目标符号处执行指令。

3.4 为什么有人说:重定位需要确保链接地址与运行地址一致

可能你也许会想,那么麻烦干嘛,既然知道了目标符号的地址,直接设置CPU下个指令的地址为目标符号的地址不就行了。这种重定位类型的也有,同时也会带来一个问题:

假设现在设置CPU下条执行指令是位于hello符号的地址0x00008340,但是当我们将程序加载到内存其它位置(只要hello的地址不为0x00008340)。此时,你会发现,不知道执行什么指令去了。只有当程序加载到内存的位置满足hello的地址为0x00008340时,程序才能正常工作。

为了分析这个问题,我们再来写个程序。代码清单4,通过修改pc寄存器中的值,来告诉CPU要执行的指令位于hello地址的内存上:

/* 代码清单4 */

/* 原程序 :start.S*/
.global _start
_start:
      ……
        //set stack
        ldr     sp, =0x02050000
        ldr     pc, =hello

查看重定位表发现重定位类型变成了 R_ARM_ABS32 :

/* 重定位表 */
RELOCATION RECORDS FOR [.text]:
OFFSET      TYPE               VALUE 
00000040    R_ARM_ABS32        hello

而它对应的指令修正操作为(S + A) | T。地址0x00000040是重定位的入口,在这里存放的是hello符号的地址,目前未知,因此设置为0:

/* 静态链接前:start.o 反汇编程序 */
00000000 <_start>:
  30:   e59fd004        ldr     sp, [pc, #4]    
  34:   e59ff004        ldr     pc, [pc, #4]   
  38:   10060000        .word   0x10060000
  3c:   02050000        .word   0x02050000
  40:   00000000        .word   0x00000000

注:ARMv7-A中规定,执行ARM指令时,pc寄存中的值 = 当前指令的地址 + 8

在静态链接后,hello的链接地址已确定,通过指令修正,将此处保存的数据修改为hello的链接地址(代码清单5):

/* 代码清单5 */
/* 静态链接后:反汇编程序 */
00000000 <_start>:
  34:   e59ff004        ldr     pc, [pc, #4]    
  38:   10060000        .word   0x10060000
  3c:   02050000        .word   0x02050000
  40:   00000044        .word   0x00000044

00000044:
  44:   e52db004        push    {fp} 

在程序运行时,ldr伪指令通过取出 该地址(0x00000040) 上保存的hello符号地址,修改pc寄存器的值,使得CPU能够去执行hello地址的指令。

也是因为在重定位入口处存放的是一个地址,也就是数据。这就造成了在文件链接后,这个地址也就确定下来,无法在加载时自动的进行变更。

现在我们已经可以回答上面提到问题了,为什么在这种情况下必须保证运行地址和链接地址一致,程序才能正常运行。

(如你喜欢,更多知识,快人一步)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值