短跳转偏移表的用法

时间:2018-07-16 04:45:58

标签: assembly x86 jump-table

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

        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并根据我在此问题中看到的偏移量On x86 assembly jump table,但gdb显示这将我带到了内存中完全不同的地方。

另一个想法是读取eip并手动添加偏移量,如图in this answer所示:

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

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

1 个答案:

答案 0 :(得分: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个字节。但是单字节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的错误预测。)