45

ポインタがあり、それをNULLで初期化するとします。

int* ptr = NULL;
*ptr = 10;

ptrこれで、はどのアドレスも指していないため、プログラムがクラッシュし、それに値を割り当てています。これは無効なアクセスです。だから、問題は、OSの内部で何が起こるのかということです。ページフォールト/セグメンテーションフォールトは発生しますか?カーネルはページテーブルを検索することさえできますか?または、その前にクラッシュが発生しますか?

私はどのプログラムでもそのようなことをしないことを知っていますが、これはそのような場合にOSまたはコンパイラの内部で何が起こるかを知るためだけのものです。そして、それは重複した質問ではありません。

4

5 に答える 5

72

簡単な答え:コンパイラ、プロセッサアーキテクチャ、特定のプロセッサモデル、OSなどの多くの要因によって異なります。

長い答え(x86およびx86-64):最も低いレベルであるCPUに行きましょう。x86およびx86-64では、そのコードは通常、次のような命令または命令シーケンスにコンパイルされます。

movl $10, 0x00000000

これは、「定数整数10を仮想メモリアドレス0に格納する」という意味です。インテル®64およびIA-32アーキテクチャーのソフトウェア開発者マニュアルには、この命令が実行されたときに何が起こるかが詳細に説明されているので、要約します。

CPUはいくつかの異なるモードで動作でき、そのうちのいくつかははるかに古いCPUとの下位互換性のためのものです。最新のオペレーティングシステムは、保護モードと呼ばれるモードでユーザーレベルのコードを実行します。このモードでは、ページングを使用して仮想アドレスを物理アドレスに変換します。

プロセスごとに、OSはアドレスのマッピング方法を指示するページテーブルを保持します。ページテーブルは、CPUが理解できる特定の形式でメモリに保存されます(ユーザーコードで変更できないように保護されます)。発生するすべてのメモリアクセスについて、CPUはページテーブルに従ってそれを変換します。変換が成功すると、物理メモリ位置への対応する読み取り/書き込みが実行されます。

アドレス変換が失敗すると、興味深いことが起こります。すべてのアドレスが有効であるとは限りません。メモリアクセスによって無効なアドレスが生成されると、プロセッサはページフォールト例外を発生させます。これにより、ユーザーモード(x86 / x86-64の現在の特権レベル(CPL)3 )からカーネルモード(別名CPL 0)への移行がトリガーされ、割り込み記述子テーブル(IDT)で定義されているカーネルのコード内の特定の場所に移動します。。

カーネルは制御を取り戻し、例外とプロセスのページテーブルからの情報に基づいて、何が起こったかを把握します。この場合、ユーザーレベルのプロセスが無効なメモリ位置にアクセスしたことを認識し、それに応じて反応します。Windowsでは、構造化例外処理を呼び出して、ユーザーコードが例外を処理できるようにします。POSIXシステムでは、OSがSIGSEGVプロセスに信号を配信します。

その他の場合、OSはページフォールトを内部で処理し、何も起こらなかったかのように現在の場所からプロセスを再開します。たとえば、ガードページはスタックの一番下に配置され、スタックに大量のメモリを事前に割り当てる代わりに、スタックをオンデマンドで制限まで拡張できるようにします。コピーオンライトメモリを実現するために、同様のメカニズムが使用されます。

最近のOSでは、ページテーブルは通常、アドレス0を無効な仮想アドレスにするように設定されています。ただし、Linuxでは疑似ファイルに0を書き込むことで変更できる場合があります/proc/sys/vm/mmap_min_addr。その後、仮想アドレス0をマップするために使用できmmap(2)ます。その場合、nullポインターを逆参照しても、ページフォールトは発生しません。

上記の説明は、元のコードがユーザースペースで実行されているときに何が起こるかについてのすべてです。しかし、これはカーネル内でも発生する可能性があります。カーネルは仮想アドレス0をマップできる(そして確かにユーザーコードよりもはるかに可能性が高い)ので、そのようなメモリアクセスは正常です。しかし、マップされていない場合、何が起こるかはほぼ同じです。CPUはページフォールトエラーを発生させ、カーネルの事前定義されたポイントにトラップし、カーネルは何が起こったかを調べ、それに応じて反応します。カーネルが例外から回復できない場合は、通常、デバッグ情報をコンソールまたはシリアルポートに出力して停止することにより、何らかの方法でパニックになります(カーネルパニックカーネルoops、WindowsのBSODなど)。

Linuxマシンでroot権限を取得するために、攻撃者がカーネル内からnullポインター逆参照バグを悪用する方法の例については、NULLについての多くの騒ぎ:カーネルのNULL逆参照の悪用も参照してください。

于 2012-09-28T19:04:25.283 に答える
6

ちなみに、アーキテクチャの違いを強制するために、3文字の頭字語名で知られ、しばしば大きな原色と呼ばれる会社によって開発および保守されている特定のOSは、最も魅力的なNULL決定を持っています。

それらは、1つの巨大な「モノ」内のすべてのデータ(メモリとディスク)に128ビットの線形アドレス空間を利用します。OSに応じて、「有効な」ポインタをそのアドレス空間内の128ビット境界に配置する必要があります。ところで、これは、ポインタを格納する構造体に、パックされているかどうかに関係なく、魅力的な副作用を引き起こします。とにかく、プロセスごとの専用ページに隠れているのは、有効なポインタを置くことができるプロセスアドレス空間内の有効な場所ごとに1ビットを割り当てるビットマップです。有効なメモリアドレスを生成して返し、それをポインタに割り当てることができるハードウェアとOS上のすべてのオペコードは、そのポインタ(ターゲットポインタ)が配置されているメモリアドレスを表すビットを設定します。

では、なぜ誰かが気にする必要があるのでしょうか?この単純な理由で:

int a = 0;
int *p = &a;
int *q = p-1;

if (p)
{
// p is valid, p's bit is lit, this code will run.
}

if (q)
{
   // the address stored in q is not valid. q's bit is not lit. this will NOT run.
}

本当に面白いのはこれです。

if (p == NULL)
{
   // p is valid. this will NOT run.
}

if (q == NULL)
{
   // q is not valid, and therefore treated as NULL, this WILL run.
}

if (!p)
{
   // same as before. p is valid, therefore this won't run
}

if (!q)
{
   // same as before, q is NOT valid, therefore this WILL run.
}

それはあなたが信じるために見なければならないものです。特にポインタ値をコピーしたり、動的メモリを解放したりするときに、そのビットマップを維持するために行われるハウスキーピングを想像することさえできません。

于 2012-09-28T19:28:31.620 に答える
4

仮想メモリをサポートするCPUでは、メモリアドレスで読み取ろうとすると、通常、ページフォールト例外が発行されます0x0。OSページフォールトハンドラーが呼び出され、OSはページが無効であると判断し、プログラムを中止します。

一部のCPUでは、メモリアドレスにも安全にアクセスできることに注意してください0x0

C標準では、nullポインターの逆参照は未定義であると述べているため、コンパイラーがコンパイル時(または実行時)にnullポインターを逆参照していることを検出できる場合は、プログラムを中止して詳細なエラーメッセージを表示するなど、必要な処理を実行できます。 。

(C99、6.5.3.2.p4)「ポインターに無効な値が割り当てられている場合、単項*演算子の動作は未定義です。87)」

87):「単項*演算子によるポインターの逆参照の無効な値の中には、nullポインター、ポイントされたオブジェクトのタイプに対して不適切に整列されたアドレス、およびその存続期間の終了後のオブジェクトのアドレスがあります。」

于 2012-09-28T18:59:26.497 に答える
4

通常、はアドレス0を指すように設定されます。C標準(およびC ++標準)は、それを必要としないように非常に注意していint *ptr = NULL;ます、それでも非常に一般的です。ptr

これを行う*ptr = 10;と、CPUは通常、アドレスラインと10データラインに0を生成し、書き込みを示すようにR / Wラインを設定します(バスにそのようなものがある場合は、メモリとI/をアサートします。 I / Oではなく、メモリへの書き込みを示すO行)。

CPUがメモリ保護をサポートしている(そしてそれを有効にするOSを使用している)と仮定すると、CPUはアクセスが発生する前にその(試行された)アクセスをチェックします。たとえば、最新のIntel / AMD CPUは、仮想アドレスを物理アドレスにマップするページングテーブルを使用します。通常、アドレス0は物理アドレスにマップされませんこの場合、CPUはアクセス違反の例外を生成します。かなり典型的な例の1つとして、Microsoft Windowsは最初の4メガバイトをマップしないままにするためその範囲内のアドレスは通常、アクセス違反になります。

古いCPU(またはCPU保護機能を有効にしない古いオペレーティングシステム)では、試行された書き込みは成功することがよくあります。たとえば、MS-DOSでは、NULLポインタを介して書き込むと、アドレス0に書き込むだけです。中小規模のモデル(データ用に16ビットアドレスを使用)では、ほとんどのコンパイラはデータセグメントの最初の数バイトに既知のパターンを書き込み、プログラムが終了すると、そのパターンがそのまま残っているかどうかを確認します(失敗した場合は、NULLポインターを介して書き込んだことを示すために何かを実行します)。コンパクトモデルまたはラージモデル(20ビットデータアドレス)では、通常、警告なしにアドレス0に書き込むだけです。

于 2012-09-28T19:07:25.990 に答える
0

これはプラットフォームとコンパイラに依存していると思います。NULLポインターは、NULLページを使用して実装できます。この場合、ページフォールトが発生するか、展開セグメントのセグメント制限を下回る可能性があります。この場合、セグメンテーション違反が発生します。

これは決定的な答えではなく、私の推測です。

于 2012-09-28T18:49:53.833 に答える