從機器碼理解RIP 相對尋址


本作品采用知識共享署名 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM