File: Personality.cpp

package info (click to toggle)
storm-lang 0.7.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 52,004 kB
  • sloc: ansic: 261,462; cpp: 140,405; sh: 14,891; perl: 9,846; python: 2,525; lisp: 2,504; asm: 860; makefile: 678; pascal: 70; java: 52; xml: 37; awk: 12
file content (296 lines) | stat: -rw-r--r-- 11,042 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#include "stdafx.h"
#include "Personality.h"
#include "FnState.h"
#include "Thunk.h"
#include "Code/Binary.h"
#include "Code/Dwarf/Registers.h"

#ifdef POSIX
#include <cxxabi.h>

#ifndef GCC
#error "This is currently untested on compilers other than GCC"
#endif

#ifdef __USING_SJLJ_EXCEPTIONS__
#error "Storm does not support SJLJ exceptions. Please use DWARF exceptions instead!"
#endif

#ifdef __ARM_EABI_UNWINDER__
#error "ARM eabi unwinder is currently not supported!"
#endif

namespace code {
	namespace eh {

		// Our representation of a stack frame.
		class StackFrame : public code::StackFrame {
		public:
			StackFrame(Nat block, Nat activation, void *rbp) : code::StackFrame(block, activation), rbp((byte *)rbp) {}

			virtual void *toPtr(int offset) {
				return rbp + offset;
			}

		private:
			byte *rbp;
		};

		// Storage from phase 1 to phase 2. Binary compatible with blocks of __cxa_exception in GCC.
		// Names from the GCC implementation for simplicity.

		/**
		 * GCC private exception struct. We need to be able to access various members in here during
		 * the exception handling. More specifically:
		 *
		 * - 'exceptionType' in 'isStormException' to be able to check the type of exception that was thrown.
		 * - 'adjustedPtr' in the personality function to store the resulting exception object in
		 *   phase 1. Otherwise __cxa_begin_catch() does not return the proper value in phase 2 of the
		 *   exception handling.
		 * - 'handlerSwitchValue' in the personality function to store the current block, so we don't
		 *   have to re-compute it in phase 2. This is not important for the implementation to function,
		 *   but GCC does something similar, so why not do the same and gain the small performance boost?
		 * - 'catchTemp' in the personality function to store the location we shall resume from so we
		 *   don't have to re-compute it in phase 2. Not important for the implementation to function,
		 *   but GCC does something similar. It is potentially dangerous to store a GC pointer in this
		 *   structure, as it is not scanned (it is allocated separately using malloc). However, during
		 *   the time the pointer is stored in here, a pointer into the same code block is pinned by
		 *   being on the execution stack anyway, so it is fine in this case.
		 * - 'actionRecord' and 'languageSpecificData' are seemingly free to use. We don't use them.
		 */
		struct ExStore {
			std::type_info *exceptionType;
			void *exceptionDestructor;

			std::unexpected_handler unexpectedHandler;
			std::terminate_handler terminateHandler;

			void *nextException;
			int handlerCount;
#ifdef __ARM_EABI_UNWINDER__
			// For some ARM architectures:
			ExStore* nextPropagatingException;
			int propagationCount;
#else
			int handlerSwitchValue;
			const byte *actionRecord;
			const byte *languageSpecificData;
			void *catchTemp;
			void *adjustedPtr;
#endif

			struct _Unwind_Exception exception;
		};

		static RootObject *isStormException(_Unwind_Exception_Class type, struct _Unwind_Exception *data) {
			// Find the type info, and a pointer to the exception we're catching.
			const std::type_info *info = null;
			void *object = null;

			// For GCC:
			// This is 'GNUCC++\0' in hex.
			if (type == 0x474e5543432b2b00LL) {
				ExStore *store = BASE_PTR(ExStore, data, exception);
				info = store->exceptionType;
				object = store + 1;
			}

			// TODO: Handle LLVM exceptions as well. They have another type signature, but I believe
			// the layout of the 'data' is the same.

			// Unable to find type-info. The exception probably originated from somewhere we don't
			// know about.
			if (!info)
				return null;

			// This should actually be platform independent as long as the C++ Itanium ABI is used!
			// However, the Itanium ABI does not specify how to check if one typeid is a subclass
			// of another. That is GCC specific, but Clang most likely follows the same convention.

#if 0
			// This is likely slower and less appropriate than calling "do_catch" directly. It does,
			// however, seem to work, and shows what we're trying to accomplish.

			// We only support pointers.
			const abi::__pointer_type_info *ptr = dynamic_cast<const abi::__pointer_type_info *>(info);
			if (!ptr)
				return null;

			// Must point to a class.
			const abi::__class_type_info *srcClass = dynamic_cast<const abi::__class_type_info *>(ptr->__pointee);
			if (!srcClass)
				return null;

			object = *(void **)object;

#if 0
			// This is a bit clumsy and unreliable as we don't have the definition of the type __upcast_result.
			const abi::__class_type_info *target = (const abi::__class_type_info *)&typeid(storm::RootObject);

			// We don't have the type declaration for __class_type_info::__upcast_result.
			struct upcast_result {
				size_t data[10];
			} result;
			if (!srcClass->__do_upcast(target, object, (abi::__class_type_info::__upcast_result &)result))
				return null;
#else
			// This seems more appropriate. It calls __do_upcast eventually, but has access to the
			// upcast_result type, so it is likely more robust. Tell the implementation that the
			// pointer is const.
			if (!typeid(RootObject).__do_catch(srcClass, &object, abi::__pbase_type_info::__const_mask))
				return null;

			return (RootObject *)object;
#endif

#else
			// This is likely the best option. We avoid doing many dynamic_casts ourselves, and rely
			// on vtables instead. This is not ABI-compliant, as __do_catch is not mandated by the ABI.

			// Try to catch a storm::RootObject *.
			if (!typeid(RootObject const * const).__do_catch(info, &object, abi::__pbase_type_info::__const_mask))
				return null;

			return *(RootObject **)object;
#endif

		}

		// The personality function called by the C++ runtime.
		_Unwind_Reason_Code stormPersonality(int version, _Unwind_Action actions, _Unwind_Exception_Class type,
											struct _Unwind_Exception *data, struct _Unwind_Context *context) {

			// We assume 'version == 1'.
			// 'type' is a 8 byte identifier. Equal to 0x47 4e 55 43 43 2b 2b 00 or 'GNUCC++\0' for exceptions from G++.
			// 'data' is compiler specific information about the exception. See 'unwind.h' for details.
			// 'context' is the unwinder state.

			// Note: using _Unwind_GetCFA seems to return the CFA of the previous frame: it is not
			// rewinded to the start of the function to be examined, rather only to the point where
			// the next function was called.
			size_t fn = _Unwind_GetRegionStart(context);
			size_t pc = _Unwind_GetIP(context);
			size_t fp = _Unwind_GetGR(context, DWARF_EH_FRAME_POINTER);

			Binary *binary = codeBinary((const void *)fn);

			if (actions & _UA_SEARCH_PHASE) {
				// Phase 1: Search for handlers.

				// See if this function has any handlers at all.
				if (!binary->hasCatch())
					return _URC_CONTINUE_UNWIND;

				// See if this is an exception Storm can catch (somewhat expensive).
				RootObject *object = isStormException(type, data);
				if (!object)
					return _URC_CONTINUE_UNWIND;

				// Find if it is desirable to actually catch it.
				Nat encoded = findFunctionState((const void *)fn, pc - fn);
				Nat block, active;
				decodeFnState(encoded, block, active);

				if (block == Block().key())
					return _URC_CONTINUE_UNWIND;

				Binary::Resume resume;
				if (!binary->hasCatch(block, object, resume))
					return _URC_CONTINUE_UNWIND;

				// Store things to phase 2, right above the _Unwind_Exception, just like GCC does:
				ExStore *store = BASE_PTR(ExStore, data, exception);

				// Adjusted pointer (otherwise __cxa_begin_catch won't work):
				store->adjustedPtr = object;

				// The following two are not too important, but we keep them at reasonable places as
				// to not confuse the GCC implementation. We recover them later, but we could compute
				// them again.
				store->catchTemp = resume.ip;
				store->languageSpecificData = (byte *)fp + resume.stackOffset;
				store->handlerSwitchValue = encoded;

				// Tell the system we have a handler! It will call us with _UA_HANDLER_FRAME later.
				return _URC_HANDLER_FOUND;
			} else if ((actions & _UA_CLEANUP_PHASE) && (actions & _UA_HANDLER_FRAME)) {
				// Phase 2: Cleanup, but resume here!

				// Read data from the exception object, like GCC does. We saved this data earlier.
				Nat block, active;
				ExStore *store = BASE_PTR(ExStore, data, exception);
				decodeFnState(Nat(store->handlerSwitchValue), block, active);
				pc = (size_t)store->catchTemp;

				// Cleanup our frame.
				StackFrame frame(block, active, (void *)fp);
				block = binary->cleanup(frame, block);

				// Note: It seems we can only set "RAX" and "RDX" here (that's what GCC uses,
				// so those are likely the only ones that will work reliably).
				// Set up the context as desired. We need to restore the stack pointer to what
				// we would expect. For this reason, we first call a 'thunk' that does this
				// for us. We also let the thunk call '__cxa_begin_catch' and '__cxa_end_catch'
				// for us (we could call them here, but doing it later matches what C++ does
				// and saves us a register). We set RDX to the PC we wish to jump to, and
				// RAX contains the new SP we should install.
				_Unwind_SetGR(context, DWARF_EH_RETURN_0, (_Unwind_Word)data);
				_Unwind_SetGR(context, DWARF_EH_RETURN_1, (_Unwind_Word)pc);
				_Unwind_SetIP(context, (_Unwind_Word)&posixResumeExceptionThunk);

				return _URC_INSTALL_CONTEXT;
			} else if (actions & _UA_CLEANUP_PHASE) {
				// Phase 2: Cleanup!

				// Clean up what we can!
				Nat block, active;
				Nat encoded = findFunctionState((const void *)fn, pc - fn);
				decodeFnState(encoded, block, active);
				if (block != Block().key()) {
					StackFrame frame(block, active, (void *)fp);
					binary->cleanup(frame);
				}
				return _URC_CONTINUE_UNWIND;
			} else {
				// Just pretend we did something useful...
				printf("WARNING: Personality function called with an unknown action!\n");
				return _URC_CONTINUE_UNWIND;
			}
		}

		extern "C" void *posixResumeException(_Unwind_Exception *data, const void **spOut) {
			// Get the exception storage so that we can extract the new stack pointer.
			ExStore *store = BASE_PTR(ExStore, data, exception);
			if (spOut)
				*spOut = store->languageSpecificData;

			// Get the exception. Since it is a pointer, we can deallocate directly.
			// Note that we store the dereferenced pointer inside the EH table, so we don't need
			// to dereference it again here!
			void *exception = __cxa_begin_catch(data);

			// We don't need the data to be alive anymore. We can deallocate it now! This
			// technically means that we are lying to the runtime slightly, (i.e. rethrows do not
			// look like rethrows), but we don't use anything that matters for this anyway.
			__cxa_end_catch();

			return exception;
		}

	}
}

#else

namespace code {
	namespace eh {

		_Unwind_Reason_Code stormPersonality(int, _Unwind_Action, _Unwind_Exception_Class,
											_Unwind_Exception *, _Unwind_Context *) {
			return 0;
		}

	}
}

#endif