2012-05-02 5 views
4

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

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

私は、以前のスタックから引数とローカル変数を抽出したいとcopy_of_buried_argとcopy_of_buried_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; 
} 
+3

直前の呼び出しのローカルのアドレスを次の関数に渡すことはできませんか?アセンブリ言語がうまくいけば、ここでは良いことにつながることはありません。 –

+0

あなたのデバッガでそれを実行し、そこから数値を取得するのはどうですか? –

+0

@GregHewgill私はアセンブリを使用して前のスタックから変数を掘ることになっています。その必要条件は – David

答えて

1

を停止し、私は助けることを試みることができますが、ガス中の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. プッシュ引数の最後のargを最初に。
  2. 関数を呼び出し、リターンアドレスを押します。
  3. すぐに古いEBPをプッシュし、以前のスタックフレームを保持します。
  4. ESP(スタックの最上部、oldEBPを含む)をEBPに移動し、新しいスタックフレームを作成します。
  5. ローカル変数のスペースを減算します。

これはEBP-nは常にローカル変数へのオフセットである等、EBP+8は常にコールの最初のパラメータになり、32ビットのスタック、EBP+12に対する効果第2のパラメータを有します。

以前locargを取得するコードは(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 

またはガス中の私の最高の推測(私はそれを知っているが、それは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' 
+0

私の推測では、どのような種類のスタック保護でも、このアプローチは完全に移植不可能になるでしょう。 – tbert

+1

@tbert:スタック保護はこれと何が関係していますか?OPのコードは、異なる最適化オプションまたは別のバージョンのコンパイラでコンパイルされたときに破損するため、移植できません。 –

+0

私は軽いです、私はちょうど、関数のさらなる再帰的反復に情報を渡す実行可能なテクニックだと思う人々から、これに関する質問の洪水の悪夢を抱いています。インストラクターがこのハックに沿った長い注意点があることを確認していない場合は、インターネット上のどこかに「注意点」と言っている人がいることを確かめます。 – tbert

0

私のコンパイラで(gcc 3.3。4)私はこれで終わった:

_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 

これらのオフセットebp(40および31から)当初に設定した。ここで

#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でそれを取得し、関連部分の解体です任意の推測値(例えば0)を計算し、逆アセンブリおよびいくつかの簡単な計算を観察することによって洗練される。それは自分自身を再帰的に呼び出すときに関数が整列およびパラメータのスタックの余分な12 + 4 = 16バイトを使用

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

はまた、リターンアドレスの4つのバイトがあります。

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

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

したがって、合計スタックは各再帰16 + 4 + 12 = 32バイトによって成長をコール。

今、私たちはebpを通じてargloc地元を取得する方法を知っている:

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

だから、私たちはそれらのオフセット8に32を追加し、-1と40と31

に到着同じことをして、あなたの "埋め込まれた"変数を取得します。

+0

少なくともx86-64では、引数は実際にはスタック上ではなくレジスタに(実際には)到着するので、これは実際には不可能です。 – perh

+0

@perh質問には64ビットについては言及していません。また、レジスタがすべてを保持するのに十分でない場合、必然的に何かがスタックに格納されます。 –

関連する問題