亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在x86機器代碼中調用絕對指針

在x86機器代碼中調用絕對指針

慕容森 2020-02-02 15:33:42
call在x86機器代碼中指向絕對指針的“正確”方法是什么?有一個好的方法可以在一條指令中完成嗎?我想做什么:我正在嘗試基于“子例程線程”構建一種簡化的mini-JIT(仍然)。從根本上講,這是從字節碼解釋器開始的最短步驟:每個操作碼都是作為單獨的函數實現的,因此可以將每個基本字節碼塊“ JITted”到它自己的新過程中,如下所示:{prologue}call {opcode procedure 1}call {opcode procedure 2}call {opcode procedure 3}...etc{epilogue}因此,我們的想法是每個塊的實際機器代碼都可以從模板中粘貼(根據需要擴展中間部分),唯一需要“動態”處理的位是將每個操作碼的功能指針復制到正確的地方,作為每個呼叫說明的一部分。我遇到的問題是了解call ...模板部分要使用什么。x86似乎沒有考慮到這種用法,而是支持相對和間接調用。它看起來像我可以使用FF 15 EFBEADDE或2E FF 15 EFBEADDE在假設調用函數DEADBEEF(通過把東西變成一個匯編和反匯編,看到什么產生有效的結果,基本上發現了這些未通過了解他們在做什么),但我不理解的東東細分和特權以及相關信息,足以看出差異,或者它們與更常見的call指令之間的行為會有何不同。英特爾架構手冊還建議這些僅在32位模式下有效,而在64位模式下“無效”。有人可以解釋這些操作碼,以及為此目的如何或是否將其使用?(通過寄存器使用間接調用也有明顯的答案,但這似乎是“錯誤的”方法-假設實際存在直接調用指令。)
查看完整描述

2 回答

?
LEATH

TA貢獻1936條經驗 獲得超7個贊

這里的所有內容也適用于jmp絕對地址,并且用于指定目標的語法相同。該問題詢問有關JITing的問題,但我還添加了NASM和AT&T語法以擴大范圍。


另請參閱在JIT中處理對遙遠的內在函數的調用,以獲取分配“附近”內存的方法,以便您可以用來rel32從JITed代碼中調用提前編譯的函數。


x86沒有對指令中的普通(近)call或jmp絕對地址進行編碼的編碼。 沒有絕對的直接調用/ jmp編碼,除非jmp far您不需要。請參閱英特爾的insn set ref手冊條目call。(有關文檔和指南的其他鏈接,另請參見x86標簽wiki。)大多數計算機體系結構都使用相對編碼來進行正常跳轉,例如x86,BTW。


最好的選擇(如果可以使位置依賴的代碼知道其自身的地址)是使用normalcall rel32,E8 rel32直接近距離調用編碼,該rel32字段為target - end_of_call_insn(2的補碼二進制整數)。


請參閱$在NASM中如何工作?以手動編碼call指令為例;在JITing期間執行此操作應該同樣容易。


在AT&T語法中: call 0x1234567

在NASM語法中:call 0x1234567

也適用于具有絕對地址的命名符號(例如使用equ或創建.set)。MASM沒有等效功能,它顯然只接受標簽作為目的地,因此人們有時會使用低效的解決方法來解決工具鏈(和/或目標文件格式重定位類型)的限制。


這些匯編和鏈接恰好在位置相關的代碼中(而不是共享的lib或PIE可執行文件)。但不是在x86-64 OS X中,該文本段映射在4GiB上方,因此無法通過到達低地址rel32。


在要調用的絕對地址范圍內分配JIT緩沖區。 例如,mmap(MAP_32BIT)在Linux上,可以在2GB的低內存中分配內存,其中+ -2GB可以到達該區域中的任何其他地址,或者在跳轉目標所在的位置附近提供非NULL的提示地址。(MAP_FIXED不過,不要使用;如果您的提示與任何現有映射重疊,則最好讓內核選擇一個不同的地址。)


(Linux非PIE可執行文件在2GB的虛擬地址空間中進行了映射,因此它們可以使用[disp32 + reg]帶有符號擴展的32位絕對地址的數組索引,或將靜態地址放入具有mov eax, imm32零擴展的絕對地址的寄存器中。因此,2GB的低地址是,不低于4GB, 但PIE可執行文件正在成為常態,因此,除非您確保與之建立+鏈接,否則請不要假設主可執行文件中的靜態地址位于32位以下-no-pie -fno-pie。并且其他操作系統(如OS X)始終將可執行文件的容量設置為4GB以上)


如果您無法call rel32使用

但是,如果您需要制作不知道其絕對地址的與位置無關的代碼,或者您需要調用的地址與調用者之間的距離大于+ -2GiB(可能為64位,但是最好放置)代碼足夠接近),則應使用間接注冊call


; use any register you like as a scratch

mov   eax, 0xdeadbeef               ; 5 byte  mov r32, imm32

     ; or mov rax, 0x7fffdeadbeef   ; for addresses that don't fit in 32 bits

call  rax                           ; 2 byte  FF D0

或AT&T語法


mov   $0xdeadbeef, %eax

# movabs $0x7fffdeadbeef, %rax      # mov r64, imm64

call  *%rax

很明顯,你可以使用任何寄存器,比如r10或r11這是呼叫重挫,但不用于ARG-傳遞的x86-64系統V. AL = XMM參數的個數數的可變參數函數,所以你需要在AL = 0之前的固定值x86-64 System V調用約定中對可變參數函數的調用。


如果確實需要避免修改任何寄存器,則可以將絕對地址保持為內存中的常數,并使用call具有RIP相對尋址模式的間接內存,例如


NASM call [rel function_pointer] ; 如果您無法破壞

AT&T的任何法規call *function_pointer(%rip)


請注意,間接調用/跳轉會使您的代碼容易受到Spectre攻擊,尤其是在同一流程中將JIT作為不信任代碼的沙箱的一部分時。(在那種情況下,僅內核補丁將無法保護您)。


您可能希望使用“ retpoline”而不是普通的間接分支來減輕Spectre的性能。


間接跳轉的分支錯誤預測懲罰也比直接(call rel32)稍差。普通直接callinsn 的目的地一經解碼就被知道,一旦它檢測到根本沒有分支,就在管道中更早地知道。


間接分支通??梢栽诂F代x86硬件上很好地預測,并且通常用于對動態庫/ DLL的調用。這并不可怕,但是call rel32絕對更好。


但是,即使直接也call需要一些分支預測來完全避免管道氣泡。(在解碼之前需要進行預測,例如,假設我們剛剛獲取了該塊,則提取階段接下來應獲取該塊。jmp next_instruction 當用完分支預測器條目時,速度會變慢)。 即使具有完美的分支預測,mov間接+ call reg也更糟糕,因為它具有更大的代碼大小和更多的微指令,但是效果很小。如果有其他mov問題,如果可能的話,內聯代碼而不是調用它是一個好主意。


有趣的事實:call 0xdeadbeef它將在Linux上匯編但不會鏈接到64位靜態可執行文件中,除非您使用鏈接程序腳本將.textsection / text segment 放在靠近該地址的位置。該.text部分通常從0x400080靜態可執行文件(或非PIE動態可執行文件)開始,即從虛擬地址空間的低2GiB開始,所有靜態代碼/數據都駐留在默認代碼模型中。但是0xdeadbeef在低32位的高半部分(即在低4G而不是低2G中),因此可以將其表示為零擴展的32位整數,而不是符號擴展的32位。并且0x00000000deadbeef - 0x0000000000400080不適合將正確擴展為64位的有符號32位整數。(負數可以到達的地址空間部分rel32從低位地址回繞的是64位地址空間的頂部2GiB;通常,地址空間的前一半保留給內核使用。)


它確實可以與組裝yasm -felf64 -gdwarf2 foo.asm,并objdump -drwC -Mintel顯示:


foo.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:

    0:   e8 00 00 00 00       call   0x5   1: R_X86_64_PC32        *ABS*+0xdeadbeeb

但是,當ld嘗試真正在那里的.text開始于它鏈接到一個靜態可執行文件0000000000400080,ld -o foo foo.o說foo.o:/tmp//foo.asm:1:(.text+0x1): relocation truncated to fit: R_X86_64_PC32 against '*ABS*'。


在32位代碼中,call 0xdeadbeef匯編和鏈接很好,因為a rel32可以從任何地方到達任何地方。相對位移不必將符號擴展為64位,而只需32位二進制加法即可。


直接遠call編碼(慢,不使用)

您可能會在的手冊條目中注意到,call并且jmp其中的編碼帶有絕對目標地址,直接編碼在指令中。但那些只存在于“遠” call/ jmp也設置CS一個新的代碼段選擇,這是緩慢的(見昂納霧指南)。


CALL ptr16:32(“在操作數中給出的遠,絕對地址調用”)具有6個字節的段:將偏移量直接編碼到指令中,而不是將其作為數據從普通尋址模式下的位置加載。因此,這是對絕對地址的直接調用。


Far call還將Push CS:EIP作為返回地址,而不僅僅是EIP,因此它甚至與call僅推送EIP的普通(附近)兼容。這不是問題jmp ptr16:32,只是緩慢和弄清楚段部分的內容。


更改CS通常僅對從32位模式更改為64位模式有效,反之亦然。通常,只有內核才能執行此操作,盡管您可以在大多數普通的OS(在GDT中保留32位和64位段描述符)下的用戶空間中執行此操作。但是,那將是更多愚蠢的計算機技巧,而不是有用的東西。(帶有iret或帶有的64位內核將返回到32位用戶空間sysexit。大多數操作系統在引導過程中僅使用遠jmp一次即可切換到內核模式下的64位代碼段。)


主流操作系統使用的平面內存模型不需要更改cs,并且cs對于用戶空間進程將使用什么值還沒有標準化。即使您想使用far jmp,也必須找出要在細分選擇器部分中輸入的值。(易而JIT編譯:剛讀當前cs有mov eax, cs,但很難提前-的即時編譯可移植的。)


call ptr16:64不存在,遠距離直接編碼僅適用于16位和32位代碼。在64位模式下,您只能call使用10字節的m16:64內存操作數,例如call far [rdi]?;驅egment:offset推入堆棧并使用retf。


查看完整回答
反對 回復 2020-02-02
?
小唯快跑啊

TA貢獻1863條經驗 獲得超2個贊

您僅憑一條指令就無法做到。一個不錯的方法是使用MOV + CALL:


0000000002347490: 48b83412000000000000  mov rax, 0x1234

000000000234749a: 48ffd0                call rax

如果要調用的過程的地址發生更改,請更改從偏移2開始的八個字節。如果調用0x1234的代碼的地址發生更改,則無需執行任何操作,因為該尋址是絕對的。


查看完整回答
反對 回復 2020-02-02
  • 2 回答
  • 0 關注
  • 890 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號