2

ここにIT工学の学生。コンテキストの切り替えを試してみるように求められましたが、ある特定の割り当てでは、かなり大雑把な試行/スローシステムを実装しました。これが私たちが書いてきたコードです:

struct ctx_s {
  int esp;
  int ebp;
};

struct ctx_s * pctx;

typedef int (func_t)(int); /* a function that returns an int from an int */

int try(func_t *f, int arg)
{
  /* saving context by storing values of %esp and %ebp */      
    asm ("movl %%esp, %0"
    : "=r"((*pctx).esp) 
    :
    );

    asm ("movl %%ebp, %0"
    : "=r"((*pctx).ebp) 
    :
    );

    /* calling the function sent to try(), returning whatever it returns */
    return f(arg);
}

int throw(int r)
{
    printf("MAGIC PRINT\n");

    static int my_return = 0;
    /* ^ to avoid "an element from initialisation is not a constant" */
    my_return = r;
    /* restituting context saved in try() */
    asm ("movl %0, %%esp"
    : 
    : "r"((*pctx).esp) 
    );

    asm ("movl %0, %%ebp"
    : 
    : "r"((*pctx).ebp) 
    );

    /* this return will go back to main() since we've restored try()'s context
     so the return address is whatever called try... */
    /* my_return is static (=> stored in the heap) so it's not been corrupted,
     unlike r which is now the second parameter received from try()'s context, 
     and who knows what that might be */
    return my_return;
}

pctxは、2つのintを保持する単純な構造体へのグローバルポインタです。fは、throw()を呼び出して42に#defineされたリターンコードを送信する関数です。main()は、基本的にpctxを割り当て、result = try(f、0)を実行します。結果を出力します。結果は42になると予想しています。

これで、throw()でMAGICPRINTを見つけたかもしれません。完全に明確ではない理由でここにあります; 基本的に、ほとんどの(すべてではない)学生はthrow()内でsegfaultingしていました。この関数内でprintf()を呼び出すと、プログラムは一見正しく機能するように見え、教​​師はシステムコールも同様に機能すると考えています。

あまり説明がつかなかったので、gcc -Sで生成されたアセンブリコードを両方のバージョン(printf()ありとなし)で比較してみましたが、あまり理解できませんでした。throw()の開始中括弧(33行目)にブレークポイントを設定し、gdbで逆アセンブルすると、次のようになります。

printf()なし:

Breakpoint 1, throw (r=42) at main4.c:38
(gdb) disass
Dump of assembler code for function throw:
0x0804845a <throw+0>:   push   %ebp
0x0804845b <throw+1>:   mov    %esp,%ebp
0x0804845d <throw+3>:   mov    0x8(%ebp),%eax
0x08048460 <throw+6>:   mov    %eax,0x8049720
0x08048465 <throw+11>:  mov    0x8049724,%eax
0x0804846a <throw+16>:  mov    (%eax),%eax
0x0804846c <throw+18>:  mov    %eax,%esp
0x0804846e <throw+20>:  mov    0x8049724,%eax
0x08048473 <throw+25>:  mov    0x4(%eax),%eax
0x08048476 <throw+28>:  mov    %eax,%ebp
0x08048478 <throw+30>:  mov    0x8049720,%eax
0x0804847d <throw+35>:  pop    %ebp
0x0804847e <throw+36>:  ret    
End of assembler dump.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xb7e846c0 in ?? ()

printf()を使用する場合:

Breakpoint 1, throw (r=42) at main4.c:34
(gdb) disassemble 
Dump of assembler code for function throw:
0x0804845a <throw+0>:   push   %ebp
0x0804845b <throw+1>:   mov    %esp,%ebp
0x0804845d <throw+3>:   sub    $0x18,%esp
0x08048460 <throw+6>:   movl   $0x80485f0,(%esp)
0x08048467 <throw+13>:  call   0x8048364 <puts@plt>
0x0804846c <throw+18>:  mov    0x8(%ebp),%eax
0x0804846f <throw+21>:  mov    %eax,0x804973c
0x08048474 <throw+26>:  mov    0x8049740,%eax
0x08048479 <throw+31>:  mov    (%eax),%eax
0x0804847b <throw+33>:  mov    %eax,%esp
0x0804847d <throw+35>:  mov    0x8049740,%eax
0x08048482 <throw+40>:  mov    0x4(%eax),%eax
0x08048485 <throw+43>:  mov    %eax,%ebp
0x08048487 <throw+45>:  mov    0x804973c,%eax
0x0804848c <throw+50>:  leave  
0x0804848d <throw+51>:  ret    
End of assembler dump.
(gdb) c
Continuing.
MAGIC PRINT
result = 42

Program exited normally.

どうしたらいいのかよくわかりません。明らかに状況は異なりますが、どちらの場合も何が起こっているのかを理解するのは非常に難しいと感じています...したがって、私の質問は、本質的に、printfを呼び出すとsegfaultではなくthrowを作成する方法です。

4

2 に答える 2

1

ESP別の関数に保存されている値に「復元」しています。ここではおそらく有用な値ではありません。

「魔法の」コードとの違いは、コンパイラがthrow関数内のスタックフレームを保存および復元することです。

最後のleave命令は同等です

mov    %ebp, %esp
pop    %ebp

これにより、スタックポインタが関数エントリの状態に戻る可能性があります。

于 2012-10-07T14:03:20.937 に答える
1

わかりました。tryの部分が表示されないため、これは分析が少し緩いですが、標準の呼び出し規約から判断すると、tryを含むメソッドはに保存%espされ%ebp、減少%espしてローカル変数用のスペースを作り、「try」コードを実行します。保存%esp%ebpます。

通常、関数が終了すると、関数は戻るleave前にを使用してそれらの変更を元に戻します。Leaveはに復元%ebp%esp、ポップ%ebpして戻ります。これにより、ローカル変数用のスペースが予約される前のポイントに確実に%esp復元されます。

のないバージョンの問題は、最初にコンテンツをに復元せずにポップをprintf使用しないことです。命令はローカル変数をポップし、それに戻ります。最高の結果ではありません。leave%ebp%espret

私の疑惑は、関数にローカル変数がないため、コンパイラーがから復元する理由を認識していないこと%espです%ebp。スタック上にスペースを予約するためprintf、コンパイラーは、そのバージョンで、%esp戻る前に復元する必要があることを認識しています。

理論をテストしたい場合は、アセンブラにコンパイルして置き換えます。

0x0804847d <throw+35>:  pop    %ebp

脱退命令で結果を組み立てます。それも同様に機能するはずです。

あるいは、破壊されたasm命令でgccに指示し%esp、それによって代わりに休暇を生成さ​​せることができると思います。

編集:どうやら%espclobberedとしてマークすることは本質的にgccのNOOPです:-/

于 2012-10-07T14:03:33.683 に答える