您如何在x86 / x64中创建跳转表?

问题描述 投票:0回答:2

我目前正在学习x86 / x64 asm,我想尝试制作一个跳转表,但我无法弄清楚自己在做什么错。

这个概念本身对我来说并不陌生,我只是想不通为什么它不起作用。我在研究[]时曾几次见过[]的用法,但是我不确定这是否是正确的方法。

.data
var qword 10

.code
main proc

    mov rax, var
    jmp [table]
back:
    ret

table:
qword subroutine, subroutine2

subroutine:

    mul var
    jmp back

subroutine2:

    mul var
    jmp back

main endp
end

当我单步执行代码时,它跳过了jmp指令,在ret上出现access violation reading location 0x00000000错误

assembly x86-64 masm
2个回答
1
投票

注意,如前所述,MASM将忽略[]。取而代之的是,MASM按标签的类型排列。在这种情况下,问题在于:表后(table :)使通常用作分支或调用目标的“代码”类型的标签,因此jmp [table]或jmp table会像在代码。

[删除:并将qword(或dq可以使用)放在同一行,将表更改为qword类型,因此jmp [table]或jmp table,将表中的qword地址加载到RIP中,并根据需要执行分支。

table   qword   subroutine, subroutine2

但是,如果要索引表,则需要使用寄存器来保存表的偏移量(例如lea r9,table),或者对于Visual Studio,请转到project / properties /链接器/系统/启用大地址:否(设置链接器参数/ LARGEADDRESSAWARE:NO)。我在下面发布了这两种情况的示例。


此示例与Visual Studio中的ML64.EXE(MASM)一起使用。该表可以在代码或数据部分中。如果表是数据的第一行,则lea生成{4C 8D 0D 79 E5 00 00},如果表是代码的第一行,则lea生成{4C 8D 0D E1 FF FF FF}。我不知道哪个对性能更好。似乎,如果未充分利用数据缓存,那么它将保留数据缓存表的副本。

        .data
tbl     dq      fun1, fun2, fun3            ;table
        .code

main    proc
        lea     r9,tbl
        mov     rax,0
main0:  jmp     qword ptr [r9+rax*8]
main1:: inc     rax
        cmp     rax,3
        jb      main0
        xor     eax,eax
        ret
main    endp

fun1    proc
        mov     rdx,1
        jmp     main1
fun1    endp

fun2    proc
        mov     rdx,2
        jmp     main1
fun2    endp

fun3    proc
        mov     rdx,3
        jmp     main1
fun3    endp

        end

使用Visual Studio链接器参数/ LARGEADDRESSAWARE:NO,无需使用第二个寄存器。该表可以在数据或代码部分中。如果表是数据的第一行,则jmp生成{FF 24 C5 00 00 3D 00},如果表是代码的第一行,则jmp生成{FF 24 C5 80 1A 2D 01}。我不知道哪个对性能更好。似乎,如果未充分利用数据缓存,那么它将保留数据缓存表的副本。

        .data
tbl     dq      fun1, fun2, fun3            ;table
        .code
main    proc
        mov     rax,0
main0:  jmp     qword ptr [tbl+rax*8]
main1:: inc     rax
        cmp     rax,3
        jb      main0
        xor     eax,eax
        ret
main    endp

fun1    proc
        mov     rdx,1
        jmp     main1
fun1    endp

fun2    proc
        mov     rdx,2
        jmp     main1
fun2    endp

fun3    proc
        mov     rdx,3
        jmp     main1
fun3    endp

0
投票

利用封装在编译器中的有关汇编级编程的大量默契知识,总是很有趣,而且通常很有趣。如果编译某种形式的开关:

int foo(int i, int a, int b) {
    switch (i) {
        case 0: return a * b;
        case 1: return a + b;
        case 4: return a - b;
        case 5: return a / b;
    }
}

您将获得一个跳转表:

foo(int, int, int):                              # @foo(int, int, int)
        mov     ecx, edx
        mov     eax, edi
        jmp     qword ptr [8*rax + .LJTI0_0]
.LBB0_5:
        imul    ecx, esi
        mov     eax, ecx
        ret
.LBB0_3:
        mov     eax, esi
        cdq
        idiv    ecx
        ret
.LBB0_1:
        add     ecx, esi
        mov     eax, ecx
        ret
.LBB0_2:
        sub     esi, ecx
        mov     eax, esi
        ret
.LBB0_4:
.LJTI0_0:
        .quad   .LBB0_5
        .quad   .LBB0_1
        .quad   .LBB0_4
        .quad   .LBB0_4
        .quad   .LBB0_2
        .quad   .LBB0_3

对于这样的摆弄,Compiler Explorer是天赐之物。

© www.soinside.com 2019 - 2024. All rights reserved.