重定位和代码修改

链接器和加载器的核心操作是重定位和代码修改,当编译器或者汇编器生产一个目标文件时候,使用文件中定义的,未经重定位操作的代码地址和数据地址来生成代码,不在这个文件定义的数据地址或者代码地址通常会被置为0,作为链接过程的一部分。链接器会修改目标代码使之与实际分配的地址相匹配。例如,考虑如下这段x86代码,它的作用是将变量a中的内容通过寄存器eax移动到变量b中。

Mov a, %eax

Mob %eax, b

如果变量a的定义就在该代码片段所在的文件中,位置是0x1234,而b是从其他地方导入的,那么生成的代码将会是:

A1 34 12 00 00 mov a, %eax

A3 00 00 00 00 mov %eax, b

每条指令都包含了一个字节的操作码,即A1和A3,和后面的4个字节的地址,第一条指令中饮用了地址0x1234(x86中的字节顺序是从右到左的,也就是小端顺序,因此在代码中显示为逆序)。第二个指令中,由于b是导入符号,当前情况下位置是未知的。因此引用的地址是0.

现在假设链接器将这段代码进行链接时将a所在的段重定位到了0x10000处,b的地址最终被置于0x9A12,则链接器会将代码修改为:

A1 34 12 01 00 mov a, %eax

A3 12 9A 00 00 mov %eax, b

在这里可以看到,链接器将第一条指令的地址加上了0x10000,现在a的重定位地址变成了0x11234,并且代码中也补上了b的地址,这个例子只能是了对指令部分的调整,实际上目标文件中数据部分如果有关联的数据,如只想a和b的指针,也需要做相应的修改。

在一些早起的计算机系统上,他们的地址空间相对较小,而且只能支持直接寻址,代码修改的过程简单,因为代码中可能只有一到两种需要作出修改的地址格式,而对于现代计算机来说,即便是RISC架构,代码修改的过程也会非常复杂,由于一条指令的长度已经不足以编码整个地址空间,指令系统中的寻址方式变得越来越复杂,因此编译器和链接器不得不使用复杂的寻址技巧来处理地址。某些情况下,可能会使用2至3条指令来组成一个地址,每个指令包含地址的一部分,然后使用位操作将他们组合为一个完整的地址,在这种情况下,链接器就不得不对每一条涉及的指令都进行修改,将符号的地址拆解为若干位,并将这些相应的位插入对应的指令中。还有一些情况,程序会为一个函数或者一组函数创建一个数组作为地址池,代码中所有使用到的地址 通常是程序中的指针,都保存在这个数组中。初始化时将某个寄存器指向这个数组的起始地址,当程序中 使用到某个地址时,代码会将该寄存器用作基地址,利用偏移量从地址池中找到所需要的指针,链接器在处理这种情况时,需要为程序创建这个数组,并扫描所有程序中使用的指针最终被绑定的地址,用以填充这个数组,并修改各条指令使得他们可以准确的找到地址池的入口,我们将在第7章详细讨论。

有些系统需要位置无关的代码, 也就是说希望diamanté无论被加载到什么位置都可以正常运行,对于这种情况,链接器需要利用一些额外的技巧,将程序中允许位置无关与无法做到位置无关的部分分离开来,然后再设法使这两部分可以相互通信。具体的细节会在第8章讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值