1

スタック上の生データを見て、スタックをスタックフレームに分割したいと思います。保存されたEBPポインタの「リンクリスト」を見つけることでそうしようと思いました。

  1. (標準で一般的に使用されている)Cコンパイラ(gccなど)は、関数プロローグの関数呼び出しで常にEBPを更新して保存すると想定できますか?

    pushl%ebp
    movl%esp、%ebp

    または、一部のコンパイラーが、パラメーターを取得せず、ローカル変数を持たない関数について、その部分をスキップする場合がありますか?

    x86の呼び出し規約と関数プロローグに関するWikiの記事は、それをあまり役に立ちません。

  2. 生データを見るだけでスタックをスタックフレームに分割するためのより良い方法はありますか?

ありがとう!

4

2 に答える 2

3

gccの一部のバージョンには、-fomit-frame-pointer最適化オプションがあります。メモリが機能する場合は、パラメータ/ローカル変数でも使用できます(EBPを使用する代わりにESPから直接インデックスを作成します)。私がひどく間違えない限り、MSVC++はほぼ同じことを行うことができます。

オフハンド、私はどこにでも普遍的に適用できる方法がわかりません。デバッグ情報を含むコードがある場合、通常は非常に簡単ですが、それ以外の場合は...

于 2011-02-08T18:02:54.407 に答える
2

フレームポインタが最適化されていても、スタックメモリを調べて保存されたリターンアドレスを探すことで、スタックフレームを区別できることがよくあります。x86の関数呼び出しシーケンスは、常に次のもので構成されていることに注意してください。

    call someFunc             ; pushes return address (instr. following `call`)
    ...
someFunc:
    push EBP                  ; if framepointer is used
    mov EBP, ESP              ; if framepointer is used
    push <nonvolatile regs>
    ...

したがって、スタックは常に(フレームポインタが欠落している場合でも)リターンアドレスをそこに持っています。

差出人住所をどのように認識しますか?

  • まず、x86では、命令の長さが異なります。つまり、他のポインタ(!)とは異なり、差出人住所は値がずれている傾向があります。統計的に、それらの3/4は4の倍数で終了しません。
    ポインタの位置がずれている場合は、差出人住所の候補として適しています。
  • 次に、callx86の命令には特定のオペコード形式があることに注意してください。リターンアドレスの数バイトを読み取り、そこにオペコードが見つかるかどうかを確認しますcall(99%の場合、直接呼び出しの場合は5バイト戻り、レジスタを介した呼び出しの場合は3バイト戻ります)。もしそうなら、あなたは差出人住所を見つけました。
    これは、C ++ vtablesとリターンアドレスを区別する方法でもあります。スタック上にあるvtableエントリポイントですが、これらのアドレスから「振り返って」みると、call指示が見つかりません。

この方法を使用すると、シンボルやフレームサイズのデバッグ情報などがなくても、スタックから呼び出しシーケンスの候補を取得できます。

これらの候補から実際の呼び出しシーケンスをつなぎ合わせる方法の詳細はそれほど単純ではありませんが、逆アセンブラと、最も低い差出人住所から最後の既知のプログラムの場所までの潜在的な呼び出しフローを追跡するためのヒューリスティックが必要です。多分いつか私はそれについてブログを書くでしょう;-)しかしこの時点で私はむしろスタックオーバーフロー投稿のマージンがこれを含むには小さすぎると言いたいです...

于 2011-02-10T18:28:11.243 に答える