找回密码
 立即注册
首页 业界区 业界 -fno-rtti导致的惨案(object has invalid vptr) ...

-fno-rtti导致的惨案(object has invalid vptr)

宛蛲 5 小时前
PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明


  • Ubuntu 24.04.2 LTS \n \l
  • gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
前言

  对于C++程序开发来说,MemoryLeek/UndefinedBehavior 等问题,简直就是大型开发过程必定会出现的问题。那么我们怎么尝试减少这些问题,在我的日常开发中,大概有以下方案:

  • 对于开发过程中来说,在c++11以后,标准库引入了智能指针,然后增强开发者内存所有权意识等,可以有效减少MemoryLeek问题。
  • 对于测试发布流程来说,我们常常引入了valgrind/sanitizer减少MemoryLeek/UndefinedBehavior 等问题。
  尤其是对于新的编译器来说,sanitizer还是比较好用的。最近遇到了一个不是那么常见的sanitizer ub错误,我觉得非常有趣,可以分享一下。




-fno-rtti导致的惨案

  下面的图片是出现的问题现场截图:
            
1.png
          上图一看就是ub错误,具体是什么原因,还要分析一番。


问题最小用例复现

  下面是最小的复现用例:
  1. //l.cpp
  2. #include <memory>
  3. #include "A.hpp"
  4.         void B::p(){printf("p(): class B\n");}
  5.         void B::p1(){printf("p1(): class B\n");}
  6.         void A::p(){printf("p(): class A\n");}
  7.         void A::p1(){printf("p1(): class A\n");}
  8. std::shared_ptr my_A(new A());
  9. void i(){
  10.         my_A->p1();
  11. }
复制代码
  1. //l.hpp
  2. void i();
复制代码
  1. //l.hpp
  2. void i();
复制代码
  1. //A.hpp
  2. #include <cstdio>
  3. class B{
  4.         public:
  5.         virtual void p();
  6.         virtual void p1();
  7. };
  8. class A:public B{
  9.         public:
  10.                 void p();
  11.                 void p1();
  12. };
复制代码
  1. //t.cpp
  2. #include <memory>
  3. #include <cstdio>
  4. #include "A.hpp"
  5. #include "l.hpp"
  6. std::shared_ptr my_AA(new A());
  7. int main(int argc, const char* argv[])
  8. {
  9.         my_AA->p();
  10.         i();
  11.         return 0;
  12. }
复制代码
  1. g++ -c l.cpp -O3 -fPIC -fsanitize=undefined
  2. g++ -shared -o libA.so l.o -O3
  3. g++ t.cpp -o t -O3 -I. -L . -l A -fno-rtti -fsanitize=undefined -Wl,-rpath=.
  4. # ./t 运行就会得到如上的错误
复制代码
注意上述例子用到了多态类,这和我原始工程中类似,但是实际情况中,一个普通的类也会有同样的问题,具体原因,见如下分析。


问题分析

  首先我们看看出现的_Sp_counted_base/_Sp_counted_ptr是什么,这个通过报错,看起来像shared_ptr引用计数相关,我们看看其实际的代码大致关系如下:
  1.     template<typename _Tp>
  2.     class shared_ptr : public __shared_ptr<_Tp>
  3.     {
  4.         //...
  5.     }
  6.     class __shared_ptr
  7.     : public __shared_ptr_access<_Tp, _Lp>
  8.     {
  9.         //...
  10.         __shared_count<_Lp>  _M_refcount;    // Reference counter.
  11.     }
  12.     template<_Lock_policy _Lp>
  13.     class __shared_count
  14.     {
  15.         template<typename _Ptr>
  16.         explicit
  17.         __shared_count(_Ptr __p) : _M_pi(0)
  18.         {
  19.         __try
  20.             {
  21.                 _M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);
  22.             }
  23.         __catch(...)
  24.             {
  25.                 delete __p;
  26.                 __throw_exception_again;
  27.             }
  28.         }
  29.         //...
  30.         _Sp_counted_base<_Lp>*  _M_pi;
  31.     }
  32.     template<typename _Ptr, _Lock_policy _Lp>
  33.     class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>
  34.     {
  35.         //...
  36.     }
  37.     template<_Lock_policy _Lp = __default_lock_policy>
  38.     class _Sp_counted_base
  39.     : public _Mutex_base<_Lp>
  40.     {
  41.         //...
  42.     }
复制代码
  我们使用如下命令,看一下t和libA.so的_Sp_counted_ptr符号,我们发现对于相同的符号来说,其大小不一样。
  1. # readelf -sW libA.so |grep counted_ptr
  2.     24: 0000000000005160    54 OBJECT  WEAK   DEFAULT   16 _ZTSSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE
  3.     25: 0000000000003970   338 FUNC    WEAK   DEFAULT   14 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EED1Ev
  4.     27: 0000000000003970   338 FUNC    WEAK   DEFAULT   14 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EED2Ev
  5.     30: 0000000000003790     7 FUNC    WEAK   DEFAULT   14 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE14_M_get_deleterERKSt9type_info
  6.     33: 0000000000006d80    56 OBJECT  WEAK   DEFAULT   22 _ZTVSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE
  7.     40: 0000000000006cf0    24 OBJECT  WEAK   DEFAULT   22 _ZTISt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE
  8.     44: 0000000000003c30   489 FUNC    WEAK   DEFAULT   14 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE10_M_destroyEv
  9.     48: 0000000000003880   225 FUNC    WEAK   DEFAULT   14 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE10_M_disposeEv
  10.     53: 0000000000003ad0   350 FUNC    WEAK   DEFAULT   14 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EED0Ev
复制代码
  1. # readelf -sW t |grep counted_ptr
  2.     20: 0000000000002700   172 FUNC    WEAK   DEFAULT   16 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EED2Ev
  3.     22: 0000000000002700   172 FUNC    WEAK   DEFAULT   16 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EED1Ev
  4.     23: 0000000000002870   233 FUNC    WEAK   DEFAULT   16 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE10_M_destroyEv
  5.     24: 00000000000027b0   188 FUNC    WEAK   DEFAULT   16 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EED0Ev
  6.     26: 00000000000026a0    92 FUNC    WEAK   DEFAULT   16 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE10_M_disposeEv
  7.     30: 0000000000002610     7 FUNC    WEAK   DEFAULT   16 _ZNSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE14_M_get_deleterERKSt9type_info
  8.     32: 0000000000005ca0    56 OBJECT  WEAK   DEFAULT   24 _ZTVSt15_Sp_counted_ptrIP1ALN9__gnu_cxx12_Lock_policyE2EE
复制代码
  这个时候我们想一下-fno-rtti的作用,其作用是禁用typeinfo+dynamic_cast,某些情况下可以提升执行性能。然后根据错误中的提示(object has invalid vptr),必定和其虚表有关系,那就意味着_Sp_counted_base/_Sp_counted_ptr的虚表存在异常。
  用ida查看t和libA.so中std::_Sp_counted_base的虚表内容,他们如下图:
            
2.png
                    
3.png
          注意,一般的虚表结构如下:
  1. +-------------------+
  2. |  Offset-to-Top    | (通常为负数,用于多重继承)
  3. +-------------------+
  4. |  type_info 指针    | (用于 RTTI)
  5. +-------------------+
  6. |  虚函数1 的地址    |
  7. +-------------------+
  8. |  虚函数2 的地址    |
  9. +-------------------+
  10. |      ...          |
复制代码
  从上面的图和虚表结构可知,就是两个同名的vtable内容不一样,导致了此问题。解决方法也很简单,大家使用同样的编译参数即可。




后记

  c++的一些错误是非常有趣的,值得细看。
参考文献





                    打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)               
4.jpeg
    PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册