ljmp(在64位模式下)被#GP(0)拒绝。为什么?

时间:2018-01-12 03:11:55

标签: assembly x86 x86-64

代码很少:

  • 使用2个条目(NULL和代码描述符)设置GDT。
  • 尝试将far-jmp插入新安装的代码部分。

但是我得到了#GP(0),这是最后一步。 为什么呢?

以下是代码(在64位模式下从已经运行):

call    do_lgdt           # 'do_lgdt' is defined at the bottom
push    $(1<<3)           # Push code-selector,TI=0,RPL=0
lea     func(%rip),%rax   # The (relocated) address of 'func'
push    %rax              # Push it.
ljmp    *(%rsp)           # FAILS!!! #GP(0) (error code 0).

func:
jmp     .                 # Hang


do_lgdt:
lea     gdt(%rip),%rax    # The (relocated) address of 'gdt'.
                          # 'gdt' is defined at the bottom.
push    %rax              # Base
pushw   $(gdt_end-gdt-1)  # Limit
lgdt    (%rsp)
add     $0xa,%rsp         # Re-align the stack.
ret                       # Thanks @mevets..

.align 8                  # Thanks @PeterCordes
gdt:
.quad 0x0000000000000000  # NULL Descriptor
.quad 0x00a09a0000000000  # limit: 0x0000 (Ignored. Thanks @MichaelPetch)
                          # base : 0x0000
                          # type :    0xa (code/data=1,non-conforming,exec,accessed=0)
                          #      :    0x9 (system=1,DPL=0,present=1)
                          # limit:    0x0 (Ignored. Thanks @MichaelPetch)
                          #      :    0xa (avl=0,l=1,d=0,granularity=1)
                          # base :   0x00

gdt_end:

2 个答案:

答案 0 :(得分:2)

GAS汇总ljmp *(%rsp)没有REX前缀,因此操作数大小为m16:32 。长模式也允许a m16:64 form for far jmp (REW.W FF /5),但不允许 dis - 允许16:32表格。

不幸的是,当使用ljmp而没有后缀时,GAS不会警告不明确的操作数大小,而binutils 2.29.1似乎甚至不了解REX形式。它不接受ljmpq,但它接受ljmpl(作为非REX表单)。

第一代AMD64 CPU(AMD K8)并不支持REX.W m16:64形式的jmp,而binutils在推出时可能永远不会更新。如果您需要64位目标地址,则推送iretqretfq was recommended的操作数,并且由于compat原因,可能仍然在AMD手册中。如果这是你唯一的错误,iretq didn't work,那你就错了。

在您的GAS来源中,我使用了

    .byte 0x48      # REX.W
    ljmp  *(%rsp)   # m16:64

在NDISASM语法中,两种形式是:

0000004F  48FF2C24          jmp far [rsp]

0000004F  FF2C24            jmp dword far [rsp]

objdump输出中(可能与GAS接受的语法相同):

# non-REX m16:32
f:   ff 2c 24                jmp    FWORD PTR [rsp]
f:   ff 2c 24                ljmp   *(%rsp)


# REX.W m16:64
f:   48 ff 2c 24             rex.W ljmp *(%rsp)
f:   48 ff 2c 24             rex.W jmp FWORD PTR [rsp]

另一种选择(如果你的指针也是低32位)可能会节省几个代码字节(虽然movw imm16 +寻址模式比push imm8长,所以可能没有。

push  %rax
movw  $(1<<3), 4(%rsp)     # rewrite the top half of what you pushed
ljmpl *(%rsp)              # m16:32

另请注意,如果您的代码在禁用中断的情况下运行,则do_gdt函数可以push %rax,然后存储到-2(%rsp),而不是执行单词推送。虽然这可能实际上并不小。但是对于代码大小,您可以使用单个pop而不是2来恢复堆栈。(pop + popw仍然会比add $10, %rsp更短(总共3B)(4B总))。

感谢@prl发现m16:32问题,我只是花时间写下来并检查两个反汇编程序的作用。

答案 1 :(得分:1)

do_lgdt不应该有一个ret?否则你将执行gdt ...