2011-10-18 13 views
2

Cで現在のスタックポインタを取得しようとしたときに奇妙な動作が発生しました(インラインASMを使用)。コードは次のようになります。Mac OS X LionのCでスタックポインタを取得

#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のオプションを使用してコードをコンパイルする場合:私は結果のバイナリを実行する場合

__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 

$ g++ test.cc -o test 

をそれは、次のアセンブリを生成し、 SIGILL(不正命令)でクラッシュします。私はコンパイルに少し最適化を追加するただし場合:

$ 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

_function_call_は、スタックアドレスを得るために設計されたものではありません。 – zneak

+0

さらに、なぜその関数は非静的クラスメソッド*ですか?あなたはおそらく 'this'ポインタのために何を望みますか? –

+0

元のコードをチェックしたところ、静的であると宣言され、質問が更新されました。私はいくつかの既存のコードを作業しています。ほとんどのユースケースでは不等式が使用されるため、値がわずかに外れている場合はあまり問題になりません。私にとって重要な問題は、違法命令例外です。 –

答えて

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 
+0

__attribute __((naked))は一部のプラットフォームでのみサポートされ、x86ではgccによって無視されるようです。 –

+0

情報ありがとうございました...私は自分の投稿を変更します。 – Jason

0

私はそのための参照を持っていませんが、GCCは(多くの場合)、インラインの存在下で、不正な動作を時折することが知られていますコンパイルがまったく最適化されていない場合は、アセンブリしたがって、となります。-O1フラグを追加してください。コンパイラはcurrent_stack_pointer()への呼び出しをインライン化すると、返される値は、このように近似かもしれないので、サイドノートとして

は、何をやろうとしていることは、最適化コンパイラの存在下で、非常に堅牢ではないです現在のスタックポインタの値(下限でさえも)。

3

のではなく、制約をregister変数を使用して、あなただけの%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;}) 
+0

関数を呼び出すには、 'call'コマンド(スタック上に戻り命令アドレスをプッシュする)が必要です。したがって、' ESP 'の値は呼び出し先のスタックフレーム内にあるスタック内の値を指しています呼び出し側 。これにより、呼び出し元で返されたスタックポインタ値は、呼び出し先が完了した時点でスタックポインタの現在のアドレスを反映しないため、使用できなくなります。私はあなたが 'assembly'タグの下で多くの評判ポイントを獲得したのを見ています。このコードスニペットが有効なスタックポインタ値を呼び出し元に返す場所がありますか? – Jason

+1

@Jason:これをマクロに変換して、その問題を取り除くことができます(私はそれを含む答えを更新しました)。しかし実際には、スタックポインタを知りたいときに、下限は実際には十分です(つまり、現在のスタックはこれより深くはありません)。とにかく、Cコンパイラは適切に見て周りのスタックポインタをシフトするので、Cコード内の正確なスタックポインタを尋ねるのは愚かです。それを読んだときと値を使用したときの間では変化しないという保証はありません。 – caf

+0

ありがとう、それは素晴らしい説明でした。 – Jason

関連する問題