Igo*_*sky 18 c++ macos exception shared-libraries rtti
问题与此问题有些类似,但接受的答案并未真正提出解决方案或解决方法.
在我们的项目中,我们有一个dylib和主要的可执行文件.dylib使用-fno-rtti,而可执行文件使用RTTI 编译.当std::bad_alloc从dylib抛出异常(例如)并在exe中捕获时,会发生此问题.
(在你大喊"异常需要RTTI所以你必须有它!",请注意,必要的例外RTTI总是产生不管的-frtti或-fno-rtti设置.这实际上是在记录-fno-rtti标志的说明.在OS X上的问题是,它不是以相同的方式生成的)
经过一番调查,发现了以下内容:
-fno-rtti)中,有一个异常的RTTI结构的本地副本; 特别是__ZTISt9bad_alloc符号(typeinfo for std::bad_alloc).-frtti)从中导入typeinfo符号libstdc++.6.dylib,但没有本地副本由于异常处理代码依赖于比较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
Run Code Online (Sandbox Code Playgroud)
运行:
$ ./test
trying lib->main exception
before throw
caught ... in main - bad!
Run Code Online (Sandbox Code Playgroud)
涉及的文件的符号:
$ 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++)
Run Code Online (Sandbox Code Playgroud)
注意: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();
}
Run Code Online (Sandbox Code Playgroud)
该函数__throw_bad_alloc是从中导入的libstdc++.6.dylib,因此始终抛出正确的类型.
您可以简单地将所有“抛出异常”基础设施移动到-frtti已启用的帮助程序库 - 并将其链接到其他内容。如果没有实际的代码,很难判断这种分解是否可能。
这是一些示例代码:
// Thrower.cc
void DoThrow() {
throw std::bad_alloc;
}
// LibraryNoRTTI.cc
void f() {
DoThrow();
}
// main.cc
int main() {
try {
f();
}
catch(std::bad_alloc&) {}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
最简单的方法是将所有throw调用移至具有适当类型的单独函数,例如throw std::logical_error("message");:void ThrowLogicError(const std::string& message) { ... }
如果封装(私有异常类)有问题,那么你可能会与抛出函数交朋友。
如果您仍然想在非 rtti 库中使用 ( throw/ ) 异常,那么您必须将内部异常和库 API 中使用的异常分开。catch
好的方法是使用本机 C++ throw-catch出于内部目的 - 然后使用基于 rtti 的库函数向外部重新抛出一些异常 - 根据您的接口:
// Thrower.cc
void Rethrow(const std::exception& e) {
throw e;
}
// LibraryNoRTTI.cc
namespace {
void internal_stuff() {
throw std::logical_error("something goes wrong!");
}
} // namespace
// You even may explicitly specify the thrown exceptions in declaration:
void f() throw(std::logical_error) {
try {
internal_stuff();
}
catch(std::exception& e) {
Rethrow(std::logical_error(std::string("Internal error: ") + e.what());
}
}
Run Code Online (Sandbox Code Playgroud)
好吧,尽管我已经接受了答案,但它并没有解决所有问题。所以我写下了最终有效的解决方案。
我制作了一个小工具,用于对目标文件进行后处理并将本地符号标记为UNDEF. 这会强制链接器使用libstdc++文件中的定义而不是本地定义。该工具的基本方法是:
LC_SYMTAB命令struct nlist) 和字符串__ZTISt9bad_alloc)N_UNDF|N_EXT.(我也为ELF做了类似的实现)
我对使用 std 异常的任何文件进行后处理,无论是抛出还是捕获。为了确保文件列表不会过时,我使用 . 添加了对不需要的本地符号的链接后检查nm。
这似乎解决了我迄今为止遇到的所有问题。
| 归档时间: |
|
| 查看次数: |
3289 次 |
| 最近记录: |