21

シンボルの可視性をよりよく理解しようとしています。GCC Wiki(http://gcc.gnu.org/wiki/Visibility)には、「C++例外の問題」に関するセクションがあります。GCC Wikiによると、エクスポートされていない例外が原因でランタイムエラーが発生する可能性があります。コンパイル時のエラー/警告のないランタイムエラーは非常に危険なので、問題をよりよく理解しようとしました。実験をしましたが、まだ再現できません。問題を再現する方法はありますか?

Wikiには3つのライブラリが相互に使用されていると記載されているので、3つの小さなライブラリを作成しました。

次のコマンドを実行します。

vtableのない例外クラス(期待どおりに機能します):

make
./dsouser

vtableを含む例外クラスですが、エクスポートされません(コンパイルすらしません):

make HAS_VIRTUAL=1

例外クラスがvtableをエクスポートしました(期待どおりに機能します):

make HAS_VIRTUAL=1 EXCEPTION_VISIBLE=1
./dsouser

Makefile:

CXX=g++-4.7.1
CFLAGS=-ggdb -O0 -fvisibility=hidden
ifdef EXCEPTION_VISIBLE
  CFLAGS+=-DEXCEPTION_VISIBLE
endif
ifdef HAS_VIRTUAL
  CFLAGS+=-DHAS_VIRTUAL
endif
all: dsouser

libmydso.so: mydso.cpp mydso.h
    $(CXX) $(CFLAGS) -fPIC -shared -Wl,-soname,$@ -o $@ $<

libmydso2.so: mydso2.cpp mydso.h mydso2.h libmydso.so
    $(CXX) $(CFLAGS) -L.  -fPIC -shared -Wl,-soname,$@ -o $@ $< -lmydso

libmydso3.so: mydso3.cpp mydso.h mydso2.h mydso3.h libmydso2.so
    $(CXX) $(CFLAGS) -L.  -fPIC -shared -Wl,-soname,$@ -o $@ $< -lmydso -lmydso2

dsouser: dsouser.cpp libmydso3.so
    $(CXX) $< $(CFLAGS) -L. -o $@ -lmydso -lmydso2 -lmydso3

clean:
    rm -f *.so *.o dsouser

.PHONY: all clean

mydso.h:

#ifndef DSO_H_INCLUDED
#define DSO_H_INCLUDED
#include <exception>
#define SYMBOL_VISIBLE __attribute__ ((visibility ("default")))
namespace dso
{
  class
#ifdef EXCEPTION_VISIBLE
    SYMBOL_VISIBLE
#endif
    MyException : public std::exception
  {
  public:
#ifdef HAS_VIRTUAL
    virtual void dump();
#endif
    void SYMBOL_VISIBLE foo();
  };
}
#endif

mydso.cpp:

#include <iostream>
#include "mydso.h"
namespace dso
{

#ifdef HAS_VIRTUAL
void MyException::dump()
{
}
#endif

void MyException::foo()
{
#ifdef HAS_VIRTUAL
  dump();
#endif
}

}

mydso2.h:

#ifndef DSO2_H_INCLUDED
#define DSO2_H_INCLUDED
#define SYMBOL_VISIBLE __attribute__ ((visibility ("default")))
namespace dso2
{
  void SYMBOL_VISIBLE some_func();
}
#endif

mydso2.cpp:

#include <iostream>
#include "mydso.h"
#include "mydso2.h"
namespace dso2
{
  void some_func()
  {
    throw dso::MyException();
  }
}

mydso3.h:

#ifndef DSO3_H_INCLUDED
#define DSO3_H_INCLUDED
#define SYMBOL_VISIBLE __attribute__ ((visibility ("default")))
namespace dso3
{
  void SYMBOL_VISIBLE some_func();
}
#endif

mydso3.cpp:

#include <iostream>

#include "mydso2.h"
#include "mydso3.h"

#include <iostream>

namespace dso3
{

  void some_func()
  {
    try
    {
      dso2::some_func();
    } catch (std::exception e)
    {
      std::cout << "Got exception\n";
    }
  }

}

dsouser.cpp:

#include <iostream>
#include "mydso3.h"
int main()
{
  dso3::some_func();
  return 0;
}

ありがとう、ダニ

4

1 に答える 1

30

私は、クラスの可視性のサポートを追加するGCCの元のパッチの作成者であり、GCCが複製した元のハウツーはhttp://www.nedprod.com/programs/gccvisibility.htmlにあります。このSOの質問について私に個人的にメールを送ってくれたVargaDに感謝します。

観察した動作は最近のGCCで有効ですが、必ずしもそうではありませんでした。2004年に最初にGCCにパッチを適用したとき、アドレスを比較する代わりに、マングルされたシンボルの文字列比較によってスローされたタイプを比較するGCC例外処理ランタイムのリクエストをGCCbugzillaに送信しました。これらの文字列のうち、これは、MSVCが行う動作であるにもかかわらず、GCCメンテナによって許容できないランタイムコストとしてその時点で拒否されました。また、例外スロー中のパフォーマンスは、まれであると想定されるため、一般的に重要とは見なされません。したがって、可視性ガイドに特定の例外を追加して、スローされたタイプをバイナリで一度も非表示にしないでください。「非表示」が「デフォルト」よりも優先されるため、単一の非表示シンボル宣言だけで、与えられたバイナリの同じシンボル。

次に何が起こったのか私たちの誰も予想していなかったと思います-KDEは私の貢献した機能を非常に公に受け入れました。これは、驚くほど短時間で、ほぼすべての大規模なGCCを使用するプロジェクトにカスケードされました。突然、シンボルの非表示が標準になりましたが、例外ではありませんでした。

残念ながら、少数の人々が例外スロータイプに私のガイドを正しく適用しませんでした。GCCでの誤った相互共有オブジェクト例外処理に関する絶え間ないバグレポートにより、最終的にGCCメンテナは諦め、何年も後に文字列比較でパッチを適用しました。私が最初に要求したように、スローされたタイプのマッチングのために。したがって、新しいGCCでは、状況はやや良くなります。このアプローチはv4.0以降すべてのGCCで最も安全であり、新しいGCCは文字列比較を使用するようになったため、例外スローの処理の信頼性が高くなりますが、ガイドのルールに従うことで問題が発生しないため、ガイドも手順も変更していません。それ。

これにより、typeinfoの問題が発生します。大きな問題は、ベストプラクティスのC ++では、常に仮想的にスロー可能な型を継承する必要があることです。これは、両方がstd :: exceptionから継承する(たとえば)2つの例外型を作成する場合、2つの等距離のstd::exception基本クラスがあるとキャッチが発生するためです。 (std :: exception&)は、一致する基本クラスを解決できないため、terminate()を自動呼び出しします。したがって、std :: exception基本クラスは1つだけにする必要があり、同じ論理的根拠が、throwableの可能な構成に適用されます。タイプ。このベストプラクティスは、サードパーティユーザーが例外タイプで何をするかわからないため、C++ライブラリで特に必要です。

言い換えると、これは、ベストプラクティスでスローされたすべての例外タイプには、基本クラスごとに連続するRTTIのチェーンが常に付属することを意味し、例外マッチングは、マッチングされるタイプに対して内部でdynamic_cast<>を成功させる場合になります。 O(基本クラスの数)操作。そして、dynamic_cast <>が仮想的に継承されたタイプのチェーン上で機能するためには、ご想像のとおり、すべてのタイプが必要です。このチェーンのデフォルトの可視性を持ちます。catch()を実行するコードから1つでも隠されている場合、caboodle全体が腹を立てて、terminate()を取得します。上記のサンプルコードを作り直して、仮想的に継承し、何が起こるかを確認していただければ、非常に興味があります。コメントの1つに、リンクを拒否していると書かれています。これはすばらしいことです。しかし、DLL AがタイプAを定義し、DLL BがタイプAをBにサブクラス化し、DLL CがタイプBをCにサブクラス化し、プログラムDがタイプCがスローされたときにタイプAの例外をキャッチしようとしたとします。プログラムDはAのタイプ情報を利用できますが、タイプBおよびCのRTTIをフェッチしようとすると、エラーが発生するはずです。ただし、最近のGCCでもこれが修正されていますか?わかりませんが、近年の私の注目はclangにあります。これは、すべてのC++コンパイラーの将来であるためです。

明らかに、これは混乱ですが、ELF固有の混乱です。これはPEやMachOには影響しません。どちらも、そもそもプロセスグローバルシンボルテーブルを使用しないことで、上記のすべてを正しく実現します。ただし、C ++17に向けて取り組んでいるWG21SG2モジュール研究グループは、ODR違反を解決するために、モジュールが機能するようにエクスポートされたテンプレートを効果的に実装する必要があります。マインド。言い換えると、C ++ 17コンパイラは、clangのように複雑なASTをディスクにダンプする必要があります。これは、RTTIが利用できることの保証が大幅に増加することを意味します。実際、C ++モジュールのASTにより、内省の可能性が大幅に増加するため、SG7リフレクション研究グループがあります。言い換えると、

だから、要するに、今のところ私の元のガイドに従ってください。そして、物事は次の10年で大幅に良くなることを願っています。そして、そのソリューションに資金を提供してくれたAppleに感謝します。それがいかに邪悪であるかという理由で、これから長い時間がかかりました。

ニール

于 2013-01-16T17:19:54.587 に答える