1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*++
6
7
8
9Module Name:
10
11 seh-unwind.cpp
12
13Abstract:
14
15 Implementation of exception API functions based on
16 the Unwind API.
17
18
19
20--*/
21
22#ifndef FEATURE_PAL_SXS
23#error FEATURE_PAL_SXS needs to be defined for this file.
24#endif // !FEATURE_PAL_SXS
25
26#include "pal/context.h"
27#include "pal.h"
28#include <dlfcn.h>
29
30#define UNW_LOCAL_ONLY
31// Sub-headers included from the libunwind.h contain an empty struct
32// and clang issues a warning. Until the libunwind is fixed, disable
33// the warning.
34#pragma clang diagnostic push
35#pragma clang diagnostic ignored "-Wextern-c-compat"
36#include <libunwind.h>
37#pragma clang diagnostic pop
38
39//----------------------------------------------------------------------
40// Virtual Unwinding
41//----------------------------------------------------------------------
42
43#if UNWIND_CONTEXT_IS_UCONTEXT_T
44
45#if defined(_AMD64_)
46#define ASSIGN_UNWIND_REGS \
47 ASSIGN_REG(Rip) \
48 ASSIGN_REG(Rsp) \
49 ASSIGN_REG(Rbp) \
50 ASSIGN_REG(Rbx) \
51 ASSIGN_REG(R12) \
52 ASSIGN_REG(R13) \
53 ASSIGN_REG(R14) \
54 ASSIGN_REG(R15)
55#elif defined(_ARM64_)
56#define ASSIGN_UNWIND_REGS \
57 ASSIGN_REG(Pc) \
58 ASSIGN_REG(Sp) \
59 ASSIGN_REG(Fp) \
60 ASSIGN_REG(Lr) \
61 ASSIGN_REG(X19) \
62 ASSIGN_REG(X20) \
63 ASSIGN_REG(X21) \
64 ASSIGN_REG(X22) \
65 ASSIGN_REG(X23) \
66 ASSIGN_REG(X24) \
67 ASSIGN_REG(X25) \
68 ASSIGN_REG(X26) \
69 ASSIGN_REG(X27) \
70 ASSIGN_REG(X28)
71#elif defined(_X86_)
72#define ASSIGN_UNWIND_REGS \
73 ASSIGN_REG(Eip) \
74 ASSIGN_REG(Esp) \
75 ASSIGN_REG(Ebp) \
76 ASSIGN_REG(Ebx) \
77 ASSIGN_REG(Esi) \
78 ASSIGN_REG(Edi)
79#else
80#error unsupported architecture
81#endif
82
83static void WinContextToUnwindContext(CONTEXT *winContext, unw_context_t *unwContext)
84{
85#define ASSIGN_REG(reg) MCREG_##reg(unwContext->uc_mcontext) = winContext->reg;
86 ASSIGN_UNWIND_REGS
87#undef ASSIGN_REG
88}
89#else
90static void WinContextToUnwindContext(CONTEXT *winContext, unw_context_t *unwContext)
91{
92#if defined(_ARM_)
93 // Assuming that unw_set_reg() on cursor will point the cursor to the
94 // supposed stack frame is dangerous for libunwind-arm in Linux.
95 // It is because libunwind's unw_cursor_t has other data structure
96 // initialized by unw_init_local(), which are not updated by
97 // unw_set_reg().
98 unwContext->regs[0] = 0;
99 unwContext->regs[1] = 0;
100 unwContext->regs[2] = 0;
101 unwContext->regs[3] = 0;
102 unwContext->regs[4] = winContext->R4;
103 unwContext->regs[5] = winContext->R5;
104 unwContext->regs[6] = winContext->R6;
105 unwContext->regs[7] = winContext->R7;
106 unwContext->regs[8] = winContext->R8;
107 unwContext->regs[9] = winContext->R9;
108 unwContext->regs[10] = winContext->R10;
109 unwContext->regs[11] = winContext->R11;
110 unwContext->regs[12] = 0;
111 unwContext->regs[13] = winContext->Sp;
112 unwContext->regs[14] = winContext->Lr;
113 unwContext->regs[15] = winContext->Pc;
114#endif
115}
116
117static void WinContextToUnwindCursor(CONTEXT *winContext, unw_cursor_t *cursor)
118{
119#if defined(_AMD64_)
120 unw_set_reg(cursor, UNW_REG_IP, winContext->Rip);
121 unw_set_reg(cursor, UNW_REG_SP, winContext->Rsp);
122 unw_set_reg(cursor, UNW_X86_64_RBP, winContext->Rbp);
123 unw_set_reg(cursor, UNW_X86_64_RBX, winContext->Rbx);
124 unw_set_reg(cursor, UNW_X86_64_R12, winContext->R12);
125 unw_set_reg(cursor, UNW_X86_64_R13, winContext->R13);
126 unw_set_reg(cursor, UNW_X86_64_R14, winContext->R14);
127 unw_set_reg(cursor, UNW_X86_64_R15, winContext->R15);
128#elif defined(_X86_)
129 unw_set_reg(cursor, UNW_REG_IP, winContext->Eip);
130 unw_set_reg(cursor, UNW_REG_SP, winContext->Esp);
131 unw_set_reg(cursor, UNW_X86_EBP, winContext->Ebp);
132 unw_set_reg(cursor, UNW_X86_EBX, winContext->Ebx);
133 unw_set_reg(cursor, UNW_X86_ESI, winContext->Esi);
134 unw_set_reg(cursor, UNW_X86_EDI, winContext->Edi);
135#endif
136}
137#endif
138
139void UnwindContextToWinContext(unw_cursor_t *cursor, CONTEXT *winContext)
140{
141#if defined(_AMD64_)
142 unw_get_reg(cursor, UNW_REG_IP, (unw_word_t *) &winContext->Rip);
143 unw_get_reg(cursor, UNW_REG_SP, (unw_word_t *) &winContext->Rsp);
144 unw_get_reg(cursor, UNW_X86_64_RBP, (unw_word_t *) &winContext->Rbp);
145 unw_get_reg(cursor, UNW_X86_64_RBX, (unw_word_t *) &winContext->Rbx);
146 unw_get_reg(cursor, UNW_X86_64_R12, (unw_word_t *) &winContext->R12);
147 unw_get_reg(cursor, UNW_X86_64_R13, (unw_word_t *) &winContext->R13);
148 unw_get_reg(cursor, UNW_X86_64_R14, (unw_word_t *) &winContext->R14);
149 unw_get_reg(cursor, UNW_X86_64_R15, (unw_word_t *) &winContext->R15);
150#elif defined(_X86_)
151 unw_get_reg(cursor, UNW_REG_IP, (unw_word_t *) &winContext->Eip);
152 unw_get_reg(cursor, UNW_REG_SP, (unw_word_t *) &winContext->Esp);
153 unw_get_reg(cursor, UNW_X86_EBP, (unw_word_t *) &winContext->Ebp);
154 unw_get_reg(cursor, UNW_X86_EBX, (unw_word_t *) &winContext->Ebx);
155 unw_get_reg(cursor, UNW_X86_ESI, (unw_word_t *) &winContext->Esi);
156 unw_get_reg(cursor, UNW_X86_EDI, (unw_word_t *) &winContext->Edi);
157#elif defined(_ARM_)
158 unw_get_reg(cursor, UNW_REG_SP, (unw_word_t *) &winContext->Sp);
159 unw_get_reg(cursor, UNW_REG_IP, (unw_word_t *) &winContext->Pc);
160 unw_get_reg(cursor, UNW_ARM_R14, (unw_word_t *) &winContext->Lr);
161 unw_get_reg(cursor, UNW_ARM_R4, (unw_word_t *) &winContext->R4);
162 unw_get_reg(cursor, UNW_ARM_R5, (unw_word_t *) &winContext->R5);
163 unw_get_reg(cursor, UNW_ARM_R6, (unw_word_t *) &winContext->R6);
164 unw_get_reg(cursor, UNW_ARM_R7, (unw_word_t *) &winContext->R7);
165 unw_get_reg(cursor, UNW_ARM_R8, (unw_word_t *) &winContext->R8);
166 unw_get_reg(cursor, UNW_ARM_R9, (unw_word_t *) &winContext->R9);
167 unw_get_reg(cursor, UNW_ARM_R10, (unw_word_t *) &winContext->R10);
168 unw_get_reg(cursor, UNW_ARM_R11, (unw_word_t *) &winContext->R11);
169#elif defined(_ARM64_)
170 unw_get_reg(cursor, UNW_REG_IP, (unw_word_t *) &winContext->Pc);
171 unw_get_reg(cursor, UNW_REG_SP, (unw_word_t *) &winContext->Sp);
172 unw_get_reg(cursor, UNW_AARCH64_X29, (unw_word_t *) &winContext->Fp);
173 unw_get_reg(cursor, UNW_AARCH64_X30, (unw_word_t *) &winContext->Lr);
174 unw_get_reg(cursor, UNW_AARCH64_X19, (unw_word_t *) &winContext->X19);
175 unw_get_reg(cursor, UNW_AARCH64_X20, (unw_word_t *) &winContext->X20);
176 unw_get_reg(cursor, UNW_AARCH64_X21, (unw_word_t *) &winContext->X21);
177 unw_get_reg(cursor, UNW_AARCH64_X22, (unw_word_t *) &winContext->X22);
178 unw_get_reg(cursor, UNW_AARCH64_X23, (unw_word_t *) &winContext->X23);
179 unw_get_reg(cursor, UNW_AARCH64_X24, (unw_word_t *) &winContext->X24);
180 unw_get_reg(cursor, UNW_AARCH64_X25, (unw_word_t *) &winContext->X25);
181 unw_get_reg(cursor, UNW_AARCH64_X26, (unw_word_t *) &winContext->X26);
182 unw_get_reg(cursor, UNW_AARCH64_X27, (unw_word_t *) &winContext->X27);
183 unw_get_reg(cursor, UNW_AARCH64_X28, (unw_word_t *) &winContext->X28);
184#else
185#error unsupported architecture
186#endif
187}
188
189static void GetContextPointer(unw_cursor_t *cursor, unw_context_t *unwContext, int reg, SIZE_T **contextPointer)
190{
191#if defined(HAVE_UNW_GET_SAVE_LOC)
192 unw_save_loc_t saveLoc;
193 unw_get_save_loc(cursor, reg, &saveLoc);
194 if (saveLoc.type == UNW_SLT_MEMORY)
195 {
196 SIZE_T *pLoc = (SIZE_T *)saveLoc.u.addr;
197 // Filter out fake save locations that point to unwContext
198 if (unwContext == NULL || (pLoc < (SIZE_T *)unwContext) || ((SIZE_T *)(unwContext + 1) <= pLoc))
199 *contextPointer = (SIZE_T *)saveLoc.u.addr;
200 }
201#else
202 // Returning NULL indicates that we don't have context pointers available
203 *contextPointer = NULL;
204#endif
205}
206
207void GetContextPointers(unw_cursor_t *cursor, unw_context_t *unwContext, KNONVOLATILE_CONTEXT_POINTERS *contextPointers)
208{
209#if defined(_AMD64_)
210 GetContextPointer(cursor, unwContext, UNW_X86_64_RBP, &contextPointers->Rbp);
211 GetContextPointer(cursor, unwContext, UNW_X86_64_RBX, &contextPointers->Rbx);
212 GetContextPointer(cursor, unwContext, UNW_X86_64_R12, &contextPointers->R12);
213 GetContextPointer(cursor, unwContext, UNW_X86_64_R13, &contextPointers->R13);
214 GetContextPointer(cursor, unwContext, UNW_X86_64_R14, &contextPointers->R14);
215 GetContextPointer(cursor, unwContext, UNW_X86_64_R15, &contextPointers->R15);
216#elif defined(_X86_)
217 GetContextPointer(cursor, unwContext, UNW_X86_EBX, &contextPointers->Ebx);
218 GetContextPointer(cursor, unwContext, UNW_X86_EBP, &contextPointers->Ebp);
219 GetContextPointer(cursor, unwContext, UNW_X86_ESI, &contextPointers->Esi);
220 GetContextPointer(cursor, unwContext, UNW_X86_EDI, &contextPointers->Edi);
221#elif defined(_ARM_)
222 GetContextPointer(cursor, unwContext, UNW_ARM_R4, &contextPointers->R4);
223 GetContextPointer(cursor, unwContext, UNW_ARM_R5, &contextPointers->R5);
224 GetContextPointer(cursor, unwContext, UNW_ARM_R6, &contextPointers->R6);
225 GetContextPointer(cursor, unwContext, UNW_ARM_R7, &contextPointers->R7);
226 GetContextPointer(cursor, unwContext, UNW_ARM_R8, &contextPointers->R8);
227 GetContextPointer(cursor, unwContext, UNW_ARM_R9, &contextPointers->R9);
228 GetContextPointer(cursor, unwContext, UNW_ARM_R10, &contextPointers->R10);
229 GetContextPointer(cursor, unwContext, UNW_ARM_R11, &contextPointers->R11);
230#elif defined(_ARM64_)
231 GetContextPointer(cursor, unwContext, UNW_AARCH64_X19, &contextPointers->X19);
232 GetContextPointer(cursor, unwContext, UNW_AARCH64_X20, &contextPointers->X20);
233 GetContextPointer(cursor, unwContext, UNW_AARCH64_X21, &contextPointers->X21);
234 GetContextPointer(cursor, unwContext, UNW_AARCH64_X22, &contextPointers->X22);
235 GetContextPointer(cursor, unwContext, UNW_AARCH64_X23, &contextPointers->X23);
236 GetContextPointer(cursor, unwContext, UNW_AARCH64_X24, &contextPointers->X24);
237 GetContextPointer(cursor, unwContext, UNW_AARCH64_X25, &contextPointers->X25);
238 GetContextPointer(cursor, unwContext, UNW_AARCH64_X26, &contextPointers->X26);
239 GetContextPointer(cursor, unwContext, UNW_AARCH64_X27, &contextPointers->X27);
240 GetContextPointer(cursor, unwContext, UNW_AARCH64_X28, &contextPointers->X28);
241 GetContextPointer(cursor, unwContext, UNW_AARCH64_X29, &contextPointers->Fp);
242#else
243#error unsupported architecture
244#endif
245}
246
247extern int g_common_signal_handler_context_locvar_offset;
248
249BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers)
250{
251 int st;
252 unw_context_t unwContext;
253 unw_cursor_t cursor;
254
255 DWORD64 curPc = CONTEXTGetPC(context);
256
257#ifndef __APPLE__
258 // Check if the PC is the return address from the SEHProcessException in the common_signal_handler.
259 // If that's the case, extract its local variable containing the windows style context of the hardware
260 // exception and return that. This skips the hardware signal handler trampoline that the libunwind
261 // cannot cross on some systems.
262 if ((void*)curPc == g_SEHProcessExceptionReturnAddress)
263 {
264 CONTEXT* signalContext = (CONTEXT*)(CONTEXTGetFP(context) + g_common_signal_handler_context_locvar_offset);
265 memcpy_s(context, sizeof(CONTEXT), signalContext, sizeof(CONTEXT));
266
267 return TRUE;
268 }
269#endif
270
271 if ((context->ContextFlags & CONTEXT_EXCEPTION_ACTIVE) != 0)
272 {
273 // The current frame is a source of hardware exception. Due to the fact that
274 // we use the low level unwinder to unwind just one frame a time, the
275 // unwinder doesn't have the signal_frame flag set. So it doesn't
276 // know that it should not decrement the PC before looking up the unwind info.
277 // So we compensate it by incrementing the PC before passing it to the unwinder.
278 // Without it, the unwinder would not find unwind info if the hardware exception
279 // happened in the first instruction of a function.
280 CONTEXTSetPC(context, curPc + 1);
281 }
282
283#if !UNWIND_CONTEXT_IS_UCONTEXT_T
284 st = unw_getcontext(&unwContext);
285 if (st < 0)
286 {
287 return FALSE;
288 }
289#endif
290
291 WinContextToUnwindContext(context, &unwContext);
292
293 st = unw_init_local(&cursor, &unwContext);
294 if (st < 0)
295 {
296 return FALSE;
297 }
298
299#if !UNWIND_CONTEXT_IS_UCONTEXT_T
300 // Set the unwind context to the specified windows context
301 WinContextToUnwindCursor(context, &cursor);
302#endif
303
304 st = unw_step(&cursor);
305 if (st < 0)
306 {
307 return FALSE;
308 }
309
310 // Check if the frame we have unwound to is a frame that caused
311 // synchronous signal, like a hardware exception and record it
312 // in the context flags.
313 if (unw_is_signal_frame(&cursor) > 0)
314 {
315 context->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
316#if defined(_ARM_) || defined(_ARM64_) || defined(_X86_)
317 context->ContextFlags &= ~CONTEXT_UNWOUND_TO_CALL;
318#endif // _ARM_ || _ARM64_
319 }
320 else
321 {
322 context->ContextFlags &= ~CONTEXT_EXCEPTION_ACTIVE;
323#if defined(_ARM_) || defined(_ARM64_) || defined(_X86_)
324 context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
325#endif // _ARM_ || _ARM64_
326 }
327
328 // Update the passed in windows context to reflect the unwind
329 //
330 UnwindContextToWinContext(&cursor, context);
331
332 // On some OSes / architectures if it unwound all the way to _start
333 // (__libc_start_main on arm64 Linux with glibc older than 2.27).
334 // >= 0 is returned from the step, but $pc will stay the same.
335 // So we detect that here and set the $pc to NULL in that case.
336 // This is the default behavior of the libunwind on x64 Linux.
337 //
338 if (st >= 0 && CONTEXTGetPC(context) == curPc)
339 {
340 CONTEXTSetPC(context, 0);
341 }
342
343 if (contextPointers != NULL)
344 {
345 GetContextPointers(&cursor, &unwContext, contextPointers);
346 }
347 return TRUE;
348}
349
350struct ExceptionRecords
351{
352 CONTEXT ContextRecord;
353 EXCEPTION_RECORD ExceptionRecord;
354};
355
356// Max number of fallback contexts that are used when malloc fails to allocate ExceptionRecords structure
357static const int MaxFallbackContexts = sizeof(size_t) * 8;
358// Array of fallback contexts
359static ExceptionRecords s_fallbackContexts[MaxFallbackContexts];
360// Bitmap used for allocating fallback contexts - bits set to 1 represent already allocated context.
361static volatile size_t s_allocatedContextsBitmap = 0;
362
363/*++
364Function:
365 AllocateExceptionRecords
366
367 Allocate EXCEPTION_RECORD and CONTEXT structures for an exception.
368Parameters:
369 exceptionRecord - output pointer to the allocated exception record
370 contextRecord - output pointer to the allocated context record
371--*/
372VOID
373AllocateExceptionRecords(EXCEPTION_RECORD** exceptionRecord, CONTEXT** contextRecord)
374{
375 ExceptionRecords* records;
376 if (posix_memalign((void**)&records, alignof(ExceptionRecords), sizeof(ExceptionRecords)) != 0)
377 {
378 size_t bitmap;
379 size_t newBitmap;
380 int index;
381
382 do
383 {
384 bitmap = s_allocatedContextsBitmap;
385 index = __builtin_ffsl(~bitmap) - 1;
386 if (index < 0)
387 {
388 PROCAbort();
389 }
390
391 newBitmap = bitmap | ((size_t)1 << index);
392 }
393 while (__sync_val_compare_and_swap(&s_allocatedContextsBitmap, bitmap, newBitmap) != bitmap);
394
395 records = &s_fallbackContexts[index];
396 }
397
398 *contextRecord = &records->ContextRecord;
399 *exceptionRecord = &records->ExceptionRecord;
400}
401
402/*++
403Function:
404 PAL_FreeExceptionRecords
405
406 Free EXCEPTION_RECORD and CONTEXT structures of an exception that were allocated by the
407 AllocateExceptionRecords.
408Parameters:
409 exceptionRecord - exception record
410 contextRecord - context record
411--*/
412VOID
413PALAPI
414PAL_FreeExceptionRecords(IN EXCEPTION_RECORD *exceptionRecord, IN CONTEXT *contextRecord)
415{
416 // Both records are allocated at once and the allocated memory starts at the contextRecord
417 ExceptionRecords* records = (ExceptionRecords*)contextRecord;
418 if ((records >= &s_fallbackContexts[0]) && (records < &s_fallbackContexts[MaxFallbackContexts]))
419 {
420 int index = records - &s_fallbackContexts[0];
421 __sync_fetch_and_and(&s_allocatedContextsBitmap, ~((size_t)1 << index));
422 }
423 else
424 {
425 free(contextRecord);
426 }
427}
428
429/*++
430Function:
431 RtlpRaiseException
432
433Parameters:
434 ExceptionRecord - the Windows exception record to throw
435
436Note:
437 The name of this function and the name of the ExceptionRecord
438 parameter is used in the sos lldb plugin code to read the exception
439 record. See coreclr\src\ToolBox\SOS\lldbplugin\services.cpp.
440
441 This function must not be inlined or optimized so the below PAL_VirtualUnwind
442 calls end up with RaiseException caller's context and so the above debugger
443 code finds the function and ExceptionRecord parameter.
444--*/
445PAL_NORETURN
446__attribute__((noinline))
447__attribute__((optnone))
448static void
449RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord, CONTEXT *ContextRecord)
450{
451 throw PAL_SEHException(ExceptionRecord, ContextRecord);
452}
453
454/*++
455Function:
456 RaiseException
457
458See MSDN doc.
459--*/
460// no PAL_NORETURN, as callers must assume this can return for continuable exceptions.
461__attribute__((noinline))
462VOID
463PALAPI
464RaiseException(IN DWORD dwExceptionCode,
465 IN DWORD dwExceptionFlags,
466 IN DWORD nNumberOfArguments,
467 IN CONST ULONG_PTR *lpArguments)
468{
469 // PERF_ENTRY_ONLY is used here because RaiseException may or may not
470 // return. We can not get latency data without PERF_EXIT. For this reason,
471 // PERF_ENTRY_ONLY is used to profile frequency only.
472 PERF_ENTRY_ONLY(RaiseException);
473 ENTRY("RaiseException(dwCode=%#x, dwFlags=%#x, nArgs=%u, lpArguments=%p)\n",
474 dwExceptionCode, dwExceptionFlags, nNumberOfArguments, lpArguments);
475
476 /* Validate parameters */
477 if (dwExceptionCode & RESERVED_SEH_BIT)
478 {
479 WARN("Exception code %08x has bit 28 set; clearing it.\n", dwExceptionCode);
480 dwExceptionCode ^= RESERVED_SEH_BIT;
481 }
482
483 if (nNumberOfArguments > EXCEPTION_MAXIMUM_PARAMETERS)
484 {
485 WARN("Number of arguments (%d) exceeds the limit "
486 "EXCEPTION_MAXIMUM_PARAMETERS (%d); ignoring extra parameters.\n",
487 nNumberOfArguments, EXCEPTION_MAXIMUM_PARAMETERS);
488 nNumberOfArguments = EXCEPTION_MAXIMUM_PARAMETERS;
489 }
490
491 CONTEXT *contextRecord;
492 EXCEPTION_RECORD *exceptionRecord;
493 AllocateExceptionRecords(&exceptionRecord, &contextRecord);
494
495 ZeroMemory(exceptionRecord, sizeof(EXCEPTION_RECORD));
496
497 exceptionRecord->ExceptionCode = dwExceptionCode;
498 exceptionRecord->ExceptionFlags = dwExceptionFlags;
499 exceptionRecord->ExceptionRecord = NULL;
500 exceptionRecord->ExceptionAddress = NULL; // will be set by RtlpRaiseException
501 exceptionRecord->NumberParameters = nNumberOfArguments;
502 if (nNumberOfArguments)
503 {
504 CopyMemory(exceptionRecord->ExceptionInformation, lpArguments,
505 nNumberOfArguments * sizeof(ULONG_PTR));
506 }
507
508 // Capture the context of RaiseException.
509 ZeroMemory(contextRecord, sizeof(CONTEXT));
510 contextRecord->ContextFlags = CONTEXT_FULL;
511 CONTEXT_CaptureContext(contextRecord);
512
513 // We have to unwind one level to get the actual context user code could be resumed at.
514 PAL_VirtualUnwind(contextRecord, NULL);
515
516 exceptionRecord->ExceptionAddress = (void *)CONTEXTGetPC(contextRecord);
517
518 RtlpRaiseException(exceptionRecord, contextRecord);
519
520 LOGEXIT("RaiseException returns\n");
521}
522