2009-03-05 20 views
13

私は移植したいライブラリを書いています。したがって、glibcやMicrosoftの拡張機能や標準にないその他のものに依存してはいけません。私はstd :: exceptionから派生したクラスの素敵な階層を持っており、ロジックと入力のエラーを処理するために使用しています。特定の種類の例外が特定のファイルと行番号でスローされたことがわかっていますが、実行結果がどのようになっているのかが分かっていると、潜在的にはもっと重要なことになるので、スタックトレースを取得する方法を検討しています。例外のポータブルC++スタックトレース

私は(question 126450を参照)execinfo.h内の関数を使用してglibcのに対して構築(question 76822を参照)、MicrosoftのC++実装でStackWalkインタフェースを介したときに、このデータが利用可能であることを承知していますが、私は非常に多くのものを避けたいですポータブルではありません。

私はこのフォームでこの機能を実装する自分の考えていた:

class myException : public std::exception 
{ 
public: 
    ... 
    void AddCall(std::string s) 
    { m_vCallStack.push_back(s); } 
    std::string ToStr() const 
    { 
    std::string l_sRet = ""; 
    ... 
    l_sRet += "Call stack:\n"; 
    for(int i = 0; i < m_vCallStack.size(); i++) 
     l_sRet += " " + m_vCallStack[i] + "\n"; 
    ... 
    return l_sRet; 
    } 
private: 
    ... 
    std::vector<std::string> m_vCallStack; 
}; 

ret_type some_function(param_1, param_2, param_3) 
{ 
    try 
    { 
    ... 
    } 
    catch(myException e) 
    { 
    e.AddCall("some_function(" + param_1 + ", " + param_2 + ", " + param_3 + ")"); 
    throw e; 
    } 
} 

int main(int argc, char * argv[]) 
{ 
    try 
    { 
    ... 
    } 
    catch (myException e) 
    { 
    std::cerr << "Caught exception: \n" << e.ToStr(); 
    return 1; 
    } 
    return 0; 
} 

これはひどいアイデアですか?これは、すべての機能にtry/catchブロックを追加する作業がたくさんあることを意味しますが、私はそのことで暮らすことができます。例外の原因がメモリ破損またはメモリ不足の場合は動作しませんが、その時点ではどうにかなります。スタック内のいくつかの関数が例外をキャッチしてリストに追加して再スローしないと、誤った情報が得られるかもしれませんが、少なくとも私のライブラリ関数のすべてがそのように機能することを保証することはできます。 "実際の"スタックトレースとは異なり、私は関数を呼び出す際に行番号を取得しませんが、少なくとも私は何かを持っています。

私の最大の関心事は、この例外が実際にスローされていなくても景気減速の原因となる可能性があります。これらのtry/catchブロックはすべて、各関数の呼び出し時に追加のセットアップと分解が必要ですか、あるいはコンパイル時に何とか処理されますか?それとも私が考えていない他の問題はありますか?

答えて

21

私はこれは本当に悪いアイデアだと思います。

移植性は非常に立派な目標ですが、それは、パフォーマンス・掘り崩し、および劣っ実装押し付けがましいある解決策になりませんとき。私が働いてきた

すべてのプラットフォームでは、(Windowsの/ Linuxの/ PS2/iPhoneの/ etc)例外が発生したときにスタックを歩くと名前を機能させるアドレスを一致させる方法を提供してきました。はい、これらのどれも移植性はありませんが、レポートフレームワークは可能であり、通常はプラットフォーム固有のバージョンのスタックウォーキングコードを書くのに1日か2日かかりません。

クロスプラットフォームソリューションの作成/保守に要する時間が短くなるだけでなく、結果ははるかに優れています。

  • (遅いとメモリ集約)機能を標準またはサードパーティのライブラリで
  • トラップがクラッシュ
  • 各関数でのtry/catchのための必要なしに変更する必要はありません
2

私はこれを行うには、「プラットフォームに依存しない」方法はないと思う - があった場合は、すべての後、StackWalkの必要性や、あなたが言及機能をトレース特別gccのスタックは存在しないでしょう。

少し面倒ですが、これを実装する方法は、スタックトレースにアクセスするための一貫したインターフェイスを提供するクラスを作成し、適切なプラットフォーム固有のメソッドを使用する実装で#ifdefsを使用することです実際にスタックトレースをまとめることができます。クラスの道あなたの使用量はプラットフォームに依存しないで、あなたには、いくつかの他のプラットフォームをターゲットにしたい場合は、単にそのクラスを変更する必要があり

0

これは次のようになります遅いですが、動作するはずです。

高速で移植性の高いスタックトレースを作成する上での問題は、スタック実装がOSとCPUに固有であることから、プラットフォーム固有の問題です。別の方法として、MS/glibc関数を使用し、#ifdefと適切なプリプロセッサ定義(たとえば_WIN32)を使用して、プラットフォーム固有のソリューションを異なるビルドで実装する方法があります。

0

スタックの使用率は非常に高いため、実装に依存するため、完全に移植可能な方法はありません。しかし、プラットフォームとコンパイラ固有のインプリメンテーションへの移植可能なインタフェースを構築して、問題を可能な限りローカライズすることができます。 IMHO、これはあなたの最善の方法でしょう。

トレーサインプリメンテーションは、プラットフォーム固有のヘルパーライブラリが利用可能なものにリンクします。例外が発生した場合にのみ動作し、キャッチブロックから呼び出された場合にのみ動作します。その最小限のAPIは、単にトレース全体を含む文字列を返します。

コールチェーンにキャッチおよび再スロー処理を挿入するようにコーダーに要求すると、一部のプラットフォームでランタイムコストが大幅に高くなり、将来大きなメンテナンスコストがかかります。

catch/throwメカニズムを使用する場合は、C++でもCプリプロセッサが使用可能であり、__FILE____LINE__のマクロが定義されていることを忘れないでください。それらを使用して、トレース情報にソースファイル名と行番号を含めることができます。

6

ルックアップNested Diagnostic Contextを一度検索してください。少しヒントです:

class NDC { 
public: 
    static NDC* getContextForCurrentThread(); 
    int addEntry(char const* file, unsigned lineNo); 
    void removeEntry(int key); 
    void dump(std::ostream& os); 
    void clear(); 
}; 

class Scope { 
public: 
    Scope(char const *file, unsigned lineNo) { 
     NDC *ctx = NDC::getContextForCurrentThread(); 
     myKey = ctx->addEntry(file,lineNo); 
    } 
    ~Scope() { 
     if (!std::uncaught_exception()) { 
      NDC *ctx = NDC::getContextForCurrentThread(); 
      ctx->removeEntry(myKey); 
     } 
    } 
private: 
    int myKey; 
}; 
#define DECLARE_NDC() Scope s__(__FILE__,__LINE__) 

void f() { 
    DECLARE_NDC(); // always declare the scope 
    // only use try/catch when you want to handle an exception 
    // and dump the stack 
    try { 
     // do stuff in here 
    } catch (...) { 
     NDC* ctx = NDC::getContextForCurrentThread(); 
     ctx->dump(std::cerr); 
     ctx->clear(); 
    } 
} 

オーバーヘッドはNDCの実装にあります。私は怠惰に評価されたバージョンだけでなく、固定数のエントリだけを保持しているもので遊んでいました。要点は、コンストラクタとデストラクタを使用してスタックを処理する場合、それらのすべてが厄介なものである必要がないようにすることです。try/catchブロックと明示的な操作がどこでも可能です。

プラットフォーム固有の頭痛だけがgetContextForCurrentThread()メソッドです。スレッドローカルストレージを使用するプラットフォーム固有の実装を使用して、すべてではないにしてもほとんどの場合ジョブを処理できます。

あなたは、よりパフォーマンス指向のファイル名と行番号へのポインタを保持し、完全にNDCの事を省略する範囲を変更、その後、ログファイルの世界に住んでいる場合:この意志

class Scope { 
public: 
    Scope(char const* f, unsigned l): fileName(f), lineNo(l) {} 
    ~Scope() { 
     if (std::uncaught_exception()) { 
      log_error("%s(%u): stack unwind due to exception\n", 
         fileName, lineNo); 
     } 
    } 
private: 
    char const* fileName; 
    unsigned lineNo; 
}; 

例外がスローされたときに、あなたのログファイルに素晴らしいスタックトレースを与えます。任意の実際のスタック歩行のための必要はありません、例外がスローされているほんの少しのログメッセージなし;)

+1

別の見通しについては、Herb Sutterのhttp://www.gotw.ca/gotw/047.htmをご覧ください。 – AJG85

1

デバッガで:

例外は、私はちょうど休憩をstcikから投げるここでのスタックトレースを取得するにはstd :: exceptionコンストラクタを指します。

したがって、例外が作成されるとデバッガが停止し、その時点でスタックトレースが表示されます。完璧ではありませんが、ほとんどの時間で動作します。

+1

彼はデバッガの指示を求めていませんでした。 –

+1

これは盗難の防止に役立ちませんか? –

+0

+1便利なヒント – Mawg

1

スタック管理は、非常に迅速に複雑になる単純なものの1つです。特殊なライブラリのために残す方が良い。あなたはlibunwindを試しましたか?私はWindows上でそれを試したことはありませんが、素晴らしい作品とそれは移植性のAFAIK。