11

プログラムを実行すると、

#include <stdio.h>
int main(int argc, char *argv[], char *env[]) {
  printf("My references are at %p, %p, %p\n", &argc, &argv, &env);
}

これらの領域が実際にスタック内にあることがわかります。しかし、他に何がありますか?Linux 3.5.3 ですべての値をループして (たとえば、segfault まで) 実行すると、いくつかの奇妙な数字と、一連のゼロで区切られた 2 つの領域のようなものを確認できます。これは、おそらく環境変数の上書きを防止しようとするためです。うっかり。

とにかく、最初の領域には、各関数呼び出しのすべてのフレームなど、多くの数値が必要です。

各フレームの終わり、パラメーターの場所、コンパイラーがカナリアを追加した場合のカナリアの場所、戻りアドレス、CPU ステータスなどをどのように区別できますか?

4

4 に答える 4

5

オーバーレイについてある程度の知識がないと、ビットまたは数字しか表示されません。一部の領域はマシンの仕様の影響を受けますが、多くの詳細はかなり標準的です。

ネストされたルーチンの外にあまり移動していない場合は、メモリのコール スタック部分を見ている可能性があります。一般に「安全ではない」と考えられている C では、関数変数にアクセスする楽しい関数をいくつかの「呼び出し」で作成できます。それらの変数がソース コードに記述されているように関数に「渡されていない」場合でも同様です。

サードパーティのライブラリは、まだ作成されていないプログラムからも呼び出せる必要があるため、コール スタックから始めるのがよいでしょう。そのため、かなり標準化されています。

プロセス メモリ境界の外に出ると、恐ろしいセグメンテーション違反が発生します。これは、メモリ フェンシングが、プロセスによる承認されていないメモリへのアクセスの試みを検出するためです。Malloc は、メモリ セグメンテーション機能を備えたシステムでポインタを「単に」返すだけではなく、そのプロセスにアクセス可能なメモリを「マーク」し、プロセスの割り当てに違反していないすべてのメモリ アクセスをチェックします。

この道をたどり続けると、遅かれ早かれ、カーネルまたはオブジェクト形式に興味を持つようになります。ソースコードが利用できる Linux で物事がどのように行われているかを調べる方がはるかに簡単です。ソース コードがあれば、バイナリを見てデータ構造をリバース エンジニアリングする必要はありません。最初に難しいのは、適切なヘッダーを見つける方法を学ぶことです。後で、いじくり回すことのない状況下ではおそらく変更すべきではないものを、いじって変更する方法を学習する予定です。

PS。このメモリを「スタック」と考えるかもしれませんが、しばらくすると、実際にはアクセス可能なメモリの大きなスラブにすぎず、その一部がスタックと見なされることがわかります...

于 2012-09-15T12:59:16.690 に答える
4

スタックの内容は基本的に次のとおりです。

  • OSがプログラムに渡すものは何でも。
  • コールフレーム(スタックフレーム、アクティベーションエリアなどとも呼ばれます)

OSはプログラムに何を渡しますか?典型的な*nixは、環境、プログラムへの引数、場合によってはいくつかの補助情報、およびに渡されるそれらへのポインターを渡しmain()ます。

Linuxでは、次のように表示されます。

  • NULL
  • プログラムのファイル名。
  • 環境文字列
  • 引数文字列(を含むargv[0]
  • ゼロでいっぱいのパディング
  • auxvカーネルからプログラムに情報を渡すために使用される配列
  • NULLポインタで終わる環境文字列へのポインタ
  • NULLポインタで終わる引数文字列へのポインタ
  • argc

次に、その下にスタックフレームがあります。これには次のものが含まれます。

  • 引数
  • 差出人住所
  • おそらくフレームポインタの古い値
  • おそらくカナリア
  • ローカル変数
  • 位置合わせのためのいくつかのパディング

各スタックフレームのどれがどれであるかをどうやって知るのですか?コンパイラは知っているので、スタックフレーム内の位置を適切に処理します。デバッガーは、可能な場合、デバッグ情報の形式で各関数の注釈を使用できます。それ以外の場合、フレームポインターがある場合は、それに関連するものを識別できます。ローカル変数はフレームポインターの下にあり、引数はスタックポインターの上にあります。それ以外の場合は、ヒューリスティックを使用する必要があります。コードアドレスのように見えるものはおそらくコードアドレスですが、これにより、スタックトレースが不正確で煩わしい場合があります。

于 2012-09-15T13:45:27.780 に答える
3

スタックの内容は、アーキテクチャABI、コンパイラ、およびおそらくさまざまなコンパイラ設定とオプションによって異なります。

開始するのに適した場所は、ターゲットアーキテクチャ用に公開されたABIです。次に、特定のコンパイラがその標準に準拠していることを確認します。最終的には、コンパイラーのアセンブラー出力を分析したり、デバッガーで命令レベルの操作を観察したりできます。

また、コンパイラはスタックを初期化する必要はなく、スタックが終了したときにスタックを「クリア」することはないため、プロセスまたはスレッドに割り当てられると、電源投入時でも値が含まれる可能性があることにも注意してください。たとえば、SDRAMには特定の値や予測可能な値は含まれません。電源がオンになってから、または同じプロセスで以前に呼び出された関数でさえ、物理RAMアドレスが以前に別のプロセスによって使用されていた場合、コンテンツにはそのプロセスが残っています。したがって、生のスタックを見ただけではあまりわかりません。

通常、ジェネリックスタックフレームには、関数が戻ったときにコントロールがジャンプするアドレス、渡されたすべてのパラメーターの値、および関数内のすべての自動ローカル変数の値が含まれる場合があります。ただし、たとえばARM ABIは、最初の4つの引数をレジスタR0〜R3の関数に渡し、リーフ関数の戻り値をLRレジスタに保持するため、すべての場合で「一般的な」実装Iほど単純ではありません。提案しています。

于 2012-09-15T13:26:30.897 に答える
2

詳細は環境に大きく依存します。通常、オペレーティング システムは ABI を定義しますが、実際にはシステム コールに対してのみ適用されます。

実際、各言語 (および同じ言語をコンパイルする場合でも各コンパイラ) は、いくつかのことを異なる方法で行う場合があります。

ただし、少なくとも動的にロードされたライブラリとのインターフェースという意味では、ある種のシステム全体の規則があります。

ただし、詳細は大きく異なります。

非常に単純な「入門書」はhttp://kernelnewbies.org/ABIです。

ABI の定義に関連する複雑さと詳細のレベルを理解するために参照できる非常に詳細で完全な仕様は、「System V Application Binary Interface AMD64 Architecture Processor Supplement」http://www.x86-64 です。 org/documentation/abi.pdf

于 2012-09-15T13:34:29.663 に答える