学习链接的时候的对重定位的疑问

2022-07-03 15:35:12 +08:00
 zamaojava

ELF 格式下,在重定位的时候,重定位类型有

  1. R_X86_64_PC32 相对寻址
  2. R_X86_64_32 绝对寻址 我感觉用一个绝对寻址类型就可以了,为什么要多此一举的用 相对寻址。

百度百科对相对寻址解释的好处: 使用相对寻址可节省指令中的地址位数,也便于程序在内存中成块搬动。

这就是用相对寻址的原因嘛?还是我理解有问题

1485 次点击
所在节点    程序员
11 条回复
ahhui
2022-07-03 15:47:21 +08:00
ELF 格式不太清楚,我好像记得 PE 格式的节载入到内存里,不一定是连续地址,所以绝对寻址可能需要转译,相对寻址会更快一些。不过也可能我记得不准确。
sujin190
2022-07-03 16:24:06 +08:00
你一个程序不止载入一个库吧,每个库都是单独编译的,那么也就是程序编译的时候其实无法确定实际地址,需要在程序载入的时候完成地址重重定向,只有绝对地址的话也就意味着你要把所有指令重写一遍,而且不同程序载入的相同库不能共享,有相对地址就简单多了,不同代码和库编译成不同段,地址编译成段内相对地址,载入只需重定向段基址即可,而且更厉害的是这个相对地址加段基址的选址过程完全由 CPU 的内存管理器自动完成,不会造成运行时性能损失,此外不同程序载入库的问题也可以放到统一的物理内存之中之后再通过段基址映射重定向到每个程序需要的地址去,大幅节省内存,我记得应该是这样的
agagega
2022-07-03 16:33:14 +08:00
相对寻址可能和指令集本身的寻址方式有关?如果指令集本身也支持这种相对寻址模式,那有时候跳转指令就会很方便
LotusChuan
2022-07-03 16:48:15 +08:00
我觉得就基本和#2 的差不多,如果你的库都是 static 的,只用绝对地址没什么大问题,但是如果你用的是 shared 库,它们本身就是加载在内存中的,内存地址会变动,如果你使用绝对地址会导致你必须要保证你的 shared 库在内存中的地址不变,这会显著影响内存利用率。用相对地址就可以解决这个问题,因为你每次载入程序的时候你实际上只需要修改 data 段中的 shared 库地址,只改一处,有硬件支持甚至不用改,和页表一样;但如果你用绝对地址,你得改 code 段中所有的有关地址,性能损失很严重。
qbqbqbqb
2022-07-04 13:09:27 +08:00
@LotusChuan 你说的这种是基于 PLT 的相对寻址( R_X86_64_PLT32 ),用于 shared 库的。

OP 说的 R_X86_64_PC32 是基于程序计数器(当前代码执行地址)的相对寻址,是用于可执行文件内部的,这种相对寻址不用修改任何东西。用这个的原因主要有两部分
1. 现在的操作系统为了安全,不仅 shared 库是随机地址加载(基于 PLT ,动态链接的时候需要修改程序里的基地址;这个特性称为 Position-independent code, PIC ),就连可执行文件本身的代码段、数据段也是随机加载(整体上加了一个随机偏移量,相对当前执行代码的位置不变,所以可以用基于 PC 的相对寻址,不用修改任何基地址;这个特性称为 Position-independent executable, PIE )
2. R_X86_64_32 绝对寻址,全称是 32 位绝对寻址,使用的地址长度只有 32 位,能寻址的范围定死在 0x0~0xffffffff ,如果用这种的话程序的全局部分(代码段+数据段)就只能使用低 4G 的虚拟地址空间了;而 R_X86_64_PC32 虽然寻址范围也只有这么大,但它的寻址窗口是随着当前执行代码的位置滑动的,整体上可以寻址范围更大。

至于为什么只有这几种寻址方式,是因为 x86 指令集里寻址的偏移量就只有 32 位。
如果需要用 64 位绝对寻址的话,就需要通过寄存器来相对寻址,原来 1 条指令变成 2 条(先把 64 位地址加载到某个通用寄存器,再执行想要的计算),效率比较低,所以只有必要的情况才会用这种方式。默认情况下程序都是用的上述几种 32 位偏移量的方式。
qbqbqbqb
2022-07-04 13:44:41 +08:00
补充一下 x86 的几种内存寻址方式,总体上其实就 2 种(这里不讨论寄存器寻址和立即数寻址):

寄存器相对寻址,AT&T 汇编写法是 offset(base, index, scale)
1 )地址计算方法是 base + index*scale + offset
2 ) base 和 index 是寄存器,offset 是 32 位立即数(直接写在指令里的常数),scale 是 1,2,4,8 中的一个
3 ) scale, index, base 可以依次省略( scale 默认为 1 ,其它默认为 0 ,有 base 的时候不能省略括号,否则省略括号),不省略 base 的时候可以省略 offset

程序计数器(PC)相对寻址,32 位写法 offset(%eip),64 位写法 offset(%rip)
1) 中间那个特殊的寄存器是程序计数器( Program counter, PC ),代表的是将要执行的下一条指令的地址。
2) 只能指定一个 32 位立即数偏移量,不能做其它计算。

全局变量的地址一般是写在 offset 的位置的。这就是为什么 x86 的 ELF 里就这几种寻址方式的原因。
qbqbqbqb
2022-07-04 13:46:51 +08:00
@qbqbqbqb 更正一下,偏移量是有符号的,所以 R_X86_64_32 的范围只有 2G ,上面错写成 4G 了
zamaojava
2022-07-04 14:30:55 +08:00
谢谢,解惑
zamaojava
2022-07-04 14:31:08 +08:00
@qbqbqbqb 谢谢,解惑
qbqbqbqb
2022-07-04 14:33:01 +08:00
@qbqbqbqb 再补充一下,PC 相对寻址其实是 64 位引入的,32 位里 offset(%eip)只能用在跳转语句上,所以 32 位里用的比较多的还是绝对寻址的方式。而 64 位的话就因为寻址范围的原因,PC 相对寻址更常用。
zamaojava
2022-07-04 14:37:00 +08:00
@qbqbqbqb callq 一般都是 相对寻址

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/863759

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX