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

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

问题描述

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



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



(在你大喊异常需要RTTI所以你必须有它!,请注意,必要的例外RTTI总是产生的无论的的的-frtti -fno-rtti 设置实际上记录在 -fno-rtti OS X上的问题是它不是以相同的方式生成的)



经过一番调查,发现了以下内容:




  • 在dylib( -fno-rtti )中,有一个异常的RTTI结构的本地副本;特别是 __ ZTISt9bad_alloc 符号( std :: bad_alloc 的typeinfo)。

  • exe( -frtti )从 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

导出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< try lib-> main exception<< std :: endl;
dothrow();
}
catch(const std :: bad_alloc&)
{
std :: cout< 捕获bad_alloc在主 - 好。 << std :: endl;
}
catch(...)
{
std :: cout< 抓...在主 - 坏! << std :: endl;
}
}

--- makefile ---
#for main exe
CFLAGS_RTTI = -m32 -frtti -fvisibility = hidden -fvisibility-内联隐藏-shared-libgcc的-funwind桌
#为dylib
CFLAGS_NORTTI = -m32 -fno-RTTI -fvisibility =隐藏-fvisibility - 内联隐藏-shared-libgcc的
#为连接;一些不帮助的开关
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 
try lib-> main exception
before throw
catch ... in main - bad!

涉及的文件符号:

  $ nm -m throw.o | grep的bad_alloc 
000001be(__TEXT,__ textcoal_nt)弱外部私人__ZNSt9bad_allocC1Ev
000001be(__TEXT,__ textcoal_nt)弱外部私人__ZNSt9bad_allocC1Ev
00000300(__TEXT,__ eh_frame)弱外部私人__ZNSt9bad_allocC1Ev.eh
(未定义)外部__ZNSt9bad_allocD1Ev
00000290(__DATA,__ const_coal)弱外部__ZTISt9bad_alloc
000002a4(__TEXT,__ const_coal)外部__ZTSSt9bad_alloc
弱(不确定)外部__ZTVSt9bad_alloc

$纳米-m libThrow.dylib | grep的bad_alloc
00000ce6(__TEXT,__文本)非外部__ZNSt9bad_allocC1Ev
(不确定)外部__ZNSt9bad_allocD1Ev(从的libstdc ++)
00001050(__DATA,__常数)弱外部__ZTISt9bad_alloc
00000e05(__TEXT, __const)weak external __ZTSSt9bad_alloc
(未定义)external __ZTVSt9bad_alloc(来自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 选项。






EDIT :对于 bad_alloc 的特定情况解决它:

  #if defined (__GLIBCXX__)||定义(_LIBCPP_VERSION)
的#define throw_nomem的std :: __ throw_bad_alloc
的#else
的#define throw_nomem扔则为std :: bad_alloc
#ENDIF

出口无效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





  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天全站免登陆