从机器码理解RIP 相对寻址

摘要:
在搜索资料的时候,发现RIP相对寻址这个概念,这并不是一个汇编器的概念,而是CPU的,所以,既然把%rip+displacement这种寻址模式单独拿出来,那么还是会有差别的。此外,在维基上看到的,RIP相对寻址是在x86-64加进去的:http://wiki.osdev.org/X86-64_Instruction_Encoding#16-bit_addressingRIP/EIP-relativeaddressingAddressinginx86-64canberelativetothecurrentinstructionpointervalue.ThisisindicatedwiththeRIPandEIPinstructionpointerregisters,whicharenototherwiseexposedtotheprogramandmaynotexistphysically.RIP-relativeaddressingallowsobjectfilestobelocationindependent.3.RIP相对寻址那么为了进一步从CPU层面解释%rip+displacement和%rbx+displacement这两种寻址模式的区别,需要来看一下CPU如何解释机器代码。

本作品采用知识共享署名 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上下文:

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

图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》截取的几张图:

从机器码理解RIP 相对寻址第2张

图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...这些扩展寄存器中选择了。

从机器码理解RIP 相对寻址第3张

图3-2

从机器码理解RIP 相对寻址第4张

图3-3

从机器码理解RIP 相对寻址第5张

图3-4

从机器码理解RIP 相对寻址第6张

图3-5

免责声明:文章转载自《从机器码理解RIP 相对寻址》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇uniapp 中出现 wx.config is not a functionRestHighLevelClient 操作es下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

标志寄存器(学习汇编)

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0   NT IOPL OF DF IF TF SF ZF   AF   PF   CF 未使用 嵌套标志 I/O权限标志占2位 溢出标志 方向标志 中断允许标志 单步标志 符号标志 零标志 未使用 辅助标志 未使用 奇偶标志 未使用 进位标志 1.CPU内部的寄存器中...

latex 页眉设置 [转]

//注意加粗部分\documentclass{article} \usepackage{CJK}\usepackage{fancyhdr} \title{\textbf{Title}} \begin{document} \begin{CJK*}{GBK}{song} \pagestyle{fancy} \lhead{中文页眉} \rhea...

Linux中的汇编简介

GNU as汇编语法 GNU汇编语法使用的是AT&T汇编它和Intel汇编的语法主要有以下一些不同: AT&T汇编中的立即操作数前面要加上'$',寄存器操作数名前要加上百分号'%',绝对跳转操作数前要加上'*',Intel的语法均不包含这些符号; AT&T语法与Intel语法中使用的源操作数和目的操作数顺序正好相反,AT&...

汇编语言程序设计读书笔记(4)- 程序设计基础之一

目录: 一、数据定义 1、变量数据定义 2、常量数据定义 3、缓冲区定义 二、寻址方式 1、立即数寻址 2、寄存器寻址 3、直接寻址 4、寄存器间接寻址 5、寄存器相对寻址 6、变址寻址 三、数据传送和mov指令 1、数据传送规则 2、mov指令 四、条件传送数据cmov指令 1、状态标志位 2、cmov指令 五、交换数据 1...

转载:堆栈溢出(Stack overflow)问题

一,堆栈溢出堆栈溢出就是不顾堆栈中分配的局部数据块大小(在栈中分配的局部数据块大小和局部变量的声明的大小有关),向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据(包括函数的返回地址)。 或者解释为在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.这东西很像病毒。 基础知识...

内中断(学习汇编)

中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。 注意,这里所说的中断信息,是为了便于理解而采用的一种逻辑上的说法。它是对几个具有先后顺序的硬件操作所产生的事件的统一描述。 “中断信息”是要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。 中断信息可以来自CPU的内部和外部。 内中断的产生 对...