2 回答

TA貢獻(xiàn)1936條經(jīng)驗(yàn) 獲得超7個(gè)贊
這里的所有內(nèi)容也適用于jmp絕對(duì)地址,并且用于指定目標(biāo)的語(yǔ)法相同。該問(wèn)題詢問(wèn)有關(guān)JITing的問(wèn)題,但我還添加了NASM和AT&T語(yǔ)法以擴(kuò)大范圍。
另請(qǐng)參閱在JIT中處理對(duì)遙遠(yuǎn)的內(nèi)在函數(shù)的調(diào)用,以獲取分配“附近”內(nèi)存的方法,以便您可以用來(lái)rel32從JITed代碼中調(diào)用提前編譯的函數(shù)。
x86沒(méi)有對(duì)指令中的普通(近)call或jmp絕對(duì)地址進(jìn)行編碼的編碼。 沒(méi)有絕對(duì)的直接調(diào)用/ jmp編碼,除非jmp far您不需要。請(qǐng)參閱英特爾的insn set ref手冊(cè)條目call。(有關(guān)文檔和指南的其他鏈接,另請(qǐng)參見(jiàn)x86標(biāo)簽wiki。)大多數(shù)計(jì)算機(jī)體系結(jié)構(gòu)都使用相對(duì)編碼來(lái)進(jìn)行正常跳轉(zhuǎn),例如x86,BTW。
最好的選擇(如果可以使位置依賴的代碼知道其自身的地址)是使用normalcall rel32,E8 rel32直接近距離調(diào)用編碼,該rel32字段為target - end_of_call_insn(2的補(bǔ)碼二進(jìn)制整數(shù))。
請(qǐng)參閱$在NASM中如何工作?以手動(dòng)編碼call指令為例;在JITing期間執(zhí)行此操作應(yīng)該同樣容易。
在AT&T語(yǔ)法中: call 0x1234567
在NASM語(yǔ)法中:call 0x1234567
也適用于具有絕對(duì)地址的命名符號(hào)(例如使用equ或創(chuàng)建.set)。MASM沒(méi)有等效功能,它顯然只接受標(biāo)簽作為目的地,因此人們有時(shí)會(huì)使用低效的解決方法來(lái)解決工具鏈(和/或目標(biāo)文件格式重定位類型)的限制。
這些匯編和鏈接恰好在位置相關(guān)的代碼中(而不是共享的lib或PIE可執(zhí)行文件)。但不是在x86-64 OS X中,該文本段映射在4GiB上方,因此無(wú)法通過(guò)到達(dá)低地址rel32。
在要調(diào)用的絕對(duì)地址范圍內(nèi)分配JIT緩沖區(qū)。 例如,mmap(MAP_32BIT)在Linux上,可以在2GB的低內(nèi)存中分配內(nèi)存,其中+ -2GB可以到達(dá)該區(qū)域中的任何其他地址,或者在跳轉(zhuǎn)目標(biāo)所在的位置附近提供非NULL的提示地址。(MAP_FIXED不過(guò),不要使用;如果您的提示與任何現(xiàn)有映射重疊,則最好讓內(nèi)核選擇一個(gè)不同的地址。)
(Linux非PIE可執(zhí)行文件在2GB的虛擬地址空間中進(jìn)行了映射,因此它們可以使用[disp32 + reg]帶有符號(hào)擴(kuò)展的32位絕對(duì)地址的數(shù)組索引,或?qū)㈧o態(tài)地址放入具有mov eax, imm32零擴(kuò)展的絕對(duì)地址的寄存器中。因此,2GB的低地址是,不低于4GB, 但PIE可執(zhí)行文件正在成為常態(tài),因此,除非您確保與之建立+鏈接,否則請(qǐng)不要假設(shè)主可執(zhí)行文件中的靜態(tài)地址位于32位以下-no-pie -fno-pie。并且其他操作系統(tǒng)(如OS X)始終將可執(zhí)行文件的容量設(shè)置為4GB以上)
如果您無(wú)法call rel32使用
但是,如果您需要制作不知道其絕對(duì)地址的與位置無(wú)關(guān)的代碼,或者您需要調(diào)用的地址與調(diào)用者之間的距離大于+ -2GiB(可能為64位,但是最好放置)代碼足夠接近),則應(yīng)使用間接注冊(cè)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語(yǔ)法
mov $0xdeadbeef, %eax
# movabs $0x7fffdeadbeef, %rax # mov r64, imm64
call *%rax
很明顯,你可以使用任何寄存器,比如r10或r11這是呼叫重挫,但不用于ARG-傳遞的x86-64系統(tǒng)V. AL = XMM參數(shù)的個(gè)數(shù)數(shù)的可變參數(shù)函數(shù),所以你需要在AL = 0之前的固定值x86-64 System V調(diào)用約定中對(duì)可變參數(shù)函數(shù)的調(diào)用。
如果確實(shí)需要避免修改任何寄存器,則可以將絕對(duì)地址保持為內(nèi)存中的常數(shù),并使用call具有RIP相對(duì)尋址模式的間接內(nèi)存,例如
NASM call [rel function_pointer] ; 如果您無(wú)法破壞
AT&T的任何法規(guī)call *function_pointer(%rip)
請(qǐng)注意,間接調(diào)用/跳轉(zhuǎn)會(huì)使您的代碼容易受到Spectre攻擊,尤其是在同一流程中將JIT作為不信任代碼的沙箱的一部分時(shí)。(在那種情況下,僅內(nèi)核補(bǔ)丁將無(wú)法保護(hù)您)。
您可能希望使用“ retpoline”而不是普通的間接分支來(lái)減輕Spectre的性能。
間接跳轉(zhuǎn)的分支錯(cuò)誤預(yù)測(cè)懲罰也比直接(call rel32)稍差。普通直接callinsn 的目的地一經(jīng)解碼就被知道,一旦它檢測(cè)到根本沒(méi)有分支,就在管道中更早地知道。
間接分支通??梢栽诂F(xiàn)代x86硬件上很好地預(yù)測(cè),并且通常用于對(duì)動(dòng)態(tài)庫(kù)/ DLL的調(diào)用。這并不可怕,但是call rel32絕對(duì)更好。
但是,即使直接也call需要一些分支預(yù)測(cè)來(lái)完全避免管道氣泡。(在解碼之前需要進(jìn)行預(yù)測(cè),例如,假設(shè)我們剛剛獲取了該塊,則提取階段接下來(lái)應(yīng)獲取該塊。jmp next_instruction 當(dāng)用完分支預(yù)測(cè)器條目時(shí),速度會(huì)變慢)。 即使具有完美的分支預(yù)測(cè),mov間接+ call reg也更糟糕,因?yàn)樗哂懈蟮拇a大小和更多的微指令,但是效果很小。如果有其他mov問(wèn)題,如果可能的話,內(nèi)聯(lián)代碼而不是調(diào)用它是一個(gè)好主意。
有趣的事實(shí):call 0xdeadbeef它將在Linux上匯編但不會(huì)鏈接到64位靜態(tài)可執(zhí)行文件中,除非您使用鏈接程序腳本將.textsection / text segment 放在靠近該地址的位置。該.text部分通常從0x400080靜態(tài)可執(zhí)行文件(或非PIE動(dòng)態(tài)可執(zhí)行文件)開(kāi)始,即從虛擬地址空間的低2GiB開(kāi)始,所有靜態(tài)代碼/數(shù)據(jù)都駐留在默認(rèn)代碼模型中。但是0xdeadbeef在低32位的高半部分(即在低4G而不是低2G中),因此可以將其表示為零擴(kuò)展的32位整數(shù),而不是符號(hào)擴(kuò)展的32位。并且0x00000000deadbeef - 0x0000000000400080不適合將正確擴(kuò)展為64位的有符號(hào)32位整數(shù)。(負(fù)數(shù)可以到達(dá)的地址空間部分rel32從低位地址回繞的是64位地址空間的頂部2GiB;通常,地址空間的前一半保留給內(nèi)核使用。)
它確實(shí)可以與組裝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
但是,當(dāng)ld嘗試真正在那里的.text開(kāi)始于它鏈接到一個(gè)靜態(tài)可執(zhí)行文件0000000000400080,ld -o foo foo.o說(shuō)foo.o:/tmp//foo.asm:1:(.text+0x1): relocation truncated to fit: R_X86_64_PC32 against '*ABS*'。
在32位代碼中,call 0xdeadbeef匯編和鏈接很好,因?yàn)閍 rel32可以從任何地方到達(dá)任何地方。相對(duì)位移不必將符號(hào)擴(kuò)展為64位,而只需32位二進(jìn)制加法即可。
直接遠(yuǎn)call編碼(慢,不使用)
您可能會(huì)在的手冊(cè)條目中注意到,call并且jmp其中的編碼帶有絕對(duì)目標(biāo)地址,直接編碼在指令中。但那些只存在于“遠(yuǎn)” call/ jmp也設(shè)置CS一個(gè)新的代碼段選擇,這是緩慢的(見(jiàn)昂納霧指南)。
CALL ptr16:32(“在操作數(shù)中給出的遠(yuǎn),絕對(duì)地址調(diào)用”)具有6個(gè)字節(jié)的段:將偏移量直接編碼到指令中,而不是將其作為數(shù)據(jù)從普通尋址模式下的位置加載。因此,這是對(duì)絕對(duì)地址的直接調(diào)用。
Far call還將Push CS:EIP作為返回地址,而不僅僅是EIP,因此它甚至與call僅推送EIP的普通(附近)兼容。這不是問(wèn)題jmp ptr16:32,只是緩慢和弄清楚段部分的內(nèi)容。
更改CS通常僅對(duì)從32位模式更改為64位模式有效,反之亦然。通常,只有內(nèi)核才能執(zhí)行此操作,盡管您可以在大多數(shù)普通的OS(在GDT中保留32位和64位段描述符)下的用戶空間中執(zhí)行此操作。但是,那將是更多愚蠢的計(jì)算機(jī)技巧,而不是有用的東西。(帶有iret或帶有的64位內(nèi)核將返回到32位用戶空間sysexit。大多數(shù)操作系統(tǒng)在引導(dǎo)過(guò)程中僅使用遠(yuǎn)jmp一次即可切換到內(nèi)核模式下的64位代碼段。)
主流操作系統(tǒng)使用的平面內(nèi)存模型不需要更改cs,并且cs對(duì)于用戶空間進(jìn)程將使用什么值還沒(méi)有標(biāo)準(zhǔn)化。即使您想使用far jmp,也必須找出要在細(xì)分選擇器部分中輸入的值。(易而JIT編譯:剛讀當(dāng)前cs有mov eax, cs,但很難提前-的即時(shí)編譯可移植的。)
call ptr16:64不存在,遠(yuǎn)距離直接編碼僅適用于16位和32位代碼。在64位模式下,您只能call使用10字節(jié)的m16:64內(nèi)存操作數(shù),例如call far [rdi]?;?qū)egment:offset推入堆棧并使用retf。

TA貢獻(xiàn)1863條經(jīng)驗(yàn) 獲得超2個(gè)贊
您僅憑一條指令就無(wú)法做到。一個(gè)不錯(cuò)的方法是使用MOV + CALL:
0000000002347490: 48b83412000000000000 mov rax, 0x1234
000000000234749a: 48ffd0 call rax
如果要調(diào)用的過(guò)程的地址發(fā)生更改,請(qǐng)更改從偏移2開(kāi)始的八個(gè)字節(jié)。如果調(diào)用0x1234的代碼的地址發(fā)生更改,則無(wú)需執(zhí)行任何操作,因?yàn)樵搶ぶ肥墙^對(duì)的。
- 2 回答
- 0 關(guān)注
- 885 瀏覽
添加回答
舉報(bào)