Assembly MASM x64中的跳转表实现?

Assembly MASM x64中的跳转表实现?,assembly,x86-64,masm,Assembly,X86 64,Masm,我正在尝试使用跳转表在汇编(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, fun

我正在尝试使用跳转表在汇编(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++中设置函数表,并通过参数告诉超级表。如果找不到更好的解决方案,我会这么做。

RIP相对寻址仅在寻址模式中没有其他寄存器时才起作用

[table+rcx*8]
只能在x86-64机器代码中编码为
[disp32+rcx*8]
,因此只能与适合32位有符号绝对地址的非大地址一起使用。Windows显然可以通过
LargeAddressware:NO
来支持这一点,就像在Linux上解决同样的问题一样

MacOS没有解决方法,根本不能使用64位绝对寻址。演示如何使用RIP相对
lea
索引静态数组,以将表地址放入寄存器,同时避免使用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
这充分利用了表之间已知的静态偏移


缓存跳转目标的位置

在你的跳转目标矩阵中,任何一次使用都会沿着一个“列”移动,以跟随一些跳转链。显然,更好的方法是变换布局,使一条跳跃链沿着“行”移动,这样所有目标都来自同一缓存线

i、 e.安排您的表格,以便
func_11
21
可以以
jmp[r10+8]
结束,然后
jmp[r10+16]
代替表格之间的+一些偏移,以改善空间位置。L1d加载延迟只有几个周期,因此与在第一个间接分支之前加载到寄存器相比,CPU在检查分支预测的正确性时不会有太多额外延迟。(我正在考虑第一个分支预测失误的情况,因此OoO exec无法“看到”内存间接jmp,直到正确的路径开始出现问题。)


避免64位绝对地址: 您还可以存储32位(或16位或8位)偏移量,这些偏移量相对于跳转目标附近的某个参考地址,或者相对于表本身

例如,查看GCC在编译位置无关代码中的
开关
跳转表时所做的工作,即使对于允许运行时修复绝对地址的目标也是如此

包括一个测试用例;在电视上看。它使用表中的
movsxd
加载,然后
添加rax、rdx
/
jmp-rax
。表格条目类似于
dd L27-L4
dd L25-L4
(其中这些是标签名称,表示跳转目标到“锚”L4的距离)


(也与该情况相关)。

RIP相对寻址仅在寻址模式中没有其他寄存器时工作

[table+rcx*8]
只能在x86-64机器代码中编码为
[disp32+rcx*8]
,因此只能与适合32位有符号绝对地址的非大地址一起使用。Windows显然可以通过
LargeAddressware:NO
来支持这一点,就像在Linux上解决同样的问题一样

MacOS没有解决方法,根本不能使用64位绝对寻址。演示如何使用RIP相对
lea
索引静态数组,以将表地址放入寄存器,同时避免使用32位绝对地址

跳转表本身很好:它们使用64位绝对地址,可以在虚拟地址空间中的任何位置重新定位。(使用加载时间修正)