• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
fyyy94
博客园    首页    新随笔    联系   管理    订阅  订阅
dlopen 加载使用了std::thread 的so 导致crash的问题分析
  1. c++11的 的create implement是在thread.cc 中实现的,这意味着创建代码在libstdc++.so 中,创建代码需要使用与平台有关的api
  2. gcc(g++ is a part of gcc)的预期:
    • 没有调用的thread的代码,不会产生对pthread的依赖,更重要的,不同配置的gcc的线程模型是不同的,依赖库也不同(即不一定是pthread),如果不去除依赖,这会导致链接的深刻耦合
    • 调用了thread的代码,必须要链接到pthread
  3. gcc 内部,通过弱符号机制来达到这个目的,以foo函数举例:
    • 通过一个包装的符号,如gcc_foo, 弱引用到foo
    • 声明foo 是一个弱符号,可以在链接时被强符号替代,弱符号默认是未定义的(可能也不是空指针)
    • g++的thread.cc 通过gcc_foo 包装后的函数,来创建线程,而不是直接使用平台api

通过弱符号,即便是业务代码没有链接pthread,thread.cc 相关的代码也不会产生链接报错。那么,g++ 又是如何完成2.2. 的呢? 答案是g++ 通过一个无用参数强制产生对pthread 的依赖,这部分实现是在 文件中,会被展开到业务代码里,让业务代码产生对pthread的依赖,注意不是libstdc++。

那么为什么dlopen 又会导致使用了std::thread的库crash呢? 这是因为符号加载顺序的问题,libdl 不是ld,他是glibc的一部分,他通过名字空间等机制支持符号的隔离等。名字空间一般有:local/global 以及其他(可能和加载顺序有关),dlopen 加载一个库时,其查找符号的顺序是:

  1. LOCAL
  2. GLOBAl
  3. 其他

前面说过,因为gcc内部搞了一个弱符号,他是存在应用程序的符号表中的,应用程序的符号表对于dlopen的so而言是全局的。可以明确的是LOCAL肯定没有pthread相关符号,GLOBAL中有gcc定义的弱符号,dl认为找到了,但实际是错误的,从而导致了不能debug的crash

其实最后的结论并不正确,因为这是一个推论,并非从代码或者实践得出的结论。其实可以再思考一下,就能发现其中的问题:

  1. libstdc++.so 在 业务代码中已经被加载一次了,符号也和业务代码符号合并了,dlopen 并不会再次重新加载libstdc++.so
  2. 真正需要可用符号的时stdc++中创建线程的实现代码,而不是我们的so代码

所以这里的问题是:需要刷新 libstdc++.so 中的弱符号,而因为应用先加载了libstdc++.so了,dlopen 加载so时不会刷新其中的符号,同时so使用了stdc++中相关实现,进而导致了使用到了弱符号。

我们验证这个猜想很简单,我们编写一个C的应用来加载这个so,这样,libstdc++的符号就能在 dlopen时刷新了, 那么其就不会crash,详见测试代码main.c

在After阶段后,so中的线程正常被创建,一切OK

不过两者从表面来看推测都正确,但是结论完全不同。

posted on 2024-08-23 16:31  feiyangyy94  阅读(235)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3