2

C で (インライン ASM を使用して) 現在のスタック ポインターを取得しようとすると、奇妙な動作に遭遇しました。コードは次のようになります。

#include <stdio.h>
class os {
  public:
    static void* current_stack_pointer();
};

void* os::current_stack_pointer() {
  register void *esp __asm__ ("rsp");
  return esp;
}

int main() {
  printf("%p\n", os::current_stack_pointer());
}

標準の gcc オプションを使用してコードをコンパイルすると、次のようになります。

$ g++ test.cc -o test

次のアセンブリが生成されます。

__ZN2os21current_stack_pointerEv:
0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp,%rbp
0000000000000004        movq    %rdi,0xf8(%rbp)
0000000000000008        movq    0xe0(%rbp),%rax
000000000000000c        movq    %rax,%rsp
000000000000000f        movq    %rsp,%rax
0000000000000012        movq    %rax,0xe8(%rbp)
0000000000000016        movq    0xe8(%rbp),%rax
000000000000001a        movq    %rax,0xf0(%rbp)
000000000000001e        movq    0xf0(%rbp),%rax
0000000000000022        popq    %rbp

結果のバイナリを実行すると、SIGILL (Illegal Instruction) でクラッシュします。ただし、コンパイルに少し最適化を追加すると、次のようになります。

$ g++ -O1 test.cc -o test

生成されたアセンブリは、はるかに単純です。

0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp,%rbp
0000000000000004        movq    %rsp,%rax
0000000000000007        popq    %rbp
0000000000000008        ret

そして、コードは正常に実行されます。それでは、質問に。Mac OS X で C コードからスタック ポインタを取得するためのより安定した方法はありますか? 同じコードは Linux では問題ありません。

4

4 に答える 4

5

関数呼び出しを介してスタック ポインターを取得しようとする際の問題は、呼び出された関数内のスタック ポインターが、関数が戻った後に完全に異なる値を指しているため、次の場所のアドレスをキャプチャしていることです。通話後は無効となります。また、そのプラットフォームでコンパイラによって追加された関数プロローグがないと仮定しています (つまり、両方の関数には現在、コンパイラが関数のスタックに現在のアクティベーション レコードをセットアップするプロローグがあり、これにより、キャプチャしようとしている RSP の値)。少なくとも、コンパイラーによって追加された関数プロローグがなければ、実際に「真」を取得するために、使用しているプラ​​ットフォーム上のポインターのサイズを差し引く必要があります。関数呼び出しから戻った後にスタックが指すアドレス。これは、アセンブリ コマンドcall命令ポインターの戻りアドレスをスタックにプッシュretし、呼び出し先でその値をスタックからポップします。したがって、呼び出し先の内部には、少なくともスタックポインターが指すリターンアドレス命令があり、その場所は関数呼び出し後は無効になります。最後に、特定のプラットフォーム (残念ながら x86 ではありません) では、__attributes__((naked))タグを使用して、プロローグなしで関数を作成できますgcc。キーワードを使用しinlineてプロローグを回避することは、コンパイラに関数のインライン化を強制しないため、完全に信頼できるとは言えません...特定の最適化レベルが低い場合、インライン化は行われず、再びプロローグになってしまいます。そのような場合にアドレスを取得することにした場合、スタックポインターは正しい場所を指していません。

スタック ポインターの値が必要な場合、信頼できる唯一の方法は、アセンブリを使用し、プラットフォームの ABI の規則に従い、アセンブラーを使用してオブジェクト ファイルにコンパイルし、そのオブジェクト ファイルを残りのファイルとリンクすることです。実行可能ファイル内のオブジェクト ファイル。次に、ヘッダー ファイルに関数宣言を含めることにより、アセンブラー関数をコードの残りの部分に公開できます。したがって、コードは次のようになります (gccアセンブリをコンパイルするために使用していると仮定します)。

//get_stack_pointer.h
extern "C" void* get_stack_ptr();

//get_stack_pointer.S
.section .text
.global get_stack_ptr

get_stack_ptr:
    movq %rsp, %rax
    addq $8, %rax
    ret
于 2011-10-18T18:40:43.040 に答える
4

制約付きの変数を使用するのではなく、register明示的なインライン アセンブラを記述して fetch を実行する必要があります%esp

static void *getsp(void)
{
    void *sp;
    __asm__ __volatile__ ("movq %%rsp,%0"
    : "=r" (sp)
    : /* No input */);
    return sp;
}

gcc ステートメント式を使用して、これをマクロに変換することもできます。

#define GETSP() ({void *sp;__asm__ __volatile__("movl %%esp,%0":"=r"(sp):);sp;})
于 2011-10-19T06:31:39.093 に答える
0

そのためのリファレンスはありませんが、GCC は、コンパイルがまったく最適化されていない場合、インライン アセンブリの存在下で時折 (しばしば) 誤動作することが知られています。したがって、常に-O1フラグを追加する必要があります。

補足として、最適化コンパイラが存在する場合、あなたがしようとしていることはあまり堅牢ではありcurrent_stack_pointer()ませ.下限)。

于 2011-10-18T19:01:09.163 に答える