0

我有一个用 64 位 x86 程序集(gcc 和 GAS 的 AT&T 语法)编写的函数,它执行一些 SSE2 操作。我已经通过使用带有反汇编的 gdb 并查看寄存器值来检查结果,所以我知道它产生了正确的结果。在 retq 指令之后,我得到一个分段错误。由于我是汇编新手(并且从未上过任何课程),我猜我没有正确处理函数/主程序接口。该函数接受 2 个指针和一个 int,并预计返回一个浮点数。这就是我在汇编函数中处理输入/输出的方式:

float foo(float *x,float *y,unsigned int s)
{
    __asm__ __volatile__(
    "movl   -0x14(%%rbp),%%ecx \n\t"   //ecx = s
    "movq   -0x8(%%rbp),%%rax \n\t"    //rax -> x
    "movq   -0x10(%%rbp),%%rdx \n\t"   //rdx -> y
    "subq   $4,%%rsp \n\t"             //function result
    #sse2 operations that end up with the answer in xmm4...
    "movss  %%xmm4,(%%rsp) \n\t"       //store result
    "flds   (%%rsp) \n\t"              //load function result
    "addq   $4,%%rsp \n\t"             //adjust stack
    "ret \n\t"
    :
    :"g"(s)
    :"%ecx","%rax","%rdx"
    );
}

这是似乎导致段错误的行(这是反汇编中 ret 之后的指令):

0x00007fffffffe0d0 in ?? ()
=> 0x00007fffffffe0d0:  00 00   add    %al,(%rax)

我不知道为什么它在执行我的函数后将 rax 的低位中的值添加回 rax,但它似乎正在崩溃。我是否不允许在我的汇编函数中使用 rax,即使它是通用的并且我声明它已被破坏?

我不确定您是否需要查看这部分,但这是 gcc 期望处理该功能的方式;我已经包含了调用我的函数的行的反汇编:

    #asm dealing with function inputs
    callq  0x400520 <foo>
    movss  %xmm0,-0x48(%rbp)
    mov    -0x48(%rbp),%eax
    mov    %eax,-0x34(%rbp)

这就引出了我的第二个问题,为什么将 xmm0 中的值任意移动到两个位置?我是否应该让我的函数以 xmm0 的结果结束,或者这是否意味着我应该避免使用 xmm0?我很困惑,不胜感激。提前感谢任何花时间阅读我的菜鸟帖子的人:)

4

1 回答 1

7

您的问题是内联汇编不会替换该功能。您的函数编译为:

_foo:
 push   %rbp              ; function prologue
 mov    %rsp,%rbp
 mov    %rdi,-0x8(%rbp)
 mov    %rsi,-0x10(%rbp)
 mov    %edx,-0x14(%rbp)
 mov    -0x14(%rbp),%eax
 mov    %eax,-0x1c(%rbp)

 mov    -0x14(%rbp),%ecx  ; your code
 mov    -0x8(%rbp),%rax
 mov    -0x10(%rbp),%rdx
 sub    $0x4,%rsp
 movss  %xmm4,(%rsp)
 flds   (%rsp)
 add    $0x4,%rsp
 retq                     ; your return

 movss  -0x18(%rbp),%xmm0 ; function epilogue
 pop    %rbp
 retq                     ; gcc's return

retq弹出堆栈的一个值,并跳转到它。如果一切顺利,这是一个被callq. gcc生成了一个函数序言(上面的前两条指令),包括push %rbp. 因此,当您retq运行时,它会弹出rbp(指向堆栈的指针)并跳转到它。这可能会导致分段错误,因为堆栈不可执行(也可能是因为 %rax 是无效指针,如果由于某种原因您的堆栈是可执行的)。它碰巧指向的堆栈上的值00 00(不出所料,在内存中出现了很多)并且巧合地反汇编为add %al,(%rax).

现在,我是 SSE 的新手,我只使用过几次 GCC 内联汇编,所以我不确定这是否是一个可行的解决方案。您真的不应该查看堆栈或返回,因为不同的编译器将具有不同的函数,从而在代码运行时介绍堆栈上参数的相对位置。

尝试类似:

#include <stdio.h>

float foo(float *x,float *y,unsigned int s)
{
    float result;

    __asm__ __volatile__(
    "movss  (%%rax),%%xmm4 \n\t"       // xmm4 = *x
    "movss  (%%rdx),%%xmm5 \n\t"       // xmm5 = *y
    "addss  %%xmm5,%%xmm4  \n\t"       // xmm4 += xmm5

    "movss  %%xmm4,(%%rbx) \n\t"       // result = xmm4
    :
    :"c"(s), "a"(x), "d"(y), "b"(&result)  // ecx = s, eax = x, edx = y, ebx = &result
    :"memory", "cc"
    );

    return result;
}

int main() {
    float x = 1.0, y = 2.0;
    printf("%f", foo(&x, &y, 99));
    return 0;
}

所有的堆栈分配、参数处理和返回都是在 C 中完成的。它还传入一个用于存储浮点结果的指针。

这会生成以下程序集,大致就是您要查找的程序集:

_foo:
 push   %rbp              ; prologue
 mov    %rsp,%rbp
 push   %rbx

 lea    -0xc(%rbp),%rbx   ; set up registers
 mov    %edx,%ecx
 mov    %rdi,%rax
 mov    %rsi,%rdx

 movss  (%rax),%xmm4      ; your code
 movss  (%rdx),%xmm5
 addss  %xmm5,%xmm4
 movss  %xmm4,(%rbx)

 movss  -0xc(%rbp),%xmm0  ; retrieve result to xmm0 (the return register)

 pop    %rbx              ; epilogue
 pop    %rbp
 retq   

另一种选择是始终将其写入汇编文件,然后将其与您的 C 代码链接。

我希望这有点帮助,但如果它没有完全回答你的问题,我很抱歉。

编辑:将代码更新为实际为我运行的东西。

于 2013-02-24T07:12:26.733 回答