符号可见性,异常,运行时错误

时间:2013-01-10 22:51:41

标签: c++ exception visibility symbols elf

我尝试更好地理解符号可见性。 GCC Wiki(http://gcc.gnu.org/wiki/Visibility)有一节关于“C ++异常问题”。根据GCC Wiki,由于未导出异常,可能会出现运行时错误。没有编译时错误/警告的运行时错误是非常危险的,所以我试图更好地理解问题。我做了一些实验,但我仍然无法重现它。任何想法如何重现问题?

Wiki提到三个图书馆互相使用,所以我创建了三个小图书馆。

我运行以下命令:

没有vtable的异常类(按预期工作):

make
./dsouser

使用vtable的异常类,但它不会导出(甚至不编译):

make HAS_VIRTUAL=1

异常类导出的vtable(按预期工作):

make HAS_VIRTUAL=1 EXCEPTION_VISIBLE=1
./dsouser

生成文件:

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;
}

谢谢, 达尼

1 个答案:

答案 0 :(得分:27)

我是GCC原始修补程序的作者添加了类可见性支持,而我原来的GCC克隆的方法是http://www.nedprod.com/programs/gccvisibility.html。感谢VargaD亲自给我发电子邮件,告诉我这个问题。

您观察到的行为对最近的海湾合作委员会有效,但并非总是如此。当我最初在2004年修补GCC时,我向GCC bugzilla提交了一个GCC异常处理运行时请求,通过字符串比较它们的错位符号来比较抛出的类型,而不是比较这些字符串的地址 - 这是GCC维护者当时拒绝承认这是一个不可接受的运行时成本,尽管这种行为是MSVC所做的,尽管在异常抛出期间的表现通常不被认为是重要的,因为它们应该是罕见的。因此,我必须在我的可见性指南中添加一个特定的例外,说任何抛出的类型都不能隐藏,而不是一次,在二进制文件中隐藏&#34;隐藏&#34;特朗普&#34;默认&#34;所以只需要一个隐藏的符号声明就可以覆盖给定二进制文件中相同符号的所有情况。

接下来发生的事情我想我们都没有想到--KDE非常公开地接受了我的贡献功能。在极短的时间内,几乎所有大型海湾合作委员会使用项目都进入了这个阶段。突然,符号隐藏是常态,而不是例外。

不幸的是,少数人没有正确地应用我的指南用于异常抛出类型,并且关于GCC中错误的交叉共享对象异常处理的常量错误报告最终导致GCC维护者放弃了多年后来用于抛出类型匹配的字符串比较补丁,就像我最初要求的那样。因此,在较新的海湾合作委员会中,情况会好一些。我没有改变我的指南或指令,因为自从v4.0以来,这种方法在每个GCC上仍然是最安全的,并且由于现在使用字符串比较,更新的GCC在处理异常抛出方面更可靠,遵循指南&#39;规则并没有伤害到它。

这将我们带入了typeinfo问题。一个很大的问题是,最佳实践C ++要求您始终在throwable类型中继承虚拟,因为如果您组成两个异常类型,则继承(让我们说)来自std :: exception,有两个等距的std :: exception基类会导致catch(std :: exception&amp;)自动调用terminate(),因为它无法解析匹配哪个基类,所以你必须只有有一个std :: exception基类,并且相同的基本原理适用于任何可抛出类型的组合。在任何C ++库中都特别需要这种最佳实践,因为您无法知道第三方用户将如何处理您的异常类型。

换句话说,这意味着在最佳实践中所有抛出的异常类型将始终为每个基类提供一系列连续的RTTI,并且异常匹配现在是内部成功执行dynamic_cast的情况&lt;&gt;到匹配的类型,O(基类数)操作。对于dynamic_cast&lt;&gt;为了研究一系列虚拟遗传类型,你猜对了,你需要这个链中的每一个才能拥有默认的可见性。如果在执行catch()的代码中隐藏了一个,那么整个caboodle就会变成肚子,然后你会得到一个终止()。如果您将上面的示例代码重新编写为虚拟继承并看看会发生什么,我会非常感兴趣 - 您的一条评论说它拒绝链接,这很棒。但是,让我们说DLL A定义类型A,DLL B子类A类型为B,DLL C子类型B类型为C,程序D尝试在抛出类型C时捕获类型A的异常。程序D将具有A可用的类型信息,但在尝试为类型B和C获取RTTI时应该是错误的。但是,最近的GCC也解决了这个问题吗?我不知道,近年来我的注意力集中在所有C ++编译器的未来上。

显然,这是一个混乱,但它是一个特定于ELF的混乱 - 这些都不会影响PE或MachO,这两者都通过不在第一个中使用进程全局符号表来获得所有上述权利地点。然而,致力于C ++ 17的WG21 SG2模块研究小组必须有效地实现模块的导出模板才能解决ODR违规问题,而C ++ 17是第一个提出的标准,我已经看到用LLVM记住了。换句话说,C ++ 17编译器必须像clang一样将复杂的AST转储到光盘上。这意味着RTTI可用保证的大幅增加 - 事实上,这就是我们拥有SG7 Reflection研究小组的原因,因为来自C ++模块的AST可以大大增加可能的自我反思机会。换句话说,在C ++ 17采用的情况下,预计上述问题很快就会消失。

因此,简而言之,请暂时关注我的原始指南。事情有望在未来十年内变得更好。感谢Apple为这个解决方案提供资金,由于它有多么恶劣,它已经很长一段时间了。

尼尔