2019年11月20日 星期三

c++ runtime - rtti

幽窗燈一點,樂處超五欲。
environment:
x86_64
g++ 8.2.0

在研究過 exception handle 之後, 輪到 rtti 了, 我想「知道」rtti 是怎麼辦到的? c++ 裡頭實在有太多神秘的機制, 用來支援這些特性。

rtti 有 2 個東西:
  1. dynamic_cast 運算子
  2. typeid 運算子
我有興趣的是 typeid, 一樣是 c++ 編譯器做的手法, 而 c++ 的 typeid 並無法取得一個 type_info 的 class, 只能透過 typeid 來呼叫 member fuction name(), 無法這樣寫, 但是 ...

ex.cpp
type_info ti(??);
ti->name();

所有 c++ 書籍都會提到, 在程式中取得 type_info object 的唯一方式, 就是使用 typeid, 但如果硬是要寫, '??' 應該要傳入什麼呢? 我不知道, 不過也不重要, 不用去深究它。

事實上在我把所有的 type_info ctor 改成 public, 也無法正確編譯 ex.cpp, 原因就不管了。反正這個 type_info object 是由 c++ 編譯器幫我們造出來的, 她在哪裡呢?

為了分析 type_info, 我透過 gdb 反組譯, 編譯之後的 .s, objdump 的反組譯來查看 type_info 相關程式碼, 終於有了一點方向。

以 rtti.cpp 這個範例來說明, list 1. L13465, typeinfo for int, 就是用來表達 int 的 type_info object, c++ 編譯器為我們產生了這個物件, 不是執行時期才建構出來, 而是在編譯時期就產生在執行檔裡頭了。typeinfo for int 的具體型別在 g++ 的實作是: __fundamental_type_info (list 3. L96)。

可以看到 __fundamental_type_info 繼承了 std::type_info。

__cxxabiv1::__fundamental_type_info fti{"123i"};

如果這麼寫的話 (當然得先 include cxxabi.h), 就可以得到一個 __fundamental_type_info (所以我才會說不用管無法直接取得 type_info 的問題, 當然喜歡追根就底的朋友還是可以去挖 code 查找這個祕密, 並不難理解), 也就是 type_info 的衍生物, 呼叫其中的 name(), 就會印出 123i。

rtti.cpp
102 void main()
103 {
107     printf("ni's name: %s\n", typeid(86).name());
108 }

從 list 1 L293, 294 可以看到這就是 typeid(86).name(), 轉成 c 函式就是

name(type_info *this);

這個 this 就是一個 type_info, 她在 list 1. L13465, 位址是 0x10a818, 所以 L293 才會把這個位址放入 edi。

rtti.s L155 的 $_ZTIi, 就是這個 type_info object。

list 6 透過 gdb 反組譯來觀察 0x10a818, 可以用 p *(std::type_info*)0x10a818 來轉出 type_info,

如果是自定義的 class, 就會出動 __cxxabiv1::__class_type_info 來儲存「自定義 class」的 type。

__cxxabiv1::__class_type_info
1 (gdb) p *(std::type_info *)0x109ec8
2 $2 = {_vptr.type_info = 0x109f68 <vtable for __cxxabiv1::__class_type_info+16>,
3   __name = 0x109ee0 <typeinfo name for TestGlobalCtorDtor> "18TestGlobalCtorDtor"}
4 (gdb) p *(__cxxabiv1::__class_type_info *)0x109ec8
5 $3 = {<std::type_info> = {_vptr.type_info = 0x109f68 <vtable for __cxxabiv1::__class_type_info+16>,
6     __name = 0x109ee0 <typeinfo name for TestGlobalCtorDtor> "18TestGlobalCtorDtor"}, <No data fields>}

18 很巧的就是 TestGlobalCtorDtor 的長度, 這是自己定義的一個 class name。

list 1 x86_64-elf-objdump -CD rtti
   288 00000000001003ad <main>:
   289   1003ad: 55                      push   %rbp
   290   1003ae: 48 89 e5                mov    %rsp,%rbp
   291   1003b1: 48 83 ec 10             sub    $0x10,%rsp
   292   1003b5: 48 89 7d f8             mov    %rdi,-0x8(%rbp)
   293   1003b9: bf 18 a8 10 00          mov    $0x10a818,%edi
   294   1003be: e8 69 00 00 00          callq  10042c <std::type_info::name() const>
   295   1003c3: 48 89 c6                mov    %rax,%rsi
   296   1003c6: bf 84 a4 10 00          mov    $0x10a484,%edi
   297   1003cb: b8 00 00 00 00          mov    $0x0,%eax
   298   1003d0: e8 f1 04 00 00          callq  1008c6 <io::printf(char const*, ...)>
   299   1003d5: 90                      nop
   300   1003d6: c9                      leaveq
   301   1003d7: c3                      retq

 13465 000000000010a818 <typeinfo for int>:
 13466   10a818: 28 ad 10 00 00 00       sub    %ch,0x10(%rbp)
 13467   10a81e: 00 00                   add    %al,(%rax)
 13468   10a820: f2 ad                   repnz lods %ds:(%rsi),%eax
 13469   10a822: 10 00                   adc    %al,(%rax)
 13470   10a824: 00 00                   add    %al,(%rax)

base class type_info 定義在 typeinfo, cxxabi.h 定義了繼承 type_info 的衍生類別, 還蠻多的, 這邊只列出一些。

list 2 libsupc++/typeinfo
  1 // RTTI support for -*- C++ -*-
 25 /** @file typeinfo
 26  *  This is a Standard C++ Library header.
 27  */
 28 
 29 #ifndef _TYPEINFO
 30 #define _TYPEINFO
 31 
 32 #pragma GCC system_header
 33 
 34 #include <bits/exception.h>
 35 #include <bits/hash_bytes.h>
 36 
 37 #pragma GCC visibility push(default)
 38 
 39 extern "C++" {
 40 
 41 namespace __cxxabiv1
 42 {
 43   class __class_type_info;
 44 } // namespace __cxxabiv1
 45 
 70 
 71 namespace std
 72 {
 73   /**
 74    *  @brief  Part of RTTI.
 75    *
 76    *  The @c type_info class describes type information generated by
 77    *  an implementation.
 78   */
 79   class type_info
 80   {
 81   public:
 82     /** Destructor first. Being the first non-inline virtual function, this
 83      *  controls in which translation unit the vtable is emitted. The
 84      *  compiler makes use of that information to know where to emit
 85      *  the runtime-mandated type_info structures in the new-abi.  */
 86     virtual ~type_info();
 87 
 88     /** Returns an @e implementation-defined byte string; this is not
 89      *  portable between compilers!  */
 90     const char* name() const noexcept
 91     { return __name[0] == '*' ? __name + 1 : __name; }
 92 
 93     // On some targets we can rely on type_info's NTBS being unique,
 94     // and therefore address comparisons are sufficient.
 95     bool before(const type_info& __arg) const noexcept
 96     { return __name < __arg.__name; }
 97 
 98     bool operator==(const type_info& __arg) const noexcept
 99     { return __name == __arg.__name; }
100 
101     bool operator!=(const type_info& __arg) const noexcept
102     { return !operator==(__arg); }
103 
104     size_t hash_code() const noexcept
105     {
106       return reinterpret_cast<size_t>(__name);
107     }
108 
109     // Return true if this is a pointer type of some kind
110     virtual bool __is_pointer_p() const;
111 
112     // Return true if this is a function type
113     virtual bool __is_function_p() const;
114 
115     // Try and catch a thrown type. Store an adjusted pointer to the
116     // caught type in THR_OBJ. If THR_TYPE is not a pointer type, then
117     // THR_OBJ points to the thrown object. If THR_TYPE is a pointer
118     // type, then THR_OBJ is the pointer itself. OUTER indicates the
119     // number of outer pointers, and whether they were const
120     // qualified.
121     virtual bool __do_catch(const type_info *__thr_type, void **__thr_obj,
122        unsigned __outer) const;
123 
124     // Internally used during catch matching
125     virtual bool __do_upcast(const __cxxabiv1::__class_type_info *__target,
126         void **__obj_ptr) const;
127 
128   protected:
129     const char *__name;
130 
131     explicit type_info(const char *__n): __name(__n) { }
132 
133   private:
134     /// Assigning type_info is not supported.
135     type_info& operator=(const type_info&);
136     type_info(const type_info&);
137   };
138 
139   /**
140    *  @brief  Thrown during incorrect typecasting.
141    *  @ingroup exceptions
142    *
143    *  If you attempt an invalid @c dynamic_cast expression, an instance of
144    *  this class (or something derived from this class) is thrown.  */
145   class bad_cast : public exception
146   {
147   public:
148     bad_cast() noexcept { }
149 
150     // This declaration is not useless:
151     // http://gcc.gnu.org/onlinedocs/gcc-3.0.2/gcc_6.html#SEC118
152     virtual ~bad_cast() noexcept;
153 
154     // See comment in eh_exception.cc.
155     virtual const char* what() const noexcept;
156   };
157 
158   /**
159    *  @brief Thrown when a NULL pointer in a @c typeid expression is used.
160    *  @ingroup exceptions
161    */
162   class bad_typeid : public exception
163   {
164   public:
165     bad_typeid () noexcept { }
166 
167     // This declaration is not useless:
168     // http://gcc.gnu.org/onlinedocs/gcc-3.0.2/gcc_6.html#SEC118
169     virtual ~bad_typeid() noexcept;
170 
171     // See comment in eh_exception.cc.
172     virtual const char* what() const noexcept;
173   };
174 } // namespace std
175 
176 } // extern "C++"
177 
178 #pragma GCC visibility pop
179 
180 #endif

list 3 libsupc++/cxxabi.h
 91 #include <typeinfo>
 92 
 93 namespace __cxxabiv1
 94 {
 95   // Type information for int, float etc.
 96   class __fundamental_type_info : public std::type_info
 97   {
 98   public:
 99     explicit
100     __fundamental_type_info(const char* __n) : std::type_info(__n) { }
101 
102     virtual
103     ~__fundamental_type_info();
104   };
105 
106   // Type information for array objects.
107   class __array_type_info : public std::type_info
108   {
109   public:
110     explicit
111     __array_type_info(const char* __n) : std::type_info(__n) { }
112 
113     virtual
114     ~__array_type_info();
115   };



rtti.s
 139     .string "ni's name: %s\n"
 140     .text
 141     .globl  main
 142     .type   main, @function
 143 main:
 144 .LFB60:
 145     .loc 2 104 1
 146     .cfi_startproc
 147     pushq   %rbp
 148     .cfi_def_cfa_offset 16
 149     .cfi_offset 6, -16
 150     movq    %rsp, %rbp
 151     .cfi_def_cfa_register 6
 152     subq    $16, %rsp
 153     movq    %rdi, -8(%rbp)
 154     .loc 2 107 15
 155     movl    $_ZTIi, %edi
 156     call    _ZNKSt9type_info4nameEv
 157     movq    %rax, %rsi
 158     movl    $.LC2, %edi
 159     movl    $0, %eax
 160     call    _ZN2io6printfEPKcz

list 6 rtti.gdb
1107     printf("ni's name: %s\n", typeid(86).name());
 2 (gdb) p *(std::type_info*)0x10a818
 3 $2 = {_vptr.type_info = 0x10ad28 <vtable for __cxxabiv1::__fundamental_type_info+16>, 
 4   __name = 0x10adf2 <typeinfo name for int> "i"}
 5 (gdb) x/x32b 0x10a818
 6 Invalid number "32b".
 7 (gdb) x/32xb 0x10a818
 8 0x10a818 <_ZTIi>: 0x28 0xad 0x10 0x00 0x00 0x00 0x00 0x00
 9 0x10a820 <_ZTIi+8>: 0xf2 0xad 0x10 0x00 0x00 0x00 0x00 0x00
10 0x10a828 <_ZTIPi>: 0xc0 0xae 0x10 0x00 0x00 0x00 0x00 0x00
11 0x10a830 <_ZTIPi+8>: 0xef 0xad 0x10 0x00 0x00 0x00 0x00 0x00

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。