幽窗燈一點,樂處超五欲。
environment:
x86_64
g++ 8.2.0
在研究過 exception handle 之後, 輪到 rtti 了, 我想「知道」rtti 是怎麼辦到的? c++ 裡頭實在有太多神秘的機制, 用來支援這些特性。
rtti 有 2 個東西:
dynamic_cast 運算子
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
1 107 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 帳號。