2

バックグラウンド

職場では、最適化されたコードのコア ダンプを使用してポストモーテン デバッグを行うことがよくあります。

特定の種類の困難で再現性のない障害については、追加情報を利用できるようにしたいと考えています。これらの場合、追加のトレースを追加することは現実的ではありません。これは、呼び出しの大部分が成功し、1 分間に数百万の「不要な」トレースが追加され、ログ ファイルが迅速にローリングされるためです。キャッチとトレースが常に実行可能であるとは限らず、一部のエラーによって環境が破損し、トレースが失敗する可能性があります。

コア ダンプにはコールスタック メモリが含まれているため、コールスタック メモリの領域を「トレース」に使用できると考えました。

問題

最適化コンパイラのおかげで、このようなコードは機能しません

void process (int i)
{
   int save_me = i;
   // Do something else
}

アイデアは、ローカル変数に代入することにより、入力変数をスタックに格納することです。これは多くの場合、デバッグ モードでは正常に機能しますが、最適化されたビルドでは、コンパイラはステートメントに副作用がないと判断し、ステートメントを削除します。

allocaサポートされていない一部のプラットフォームをターゲットallocaにしており、C++ とどの程度うまく機能するかはわかりません。

少し実験したところ、次のコードは、最適化されたビルドでもスタックに状態を「固定」できるようです。

#include <cstdint>
#include <stdexcept>
#include <istream>
#include <sstream>

struct saved_state
{
  saved_state ()
    : head  (0xAABBCCDD)
    , tail  (0xEEFF0000)
  {
    std::fill (state, state + 16, 0);
  }

  void push (std::int32_t input) volatile
  {
    for (auto i = 15U; i > 0U; --i)
    {
      state[i] = state[i - 1];
    }
    state[0] = input;
  }

  volatile std::uint32_t  head      ;
  volatile std::int32_t   state [16];
  volatile std::uint32_t  tail      ;
};

void invoke (std::int32_t i)
{
  if (i > 10)
  {
    throw std::runtime_error ("Busted");
  }
}

void process (std::istream & input)
{
  saved_state volatile ss;

  while (!input.eof ())
  {
    std::int32_t i;
    if (input >> i)
    {
      ss.push (i);
      invoke (i);
    }
  }
}

int main()
{
  std::istringstream input ("1\n2\n30\n");
  process (input);
  return 0;
}

質問

コードがやりたいことを実行することを期待できますか? 現在の一連のコンパイラ (clang & gcc) で動作するようですが、引き続き動作することを期待できますか?

私がやりたいことを達成するためのより良い方法はありますか?

より良いとは、よりシンプルで、より堅牢で、標準に準拠していることを意味します。

4

2 に答える 2

1

最適化されたコンパイルは、デバッグが難しい場合があります。

次のようなものを試すことができます。

あなたの例では:

void process (int i)
{
   int save_me = i;
   // Do something else
}

(事前に初期化された) 仮パラメーターと auto 変数の両方が同じスタック上にあり、数バイト離れています。「Do something else」中にクラッシュが発生した場合、オプティマイザーは、もう使用する必要のないスタック項目に対して既に処理を完了しています。

私が運が良かったのは次のとおりです。

void process (int i)
{
   // Do something else

   if (bool_that_compiler_can_not_predetermine_is_always_false)
   {
       std::cerr << "error:  int i is " << i << std::endl;
   }
}

コンパイラは cerr 行が決して実行されないと判断できないため、コードを生成し、仮パラメータをスコープ内に保持します。

もちろん、cerr 以外にも選択できるアクションがあります。おそらくログエントリ?おそらくもっと小さいもの。ポイントは、「プロセス」が終了するまで、i (または、まだ必要な場合は save_me) の値を破棄した後、コア ダンプの失敗が発生しないことです。

オプティマイザーはコードの順序を変更することもできますが、プロセスの最後にある if 句の位置 (私が思うに) により、do-something-else のすべての部分がその句の前に実行されます。


私は時々、タイムスタンプを使用して、真実ではない節を作成します。( ::time(0) は非常に効率的であるため)。

メインがある場合、argc は使いやすく、つまり (0 == argc) または (argc > 100) であり、余分な引数は簡単に無視されます。

于 2015-09-19T20:28:51.953 に答える