本作品采用知识共享署名 4.0 国际许可协议进行许可。转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/rip-relative-addressing

本博客同步在 http://www.cnblogs.com/papertree/p/6298763.html


1. 情景

在调试linux-3.0.0内核源码过程中,碰到一处lea指令,略有疑问。

代码如下(路径linux/arch/x86/boot/compressed/head_64.S):

249 /*
250 * Copy the compressed kernel to the end of our buffer
251 * where decompression in place becomes safe.
252 */
253 pushq %rsi
254 leaq (_bss-8)(%rip), %rsi
255 leaq (_bss-8)(%rbx), %rdi
256 movq $_bss /* - $startup_32 */, %rcx
257 shrq $3, %rcx
258 std
259 rep movsq
260 cld
261 popq %rsi

下图1-1是调试过程中的CPU上下文:

图1-1

从“mov rcx,0x243e80”中可以看到,_bss的值为0x243e80的,这是_bss这个symbol在进行汇编时,其所在的section内的偏移位置。

从“lea rdi,[rbx+0x243e78]”中可以看到,加到%rbx的值是_bss-8,这跟汇编源代码是一致的。

而从“lea rsi,[rip+0x243c30]” 中可以看到,加到%rip的偏移值并不是_bss-8 的值。

先说明一点,这个section是加载在0x1000000的内存位置,所以0x1000241这条指令,相对于所在section的起始偏移是0x241。

那么,上面的0x243c30这个值,是由(_bss-8)再减去0x248(下一条指令相对于section的起始偏移值)而得来。

那么,前后两条看起来十分相似的汇编代码为什么有这样的区别呢?


2. RIP的特殊性以及PIC(位置无关代码)

因为RIP寄存器存放着当前指令的地址,所以有它的特殊性。

比如上面的%rip + displacement,其中displacement存放的如果是_bss这个symbol与该指令的“距离值”,那么不管这段代码所在的section装载到哪个位置,都可以通过这个计算,访问到_bss实际装载的位置。

比如section装载在0x1000000,那么指令的%rip为0x1000241,_bss的值为0x1243c30。

而如果装载在0x5000000,那么指令的%rip为0x5000241,_bss的值为0x5243c30。

那么如果displacement存放的是_bss与指令之间的距离值,那么不管实际加载到哪个位置,都可以访问到实际的_bss位置。

这里解释了上面的问题 —— 这两条相似汇编代码的区别,正好利用rip的特殊性,实现了PIC的功能。

但是,还是有疑问。这里的解释仅仅是解释了displacement为什么有“距离值”和“实际值”两种情况,这里的区别似乎只是停留在汇编层面,因为gas汇编器就可以这样实现,当发现base register是%rip,那么displacement就使用_bss与当前指令的下一条指令的“距离值”,而当base register是其他寄存器时,displacement就等于_bss自身的值。

而汇编成机器码之后,displacement的值已经由汇编器计算好了,CPU在执行的时候,%rip + displacement 和 %rbx + displacement不是一样的模式吗?

在搜索资料的时候,发现RIP相对寻址这个概念,这并不是一个汇编器的概念,而是CPU的,所以,既然把%rip + displacement这种寻址模式单独拿出来,那么还是会有差别的。

此外,在维基上看到的,RIP相对寻址是在x86-64加进去的:

http://wiki.osdev.org/X86-64_Instruction_Encoding#16-bit_addressing

RIP/EIP-relative addressing

Addressing in x86-64 can be relative to the current instruction pointer value. This is indicated with the RIP (64-bit) and EIP (32-bit) instruction pointer registers, which are not otherwise exposed to the program and may not exist physically. RIP-relative addressing allows object files to be location independent.


3. RIP相对寻址

那么为了进一步从CPU层面解释%rip + displacement和%rbx + displacement这两种寻址模式的区别,需要来看一下CPU如何解释机器代码。

下面是从《Intel 64 and IA-32 Architectures Software Developer's Manual》截取的几张图:

图3-1

这张图展示了一条机器码指令的结构,下面结合实际指令解释一下。

首先,在上面图1-1的例子中,查看一下两条lea指令所在的内存数据:

gdb$ x /14xb 0x1000241
0x1000241: 0x48 0x8d 0x35 0x30 0x3c 0x24 0x00 0x48
0x1000249: 0x8d 0xbb 0x78 0x3e 0x24 0x00

这里两条指令分别7个字节。

其中0x48是Prefixs,0x8d是lea指令的opcode,0x35和0xbb分别是两条指令的ModR/M,这里面没有SIB(下面解释),剩下的0x243c80和0x243e78就是两条指令的Displacement了。

Instruction Prefixs可以有很多种,上面的wiki链接也解释得很全了。这里的0x48是一种64位长模式特有的REX Prefix。对于REX Prefix的解释见下图3-2和3-3,其中高4位0100是固定的,低四位分别作为指令其他部分的扩展位。下面再进行解释。

那么上面的0x48,即为0100 1000,即W位为1,R X B 三个位都为0。

ModR/M 可以划分成3个field,高2位mod,中间3位reg,低3位r/m。例子中的0x35即为(00 110 101),还有0xbb即为(10 111 011),图3-4给出了一份助记表,可以找到0x35的坐标位(disp32,ESI),还有0xbb的坐标为([EBX]+disp32, EDI)。

看回例子中的“lea rsi,[rip+disp]” 和“lea rdi, [rbx]+disp”,rip作为base register和其他通用寄存器的区别在这里。但是,我也不知道该说这特不特殊了,全部是0和1之间的差别。

SIB在这两条指令中没有,答案可以从图3-4的NOTES.1中看到,当ModR/M中的mod域和R/M域为某些特定组合时,才存在SIB字节。

再看会刚刚的REX Prefix的R X B三个位,如何做其他部分的扩展在上面的wiki链接中挺全面。这里截了其中一个图作为解释性说明,见图3-5,当其中的B位为0时,ModR/M的r/m域是符合图3-4的,但是当B位为1时,r/m域选择的寄存器变成了从R8、R9...这些扩展寄存器中选择了。

图3-2

图3-3

图3-4

图3-5

RIP 相对寻址的更多相关文章

  1. 从机器码理解RIP 相对寻址

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/rip-relative-addressing 本博客同步在 ...

  2. X86/X64处理器体系结构及寻址模式

    由8086/8088.x86.Pentium发展到core系列短短40多年间,处理器的时钟频率差点儿已接近极限.尽管如此,自从86年Intel推出386至今除了添加一些有关流媒体的指令如mmx/sse ...

  3. 对抗栈帧地址随机化/ASLR的两种思路和一些技巧

    栈帧地址随机化是地址空间布局随机化(Address space layout randomization,ASLR)的一种,它实现了栈帧起始地址一定程度上的随机化,令攻击者难以猜测需要攻击位置的地址. ...

  4. 面试必问的 volatile,你了解多少?

    前言 Java中volatile这个热门的关键字,在面试中经常会被提及,在各种技术交流群中也经常被讨论,但似乎讨论不出一个完美的结果,带着种种疑惑,准备从JVM.C++.汇编的角度重新梳理一遍. vo ...

  5. ELF文件加载与动态链接(一)

    关于ELF文件的详细介绍,推荐阅读: ELF文件格式分析 —— 滕启明.ELF文件由ELF头部.程序头部表.节区头部表以及节区4部分组成. 通过objdump工具和readelf工具,可以观察ELF文 ...

  6. ELF文件加载与动态链接(二)

    GOT应该保存的是puts函数的绝对虚地址,这里为什么保存的却是puts@plt的第二条指令呢? 原来“解释器”将动态库载入内存后,并没有直接将函数地址更新到GOT表中,而是在函数第一次被调用时,才会 ...

  7. x86 体系指令

    FASM 第二章 - 2.1 x86 体系指令 Author: 徐艺波  From: xuyibo.org  Updated: 2008-04-17   官方论坛   本站软件反馈.软件开发交流.   ...

  8. 64位下的InlineHook

    目录 x64下手工HOOK的方法 一丶HOOK的几种方法之远跳 1. 远跳 不影响寄存器 + 15字节方法 2.远跳 影响寄存器 + 12字节方法 3.影响寄存器,恢复寄存器 进行跳转. 4. 常用 ...

  9. easyhook源码分析三——申请钩子

    EasyHook 中申请钩子的原理介绍 函数原型 内部使用的函数,为给定的入口函数申请一个hook结构. 准备将目标函数的所有调用重定向到目标函数,但是尚未实施hook. EASYHOOK_NT_IN ...

随机推荐

  1. android init.rc 语法分析

    此文来自于对http://source.android.com/porting/index.html中bring up段的简译.其中有一处与源码的 system/core/init/readme.tx ...

  2. PAT (Advanced Level) 1005. Spell It Right (20)

    简单题. #include<iostream> #include<cstring> #include<cmath> #include<algorithm> ...

  3. BNU OJ 50999 BQG's Approaching Deadline

    #include<cstdio> #include<algorithm> using namespace std; +; struct Homework { long long ...

  4. python2.7 与 go1.2简单性能比较

    过完年刚上班,项目还没有开始,对于即将到来的项目,想尝试是否可以找到一个开发效率接近python,运行效率接近静态语言的编程语言,选择基本就是scala和go,公司的技术组成基本都是c派的,scala ...

  5. bi api 软件

    https://www.interactivebrokers.com.hk/cn/index.php?f=5234&ns=T

  6. [iOS]C语言技术视频-04-程序循环结构(while{})

    下载地址: 链接: http://pan.baidu.com/s/1o6imQ4U 密码: imuy

  7. Bootstrap学习指南

    一.Bootstrap简介 二.Bootstrap安装 三.Bootstrap CSS 四.Bootstrap 布局组件 五.Bootstrap 插件 六.Bootstrap定制

  8. 浅谈web应用的负载均衡、集群、高可用(HA)解决方案(转)

    1.熟悉几个组件 1.1.apache     —— 它是Apache软件基金会的一个开放源代码的跨平台的网页服务器,属于老牌的web服务器了,支持基于Ip或者域名的虚拟主机,支持代理服务器,支持安 ...

  9. jquery toggle 替换的实现

    $('#example').click(function(){$("#exampleBox").toggle();}) 改为 $('#example').click(functio ...

  10. [Unity UGUI]UGUI提供多种不同的解决方案

    现代游戏和应用程序经常需要支持各种不同的屏幕分辨率,特别是UI布局需要能够适应.UI系统在统一中包括各种工具来实现此目的,可以以多种方式组合在一起. 在这个小节我们将使用一个简单的案例研究和观察和比较 ...