0

いくつかの SSE2 操作を実行する 64 ビット x86 アセンブリ (gcc および GAS の AT&T 構文) で記述された関数があります。逆アセンブルで gdb を使用し、レジスタ値を調べて結果を確認したので、正しい結果が生成されていることがわかりました。retq 命令の後、セグメンテーション違反が発生します。私はアセンブリに不慣れなので(そしてクラスを受講したことはありません)、関数/メインプログラムインターフェイスを正しく処理していないと推測しています。関数は 2 つのポインターと int を受け取り、float を返すことが期待されます。これは、アセンブリ関数で入力/出力を処理する方法です。

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)

2 番目の質問は、なぜ xmm0 の値を勝手に 2 つの場所に移動するのですか? 関数の結果を 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スタックの値をポップし、それにジャンプします。すべてがうまくいけば、それは によってプッシュされた値でしたcallqgccを含む関数プロローグ (上記の最初の 2 つの命令) を生成しました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 で行われます。また、float の結果を格納するためのポインターも渡します。

これにより、次のアセンブリが生成されます。これは、おおよそ探していたものです。

_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   

もう 1 つのオプションは、常にアセンブリ ファイルに記述し、後で C コードとリンクすることです。

少しでもお役に立てば幸いですが、質問への回答が不十分でしたら申し訳ありません。

編集:コードを実際に実行できるものに更新しました。

于 2013-02-24T07:12:26.733 に答える