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

2023-12-27

我尝试更好地理解符号可见性。海湾合作委员会维基百科 (http://gcc.gnu.org/wiki/Visibility http://gcc.gnu.org/wiki/Visibility)有一个关于“C++ 异常问题”的部分。根据 GCC Wiki 有可能runtime由于未导出异常而导致错误。没有编译时错误/警告的运行时错误非常危险,所以我试图更好地理解这个问题。我做了一些实验,但仍然无法重现。有什么想法如何重现该问题吗?

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

}

dsuser.cpp:

#include <iostream>
#include "mydso3.h"
int main()
{
  dso3::some_func();
  return 0;
}

谢谢, 达尼


我是添加类可见性支持的 GCC 原始补丁的作者,我的 GCC 克隆原始指南位于http://www.nedprod.com/programs/gccvisibility.html http://www.nedprod.com/programs/gccvisibility.html。我感谢 VargaD 亲自给我发电子邮件告诉我这个问题。

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

接下来发生的事情我想我们都没有预料到 - KDE 非常公开地接受了我贡献的功能。这在极短的时间内渗透到了几乎所有使用 GCC 的大型项目中。突然之间,符号隐藏成为常态,而不是例外。

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

这给我们带来了类型信息问题。一个大问题是 C++ 最佳实践要求您always继承几乎在可抛出类型中,因为如果您编写两个都从 std::exception 继承(比方说)的异常类型,那么拥有两个等距的 std::exception 基类将导致 catch(std::exception&) 自动调用 Terminate() 因为它无法解析要匹配的基类,因此您只能拥有一个 std::exception 基类,并且相同的原理适用于可抛出类型的任何可能组合。在任何 C++ 库中尤其需要这种最佳实践,因为您无法知道第三方用户将如何处理您的异常类型。

换句话说,这意味着在最佳实践中,所有抛出的异常类型将始终为每个基类附带一系列连续的 RTTI,并且异常匹配现在是在内部对匹配的类型执行成功的dynamic_cast的情况, O(基类数量)操作。为了让dynamic_cast能够在一系列虚拟继承的类型上工作,你猜对了,你需要每一个该链具有默认可见性。如果执行 catch() 的代码中甚至隐藏了一个,那么整个一堆就会崩溃,并且您会得到一个终止()。如果您重新设计上面的示例代码以虚拟继承并看看会发生什么,我会非常感兴趣 - 您的评论之一说它拒绝链接,这很棒。但是,假设 DLL A 定义了类型 A,DLL B 将类型 A 子类化为 B,DLL C 将类型 B 子类化为 C,程序 D 在抛出类型 C 时尝试捕获类型 A 的异常。程序 D 将具有可用的 A 类型信息,但在尝试获取类型 B 和 C 的 RTTI 时应该会出错。不过,也许最近的 GCC 也修复了这个问题?我不知道,近年来我的注意力集中在 clang 上,因为那是所有 C++ 编译器的未来。

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

所以,简而言之,现在请继续遵循我原来的指南。未来十年情况有望变得更好。感谢苹果公司为该解决方案提供资金支持,由于它的难度非常大,所以已经等了很长时间了。

Niall

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

符号可见性、异常、运行时错误 的相关文章

随机推荐

  • Python 3.5+ 中类型提示的动态检查[重复]

    这个问题在这里已经有答案了 The typing https docs python org 3 library typing html模块在 Python 3 5 中实现类型提示 然而 这并不是强制执行的 它目前似乎只是为了静态类型检查器
  • 在 Google People API 中未给出的特定时间后更新联系人条目的选项

    我们使用 Google Contacts API 获取在特定时间后更新的联系人条目 以便在我们端保留联系人的更新副本 在 Google Contacts API 中 有一个选项可以使用以下命令在特定时间后更新联系人条目 更新分钟 https
  • 使用 Python 识别 Mac OS X 中的包目录

    Mac OS X Finder 使用 包 的概念使某些文件夹的内容对用户不透明 我在用着os walk 枚举目录树 并且我想跳过对应用程序包等包的枚举 The mdls命令行实用程序可用于检查是否com apple package在里面kM
  • XSL:只有文件名,没有路径

    我是 XSL 编程新手 我想这是一个简单的问题 如何获取没有路径的文件名 目前我的代码如下所示 我得到了整个路径 结果 xml
  • Spring集成测试不回滚

    我正在使用 Spring Hibernate H2 我在集成测试中执行数据库操作 通过调用服务类 我希望 Spring 在每个测试方法之后回滚更改 但我无法让它工作 起初我使用MySQL 带有MyISAM 不支持事务 但是换成H2后问题仍然
  • 使用 $lookup mongodb 填充到深层

    我正在使用 lookup 加入两个集合并从以下查询中获取数据 let condition status ne config PROJECT STATUS completed assignId mongoose Types ObjectId
  • 具有最大高度和最大宽度的响应式图像

    我有这个 html 和 CSS div class kalim img src div CSS kalim display inline block kalim img max width 800px width auto max heig
  • Hibernate 和 Jackson 惰性序列化

    我正在开发一个使用 Hibernate 和 Jackson 来序列化我的对象的项目 我想我明白它应该如何工作 但我无法让它发挥作用 如果我理解得很好 一旦关系获取模式设置为LAZY 如果你想要这个关系 你就必须初始化它 这是我的课程 Ent
  • TensorFlow 中 AdamOptimizer 的学习率不会改变

    我想看看训练期间学习率如何变化 打印出来或创建摘要并在张量板上可视化 这是我迄今为止所掌握的代码片段 optimizer tf train AdamOptimizer 1e 3 grads and vars optimizer comput
  • 通过 Composer 安装或复制供应商文件夹

    我想知道 在部署基于 Composer 的项目时 为什么大多数人建议通过 SSH 连接到服务器并安装 Composer 并下载依赖项 如下所示 curl sS https getcomposer org installer php mv c
  • Angular ng-click 在 Bootstrap 下拉菜单中不起作用

    我有一些基于 Bootstrap 3 下拉菜单的过滤器 但由于某些奇怪的原因 它们在实际的下拉菜单中不起作用 但如果我复制粘贴它并将其放在外面 它就可以正常工作 div div class btn group div div
  • 限制相机旋转角度

    我希望能够在某个点之后限制相机旋转 并且只能在某个区域内旋转 这是到目前为止的代码 void Update float mouseX Input GetAxis Mouse X float mouseY Input GetAxis Mous
  • 有没有办法使用 wrk 将参数传递给 GET 请求?

    我需要对以参数作为输入的 REST API 进行基准测试 我想知道是否有办法使用wrk 现在我没有看到这样的选项 user Ubuntu K56CA wrk wrk Usage wrk
  • 如何设置默认使用的 php.ini,OSX Yosemite

    我使用 OSX Yosemite 设置了一个新环境 我正在使用内置的 PHP 我想更改 php ini 中的一些配置 例如 date timezone 但尽管重新启动了 apache 服务器 sudo apachectl restart 但
  • 逐字字符串文字 v 转义序列

    C 编译器或 NET 运行时处理逐字字符串文本与使用转义序列 即性能 的方式有什么区别 还是只是设计时风格的问题 例如 var pathA c somewhere var pathB c somewhere 我想它们的编译方式是相同的 这并
  • 什么是未定义的引用/未解析的外部符号错误以及如何在 Fortran 中修复它?

    我正在尝试构建 Fortran 程序 但收到有关未定义引用或未解析的外部符号的错误 我见过另一个问题 https stackoverflow com q 12573816关于这些错误 但答案大多是针对 C 的 使用 Fortran 编写时出
  • 由于“只读文件系统”,Google Kubernetes Engine (GKE) 集群“创建挂载源路径时出错”

    我有一个具有以下配置的容器 spec template spec restartPolicy OnFailure volumes name local src hostPath path src analysis src type Dire
  • 如何强制协调员行动以特定频率实现?

    我想知道是否有可能 如何强制协调员定期具体化或实例化工作流程 即使先前实例化的工作流程尚未完成 让我解释 我有一个简单的协调员 如下所示
  • 如何在C#中实现单例?

    如何在 C 中实现单例模式 我想将常量和一些基本函数放入其中 因为我在项目中到处都使用它们 我想让它们 全局 而不需要手动绑定它们我创建的每个对象 如果您只是存储一些全局值并且有一些不需要状态的方法 则不需要单例 只需将类及其属性 方法设为
  • 符号可见性、异常、运行时错误

    我尝试更好地理解符号可见性 海湾合作委员会维基百科 http gcc gnu org wiki Visibility http gcc gnu org wiki Visibility 有一个关于 C 异常问题 的部分 根据 GCC Wiki