短跳偏移量表使用情况



我正在尝试使用一个具有短跳偏移量的表:

mov     $4, %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+,这是基于我在这个问题中看到的偏移量:在x86程序集跳转表上,但gdb显示这将我带到内存中完全不同的地方。

另一个想法是读取eip并手动添加偏移量,如以下答案所示:

call get_eip
get_eip:
pop %eax
add %edx, %eax

理想情况下,对于代码高尔夫的兴趣来说,解决方案尽可能短。那么,如何在每个偏移量仅使用1字节的情况下,指定一个跳转表到附近的代码段呢?

x86没有相对的间接跳转。您总是需要计算(或加载(绝对目标地址。

jmp *(%edx)使用%edx作为指针,%edx指向的32位位置加载新的EIP值。也就是说,这是一个内存间接跳跃。

jmp *r1(%edx)也是如此。您链接的问题中的代码是jmp *operations(,%ecx,4),它从指针表中加载一个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:     
...

如果每个块都是相同的大小(或者你可以把它垫成相同的大小(,你根本不需要一张表;您可以缩放选择器

# 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个字节。但如果有超过1个字节,则单字节NOP很糟糕,并且它们被执行(。无论如何,我不记得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不会使Intel P6、SnB系列或AMD K8/推土机上的返回地址预测器堆栈不平衡。因此call/pop可以安全使用。不过,Henry没有在Silvermont上进行测试,它确实会导致Nano3000预测错误。(

最新更新