0

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

        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)跳转到标签+ 偏移量: On x86 assembly jump table,但是 gdb 显示这会将我带到内存中完全不同的地方。r1

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

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

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

4

1 回答 1

2

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

jmp *(%edx)用作%edx指针,并从 . 指向的 32 位位置加载%edx新的 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插入足够的填充字节以达到超过 9 个字节.L1。但是如果单字节 NOP 超过 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/Bulldozer 上的返回地址预测器堆栈失衡call。所以/pop可以安全使用。不过,Henry 没有在 Silvermont 上进行测试,它确实会导致错误预测纳米3000。)

于 2018-07-16T05:58:24.850 回答