x86_64对齐堆栈并在不保存寄存器的情况下恢复

时间:2012-03-06 21:39:51

标签: c stack alignment x86-64

我正在为x86_64编写中断处理例程。 ABI指定在调用C函数之前,我必须将堆栈对齐到16个字节。 x86_64 ISA指定在进入ISR时,我的堆栈是8字节对齐的。我需要将我的堆栈指针对齐到16个字节。问题是,从我的C函数返回时,我必须恢复(可能)未对齐的堆栈指针,以便我可以正确地从我的中断返回。

我想知道是否有办法在不使用通用寄存器的情况下执行此操作?

4 个答案:

答案 0 :(得分:9)

以下是我对问题的解决方案:

pushq %rsp
pushq (%rsp)
andq $-0x10, %rsp
    call function
movq 8(%rsp), %rsp

两次推送使堆栈具有与原始时相同的对齐方式,以及%rsp(%rsp)处原始8(%rsp)的副本。 andq然后对齐堆栈 - 如果它已经是16字节对齐,则没有任何变化,如果它是8字节对齐,那么它从%rsp减去8,这意味着原始%rsp现在处于8(%rsp)16(%rsp)。因此,我们可以无条件地从8(%rsp)恢复它。

答案 1 :(得分:4)

没有额外的寄存器就无法做到这一点,因为对齐操作对rsp寄存器具有破坏性。你需要做点什么

push %rbp          ;save rbp for stack pointer
mov  %rsp, %rbp    ;move old sp to rbp
and  $-0x10, %rsp  ;align stack
...                
...                ;if you want to use %rbp here, save it on the stack before
...  
mov  %rbp, %rsp    ;old stack pointer
pop  %rbp
iret

答案 2 :(得分:1)

可能比其他人描述的那样使用%ebp慢,但是如何:

    push %rsp
    test $0xf, %rsp
    jz aligned
    push (%rsp)   // duplicate the top of the stack
aligned:
    // now have 16-byte alignment with the original stack pointer
    // on the top of the stack, either once or twice
         :
    pop %rsp
    iret

这利用了堆栈已经是8字节对齐的事实,并且push指令可以读取要从内存中推送的值。

答案 3 :(得分:0)

我怀疑在不使用额外寄存器的情况下实现它的唯一方法是需要额外写入和读取存储器,这是不使用额外寄存器的唯一要点。

我会提供我现有的解决方案。我存储rbp所以我可以使用它进行临时存储,然后在调用函数之前恢复它。这类似于drhirsch的回答

movq    %rbp, -24(%rsp) //store original rbp 3 words beyond the stack
movq    %rsp, %rbp //store original rsp
subq    $8, %rsp //buy a word on the stack
andq    $-0x10, %rsp //16 byte align the stack (growing downwards)
//We now have 1 or 2 words free on the stack (depending on the original
// alignment). This is why we put rbp 3 words beyond the stack
movq    %rbp, (%rsp) //store the original rsp right here
movq    -24(%rbp), %rbp //restore original rbp
call    foo
movq    (%rsp), %rsp //restore original rsp
iretq