短跳转偏移 table 用法
Short jump offset table usage
我正在尝试使用具有短跳偏移的 table:
mov , %eax
j1:
movzbl offset(%eax),%edx # load jump offset
jmp *(%edx)
r1:
...
offset:
.byte 0, 1, 2, 3, 4 # Example values
Objdump 显示编码为 ff 22
的跳转,这不是短跳转。
我还尝试 jmp *r1(%edx)
跳转到标签 r1
+ 基于我在这个问题中看到的偏移量:On x86 assembly jump table,但 gdb 显示将我带到完全不同的地方在记忆中。
另一个想法是阅读 eip
并手动添加偏移量,如图所示 in this answer:
call get_eip
get_eip:
pop %eax
add %edx, %eax
理想情况下,为了代码高尔夫的兴趣,解决方案尽可能短。那么,如何在每个偏移量仅使用 1 个字节的情况下指定跳转 table 到附近的代码段?
x86 没有相对间接跳转。您总是必须计算(或加载)绝对目标地址。
jmp *(%edx)
使用%edx
作为指针,从%edx
指向的32位位置加载一个新的EIP值.即这是一个内存间接跳转。
jmp *r1(%edx)
也是如此。您链接的问题中的代码是 jmp *operations(,%ecx,4)
,它从 table 指针加载 32 位目标地址。 (这就是它按 4 缩放索引的原因。)如果 EIP 作为通用寄存器公开,则 jmp
将是 mov r1(%edx), %eip
,因此使用 4 个字节的指令 也就不足为奇了作为一个点是没有用的。
要计算目标地址,您可能需要使用寄存器间接跳转,如jmp *%eax
。这会将 EIP 设置为 EAX 的值,因此唯一的内存访问将是从新地址获取指令。
您显然使用的是 32 位模式,因此您不能将相对于 RIP 的 LEA 用于与位置无关的代码。 但是如果你可以让你的代码位置相关,你可以使用标签的地址作为立即数。您已经为 offset(%eax)
使用位置相关寻址(32 位绝对地址作为 disp32),因此您不妨这样做。
.section .rodata
jump_offset: .byte 0, .L2-.L1, .L3-.L1, ...
.section .text
# selector in EAX
movzbl jump_offset(%eax), %eax
add $.L1, %eax
jmp *%eax # EIP = EAX
# put the most common label first: when no branch-target prediction is available,
# the default prediction for an indirect jmp is fall-through.
.L1:
...
.L2:
...
.L3:
...
如果每个块大小相同(或者你可以将其填充到相同大小),则根本不需要 table;你可以缩放选择器:
# selector in EAX
lea .L1(,%eax,8), %eax # or shift or multiply + add for other sizes
jmp *%eax
.p2align 3 # ideally arrange for this to be 0 bytes, by lengthening earlier instructions or padding earlier
.L1: ...
.p2align 3 # pad to a multiple of 8
.L2: ...
.p2align 3
.L3: ...
块大小不是 2 的幂:lea .L1(%eax,%eax,8), %eax
按 9 缩放并添加基数可能比每个浪费 7 个字节更好堵塞。但这意味着您不能再使用 .p2align
来帮助您使每个块大小相同。 (我认为 GAS 可能能够按照 NASM 的方式计算填充(times 9-($-.L1) nop
以插入足够的填充字节以达到超出 .L1
的 9 个字节。但是如果超过 .L1
,则单字节 NOP 很糟糕1 然后它们被执行了。反正我不记得 GAS 语法了。)
在 64 位 PIC 代码中,lea .L1(%rip), %rdx
/ add %rax, %rdx
。
在32位PIC代码中,使用
call .LPIC_reference_point
.LPIC_reference_point:
pop %edx
movzbl jump_offsets - .LPIC_reference_point(%eax), %eax
add %edx, %eax
jmp *%eax
或者像编译器那样使用 GOT 访问静态数据的 PIC(查看 gcc -O3 -m32 -fPIE
输出。)
(call +0
does not unbalance the return-address predictor stack 在 Intel P6 或 SnB 系列上,或 AMD K8/Bulldozer。所以 call
/pop
可以安全使用。Henry 没有测试不过,在 Silvermont 上,它确实会导致错误预测 Nano3000。)
我正在尝试使用具有短跳偏移的 table:
mov , %eax
j1:
movzbl offset(%eax),%edx # load jump offset
jmp *(%edx)
r1:
...
offset:
.byte 0, 1, 2, 3, 4 # Example values
Objdump 显示编码为 ff 22
的跳转,这不是短跳转。
我还尝试 jmp *r1(%edx)
跳转到标签 r1
+ 基于我在这个问题中看到的偏移量:On x86 assembly jump table,但 gdb 显示将我带到完全不同的地方在记忆中。
另一个想法是阅读 eip
并手动添加偏移量,如图所示 in this answer:
call get_eip
get_eip:
pop %eax
add %edx, %eax
理想情况下,为了代码高尔夫的兴趣,解决方案尽可能短。那么,如何在每个偏移量仅使用 1 个字节的情况下指定跳转 table 到附近的代码段?
x86 没有相对间接跳转。您总是必须计算(或加载)绝对目标地址。
jmp *(%edx)
使用%edx
作为指针,从%edx
指向的32位位置加载一个新的EIP值.即这是一个内存间接跳转。
jmp *r1(%edx)
也是如此。您链接的问题中的代码是 jmp *operations(,%ecx,4)
,它从 table 指针加载 32 位目标地址。 (这就是它按 4 缩放索引的原因。)如果 EIP 作为通用寄存器公开,则 jmp
将是 mov r1(%edx), %eip
,因此使用 4 个字节的指令 也就不足为奇了作为一个点是没有用的。
要计算目标地址,您可能需要使用寄存器间接跳转,如jmp *%eax
。这会将 EIP 设置为 EAX 的值,因此唯一的内存访问将是从新地址获取指令。
您显然使用的是 32 位模式,因此您不能将相对于 RIP 的 LEA 用于与位置无关的代码。 但是如果你可以让你的代码位置相关,你可以使用标签的地址作为立即数。您已经为 offset(%eax)
使用位置相关寻址(32 位绝对地址作为 disp32),因此您不妨这样做。
.section .rodata
jump_offset: .byte 0, .L2-.L1, .L3-.L1, ...
.section .text
# selector in EAX
movzbl jump_offset(%eax), %eax
add $.L1, %eax
jmp *%eax # EIP = EAX
# put the most common label first: when no branch-target prediction is available,
# the default prediction for an indirect jmp is fall-through.
.L1:
...
.L2:
...
.L3:
...
如果每个块大小相同(或者你可以将其填充到相同大小),则根本不需要 table;你可以缩放选择器:
# selector in EAX
lea .L1(,%eax,8), %eax # or shift or multiply + add for other sizes
jmp *%eax
.p2align 3 # ideally arrange for this to be 0 bytes, by lengthening earlier instructions or padding earlier
.L1: ...
.p2align 3 # pad to a multiple of 8
.L2: ...
.p2align 3
.L3: ...
块大小不是 2 的幂:lea .L1(%eax,%eax,8), %eax
按 9 缩放并添加基数可能比每个浪费 7 个字节更好堵塞。但这意味着您不能再使用 .p2align
来帮助您使每个块大小相同。 (我认为 GAS 可能能够按照 NASM 的方式计算填充(times 9-($-.L1) nop
以插入足够的填充字节以达到超出 .L1
的 9 个字节。但是如果超过 .L1
,则单字节 NOP 很糟糕1 然后它们被执行了。反正我不记得 GAS 语法了。)
在 64 位 PIC 代码中,lea .L1(%rip), %rdx
/ add %rax, %rdx
。
在32位PIC代码中,使用
call .LPIC_reference_point
.LPIC_reference_point:
pop %edx
movzbl jump_offsets - .LPIC_reference_point(%eax), %eax
add %edx, %eax
jmp *%eax
或者像编译器那样使用 GOT 访问静态数据的 PIC(查看 gcc -O3 -m32 -fPIE
输出。)
(call +0
does not unbalance the return-address predictor stack 在 Intel P6 或 SnB 系列上,或 AMD K8/Bulldozer。所以 call
/pop
可以安全使用。Henry 没有测试不过,在 Silvermont 上,它确实会导致错误预测 Nano3000。)