4

スタック ポインタの値を指定すると、関数に渡された引数の値を特定できますか? スタック フレームに格納されている引数はどこにありますか。

たとえば、 Linux プラットフォームのアーキテクチャでgccコンパイル済みの ELF バイナリを実行するとします。x86

int foo(int a, int b)
{
...
}

foo(a,b)から呼び出され、現在main()指しているスタック ポインター (SP) の値を知っていますfoo()。引数aとの値を取得するにはどうすればよいbですか?

編集: スタックが小さなアドレスから大きなアドレスに成長し、引数が を使用して右から左に渡されるcdecl場合、次のように args 値を取得できますか:

b = *(SP + 1);
a = *(SP + 2);

EDIT : 次のプログラムは、上記のアーキテクチャと仕様を使用してa、関数 args の値を出力します。b

void foo(int a, int b)
{
        int i;
        register int stackptr asm("sp");
        int *sp = (int *)stackptr;
        printf("\n\ta=%d b=%d\n", a, b);
        for (i=0; i<16; i++) {
                printf("*(sp + %d) = %d\n", i, *(sp +i));
        }
}

int main()
{
        foo(3, 8);
        foo(9, 2);
        foo(1, 4);
        return 0;
}

上記のコードの出力は次のとおりです。

        a=3 b=8
*(sp + 0) = 134514016
*(sp + 1) = 0
*(sp + 2) = 0
*(sp + 3) = 134513373
*(sp + 4) = 8239384
*(sp + 5) = 134513228
*(sp + 6) = 6
*(sp + 7) = -1076716032
*(sp + 8) = 134513456
*(sp + 9) = 0
*(sp + 10) = -1076715960
*(sp + 11) = 134513759
*(sp + 12) = 3  //value of arg a
*(sp + 13) = 8  //value of arg b
*(sp + 14) = 134513817
*(sp + 15) = 10612724

        a=9 b=2
*(sp + 0) = 134514016
*(sp + 1) = 0
*(sp + 2) = 0
*(sp + 3) = 134513373
*(sp + 4) = 8239384
*(sp + 5) = 134513228
*(sp + 6) = 6
*(sp + 7) = -1076716032
*(sp + 8) = 134513456
*(sp + 9) = 0
*(sp + 10) = -1076715960
*(sp + 11) = 134513779
*(sp + 12) = 9  //value of arg a
*(sp + 13) = 2  //value of arg b
*(sp + 14) = 134513817
*(sp + 15) = 10612724

        a=1 b=4
*(sp + 0) = 134514016
*(sp + 1) = 0
*(sp + 2) = 0
*(sp + 3) = 134513373
*(sp + 4) = 8239384
*(sp + 5) = 134513228
*(sp + 6) = 6
*(sp + 7) = -1076716032
*(sp + 8) = 134513456
*(sp + 9) = 0
*(sp + 10) = -1076715960
*(sp + 11) = 134513799
*(sp + 12) = 1  //value of arg a
*(sp + 13) = 4  //value of arg b 
*(sp + 14) = 134513817
*(sp + 15) = 10612724

関数の引数がSP のオフセット 12から格納されるのはなぜですか? また、オフセット 0 から 10 の値は常に同じであり、オフセット 11 の値は function の呼び出しごとに 20 ずつ増加しますfoo()

更新:フレーム ポインター アドレスを取得する組み込み関数gccがあることがわかりました

void * __builtin_frame_address (unsigned int level)

__builtin_frame_address(0)関数の引数から始まるオフセットで値を出力すると、 offset 2. この動作が常に一貫していることを確認するにはどうすればよいですか?

4

3 に答える 3

2

引数がスタックにプッシュされる順序、またはそれらがスタック上にあるかどうかを知るには、呼び出し規則を知っている必要があります。多くの場合、最初のいくつかの引数をレジスターに渡します。x86 でも、fastcall、pascal、register、stdcall、cdecl などがあります。

printf編集:これも関数であることを忘れないでください。ローカル変数もスタックに置かれます。したがって、サンプルアプリでは、パラメータ(cdeclであるため)、次にローカル、次に関数が保存された状態と戻りアドレス、次にパラメータ(またはprintfかどうかはわかりません)、そしてその時点でのローカルがあります何でも実際に画面に表示されます。cdeclfastcallprintf

于 2012-12-12T06:58:12.303 に答える
2

簡単な方法はなく、確かに移植可能な方法はありません (同じソースファイルの場合、gcc 4.1 と gcc 4.2 の間で変更される可能性さえあります) が、gdb は確かにそれを実行できます。gcc を使用すると、おそらく DWARF 情報を分析するために必要なものをすべて見つけることができます。

gdb はまた、プロローグ分析を使用して、スタック内でローカル変数がどのように割り当てられているかを検出しますが (とりわけ)、「呼び出し分析」のようなものが gdb のソースに存在するかどうかはわかりません。gdb のソースで prologue-value.h を読むと役立つ場合があります。

于 2012-12-12T09:59:05.963 に答える
0

ローカル変数はスタックに割り当てられるため、変数とiコールスタックに割り当てられます。したがって、すべてのスタック レコードを出力すると、これらの変数、リターン ポインター、保存されたフレーム ポインター (保存されている場合)、関数パラメーターの順に表示されます。したがって、上記の例では 12 で開始します。stackptrspargs

関数呼び出しパラメータにすぐにアクセスしたい場合は、 を使用して取得したフレーム ポインタ アドレスから開始する必要があります__builtin_frame_address(unsigned int level)。引数は、保存されたフレーム ポインターの前にスタックにプッシュされるため、スタック上の保存されたフレーム ポインター レコードの先頭から開始する場合は、フレーム ポインターのアドレス サイズに等しいオフセットを追加する必要があります。したがって、上記の例では、argsオフセット 2 から開始します。

于 2012-12-12T14:13:07.840 に答える