我目前正在学习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
错误
注意,如前所述,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
利用封装在编译器中的有关汇编级编程的大量默契知识,总是很有趣,而且通常很有趣。如果编译某种形式的开关:
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是天赐之物。