20

私の同僚は最近、スタック上の静的配列に範囲外の書き込みをしてひどく噛まれました (彼は配列サイズを増やさずに要素を追加しました)。コンパイラはこの種のエラーをキャッチするべきではありませんか? 次のコードは、オプションを使用しても gcc で問題なくコンパイルされ-Wall -Wextraますが、明らかに誤りです。

int main(void)
{
  int a[10];
  a[13] = 3;  // oops, overwrote the return address
  return 0;
}

私はこれが未定義の動作であることを確信していますが、現時点では C99 標準からの抜粋を見つけることができません。しかし、最も単純なケースでは、配列のサイズがコンパイル時に認識され、インデックスがコンパイル時に認識される場合、コンパイラは少なくとも警告を発するべきではありませんか?

4

10 に答える 10

28

GCCこれについて警告します。ただし、次の2つのことを行う必要があります。

  1. 最適化を有効にします。少なくとも-O2がないと、GCCは何が何でaあるかを知るのに十分な分析を行っておらず、あなたは端から逃げ出しました。
  2. a []が実際に使用されるように例を変更してください。そうしないと、GCCはno-opプログラムを生成し、割り当てを完全に破棄します。

$ cat foo.c 
int main(void)
{
  int a[10];
  a[13] = 3;  // oops, overwrote the return address
  return a[1];
}
$ gcc -Wall -Wextra  -O2 -c foo.c 
foo.c: In function ‘main’:
foo.c:4: warning: array subscript is above array bounds

ところで:テストプログラムでa [13]を返した場合、GCCが配列を再度最適化するため、これも機能しません。

于 2008-12-20T08:28:36.673 に答える
10

-fmudflapGCCで試しましたか?これらは実行時チェックですが、とにかく実行時に計算されたインデックスを処理する必要がある場合が多いため、便利です。黙って作業を続ける代わりに、それらのバグについて通知します。

-fmudflap -fmudflapth -fmudflapir それをサポートするフロントエンド (C および C++) の場合、すべての危険なポインター/配列の逆参照操作、いくつかの標準ライブラリの文字列/ヒープ関数、および範囲/有効性テストを備えたその他の関連する構成要素をインストルメント化します。このようにインストルメント化されたモジュールは、バッファ オーバーフロー、無効なヒープの使用、およびその他のクラスの C/C++ プログラミング エラーの影響を受けないようにする必要があります。インスツルメンテーションは、リンク時に -fmudflap が指定された場合にプログラムにリンクされる別のランタイム ライブラリ (libmudflap) に依存します。インストルメント化されたプログラムの実行時の動作は、MUDFLAP_OPTIONS 環境変数によって制御されます。オプションについては、「env MUDFLAP_OPTIONS=-help a.out」を参照してください。

プログラムがマルチスレッドの場合は、-fmudflap の代わりに -fmudflapth を使用してコンパイルおよびリンクします。計測器がポインターの読み取りを無視する必要がある場合は、-fmudflap または -fmudflapth に加えて -fmudflapir を使用します。これにより、インスツルメンテーションが少なくなり (したがって実行が速くなり)、あからさまなメモリ破損書き込みに対するある程度の保護が提供されますが、誤って読み取られたデータがプログラム内で伝播することを許してしまいます。

あなたの例でマッドフラップが私に与えるものは次のとおりです。

[js@HOST2 cpp]$ gcc -fstack-protector-all -fmudflap -lmudflap mudf.c        
[js@HOST2 cpp]$ ./a.out
*******
mudflap violation 1 (check/write): time=1229801723.191441 ptr=0xbfdd9c04 size=56
pc=0xb7fb126d location=`mudf.c:4:3 (main)'
      /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7fb126d]
      ./a.out(main+0xb9) [0x804887d]
      /usr/lib/libmudflap.so.0(__wrap_main+0x4f) [0xb7fb0a5f]
Nearby object 1: checked region begins 0B into and ends 16B after
mudflap object 0x8509cd8: name=`mudf.c:3:7 (main) a'
bounds=[0xbfdd9c04,0xbfdd9c2b] size=40 area=stack check=0r/3w liveness=3
alloc time=1229801723.191433 pc=0xb7fb09fd
number of nearby objects: 1
[js@HOST2 cpp]$

たくさんのオプションがあります。たとえば、違反時に gdb プロセスをフォークしたり、プログラムがリークした場所を表示したり (を使用-print-leaks)、初期化されていない変数の読み取りを検出したりできます。MUDFLAP_OPTIONS=-help ./a.outオプションのリストを取得するために使用します。マッドフラップはアドレスのみを出力し、ソースのファイル名や行は出力しないので、ちょっとした gawk スクリプトを書きました:

/^ / {
    file = gensub(/([^(]*).*/, "\\1", 1);
    addr = gensub(/.*\[([x[:xdigit:]]*)\]$/, "\\1", 1);
    if(file && addr) {
        cmd = "addr2line -e " file " " addr
        cmd | getline laddr
        print $0 " (" laddr ")"
        close (cmd)
        next;
    }
}

1 # print all other lines

これにマッドフラップの出力をパイプすると、ソースファイルと各バックトレース エントリの行が表示されます。

また-fstack-protector[-all]

-fstack-protector スタック破壊攻撃などのバッファ オーバーフローをチェックする追加のコードを発行します。これは、脆弱なオブジェクトを持つ関数にガード変数を追加することによって行われます。これには、alloca を呼び出す関数と、8 バイトを超えるバッファーを持つ関数が含まれます。ガードは、関数に入るときに初期化され、関数が終了するときにチェックされます。ガード チェックが失敗すると、エラー メッセージが出力され、プログラムが終了します。

-fstack-protector-all -fstack-protector と同様ですが、すべての機能が保護されています。

于 2008-12-20T18:27:54.423 に答える
7

そうです、動作は undefinedです。C99 ポインターは、宣言されたデータ構造またはヒープに割り当てられたデータ構造内またはその 1 つの要素だけを指す必要があります。

gcc人々がいつ警告するかをどのように決定するのか、私には理解できませんでした。-Wall それ自体では、初期化されていない変数について警告しないことを知ってショックを受けました。少なくとも が必要ですが-O、それでも警告が省略されることがあります。

無制限の配列は C では非常に一般的であるため、コンパイラには、コンパイル時に既知のサイズを持つ配列を表現する式ツリーの方法がない可能性が高いと推測されます。したがって、情報は宣言時に存在しますが、使用時にはすでに失われていると推測されます.

私はvalgrindの推奨に二番目です。 C でプログラミングしている場合は、パフォーマンスの影響を受けなくなるまで、常にすべてのプログラムで valgrind を実行する必要があります。

于 2008-12-20T06:21:17.010 に答える
5

静的配列ではありません。

未定義の動作であろうとなかろうと、配列の先頭から 13 整数のアドレスに書き込みます。そこにあるのはあなたの責任です。合理的な理由で意図的に配列を誤って割り当てる C 手法がいくつかあります。この状況は、不完全なコンパイル単位では珍しいことではありません。

フラグの設定によっては、配列が使用されないなど、フラグが立てられるこのプログラムの機能がいくつかあります。そして、コンパイラーはそれを簡単に最適化して存在から消し去り、あなたに知らせないかもしれません - 森に落ちた木。

Cのやり方です。それはあなたの配列であり、あなたの記憶であり、あなたが望むことをしてください。:)

(この種のものを見つけるのに役立つ lint ツールはいくらでもあります。それらを自由に使用する必要があります。ただし、それらがすべてコンパイラを介して機能するわけではありません。コンパイルとリンクは、多くの場合、そのままでは十分に面倒です。)

于 2008-12-20T06:21:59.643 に答える
4

C の哲学は、プログラマーは常に正しいというものです。したがって、自分が何をしているのかを常に知っていて、警告で煩わされないと仮定すると、そこに指定したメモリアドレスに静かにアクセスできます。

于 2008-12-21T04:37:41.197 に答える
4

Cがそうしない理由は、Cが情報を持っていないからです。みたいな発言

int a[10];

2 つのことを行います:sizeof(int)*10スペースのバイトを割り当て (さらに、位置合わせのための少しのデッド スペースを追加する可能性があります)、シンボル テーブルにエントリを配置します。

a : address of a[0]

またはC用語で

a : &a[0]

それだけです。*(a+i)実際、Cではa[i]、(ほぼ*) すべてのケースで定義による影響なしで交換できます。したがって、あなたの質問は、「この (アドレス) 値に整数を追加できるのはなぜですか?」と尋ねるのと同じです。

* 簡単なクイズ: これが正しくない1 つのケースはどれですか?

于 2008-12-21T00:41:52.263 に答える
2

一部のコンパイラは特定のケースでそうすると思います。たとえば、私のメモリが正しく機能する場合、新しい Microsoft コンパイラには、バッファ オーバーランの些細なケースを検出する「バッファ セキュリティ チェック」オプションがあります。

すべてのコンパイラがこれを行わないのはなぜですか? (前述のように) コンパイラが使用する内部表現がこの種の静的分析に適していないか、ライターの優先順位リストの高さが十分でないかのいずれかです。正直なところ、どちらにしても残念です。

于 2008-12-20T06:34:36.703 に答える
2

コンパイラは少なくとも警告を発するべきではありませんか?

いいえ; C コンパイラは通常、配列境界チェックを実行しません。これの明らかな悪影響は、あなたが言及したように、未定義の動作を伴うエラーであり、これを見つけるのは非常に困難です。

これの良い面は、場合によってはパフォーマンスがわずかに向上する可能性があることです。

于 2008-12-20T06:22:41.360 に答える
0

-fbounds-checkingオプションはgccで使用できます。

この記事を読む価値があり ますhttp://www.doc.ic.ac.uk/~phjk/BoundsChecking.html

'le dorfier'はあなたの質問に適切な答えを与えました、それはあなたのプログラムであり、それはCの振る舞いです。

于 2008-12-20T08:06:17.267 に答える
0

そのためのgccにはいくつかの拡張があります(コンパイラ側から) http://www.doc.ic.ac.uk/~awl03/projects/miro/

一方、スプリント、ラット、および他のかなりの数の静的コード分析ツールがそれを発見したでしょう.

コードで valgrind を使用して、出力を確認することもできます。 http://valgrind.org/

別の広く使用されているライブラリは libefence のようです

それは単なる設計上の決定です。それが今、このことにつながります。

よろしく フリードリヒ

于 2008-12-20T06:15:08.147 に答える