V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zamaojava
V2EX  ›  程序员

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

  •  
  •   zamaojava · 38 天前 · 1023 次点击
    这是一个创建于 38 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    11 条回复    2022-07-04 14:37:00 +08:00
    ahhui
        1
    ahhui  
       38 天前   ❤️ 1
    ELF 格式不太清楚,我好像记得 PE 格式的节载入到内存里,不一定是连续地址,所以绝对寻址可能需要转译,相对寻址会更快一些。不过也可能我记得不准确。
    sujin190
        2
    sujin190  
       38 天前   ❤️ 2
    你一个程序不止载入一个库吧,每个库都是单独编译的,那么也就是程序编译的时候其实无法确定实际地址,需要在程序载入的时候完成地址重重定向,只有绝对地址的话也就意味着你要把所有指令重写一遍,而且不同程序载入的相同库不能共享,有相对地址就简单多了,不同代码和库编译成不同段,地址编译成段内相对地址,载入只需重定向段基址即可,而且更厉害的是这个相对地址加段基址的选址过程完全由 CPU 的内存管理器自动完成,不会造成运行时性能损失,此外不同程序载入库的问题也可以放到统一的物理内存之中之后再通过段基址映射重定向到每个程序需要的地址去,大幅节省内存,我记得应该是这样的
    agagega
        3
    agagega  
       38 天前
    相对寻址可能和指令集本身的寻址方式有关?如果指令集本身也支持这种相对寻址模式,那有时候跳转指令就会很方便
    LotusChuan
        4
    LotusChuan  
       38 天前
    我觉得就基本和#2 的差不多,如果你的库都是 static 的,只用绝对地址没什么大问题,但是如果你用的是 shared 库,它们本身就是加载在内存中的,内存地址会变动,如果你使用绝对地址会导致你必须要保证你的 shared 库在内存中的地址不变,这会显著影响内存利用率。用相对地址就可以解决这个问题,因为你每次载入程序的时候你实际上只需要修改 data 段中的 shared 库地址,只改一处,有硬件支持甚至不用改,和页表一样;但如果你用绝对地址,你得改 code 段中所有的有关地址,性能损失很严重。
    qbqbqbqb
        5
    qbqbqbqb  
       37 天前
    @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
        6
    qbqbqbqb  
       37 天前
    补充一下 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
        7
    qbqbqbqb  
       37 天前
    @qbqbqbqb 更正一下,偏移量是有符号的,所以 R_X86_64_32 的范围只有 2G ,上面错写成 4G 了
    zamaojava
        8
    zamaojava  
    OP
       37 天前
    谢谢,解惑
    zamaojava
        9
    zamaojava  
    OP
       37 天前
    @qbqbqbqb 谢谢,解惑
    qbqbqbqb
        10
    qbqbqbqb  
       37 天前
    @qbqbqbqb 再补充一下,PC 相对寻址其实是 64 位引入的,32 位里 offset(%eip)只能用在跳转语句上,所以 32 位里用的比较多的还是绝对寻址的方式。而 64 位的话就因为寻址范围的原因,PC 相对寻址更常用。
    zamaojava
        11
    zamaojava  
    OP
       37 天前
    @qbqbqbqb callq 一般都是 相对寻址
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2828 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 13:55 · PVG 21:55 · LAX 06:55 · JFK 09:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.