4

私はこの問題を抱えています.Cで関数を再帰的に呼び出していますが、Cはレキシカルスコープであるため、現在のスタックフレームにしかアクセスできません。現在のスタック フレームにいる間に、前の関数呼び出しで作成された前のスタック フレームから引数とローカル変数を抽出したい

前の再帰呼び出しの値がまだスタック上にあることはわかっていますが、アクティブなスタック フレームの下に「埋もれている」ため、これらの値にアクセスできませんか?

前のスタックから引数とローカル変数を抽出し、それらを copy_of_bured_arg と copy_of_bured_loc にコピーしたいと考えています。

変数を抽出するために GAS を使用してインライン アセンブリを使用する必要があります。スタックがきれいになるように、printfへの呼び出しも削除しようとしましたが、正しい算術演算がわかりません。これまでのコードは次のとおりです。私の関数は2回目の反復で停止します

#include <stdio.h>

char glo = 97;   // just for fun 97 is ascii lowercase 'a'
int copy_of_buried_arg;
char copy_of_buried_loc;

void rec(int arg) {
  char loc;

  loc = glo + arg * 2; // just for fun, some char arithmetic
  printf("inside rec() arg=%d loc='%c'\n", arg, loc);

  if (arg != 0) {
    // after this assembly code runs, the copy_of_buried_arg and
    // copy_of_buried_loc variables will have arg, loc values from
    // the frame of the previous call to rec().
    __asm__("\n\
            movl 28(%esp), %eax #moving stack pointer to old ebp (pointing it to old ebp)\n\
            addl $8, %eax       #now eax points to the first argument for the old ebp \n\
            movl (%eax), %ecx   #copy the value inside eax to ecx\n\ 
            movl %ecx, copy_of_buried_arg   # copies the old argument\n\
    \n\

");

    printf("copy_of_buried_arg=%u copy_of_buried_loc='%c'\n",
       copy_of_buried_arg, copy_of_buried_loc);
  } else {
      printf("there is no buried stack frame\n");// runs if argument = 0 so only the first time
  }

  if (arg < 10) {
    rec(arg + 1);
  }
}

int main (int argc, char **argv) {
  rec(0);

  return 0;
}
4

2 に答える 2

1

手伝うことはできますが、GAS に Linux やアセンブリがありません。しかし、計算は似ているはずです:

数回呼び出した後のスタックを次に示します。典型的なスタック フレームのセットアップでは、スタック フレームのリンク リストが作成されます。ここで、EBP は現在のスタック フレームであり、前のスタック フレームの古い値を指します。

      +-------+
ESP-> |loc='c'|     <- ESP currently points here.
      +-------+
EBP-> |oldEBP |--+  <- rec(0)'s call frame
      +-------+  |
      |retaddr|  |  <- return value of rec(1)
      +-------+  |
      |arg=1  |  |  <- pushed argument of rec(1)
      +-------+  |
      |loc='a'|  |  <- local variable of rec(0)
      +-------+  |
   +--|oldEBP |<-+  <- main's call frame
   |  +-------+
   |  |retaddr|     <- return value of rec(0)
   |  +-------+ 
   |  |arg=0  |     <- pushed argument of rec(0)
   |  +-------+
  \|/ 
to main's call frame

これは、次の順序で作成されます。

  1. 最後の引数を最初にプッシュします。
  2. 関数を呼び出して、リターン アドレスをプッシュします。
  3. 以前のスタック フレームを保持しながら、まもなく古い EBP をプッシュします。
  4. ESP (oldEBP を含むスタックの最上位) を EBP に移動し、新しいスタック フレームを作成します。
  5. ローカル変数のスペースを減算します。

EBP+8これは、常に呼び出しの最初のパラメーターになる32 ビット スタックに影響しEBP+12、2 番目のパラメーターなど EBP-nは常にローカル変数へのオフセットになります。

以前のものを取得するコードlocは次のargとおりです (MASM 内):

mov ecx,[ebp]              // get previous stack frame
mov edx,[ecx]+8            // get first argument
mov copy_of_buried_arg,edx // save it
mov dl,[ecx]-1             // get first char-sized local variable.
mov copy_of_buried_loc,dl  // save it

またはGASでの私の最善の推測(わかりませんが、MASMに逆行していることはわかっています):

movl (%ebp),ecx
movl 8(%ecx),edx
movl edx,copy_of_buried_arg
movb -1(%ecx),dl
movb dl,copy_of_buried_loc

Windows で VS2010 を使用した私の MASM でのコードの出力:

inside rec() arg=0 loc='a'
there is no buried stack frame
inside rec() arg=1 loc='c'
copy_of_buried_arg=0 copy_of_buried_loc='a'
inside rec() arg=2 loc='e'
copy_of_buried_arg=1 copy_of_buried_loc='c'
inside rec() arg=3 loc='g'
copy_of_buried_arg=2 copy_of_buried_loc='e'
inside rec() arg=4 loc='i'
copy_of_buried_arg=3 copy_of_buried_loc='g'
inside rec() arg=5 loc='k'
copy_of_buried_arg=4 copy_of_buried_loc='i'
inside rec() arg=6 loc='m'
copy_of_buried_arg=5 copy_of_buried_loc='k'
inside rec() arg=7 loc='o'
copy_of_buried_arg=6 copy_of_buried_loc='m'
inside rec() arg=8 loc='q'
copy_of_buried_arg=7 copy_of_buried_loc='o'
inside rec() arg=9 loc='s'
copy_of_buried_arg=8 copy_of_buried_loc='q'
inside rec() arg=10 loc='u'
copy_of_buried_arg=9 copy_of_buried_loc='s'
于 2012-05-02T04:10:59.757 に答える
0

私のコンパイラ(gcc 3.3.4)では、次のようになりました。

#include <stdio.h>

char glo = 97;   // just for fun 97 is ascii lowercase 'a'
int copy_of_buried_arg;
char copy_of_buried_loc;

void rec(int arg) {
  char loc;

  loc = glo + arg * 2; // just for fun, some char arithmetic
  printf("inside rec() arg=%d loc='%c'\n", arg, loc);

  if (arg != 0) {
    // after this assembly code runs, the copy_of_buried_arg and
    // copy_of_buried_loc variables will have arg, loc values from
    // the frame of the previous call to rec().
    __asm__ __volatile__ (
            "movl 40(%%ebp), %%eax #\n"
            "movl %%eax, %0 #\n"
            "movb 31(%%ebp), %%al #\n"
            "movb %%al, %1 #\n"
            : "=m" (copy_of_buried_arg), "=m" (copy_of_buried_loc)
            :
            : "eax"
    );

    printf("copy_of_buried_arg=%u copy_of_buried_loc='%c'\n",
       copy_of_buried_arg, copy_of_buried_loc);
  } else {
      printf("there is no buried stack frame\n");// runs if argument = 0 so only the first time
  }

  if (arg < 10) {
    rec(arg + 1);
  }
}

int main (int argc, char **argv) {
  rec(0);

  return 0;
}

関連する部分の分解は次のとおりです( で取得gcc file.c -S -o file.s):

_rec:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    8(%ebp), %eax
        addl    %eax, %eax
        addb    _glo, %al
        movb    %al, -1(%ebp)
        subl    $4, %esp
        movsbl  -1(%ebp),%eax
        pushl   %eax
        pushl   8(%ebp)
        pushl   $LC0
        call    _printf
        addl    $16, %esp
        cmpl    $0, 8(%ebp)
        je      L2
/APP
        movl 40(%ebp), %eax #
movl %eax, _copy_of_buried_arg #
movb 31(%ebp), %al #
movb %al, _copy_of_buried_loc #

/NO_APP
        subl    $4, %esp
        movsbl  _copy_of_buried_loc,%eax
        pushl   %eax
        pushl   _copy_of_buried_arg
        pushl   $LC1
        call    _printf
        addl    $16, %esp
        jmp     L3
L2:
        subl    $12, %esp
        pushl   $LC2
        call    _printf
        addl    $16, %esp
L3:
        cmpl    $9, 8(%ebp)
        jg      L1
        subl    $12, %esp
        movl    8(%ebp), %eax
        incl    %eax
        pushl   %eax
        call    _rec
        addl    $16, %esp
L1:
        leave
        ret

(40 と 31)からのこれらのオフセットは、ebp最初に任意の推測値 (たとえば 0) に設定され、分解の観察といくつかの簡単な計算によって調整されました。

この関数は、自身を再帰的に呼び出すときに、アラインメントとパラメーターに追加の 12+4=16 バイトのスタックを使用することに注意してください。

        subl    $12, %esp
        movl    8(%ebp), %eax
        incl    %eax
        pushl   %eax
        call    _rec
        addl    $16, %esp

戻りアドレスの 4 バイトもあります。

そして、関数は古いebp変数とそのローカル変数に 4+8=12 バイトを使用します。

_rec:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp

したがって、スタックは再帰呼び出しごとに 16+4+12=32 バイトずつ増加します。

argこれで、 localとlocthroughを取得する方法がわかりましたebp

        movl    8(%ebp), %eax ; <- arg
        addl    %eax, %eax
        addb    _glo, %al
        movb    %al, -1(%ebp) ; <- loc

したがって、これらのオフセット 8 と -1 に 32 を加算すると、40 と 31 になります。

同じことをすると、「埋められた」変数が得られます。

于 2012-05-02T04:55:06.497 に答える