我试图使用跳转表在程序集中(MASM64、Windows、x64)实现一种算法。基本思想是:我需要对数据进行3种不同类型的操作。这些操作依赖于一些变量,但我发现实现大量的切换和许多长的实现非常繁琐。
PUBLIC superFunc@@40 ;__vectorcall decoration
.DATA
ALIGN 16
jumpTable1 qword func_11, func_12, func_13, func_14
jumpTable2 qword func_21, func_22, func_23, func_24
jumpTable3 qword func_31, func_32, func_33, func_34
.CODE
superFunc@@40 PROC
;no stack actions, as we should do our stuff as a leaf function
;assume the first parameter (rcx) is our jumpTable index, and it's
;the same index for all functions
mov rax, qword ptr [rcx*8 + offset jumpTable1]
mov r10, qword ptr [rcx*8 + offset jumpTable2]
mov r11, qword ptr [rcx*8 + offset jumpTable3]
jmp qword ptr [rax]
superFunc@@40 ENDP
func_11:
[...] do something with data
jmp qword ptr [r10]
func_12: ; shorted, simply does something else to the data and jumps thru r10
[...]
func_21:
[...] do something with data
jmp qword ptr [r11]
func_22: ; shorted, simply does something else to the data and jumps thru r11
[...]
func_31:
[...] do something with data
ret
func_32: ; shorted, simply does something else to the data and returns
END现在,它编译得很好,但是它没有链接到我的主C++插件( DLL),给出了以下链接器错误:
LINK : warning LNK4075: ignoring '/LARGEADDRESSAWARE:NO' due to '/DLL' specification
error LNK2017: 'ADDR32' relocation to 'jumpTable1' invalid without /LARGEADDRESSAWARE:NO我怎样才能正确地实现这样的东西?也许更好的措辞是:如何在MASM64中正确地实现跳转表和跳/调用这些表中的地址?
P.S.:我可以在C++中设置一个函数表,并通过一个参数告诉superFunc。如果我找不到更好的解决办法,我就会这么做。
发布于 2019-08-20 10:53:00
只有在寻址模式中没有其他寄存器时,RIP相对寻址才能工作。
[table + rcx*8]只能以[disp32 + rcx*8]的形式在x86-64机器代码中编码,因此只适用于适合32位签名的绝对地址的非大型地址。Windows显然可以通过LARGEADDRESSAWARE:NO支持这一点,就像在Linux -no-pie上解决同样的问题一样。
MacOS没有解决办法,您根本不能在那里使用64位绝对寻址。lea Mach-O 64-bit format does not support 32-bit absolute addresses. NASM Accessing Array向展示了如何使用RIP相对的索引静态数组,从而将表地址输入寄存器,同时避免32位绝对地址。
跳转表本身很好:它们使用64位绝对地址,可以在虚拟地址空间中的任何位置重新定位。(在ASLR之后使用加载时间修复。)
,我想你有太多的间接方向了。因为您已经将函数指针加载到寄存器中,所以应该使用jmp r10而不是jmp [r10]。预先将所有负载加载到寄存器中,在任何可能的分支错误预测之前,都会使它们更快地投入使用,因此,如果您有大量的寄存器可供使用,也可能是个好主意。
更好的方法是内联后面的一些块(如果它们很小),因为任何给定的RCX值所能访问的块都是不可用其他方式到达的。因此,最好将所有func_21和func_31内联到func_11中,等等,用于func_12。您可以使用汇编程序宏使这更容易。
实际上,重要的是,func_11结尾处的跳转总是流向func_21。有其他方法可以到达这个块,例如跳过表1的其他间接分支。这不是func_11不进入该块的原因;它只限制了在这两个块之间进行的优化,如果func_21仍然必须是执行路径的有效入口点,而这些路径没有从func_11掉过。
但是无论如何,您可以像这样实现代码。如果对其进行优化,则可以删除后面的调度步骤和相应的负载。
我认为这是有效的MASM语法。如果不是,那么仍然应该清楚所需的机器代码是什么。
lea rax, [jumpTable1] ; RIP-relative by default in MASM, like GAS [RIP + jumpTable1] or NASM [rel jumpTable1]
; The other tables are at assemble-time-constant small offsets from RAX
mov r10, [rax + rcx*8 + jumpTable3 - jumpTable1]
mov r11, [rax + rcx*8 + jumpTable2 - jumpTable1]
jmp [rax + rcx*8]
func_11:
...
jmp r10 ; TODO: inline func_21 or at least use jmp func_21
; you can use macros to help with either of those或者,如果您只想为一个表绑定一个寄存器,可以使用:
lea r10, [jumpTable1] ; RIP-relative LEA
lea r10, [r10 + rcx*8] ; address of the function pointer we want
jmp [r10]
align 8
func_11:
...
jmp [r10 + jumpTable2 - jumpTable1] ; same index in another table
align 8
func_12:
...
jmp [r10 + jumpTable3 - jumpTable1] ; same index in *another* table这充分利用了表之间已知的静态偏移量。
跳目标的缓存局部性()
在你的跳变目标矩阵中,任何一种用法都会沿着“列”的方向跨出,以跟随某种跳跃链。显然,更好的方法是转换布局,使一条跳转链沿着“行”进行,因此所有目标都来自同一条缓存线。
也就是说,安排您的表,以便jmp [r10+8]**,和21可以结束,然后** jmp [r10+16],,而不是表之间的一些偏移,以改善空间局部性。L1d加载延迟只有几个周期,所以在检查分支预测的正确性时,CPU没有太多额外的延迟,而不是在第一个间接分支之前加载到寄存器中。(我正在考虑第一个分支错误预测的情况,因此OoO高管在发出正确的路径后才能“看到”内存--间接jmp。)
避免64位绝对地址:
您还可以存储32位(或16位或8位)偏移量,这些偏移量相对于跳转目标附近的某个参考地址,或者相对于表本身。
例如,看看GCC在以独立位置的代码编译switch跳转表时所做的工作,甚至对于允许绝对地址运行时修复的目标也是如此。
bug.cgi?id=84011包括一个测试用例;请在.intel_syntax上查看它。它使用表中的movsxd加载,然后是add rax, rdx / jmp rax。表条目类似于dd L27 - L4和dd L25 - L4 (其中它们是标签名,给出了从跳转目标到“锚”L4的距离)。
(在这种情况下也与bug.cgi?id=85585有关)。
https://stackoverflow.com/questions/57571027
复制相似问题