【问题标题】:Short jump offset table usage短跳转偏移表使用
【发布时间】:2018-12-23 13:58:00
【问题描述】:

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

        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 个字节的情况下指定一个跳转表到附近的代码段?

【问题讨论】:

    标签: assembly x86 jump-table


    【解决方案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。)

    【讨论】:

    • 标签减法整齐
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-13
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 2020-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多