ポインターチェッカー:ポインターを確認してください

ポインターが適切に機能しなかったときに発生する問題に直面しました:配列から出てバッファーをオーバーフローさせ、誤って未知のメモリに書き込み、次にこの「ガベージ」を別の場所で読み取り、場合によってはシステム全体がクラッシュする可能性があります 時々、それは単なる「ゲーム」です! そして、この「ゲーム」に正しく対処できるようにする必要があります。そのようなエラーや問題を見つけて修正するのに間に合います。 これは、Intelの「プラス」コンパイラが数リリース前に行ったこととまったく同じです。 さらに、多くのアイデアがさらに進められ、 Intel Memory Protection Extensionsを介してハードウェアに実装されます。 これがコンパイラでどのように機能するか見てみましょう。



「コード内ですぐにポインターを使用してエラーを検出し、修正し、出力でデバッグされた動作中のアプリケーションを提供するようなコンパイラーオプションがあればいいと思います」と、ある開発者は夢を見ました。 実際、これは計画されておらず、計画されていないようです。 Intelコンパイラは、コードを動的にチェックする手段のみを提供します。 これは、通常どおり、マジックオプションの1つを接続し、コードを収集して実行するためにアプリケーションを実行する必要があることを意味します。その間、エラーが発生します。 それがすべてです。 詳しくは、このように見えます。



ポインターチェッカー機能を使用すると、アプリケーション全体のポインターを使用して、メモリーの処理をキャッチできます。 これを行うために、各ポインターには下限と上限の許容境界があり、メモリを操作するときにチェックされ、正しい動作が保証されます。 当然、この情報はあるアドレスの特別なプレートに保存されます。 その中で、ポインターの下限(下限)および上限(upper_bound)の値を見つけることができます。



最も単純な場合、 pmalloc(size)を介してメモリを割り当てた場合、 lower_bound(p)にはアドレス(char *)pがあり、 upper_bound(p)にはアドレス(lower_bound(p)+ size-1)があります。 そして、最も些細な例では、これは問題を検出します:



char * buffer = (char*)malloc(5); for (int i = 0; i <= 5; i++) buffer[i] = 'A' + i;
      
      





配列には5つの要素しかありません。許容上限を超えるアドレスに書き込もうとすると、範囲外になるというランタイムエラーが発生します。

次のようになります。



 CHKP: Bounds check error ptr=0X012062ED sz=1 lb=0X012062E8 ub=0X012062EC loc=0X0 0131149 Traceback: wmain [0x131149] in file C:\ConsoleApplication1.cpp at line 12 __tmainCRTStartup [0x13F959] in file f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c at line 623 wmainCRTStartup [0x13FA9D] in file f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c at line 466 BaseThreadInitThunk [0x76D3919F] RtlInitializeExceptionChain [0x77550BBB] RtlInitializeExceptionChain [0x77550B91] CHKP Total number of bounds violations: 1
      
      





明らかに、私たちのポインターは範囲外でした。 ptr値は0x012062EDであり、上限はub0x012062EC)以下でなければなりません。 そうすることで、トレースバックを取得し、問題箇所を簡単に見つけることができます。 これはすべて、 C / C ++->コード生成->ポインターのチェックタブで Visual Studioから設定できるQcheck-pointersキー(Windows)を使用してアプリケーションをコンパイルした条件下で発生します 。 Linuxの場合、 -check-pointerスイッチを使用します 。 あなたがあまりにも怠wereでなく、正直にWindowsの下でVSインターフェースを通してそれを公開しようとしたなら、あなたはおそらく異なるPointer Checkerの動作モードがあることに気づいたでしょう:



最後の2つのオプションは、アプリケーションの実際の起動時に何も提供しません。使用するためのハードウェアがまだ利用できないためです。 実際、ソフトウェアの機能が少し前に現れるのは一般的な習慣です。 AVXなど、他のテクノロジーでも同じことが起こります。

私たちにとって興味深いのは、読み取り操作と書き込み操作の両方で、書き込み時のみにポインターをチェックする機能です。 Qcheck-pointers:writeオプションを使用すると、読み取り操作中にバッファーポインターの設定範囲を超えてもエラーは発生しません。 たとえば、この場合、配列が正しく初期化されている場合:



  for (int i = 0; i <= 5; i++) printf("%c", buffer[i]);
      
      





Qcheck-pointers:rwキーでコンパイルした 、読み取りを含むすべてのケースをキャッチします。 ところで、関数にポインタを渡すと、境界に関する情報も保存されます。

もう1つの興味深い機能があります。メモリを操作するという概念と、ポインターを使用する単純な算術を区別できる必要があります。 例:



  char *p = (char *)malloc(100); p += 200; p[-101] = 0; p[0] = 0;
      
      





最初の式では、ポインタを移動するだけで、有効な領域にあるメモリを参照します-この場合のp [-101]p [99]です。 したがって、すべてがスムーズに進みます。 実際にp [200]に書き込もうとしているため、範囲外に出るエラーは最後の行でのみ発生します。

ダングリングポインターを見つけるための特別なアルゴリズムがあり、 Qcheck-pointers-danglingオプションが使用されます( Qcheck-pointersと一緒に指定する必要があります)。 これらは、メモリがすでにクリアされている場合であり、ポインターを使用して継続的に何かを実行しようとしています。 バッファの例を続けると、このカテゴリの何か:



  free(buffer); printf("%c", buffer[2]);
      
      





ただし、 Qcheck-pointers-danglingを追加インストールしないとこのケースはエラーとは見なされません。 Qcheck-pointers-danglingを登録すると、コンパイラーは、 空き関数と削除演算子に特別なラッパーを使用します。 クリアされたメモリを持つすべてのポインタを検出し、下限を2に設定し、上限を0に設定します。したがって、このポインタを使用してメモリを操作しようとすると、エラーが発生します。 検討した例では、エラーは次のようになります(コンパクト化のためにトレースバック情報が削除されています)。



 CHKP: Bounds check error ptr=0X007F62EA sz=1 lb=0X00000002 ub=00000000 loc=0X00DB11D5
      
      





ところで、メモリを操作するための関数の独自の実装がある場合、 chkp.hで宣言された__chkp_invalidate_dangling関数を呼び出すことで、その中にハンギングポインターをチェックする関数を含めることができます。

メモリをクリアする関数の例は次のようになります。



  #include <chkp.h> void my_free(void *ptr) { size_t size = my_get_size(ptr); // do the free __chkp_invalidate_dangling(ptr, size); }
      
      







結論として、Pointer Checkerには多くの可能性があり、これらすべてをペンで試す必要があると思います。 たとえば、1つ以上のモジュールをポインターチェック付きでコンパイルし、他のモジュールを選択せず​​にコンパイルすることが可能です。 さらに、より柔軟な操作のために、多くのAPI関数を使用できます。 Pointer CheckerはWindowsおよびLinuxでのみサポートされ、Macではサポートされていないことに注意してください。

コインの裏側もあります-アプリケーションの実行は大幅に遅くなります(少なくとも2回、ただし5回以下)。 当然、コードのサイズも大きくなります。 それでも、アプリケーションのデバッグを目的とする場合、機能は非常に興味深いものであり、ハードウェアに実装することにより、すべてがより効率的になります。



そして最後に、小さな質問です。 この全体がマルチスレッドアプリケーションでどのように機能すると思いますか? ポインターにアクセスするたびに、境界に関する情報の読み取りまたは書き込みを行い、いくつかの命令を費やし、異なるポインターが同じポインターに対して異なる情報を書き込もうとするという事実を考えます。



All Articles