【发布时间】:2014-03-11 07:48:53
【问题描述】:
这个问题有点类似于this question,但接受的答案并没有真正提出解决方案或解决方法。
在我们的项目中,我们有一个 dylib 和主可执行文件。 dylib 使用-fno-rtti 编译,而可执行文件使用 RTTI。当 dylib 抛出异常(例如std::bad_alloc)并在 exe 中捕获时,就会出现问题。
(在您大喊“例外需要 RTTI,因此您必须启用它!”之前,请注意,无论-frtti 或-fno-rtti 设置,无论,总是会生成例外所需的 RTTI。这实际上记录在-fno-rtti 标志描述中。OS X 上的问题是它不是以相同的方式生成的)
经过一番调查,发现如下:
- 在dylib (
-fno-rtti) 中,有异常的RTTI 结构的本地副本;特别是__ZTISt9bad_alloc符号 (typeinfo for std::bad_alloc)。 - exe (
-frtti) 从libstdc++.6.dylib导入 typeinfo 符号,并且没有本地副本
由于异常处理代码依赖于比较typeinfo指针来判断异常匹配,匹配失败,只有catch(...)匹配成功。
到目前为止,我看到了以下选项:
1) 使用-frtti 编译所有内容,或者至少编译抛出和捕获异常的文件。这是可行的,但我不喜欢为所有内容生成 RTTI,即使我们不使用它;并且处理异常的文件列表很容易过时。
2) 链接 dylib 时,以某种方式使链接器丢弃目标文件中的弱异常定义并使用来自libstdc++.6.dylib 的异常定义。到目前为止,我还没有成功。
3) ???
我做了一个小测试来说明问题。
--- throw.cpp ---
#include <iostream>
#if defined(__GNUC__)
#define EXPORT __attribute__((visibility("default")))
#else
#define EXPORT __declspec(dllexport)
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw std::bad_alloc();
}
--- main.cpp ---
#include <stdio.h>
#include <iostream>
#if defined(__GNUC__)
#define IMPORT extern
#else
#define IMPORT __declspec(dllimport)
#endif
IMPORT void dothrow ();
int main (void) {
try {
std::cout << "trying lib->main exception" << std::endl;
dothrow ();
}
catch ( const std::bad_alloc& )
{
std::cout << "caught bad_alloc in main - good." << std::endl;
}
catch (...)
{
std::cout << "caught ... in main - bad!" << std::endl;
}
}
--- makefile ---
# for main exe
CFLAGS_RTTI=-m32 -frtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc -funwind-tables
# for dylib
CFLAGS_NORTTI=-m32 -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc
# for linking; some switches which don't help
CLFLAGS=-Wl,-why_live,-warn_commons,-weak_reference_mismatches,error,-commons,error
all: test
test: libThrow.dylib main.o
g++ $(CFLAGS_RTTI) -o test main.o -lthrow -L./ $(CLFLAGS)
main.o: main.cpp
g++ $(CFLAGS_RTTI) -c -o main.o main.cpp
throw.o: throw.cpp
g++ $(CFLAGS_NORTTI) -c -o throw.o throw.cpp
libThrow.dylib: throw.o
g++ $(CFLAGS_NORTTI) -dynamiclib -o libThrow.dylib throw.o
clean:
rm test main.o throw.o
跑步:
$ ./test
trying lib->main exception
before throw
caught ... in main - bad!
所涉及文件的符号:
$ nm -m throw.o | grep bad_alloc
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
00000300 (__TEXT,__eh_frame) weak private external __ZNSt9bad_allocC1Ev.eh
(undefined) external __ZNSt9bad_allocD1Ev
00000290 (__DATA,__const_coal) weak external __ZTISt9bad_alloc
000002a4 (__TEXT,__const_coal) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc
$ nm -m libThrow.dylib | grep bad_alloc
00000ce6 (__TEXT,__text) non-external __ZNSt9bad_allocC1Ev
(undefined) external __ZNSt9bad_allocD1Ev (from libstdc++)
00001050 (__DATA,__const) weak external __ZTISt9bad_alloc
00000e05 (__TEXT,__const) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc (from libstdc++)
$ nm -m main.o | grep bad_alloc
(undefined) external __ZTISt9bad_alloc
$ nm -m test | grep bad_alloc
(undefined) external __ZTISt9bad_alloc (from libstdc++)
注意:Linux 和 Windows 上的类似编译选项可以正常工作。我可以从共享对象/dll 中抛出异常并在主 exe 中捕获它们,即使它们是使用不同的 -frtti/-fno-rtti 选项编译的。
编辑:这就是我最终针对bad_alloc 的具体情况解决它的方法:
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
#define throw_nomem std::__throw_bad_alloc
#else
#define throw_nomem throw std::bad_alloc
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw_nomem();
}
函数__throw_bad_alloc 是从libstdc++.6.dylib 导入的,因此总是抛出正确的类型。
【问题讨论】:
-
(1) 如果您有办法为您不需要的所有内容(例如依赖于平台的属性)压制 rtti,则至少是可行的,或以上述方式打开它。有趣的问题。由于这些原因,我避免在 dylib 之间抛出异常,但这可能不是你的选择。
-
会不会是
-fvisibility标志导致了这种行为? -
你能用
-fno-rtti编译包含throw语句的代码吗?我的 GCC(诚然不是 OSX,但仍然)根本不允许我这样做(立即因错误而退出),这让我很伤心。此外,它为任何翻译单元中的每个类型添加 RTTI,无论该类型是否被抛出。 -
@Damon:是的,它适用于我在 Linux 上的 GCC 4.2 和 4.4 中(目前没有任何更新的要测试)。
-
我不明白为什么
throw在有 strong 时会在libThrow.dylib中使用 weak__ZTISt9bad_alloc符号在libstdc++.dylib中使用它的版本(尽管间接来自libstdc++abi),catch可能正在使用它。这对我来说似乎是ld的“错误”(或至少是“怪癖”)。这似乎是怎么回事?
标签: c++ macos exception shared-libraries rtti