source: doc/working/exception/impl/exception.c @ f9ad69d

Last change on this file since f9ad69d was 3e06da2, checked in by Thierry Delisle <tdelisle@…>, 6 years ago

Apparent fix for debug resolution problem with exceptions

  • Property mode set to 100644
File size: 14.8 KB
Line 
1#include "exception.h"
2
3// Implementation of the secret header.
4
5#include <stdlib.h>
6#include <stdio.h>
7#include <unwind.h>
8
9#include "lsda.h"
10
11struct shared_stack_t shared_stack;
12
13
14// This macro should be the only thing that needs to change across machines.
15// Used in the personality function, way down in termination.
16// struct _Unwind_Context * -> _Unwind_Reason_Code(*)()
17#define MATCHER_FROM_CONTEXT(ptr_to_context) \
18        (*(_Unwind_Reason_Code(**)())(_Unwind_GetCFA(ptr_to_context) + 8))
19
20
21// RESUMPTION ================================================================
22
23void __throw_resume(exception except) {
24
25        // DEBUG
26        printf("Throwing resumption exception %d\n", except);
27
28        struct __try_resume_node * original_head = shared_stack.current_resume;
29        struct __try_resume_node * current =
30                (original_head) ? original_head->next : shared_stack.top_resume;
31
32        for ( ; current ; current = current->next) {
33                shared_stack.current_resume = current;
34                if (current->try_to_handle(except)) {
35                        shared_stack.current_resume = original_head;
36                        return;
37                }
38        }
39
40        printf("Unhandled exception %d\n", except);
41        shared_stack.current_resume = original_head;
42
43        // Fall back to termination:
44        __throw_terminate(except);
45        // TODO: Default handler for resumption.
46}
47
48/* QUESTION: Could interupts interact with exception handling?
49Ex. could an context switch stop execution, and we get an exception when we
50come back? Is so resumption has to go:
51+ create node (init next and handler)
52+ prepare cleanup (add a cleanup hook with the ..._cleaup function)
53  also the cleanup has to be the next node, not just a pop from the list.
54+ push node on the list (change the existing node)
55Which if an exception can come from anywhere, might just be required to ensure
56the list is valid.
57
58void __try_resume_setup(struct __try_resume_node * node,
59                        bool (*handler)(exception except) {
60        node->next = shared_stack.top_resume;
61        node->try_to_handle = handler;
62        shared_stack.top_resume = node;
63}*/
64
65// We have a single cleanup function
66void __try_resume_cleanup(struct __try_resume_node * node) {
67        shared_stack.top_resume = node->next;
68}
69
70
71// TERMINATION ===============================================================
72
73// Requires -fexceptions to work.
74
75// Global which defines the current exception
76// Currently an int just to make matching easier
77//int this_exception; (became shared_stack.current_exception)
78
79// We need a piece of storage to raise the exception
80struct _Unwind_Exception this_exception_storage;
81
82// Function needed by force unwind
83// It basically says to unwind the whole stack and then exit when we reach the end of the stack
84static _Unwind_Reason_Code _Stop_Fn(
85                int version,
86                _Unwind_Action actions,
87                _Unwind_Exception_Class exceptionClass,
88                struct _Unwind_Exception * unwind_exception,
89                struct _Unwind_Context * context,
90                void * some_param) {
91        if( actions & _UA_END_OF_STACK  ) exit(1);
92        if( actions & _UA_CLEANUP_PHASE ) return _URC_NO_REASON;
93
94        return _URC_FATAL_PHASE2_ERROR;
95}
96
97void __throw_terminate( int val ) {
98        // Store the current exception
99        shared_stack.current_exception = val;
100
101        // DEBUG
102        printf("Throwing termination exception %d\n", val);
103
104        // Call stdlibc to raise the exception
105        _Unwind_Reason_Code ret = _Unwind_RaiseException( &this_exception_storage );
106
107        // If we reach here it means something happened
108        // For resumption to work we need to find a way to return back to here
109        // Most of them will probably boil down to setting a global flag and making the phase 1 either stop or fail.
110        // Causing an error on purpose may help avoiding unnecessary work but it might have some weird side effects.
111        // If we just pretend no handler was found that would work but may be expensive for no reason since we will always
112        // search the whole stack
113
114        if( ret == _URC_END_OF_STACK ) {
115                // No proper handler was found
116                // This can be handled in several way
117                // C++ calls std::terminate
118                // Here we force unwind the stack, basically raising a cancellation
119                printf("Uncaught exception %p\n", &this_exception_storage);
120
121                ret = _Unwind_ForcedUnwind( &this_exception_storage, _Stop_Fn, (void*)0x22 );
122                printf("UNWIND ERROR %d after force unwind\n", ret);
123                abort();
124        }
125
126        // We did not simply reach the end of the stack without finding a handler,
127        // Something wen't wrong
128        printf("UNWIND ERROR %d after raise exception\n", ret);
129        abort();
130}
131
132// Nesting this the other way would probably be faster.
133void __rethrow_terminate(void) {
134        // DEBUG
135        printf("Rethrowing termination exception\n");
136
137        __throw_terminate(shared_stack.current_exception);
138}
139
140// This is our personality routine
141// For every stack frame anotated with ".cfi_personality 0x3,__gcfa_personality_v0"
142// This function will be called twice when unwinding
143// Once in the search phased and once in the cleanup phase
144_Unwind_Reason_Code __gcfa_personality_v0 (
145                int version, _Unwind_Action actions, unsigned long long exceptionClass,
146                struct _Unwind_Exception* unwind_exception,
147                struct _Unwind_Context* context)
148{
149
150        // DEBUG
151        //printf("CFA: 0x%lx\n", _Unwind_GetCFA(context));
152        printf("Personality function (%d, %x, %llu, %p, %p):", version, actions, exceptionClass, unwind_exception, context);
153
154        // If we've reached the end of the stack then there is nothing much we can do...
155        if( actions & _UA_END_OF_STACK ) return _URC_END_OF_STACK;
156
157        // DEBUG
158        if (actions & _UA_SEARCH_PHASE) {
159                printf(" lookup phase");
160        }
161        // DEBUG
162        else if (actions & _UA_CLEANUP_PHASE) {
163                printf(" cleanup phase");
164        }
165        // Just in case, probably can't actually happen
166        else {
167                printf(" error\n");
168                return _URC_FATAL_PHASE1_ERROR;
169        }
170
171        // Get a pointer to the language specific data from which we will read what we need
172        const unsigned char * lsd = (const unsigned char*) _Unwind_GetLanguageSpecificData( context );
173
174        if( !lsd ) {    //Nothing to do, keep unwinding
175                printf(" no LSD");
176                goto UNWIND;
177        }
178
179        // Get the instuction pointer and a reading pointer into the exception table
180        lsda_header_info lsd_info;
181        const unsigned char * cur_ptr = parse_lsda_header( context, lsd, &lsd_info);
182        _Unwind_Ptr instruction_ptr = _Unwind_GetIP( context );
183
184        // Linearly search the table for stuff to do
185        while( cur_ptr < lsd_info.action_table ) {
186                _Unwind_Ptr callsite_start;
187                _Unwind_Ptr callsite_len;
188                _Unwind_Ptr callsite_landing_pad;
189                _uleb128_t  callsite_action;
190
191                // Decode the common stuff we have in here
192                cur_ptr = read_encoded_value (0, lsd_info.call_site_encoding, cur_ptr, &callsite_start);
193                cur_ptr = read_encoded_value (0, lsd_info.call_site_encoding, cur_ptr, &callsite_len);
194                cur_ptr = read_encoded_value (0, lsd_info.call_site_encoding, cur_ptr, &callsite_landing_pad);
195                cur_ptr = read_uleb128 (cur_ptr, &callsite_action);
196
197                // Have we reach the correct frame info yet?
198                if( lsd_info.Start + callsite_start + callsite_len < instruction_ptr ) {
199                        //DEBUG BEGIN
200                        void * ls = (void*)lsd_info.Start;
201                        void * cs = (void*)callsite_start;
202                        void * cl = (void*)callsite_len;
203                        void * bp = (void*)lsd_info.Start + callsite_start;
204                        void * ep = (void*)lsd_info.Start + callsite_start + callsite_len;
205                        void * ip = (void*)instruction_ptr;
206                        printf("\nfound %p - %p (%p, %p, %p), looking for %p\n", bp, ep, ls, cs, cl, ip);
207                        //DEBUG END
208                        continue;
209                }
210
211                // Have we gone too far
212                if( lsd_info.Start + callsite_start > instruction_ptr ) {
213                        printf(" gone too far");
214                        break;
215                }
216
217                // Something to do?
218                if( callsite_landing_pad ) {
219                        // Which phase are we in
220                        if (actions & _UA_SEARCH_PHASE) {
221                                // Search phase, this means we probably found a potential handler and must check if it is a match
222
223                                // If we have arbitrarily decided that 0 means nothing to do and 1 means there is a potential handler
224                                // This doesn't seem to conflict the gcc default behavior
225                                if (callsite_action != 0) {
226                                        // Now we want to run some code to see if the handler matches
227                                        // This is the tricky part where we want to the power to run arbitrary code
228                                        // However, generating a new exception table entry and try routine every time
229                                        // is way more expansive than we might like
230                                        // The information we have is :
231                                        //  - The GR (Series of registers)
232                                        //    GR1=GP Global Pointer of frame ref by context
233                                        //  - The instruction pointer
234                                        //  - The instruction pointer info (???)
235                                        //  - The CFA (Canonical Frame Address)
236                                        //  - The BSP (Probably the base stack pointer)
237
238
239                                        // The current apprach uses one exception table entry per try block
240                                        _uleb128_t imatcher;
241                                        // Get the relative offset to the
242                                        cur_ptr = read_uleb128 (cur_ptr, &imatcher);
243
244                                        // Get a function pointer from the relative offset and call it
245                                        // _Unwind_Reason_Code (*matcher)() = (_Unwind_Reason_Code (*)())lsd_info.LPStart + imatcher;
246
247                                        _Unwind_Reason_Code (*matcher)() =
248                                                MATCHER_FROM_CONTEXT(context);
249                                        int index = matcher(shared_stack.current_exception);
250                                        _Unwind_Reason_Code ret = (0 == index)
251                                                ? _URC_CONTINUE_UNWIND : _URC_HANDLER_FOUND;
252                                        shared_stack.current_handler_index = index;
253
254                                        // Based on the return value, check if we matched the exception
255                                        if( ret == _URC_HANDLER_FOUND) printf(" handler found\n");
256                                        else printf(" no handler\n");
257                                        return ret;
258                                }
259
260                                // This is only a cleanup handler, ignore it
261                                printf(" no action");
262                        }
263                        else if (actions & _UA_CLEANUP_PHASE) {
264
265                                if( (callsite_action != 0) && !(actions & _UA_HANDLER_FRAME) ){
266                                        // If this is a potential exception handler
267                                        // but not the one that matched the exception in the seach phase,
268                                        // just ignore it
269                                        goto UNWIND;
270                                }
271
272                                // We need to run some clean-up or a handler
273                                // These statment do the right thing but I don't know any specifics at all
274                                _Unwind_SetGR( context, __builtin_eh_return_data_regno(0), (_Unwind_Ptr) unwind_exception );
275                                _Unwind_SetGR( context, __builtin_eh_return_data_regno(1), 0 );
276
277                                // I assume this sets the instruction pointer to the adress of the landing pad
278                                // It doesn't actually set it, it only state the value that needs to be set once we return _URC_INSTALL_CONTEXT
279                                _Unwind_SetIP( context, lsd_info.LPStart + callsite_landing_pad );
280
281                                // DEBUG
282                                printf(" action\n");
283
284                                // Return have some action to run
285                                return _URC_INSTALL_CONTEXT;
286                        }
287                }
288
289                // Nothing to do, move along
290                printf(" no landing pad");
291        }
292        // No handling found
293        printf(" table end reached\n");
294
295        // DEBUG
296        UNWIND:
297        printf(" unwind\n");
298
299        // Keep unwinding the stack
300        return _URC_CONTINUE_UNWIND;
301}
302
303// Try statements are hoisted out see comments for details
304// With this could probably be unique and simply linked from
305// libcfa but there is one problem left, see the exception table
306// for details
307__attribute__((noinline))
308void __try_terminate(void (*try_block)(),
309                void (*catch_block)(int index, exception except),
310                __attribute__((unused)) int (*match_block)(exception except)) {
311        //! volatile int xy = 0;
312        //! printf("%p %p %p %p\n", &try_block, &catch_block, &match_block, &xy);
313
314        // Setup statments
315        // These 2 statments won't actually result in any code,
316        // they only setup global tables.
317        // However, they clobber gcc cancellation support from gcc.
318        // We can replace the personality routine but replacing the exception
319        // table gcc generates is not really doable, it generates labels based
320        // on how the assembly works.
321        // Setup the personality routine
322        #if defined(__PIC__)
323        asm volatile (".cfi_personality 0x9b,CFA.ref.__gcfa_personality_v0");
324        // Setup the exception table
325        asm volatile (".cfi_lsda 0x1b, .LLSDACFA2");
326        #else
327        asm volatile (".cfi_personality 0x3,__gcfa_personality_v0");
328        // Setup the exception table
329        asm volatile (".cfi_lsda 0x3, .LLSDACFA2");
330        #endif
331
332        // Label which defines the start of the area for which the handler is setup
333        asm volatile (".TRYSTART:");
334
335        // The actual statements of the try blocks
336        try_block();
337
338        // asm statement to prevent deadcode removal
339        asm volatile goto ("" : : : : CATCH );
340
341        // Normal return
342        return;
343
344        // Exceptionnal path
345        CATCH : __attribute__(( unused ));
346        // Label which defines the end of the area for which the handler is setup
347        asm volatile (".TRYEND:");
348        // Label which defines the start of the exception landing pad
349        // basically what will be called when the exception is caught
350        // Note, if multiple handlers are given, the multiplexing should be done
351        // by the generated code, not the exception runtime
352        asm volatile (".CATCH:");
353
354        // Exception handler
355        catch_block(shared_stack.current_handler_index,
356                    shared_stack.current_exception);
357}
358
359// Exception table data we need to generate
360// While this is almost generic, the custom data refers to
361// foo_try_match try match, which is no way generic
362// Some more works need to be done if we want to have a single
363// call to the try routine
364#if defined(__PIC__)
365asm (
366        //HEADER
367        ".LFECFA1:\n"
368        "       .globl  __gcfa_personality_v0\n"
369        "       .section        .gcc_except_table,\"a\",@progbits\n"
370        ".LLSDACFA2:\n"                                                 //TABLE header
371        "       .byte   0xff\n"
372        "       .byte   0xff\n"
373        "       .byte   0x1\n"
374        "       .uleb128 .LLSDACSECFA2-.LLSDACSBCFA2\n"         // BODY length
375        // Body uses language specific data and therefore could be modified arbitrarily
376        ".LLSDACSBCFA2:\n"                                              // BODY start
377        "       .uleb128 .TRYSTART-__try_terminate\n"           // Handled area start  (relative to start of function)
378        "       .uleb128 .TRYEND-.TRYSTART\n"                           // Handled area length
379        "       .uleb128 .CATCH-__try_terminate\n"                      // Handler landing pad adress  (relative to start of function)
380        "       .uleb128 1\n"                                           // Action code, gcc seems to use always 0
381        ".LLSDACSECFA2:\n"                                              // BODY end
382        "       .text\n"                                                        // TABLE footer
383        "       .size   __try_terminate, .-__try_terminate\n"
384);
385
386// Somehow this piece of helps with the resolution of debug symbols.
387__attribute__((unused)) static const int dummy = 0;
388asm (
389        "       .hidden CFA.ref.__gcfa_personality_v0\n"        // Declare an new hidden symbol
390        "       .weak   CFA.ref.__gcfa_personality_v0\n"
391        "       .section        .data.rel.local.CFA.ref.__gcfa_personality_v0,\"awG\",@progbits,CFA.ref.__gcfa_personality_v0,comdat\n" // No clue what this does specifically
392        "       .align 8\n"
393        "       .type CFA.ref.__gcfa_personality_v0, @object\n" // Type of our hidden symbol (it's not actually the function itself)
394        "       .size CFA.ref.__gcfa_personality_v0, 8\n"               // Size of our hidden symbol
395        "CFA.ref.__gcfa_personality_v0:\n"
396        "       .quad __gcfa_personality_v0\n"
397);
398#else
399asm (
400        //HEADER
401        ".LFECFA1:\n"
402        "       .globl  __gcfa_personality_v0\n"
403        "       .section        .gcc_except_table,\"a\",@progbits\n"
404        ".LLSDACFA2:\n"                                                 //TABLE header
405        "       .byte   0xff\n"
406        "       .byte   0xff\n"
407        "       .byte   0x1\n"
408        "       .uleb128 .LLSDACSECFA2-.LLSDACSBCFA2\n"         // BODY length
409        // Body uses language specific data and therefore could be modified arbitrarily
410        ".LLSDACSBCFA2:\n"                                              // BODY start
411        "       .uleb128 .TRYSTART-__try_terminate\n"           // Handled area start  (relative to start of function)
412        "       .uleb128 .TRYEND-.TRYSTART\n"                           // Handled area length
413        "       .uleb128 .CATCH-__try_terminate\n"                              // Hanlder landing pad adress  (relative to start of function)
414        "       .uleb128 1\n"                                           // Action code, gcc seems to use always 0
415        ".LLSDACSECFA2:\n"                                              // BODY end
416        "       .text\n"                                                        // TABLE footer
417        "       .size   __try_terminate, .-__try_terminate\n"
418);
419#endif
Note: See TracBrowser for help on using the repository browser.