使用-fno-rtti在OS X上抛出异常并捕获异常 [英] Problems throwing and catching exceptions on OS X with -fno-rtti

查看:403
本文介绍了使用-fno-rtti在OS X上抛出异常并捕获异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题有点类似于这个问题,但接受的答案并没有真正提出解决方案或解决方法。



在我们的项目中,我们有一个dylib和main executalble。 dylib使用 -fno-rtti 编译,而可执行文件则使用RTTI。当一个异常(例如 std :: bad_alloc )从dylib抛出并在exe中被捕获时,会出现此问题。



(在你吼叫异常需要RTTI,所以你必须有它!,请注意,异常所需的RTTI总是生成而不管 -frtti -fno-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定义(__ 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定义(__ GNUC__)
#define IMPORT extern
#else
#define IMPORT __declspec(dllimport)
#endif

IMPORT void dothrow();

int main(void){
try {
std :: cout<< 尝试lib->主异常<<的std :: ENDL;
dothrow();
}
catch(const std :: bad_alloc&)
{
std :: cout<<< 抓住bad_alloc在主 - 好。 <<的std :: ENDL;
}
catch(...)
{
std :: cout<<< 抓到...主要 - 坏! <<的std :: ENDL;
}
}

--- makefile ---
#为主exe
CFLAGS_RTTI = -m32 -frtti -fvisibility = hidden -fvisibility- inline-hidden -shared-libgcc -funwind-tables
#for dylib
CFLAGS_NORTTI = -m32 -fno-rtti -fvisibility = hidden -fvisibility-inlines-hidden -shared-libgcc
#for连接;一些开关不帮助
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。 $ c

test main.o throw.o

运行:

  $ ./test 
尝试lib->主异常
抛出
抓住...在主 - 坏!

涉及文件的符号:

  $ 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
(未定义)外部__ZNSt9bad_allocD1Ev
00000290(__DATA,__ const_coal)弱外部__ZTISt9bad_alloc
000002a4(__TEXT,__ const_coal)弱外部__ZTSSt9bad_alloc
(未定义)外部__ZTVSt9bad_alloc

$ nm -m libThrow.dylib | grep bad_alloc
00000ce6(__TEXT,__ text)非外部__ZNSt9bad_allocC1Ev
(未定义)外部__ZNSt9bad_allocD1Ev(来自libstdc ++)
00001050(__DATA,__ const)弱外部__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(来自libstdc ++)

注意:类似的编译选项Linux和Windows工作正常。我可以从共享对象/ DLL中抛出异常,并捕获它们在主exe中,即使它们被编译成不同的 -frtti / -fno -rtti 选项。






编辑:这是我如何结束针对 bad_alloc 的具体情况解决问题:

  #if defined (__GLIBCXX__)||定义(_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 导入,所以总是抛出一个正确的类型。

解决方案

我做了一个小工具,用于后处理对象文件,并将本地符号标记为 UNDEF 。这将强制链接器使用 libstdc ++ 中的定义,而不是文件中的定制。该工具的基本方法是:


  1. 加载Mach-O标题

  2. 命令,并找到 LC_SYMTAB 命令

  3. 加载符号列表( struct nlist )和字符串

  4. 符号并寻找我们需要的(例如 __ ZTISt9bad_alloc

  5. 将找到的符号类型设置为 N_UNDF | N_EXT

  6. 处理完毕后,将修改的符号表写回文件。

(我也为ELF做了类似的实现)



我对使用std异常的任何文件进行后期处理,无论是投掷还是捕捉。为了确保文件列表不会过时,我使用 nm 添加了不需要的本地符号的链接后检查。



这似乎解决了迄今为止所有的问题。


The issue is somewhat similar to this question but the accepted answer does not really propose a solution or workaround.

In our project, we have a dylib and the main executalble. The dylib is compiled with -fno-rtti, while the executable does use RTTI. The problem happens when an exception (e.g. std::bad_alloc) is thrown from the dylib and is caught in the exe.

(Before you yell "exceptions need RTTI so you must have it on!", please note that the RTTI necessary for exceptions is always generated regardless of the -frtti or -fno-rtti setting. This is actually documented in the -fno-rtti flag description. The issue on OS X is that it's not generated in the same way)

After some investigation, the following was discovered:

  • In the dylib (-fno-rtti), there is a local copy of the exception's RTTI structures; in particular, the __ZTISt9bad_alloc symbol (typeinfo for std::bad_alloc).
  • The exe (-frtti) imports the typeinfo symbol from libstdc++.6.dylib and does not have a local copy

Since the exception handling code relies on comparing typeinfo pointers to determine exception match, the matching fails, and only the catch(...) succeeds.

So far I see the following options:

1) compile everything, or at least the files that throw and catch exceptions, with -frtti. This is doable but I don't like the idea of generating RTTI for everything even if we don't use it; and the list of files which work with exceptions is prone to get stale.

2) when linking the dylib, somehow make the linker throw away the weak exception definition from the object file and use the one from libstdc++.6.dylib. So far I was not successful.

3) ???

I made a small test illustrating the problem.

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

Running:

$ ./test
trying lib->main exception
before throw
caught ... in main - bad!

Symbols of the files involved:

$ 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++)

Note: similar compilation options on Linux and Windows works fine. I can throw exceptions from a shared object/dll and catch them in the main exe, even if they're compiled with different -frtti/-fno-rtti options.


EDIT: here's how I ended up solving it for the specific case of 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();
}

The function __throw_bad_alloc is imported from libstdc++.6.dylib and so always throws a correct type.

解决方案

Well, even though I have accepted an answer it did not solve all problems. So I'm writing down the solution which did work in the end.

I made a small tool for post-processing the object files and marking the local symbols as UNDEF. This forces the linker to use definitions from libstdc++ and not local ones from the file. The basic approach of the tool is:

  1. load the Mach-O header
  2. walk the load commands and find the LC_SYMTAB command
  3. load the list of symbols (struct nlist) and the strings
  4. walk the symbols and look for those that we need (e.g. __ZTISt9bad_alloc)
  5. set the found symbols' type to N_UNDF|N_EXT.
  6. after processing, write the modified symbol table back to the file.

(I also made a similar implementation for ELF)

I post-process any file that's using std exceptions, either for throwing or for catching. To make sure the file list does not go stale, I added a post-link check for unwanted local symbols using nm.

This seems to resolve all the problems I've had so far.

这篇关于使用-fno-rtti在OS X上抛出异常并捕获异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆