代码之家  ›  专栏  ›  技术社区  ›  qwr

短跳转偏移表用法

  •  0
  • qwr  · 技术社区  · 6 年前

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

            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 回复  |  直到 5 年前
        1
  •  1
  •   Peter Cordes    6 年前

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

    jmp *(%edx) 使用 %edx 作为指针,以及 荷载 从32位位置指向的新EIP值 %EDX . 也就是说,这是记忆的间接跳跃。

    所以是 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个并且它们被执行的话很糟糕)。总之,我不记得气体语法。)


    在64位图像代码中, 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访问静态数据(请看 gcc -O3 -m32 -fPIE 输出。)

    ( call +0 does not unbalance the return-address predictor stack 在Intel P6或SNB系列,或AMD K8/推土机上。所以 call / pop 使用安全。不过,亨利没有对西尔弗蒙特进行测试,这确实导致了对纳米3000的错误预测。)

    推荐文章