#include #include #include #include #include #include "artiq_personality.h" /* Logging */ #ifndef NDEBUG #define EH_LOG0(fmt) fprintf(stderr, "%s: " fmt "\n", __func__) #define EH_LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, __VA_ARGS__) #else #define EH_LOG0(fmt) #define EH_LOG(fmt, ...) #endif #define EH_FAIL(err) \ do { \ fprintf(stderr, "%s fatal: %s\n", __func__, err); \ abort(); \ } while(0) #define EH_ASSERT(expr) \ if(!(expr)) EH_FAIL(#expr) /* DWARF format handling */ enum { DW_EH_PE_absptr = 0x00, DW_EH_PE_uleb128 = 0x01, DW_EH_PE_udata2 = 0x02, DW_EH_PE_udata4 = 0x03, DW_EH_PE_udata8 = 0x04, DW_EH_PE_sleb128 = 0x09, DW_EH_PE_sdata2 = 0x0A, DW_EH_PE_sdata4 = 0x0B, DW_EH_PE_sdata8 = 0x0C, DW_EH_PE_pcrel = 0x10, DW_EH_PE_textrel = 0x20, DW_EH_PE_datarel = 0x30, DW_EH_PE_funcrel = 0x40, DW_EH_PE_aligned = 0x50, DW_EH_PE_indirect = 0x80, DW_EH_PE_omit = 0xFF }; // Read a uleb128 encoded value and advance pointer // See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf static uintptr_t readULEB128(const uint8_t **data) { uintptr_t result = 0; uintptr_t shift = 0; unsigned char byte; const uint8_t *p = *data; do { byte = *p++; result |= (byte & 0x7f) << shift; shift += 7; } while (byte & 0x80); *data = p; return result; } // Read a sleb128 encoded value and advance pointer // See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf static uintptr_t readSLEB128(const uint8_t **data) { uintptr_t result = 0; uintptr_t shift = 0; unsigned char byte; const uint8_t *p = *data; do { byte = *p++; result |= (byte & 0x7f) << shift; shift += 7; } while (byte & 0x80); *data = p; if ((byte & 0x40) && (shift < (sizeof(result) << 3))) { result |= (~0 << shift); } return result; } static unsigned getEncodingSize(uint8_t Encoding) { if (Encoding == DW_EH_PE_omit) return 0; switch (Encoding & 0x0F) { case DW_EH_PE_absptr: return sizeof(uintptr_t); case DW_EH_PE_udata2: return sizeof(uint16_t); case DW_EH_PE_udata4: return sizeof(uint32_t); case DW_EH_PE_udata8: return sizeof(uint64_t); case DW_EH_PE_sdata2: return sizeof(int16_t); case DW_EH_PE_sdata4: return sizeof(int32_t); case DW_EH_PE_sdata8: return sizeof(int64_t); default: // not supported abort(); } } // Read a pointer encoded value and advance pointer // See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { uintptr_t result = 0; const uint8_t *p = *data; if (encoding == DW_EH_PE_omit) return(result); // first get value switch (encoding & 0x0F) { case DW_EH_PE_absptr: memcpy(&result, p, sizeof(uintptr_t)); p += sizeof(uintptr_t); break; case DW_EH_PE_uleb128: result = readULEB128(&p); break; // Note: This case has not been tested case DW_EH_PE_sleb128: result = readSLEB128(&p); break; case DW_EH_PE_udata2: { uint16_t valu16; memcpy(&valu16, p, sizeof(uint16_t)); result = valu16; } p += sizeof(uint16_t); break; case DW_EH_PE_udata4: { uint32_t valu32; memcpy(&valu32, p, sizeof(uint32_t)); result = valu32; } p += sizeof(uint32_t); break; case DW_EH_PE_udata8: { uint64_t valu64; memcpy(&valu64, p, sizeof(uint64_t)); result = valu64; } p += sizeof(uint64_t); break; case DW_EH_PE_sdata2: { int16_t val16; memcpy(&val16, p, sizeof(int16_t)); result = val16; } p += sizeof(int16_t); break; case DW_EH_PE_sdata4: { int32_t val32; memcpy(&val32, p, sizeof(int32_t)); result = val32; } p += sizeof(int32_t); break; case DW_EH_PE_sdata8: { int64_t val64; memcpy(&val64, p, sizeof(int64_t)); result = val64; } p += sizeof(int64_t); break; default: // not supported abort(); break; } // then add relative offset switch (encoding & 0x70) { case DW_EH_PE_absptr: // do nothing break; case DW_EH_PE_pcrel: result += (uintptr_t)(*data); break; case DW_EH_PE_textrel: case DW_EH_PE_datarel: case DW_EH_PE_funcrel: case DW_EH_PE_aligned: default: // not supported abort(); break; } // then apply indirection if (encoding & DW_EH_PE_indirect) { result = *((uintptr_t*)result); } *data = p; return result; } /* Raising */ #define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc); static _Unwind_Reason_Code __artiq_uncaught_exception( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context, void *stop_parameter); struct artiq_raised_exception { struct _Unwind_Exception unwind; struct artiq_exception artiq; int handled; struct artiq_backtrace_item backtrace[1024]; size_t backtrace_size; }; static struct artiq_raised_exception inflight; void __artiq_raise(struct artiq_exception *artiq_exn) { EH_LOG("===> raise (name=%s, msg=%s, params=[%lld,%lld,%lld])", artiq_exn->name, artiq_exn->message, artiq_exn->param[0], artiq_exn->param[1], artiq_exn->param[2]); memmove(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception)); inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; inflight.unwind.exception_cleanup = &__artiq_cleanup; inflight.handled = 0; inflight.backtrace_size = 0; _Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind); EH_ASSERT((result == _URC_END_OF_STACK) && "Unexpected error during unwinding"); // If we're here, there are no handlers, only cleanups. // Force unwinding anyway; we shall stop at nothing except the end of stack. result = _Unwind_ForcedUnwind(&inflight.unwind, &__artiq_uncaught_exception, NULL); EH_FAIL("_Unwind_ForcedUnwind should not return"); } void __artiq_reraise() { if(inflight.handled) { EH_LOG0("===> reraise"); __artiq_raise(&inflight.artiq); } else { EH_LOG0("===> resume"); EH_ASSERT((inflight.artiq.typeinfo != 0) && "Need an exception to reraise"); _Unwind_Resume(&inflight.unwind); abort(); } } /* Unwinding */ // The code below does not refer to the `inflight` global. static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc) { EH_LOG0("===> cleanup"); struct artiq_raised_exception *inflight = (struct artiq_raised_exception*) exc; // The in-flight exception is statically allocated, so we don't need to free it. // But, we clear it to mark it as processed. memset(&inflight->artiq, 0, sizeof(struct artiq_exception)); } static _Unwind_Reason_Code __artiq_uncaught_exception( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context, void *stop_parameter) { struct artiq_raised_exception *inflight = (struct artiq_raised_exception*)exceptionObject; EH_ASSERT(inflight->backtrace_size < sizeof(inflight->backtrace) / sizeof(inflight->backtrace[0]) && "Out of space for backtrace"); uintptr_t pc = _Unwind_GetIP(context); uintptr_t funcStart = _Unwind_GetRegionStart(context); uintptr_t pcOffset = pc - funcStart; EH_LOG("===> uncaught (pc=%p+%p)", (void*)funcStart, (void*)pcOffset); inflight->backtrace[inflight->backtrace_size].function = funcStart; inflight->backtrace[inflight->backtrace_size].offset = pcOffset; ++inflight->backtrace_size; if(actions & _UA_END_OF_STACK) { EH_LOG0("end of stack"); __artiq_terminate(&inflight->artiq, inflight->backtrace, inflight->backtrace_size); } else { EH_LOG0("continue"); return _URC_NO_REASON; } } _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { EH_LOG("===> entry (actions =%s%s%s%s; class=%08lx; object=%p, context=%p)", (actions & _UA_SEARCH_PHASE ? " search" : ""), (actions & _UA_CLEANUP_PHASE ? " cleanup" : ""), (actions & _UA_HANDLER_FRAME ? " handler" : ""), (actions & _UA_FORCE_UNWIND ? " force-unwind" : ""), exceptionClass, exceptionObject, context); EH_ASSERT((exceptionClass == ARTIQ_EXCEPTION_CLASS) && "Foreign exceptions are not supported"); struct artiq_raised_exception *inflight = (struct artiq_raised_exception*)exceptionObject; EH_LOG("=> exception name=%s", inflight->artiq.name); // Get a pointer to LSDA. If there's no LSDA, this function doesn't // actually handle any exceptions. const uint8_t *lsda = (const uint8_t*) _Unwind_GetLanguageSpecificData(context); if(lsda == NULL) return _URC_CONTINUE_UNWIND; EH_LOG("lsda=%p", lsda); // Get the current instruction pointer and offset it before next // instruction in the current frame which threw the exception. uintptr_t pc = _Unwind_GetIP(context) - 1; // Get beginning of the current frame's code. uintptr_t funcStart = _Unwind_GetRegionStart(context); uintptr_t pcOffset = pc - funcStart; EH_LOG("=> pc=%p (%p+%p)", (void*)pc, (void*)funcStart, (void*)pcOffset); // Parse LSDA header. uint8_t lpStartEncoding = *lsda++; if (lpStartEncoding != DW_EH_PE_omit) { readEncodedPointer(&lsda, lpStartEncoding); } uint8_t ttypeEncoding = *lsda++; const uint8_t *classInfo = NULL; if (ttypeEncoding != DW_EH_PE_omit) { // Calculate type info locations in emitted dwarf code which // were flagged by type info arguments to llvm.eh.selector // intrinsic uintptr_t classInfoOffset = readULEB128(&lsda); classInfo = lsda + classInfoOffset; EH_LOG("classInfo=%p", classInfo); } // Walk call-site table looking for range that includes current PC. uint8_t callSiteEncoding = *lsda++; uint32_t callSiteTableLength = readULEB128(&lsda); const uint8_t *callSiteTableStart = lsda; const uint8_t *callSiteTableEnd = callSiteTableStart + callSiteTableLength; const uint8_t *actionTableStart = callSiteTableEnd; const uint8_t *callSitePtr = callSiteTableStart; while(callSitePtr < callSiteTableEnd) { uintptr_t start = readEncodedPointer(&callSitePtr, callSiteEncoding); uintptr_t length = readEncodedPointer(&callSitePtr, callSiteEncoding); uintptr_t landingPad = readEncodedPointer(&callSitePtr, callSiteEncoding); uintptr_t actionValue = readULEB128(&callSitePtr); EH_LOG("call site (start=+%p, len=%d, landingPad=+%p, actionValue=%d)", (void*)start, (int)length, (void*)landingPad, (int)actionValue); if(landingPad == 0) { EH_LOG0("no landing pad, skipping"); continue; } if((start <= pcOffset) && (pcOffset < (start + length))) { EH_LOG0("=> call site matches pc"); int exceptionMatched = 0; if(actionValue) { const uint8_t *actionEntry = actionTableStart + (actionValue - 1); EH_LOG("actionEntry=%p", actionEntry); for(;;) { // Each emitted DWARF action corresponds to a 2 tuple of // type info address offset, and action offset to the next // emitted action. intptr_t typeInfoOffset = readSLEB128(&actionEntry); const uint8_t *tempActionEntry = actionEntry; intptr_t actionOffset = readSLEB128(&tempActionEntry); EH_LOG("typeInfoOffset=%p actionOffset=%p", (void*)typeInfoOffset, (void*)actionOffset); EH_ASSERT((typeInfoOffset >= 0) && "Filter clauses are not supported"); unsigned encodingSize = getEncodingSize(ttypeEncoding); const uint8_t *typeInfoPtrPtr = classInfo - typeInfoOffset * encodingSize; uintptr_t typeInfoPtr = readEncodedPointer(&typeInfoPtrPtr, ttypeEncoding); EH_LOG("encodingSize=%u typeInfoPtrPtr=%p typeInfoPtr=%p", encodingSize, typeInfoPtrPtr, (void*)typeInfoPtr); EH_LOG("typeInfo=%s", (char*)typeInfoPtr); if(typeInfoPtr == 0 || inflight->artiq.typeinfo == typeInfoPtr) { EH_LOG0("matching action found"); exceptionMatched = 1; break; } if (!actionOffset) break; actionEntry += actionOffset; } } if(!(actions & _UA_SEARCH_PHASE)) { EH_LOG0("=> jumping to landing pad"); if(actions & _UA_HANDLER_FRAME) inflight->handled = 1; _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), (uintptr_t)exceptionObject); _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), (uintptr_t)&inflight->artiq); _Unwind_SetIP(context, funcStart + landingPad); return _URC_INSTALL_CONTEXT; } else if(exceptionMatched) { EH_LOG0("=> handler found"); return _URC_HANDLER_FOUND; } else { EH_LOG0("=> handler not found"); return _URC_CONTINUE_UNWIND; } } } return _URC_CONTINUE_UNWIND; }