| /* go-unwind.c -- unwind the stack for panic/recover. |
| |
| Copyright 2010 The Go Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style |
| license that can be found in the LICENSE file. */ |
| |
| #include "config.h" |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include "unwind.h" |
| |
| #include "runtime.h" |
| |
| /* These constants are documented here: |
| https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/dwarfext.html |
| */ |
| |
| #define DW_EH_PE_omit 0xff |
| #define DW_EH_PE_absptr 0x00 |
| #define DW_EH_PE_uleb128 0x01 |
| #define DW_EH_PE_udata2 0x02 |
| #define DW_EH_PE_udata4 0x03 |
| #define DW_EH_PE_udata8 0x04 |
| #define DW_EH_PE_sleb128 0x09 |
| #define DW_EH_PE_sdata2 0x0A |
| #define DW_EH_PE_sdata4 0x0B |
| #define DW_EH_PE_sdata8 0x0C |
| #define DW_EH_PE_pcrel 0x10 |
| #define DW_EH_PE_textrel 0x20 |
| #define DW_EH_PE_datarel 0x30 |
| #define DW_EH_PE_funcrel 0x40 |
| #define DW_EH_PE_aligned 0x50 |
| #define DW_EH_PE_indirect 0x80 |
| |
| /* The code for a Go exception. */ |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| static const _Unwind_Exception_Class __go_exception_class = |
| { 'G', 'N', 'U', 'C', 'G', 'O', '\0', '\0' }; |
| #else |
| static const _Unwind_Exception_Class __go_exception_class = |
| ((((((((_Unwind_Exception_Class) 'G' |
| << 8 | (_Unwind_Exception_Class) 'N') |
| << 8 | (_Unwind_Exception_Class) 'U') |
| << 8 | (_Unwind_Exception_Class) 'C') |
| << 8 | (_Unwind_Exception_Class) 'G') |
| << 8 | (_Unwind_Exception_Class) 'O') |
| << 8 | (_Unwind_Exception_Class) '\0') |
| << 8 | (_Unwind_Exception_Class) '\0'); |
| #endif |
| |
| /* Rethrow an exception. */ |
| |
| void rethrowException (void) __asm__(GOSYM_PREFIX "runtime.rethrowException"); |
| |
| void |
| rethrowException () |
| { |
| struct _Unwind_Exception *hdr; |
| |
| hdr = (struct _Unwind_Exception *) runtime_g()->exception; |
| |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| _Unwind_SjLj_Resume_or_Rethrow (hdr); |
| #else |
| #if defined(_LIBUNWIND_STD_ABI) |
| _Unwind_RaiseException (hdr); |
| #else |
| _Unwind_Resume_or_Rethrow (hdr); |
| #endif |
| #endif |
| |
| /* Rethrowing the exception should not return. */ |
| abort(); |
| } |
| |
| /* Return the size of the type that holds an exception header, so that |
| it can be allocated by Go code. */ |
| |
| uintptr unwindExceptionSize(void) |
| __asm__ (GOSYM_PREFIX "runtime.unwindExceptionSize"); |
| |
| uintptr |
| unwindExceptionSize () |
| { |
| uintptr ret, align; |
| |
| ret = sizeof (struct _Unwind_Exception); |
| /* Adjust the size fo make sure that we can get an aligned value. */ |
| align = __alignof__ (struct _Unwind_Exception); |
| if (align > __alignof__ (uintptr)) |
| ret += align - __alignof__ (uintptr); |
| return ret; |
| } |
| |
| /* Throw an exception. This is called with g->exception pointing to |
| an uninitialized _Unwind_Exception instance. */ |
| |
| void throwException (void) __asm__(GOSYM_PREFIX "runtime.throwException"); |
| |
| void |
| throwException () |
| { |
| struct _Unwind_Exception *hdr; |
| uintptr align; |
| |
| hdr = (struct _Unwind_Exception *)runtime_g ()->exception; |
| |
| /* Make sure the value is correctly aligned. It will be large |
| enough, because of unwindExceptionSize. */ |
| align = __alignof__ (struct _Unwind_Exception); |
| hdr = ((struct _Unwind_Exception *) |
| (((uintptr) hdr + align - 1) &~ (align - 1))); |
| |
| __builtin_memcpy (&hdr->exception_class, &__go_exception_class, |
| sizeof hdr->exception_class); |
| hdr->exception_cleanup = NULL; |
| |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| _Unwind_SjLj_RaiseException (hdr); |
| #else |
| _Unwind_RaiseException (hdr); |
| #endif |
| |
| /* Raising an exception should not return. */ |
| abort (); |
| } |
| |
| static inline _Unwind_Ptr |
| encoded_value_base (uint8_t encoding, struct _Unwind_Context *context) |
| { |
| if (encoding == DW_EH_PE_omit) |
| return 0; |
| switch (encoding & 0x70) |
| { |
| case DW_EH_PE_absptr: |
| case DW_EH_PE_pcrel: |
| case DW_EH_PE_aligned: |
| return 0; |
| case DW_EH_PE_textrel: |
| return _Unwind_GetTextRelBase(context); |
| case DW_EH_PE_datarel: |
| return _Unwind_GetDataRelBase(context); |
| case DW_EH_PE_funcrel: |
| return _Unwind_GetRegionStart(context); |
| } |
| abort (); |
| } |
| |
| /* Read an unsigned leb128 value. */ |
| |
| static inline const uint8_t * |
| read_uleb128 (const uint8_t *p, _uleb128_t *val) |
| { |
| unsigned int shift = 0; |
| _uleb128_t result = 0; |
| uint8_t byte; |
| |
| do |
| { |
| byte = *p++; |
| result |= ((_uleb128_t)byte & 0x7f) << shift; |
| shift += 7; |
| } |
| while (byte & 0x80); |
| |
| *val = result; |
| return p; |
| } |
| |
| /* Similar, but read a signed leb128 value. */ |
| |
| static inline const uint8_t * |
| read_sleb128 (const uint8_t *p, _sleb128_t *val) |
| { |
| unsigned int shift = 0; |
| _uleb128_t result = 0; |
| uint8_t byte; |
| |
| do |
| { |
| byte = *p++; |
| result |= ((_uleb128_t)byte & 0x7f) << shift; |
| shift += 7; |
| } |
| while (byte & 0x80); |
| |
| /* sign extension */ |
| if (shift < (8 * sizeof(result)) && (byte & 0x40) != 0) |
| result |= (((_uleb128_t)~0) << shift); |
| |
| *val = (_sleb128_t)result; |
| return p; |
| } |
| |
| #define ROUND_UP_TO_PVB(x) (x + sizeof(void *) - 1) &- sizeof(void *) |
| |
| static inline const uint8_t * |
| read_encoded_value (struct _Unwind_Context *context, uint8_t encoding, |
| const uint8_t *p, _Unwind_Ptr *val) |
| { |
| _Unwind_Ptr base = encoded_value_base (encoding, context); |
| _Unwind_Internal_Ptr decoded = 0; |
| const uint8_t *origp = p; |
| |
| if (encoding == DW_EH_PE_aligned) |
| { |
| _Unwind_Internal_Ptr uip = (_Unwind_Internal_Ptr)p; |
| uip = ROUND_UP_TO_PVB (uip); |
| decoded = *(_Unwind_Internal_Ptr *)uip; |
| p = (const uint8_t *)(uip + sizeof(void *)); |
| } |
| else |
| { |
| switch (encoding & 0x0f) |
| { |
| case DW_EH_PE_sdata2: |
| { |
| int16_t result; |
| __builtin_memcpy (&result, p, sizeof(int16_t)); |
| decoded = result; |
| p += sizeof(int16_t); |
| break; |
| } |
| case DW_EH_PE_udata2: |
| { |
| uint16_t result; |
| __builtin_memcpy (&result, p, sizeof(uint16_t)); |
| decoded = result; |
| p += sizeof(uint16_t); |
| break; |
| } |
| case DW_EH_PE_sdata4: |
| { |
| int32_t result; |
| __builtin_memcpy (&result, p, sizeof(int32_t)); |
| decoded = result; |
| p += sizeof(int32_t); |
| break; |
| } |
| case DW_EH_PE_udata4: |
| { |
| uint32_t result; |
| __builtin_memcpy (&result, p, sizeof(uint32_t)); |
| decoded = result; |
| p += sizeof(uint32_t); |
| break; |
| } |
| case DW_EH_PE_sdata8: |
| { |
| int64_t result; |
| __builtin_memcpy (&result, p, sizeof(int64_t)); |
| decoded = result; |
| p += sizeof(int64_t); |
| break; |
| } |
| case DW_EH_PE_udata8: |
| { |
| uint64_t result; |
| __builtin_memcpy (&result, p, sizeof(uint64_t)); |
| decoded = result; |
| p += sizeof(uint64_t); |
| break; |
| } |
| case DW_EH_PE_uleb128: |
| { |
| _uleb128_t value; |
| p = read_uleb128 (p, &value); |
| decoded = (_Unwind_Internal_Ptr)value; |
| break; |
| } |
| case DW_EH_PE_sleb128: |
| { |
| _sleb128_t value; |
| p = read_sleb128 (p, &value); |
| decoded = (_Unwind_Internal_Ptr)value; |
| break; |
| } |
| case DW_EH_PE_absptr: |
| __builtin_memcpy (&decoded, (const void *)p, sizeof(const void*)); |
| p += sizeof(void *); |
| break; |
| default: |
| abort (); |
| } |
| |
| if (decoded == 0) |
| { |
| *val = decoded; |
| return p; |
| } |
| |
| if ((encoding & 0x70) == DW_EH_PE_pcrel) |
| decoded += ((_Unwind_Internal_Ptr)origp); |
| else |
| decoded += base; |
| |
| if ((encoding & DW_EH_PE_indirect) != 0) |
| decoded = *(_Unwind_Internal_Ptr *)decoded; |
| } |
| *val = decoded; |
| return p; |
| } |
| |
| /* The rest of this code is really similar to gcc/unwind-c.c and |
| libjava/exception.cc. */ |
| |
| typedef struct |
| { |
| _Unwind_Ptr Start; |
| _Unwind_Ptr LPStart; |
| _Unwind_Ptr ttype_base; |
| const unsigned char *TType; |
| const unsigned char *action_table; |
| unsigned char ttype_encoding; |
| unsigned char call_site_encoding; |
| } lsda_header_info; |
| |
| static const unsigned char * |
| parse_lsda_header (struct _Unwind_Context *context, const unsigned char *p, |
| lsda_header_info *info) |
| { |
| _uleb128_t tmp; |
| unsigned char lpstart_encoding; |
| |
| info->Start = (context ? _Unwind_GetRegionStart (context) : 0); |
| |
| /* Find @LPStart, the base to which landing pad offsets are relative. */ |
| lpstart_encoding = *p++; |
| if (lpstart_encoding != DW_EH_PE_omit) |
| p = read_encoded_value (context, lpstart_encoding, p, &info->LPStart); |
| else |
| info->LPStart = info->Start; |
| |
| /* Find @TType, the base of the handler and exception spec type data. */ |
| info->ttype_encoding = *p++; |
| if (info->ttype_encoding != DW_EH_PE_omit) |
| { |
| p = read_uleb128 (p, &tmp); |
| info->TType = p + tmp; |
| } |
| else |
| info->TType = 0; |
| |
| /* The encoding and length of the call-site table; the action table |
| immediately follows. */ |
| info->call_site_encoding = *p++; |
| p = read_uleb128 (p, &tmp); |
| info->action_table = p + tmp; |
| |
| return p; |
| } |
| |
| /* The personality function is invoked when unwinding the stack due to |
| a panic. Its job is to find the cleanup and exception handlers to |
| run. We can't split the stack here, because we won't be able to |
| unwind from that split. */ |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| /* ARM EABI personality routines must also unwind the stack. */ |
| #define CONTINUE_UNWINDING \ |
| do \ |
| { \ |
| if (__gnu_unwind_frame (ue_header, context) != _URC_OK) \ |
| return _URC_FAILURE; \ |
| return _URC_CONTINUE_UNWIND; \ |
| } \ |
| while (0) |
| #else |
| #define CONTINUE_UNWINDING return _URC_CONTINUE_UNWIND |
| #endif |
| |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| #define PERSONALITY_FUNCTION __gccgo_personality_sj0 |
| #define __builtin_eh_return_data_regno(x) x |
| #else |
| #define PERSONALITY_FUNCTION __gccgo_personality_v0 |
| #endif |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| _Unwind_Reason_Code |
| PERSONALITY_FUNCTION (_Unwind_State, struct _Unwind_Exception *, |
| struct _Unwind_Context *) |
| __attribute__ ((no_split_stack, flatten)); |
| |
| _Unwind_Reason_Code |
| PERSONALITY_FUNCTION (_Unwind_State state, |
| struct _Unwind_Exception * ue_header, |
| struct _Unwind_Context * context) |
| #else |
| _Unwind_Reason_Code |
| PERSONALITY_FUNCTION (int, _Unwind_Action, _Unwind_Exception_Class, |
| struct _Unwind_Exception *, struct _Unwind_Context *) |
| __attribute__ ((no_split_stack, flatten)); |
| |
| _Unwind_Reason_Code |
| PERSONALITY_FUNCTION (int version, |
| _Unwind_Action actions, |
| _Unwind_Exception_Class exception_class, |
| struct _Unwind_Exception *ue_header, |
| struct _Unwind_Context *context) |
| #endif |
| { |
| lsda_header_info info; |
| const unsigned char *language_specific_data, *p, *action_record; |
| _Unwind_Ptr landing_pad, ip; |
| int ip_before_insn = 0; |
| _Bool is_foreign; |
| G *g; |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| _Unwind_Action actions; |
| |
| switch (state & _US_ACTION_MASK) |
| { |
| case _US_VIRTUAL_UNWIND_FRAME: |
| actions = _UA_SEARCH_PHASE; |
| break; |
| |
| case _US_UNWIND_FRAME_STARTING: |
| actions = _UA_CLEANUP_PHASE; |
| if (!(state & _US_FORCE_UNWIND) |
| && ue_header->barrier_cache.sp == _Unwind_GetGR(context, 13)) |
| actions |= _UA_HANDLER_FRAME; |
| break; |
| |
| case _US_UNWIND_FRAME_RESUME: |
| CONTINUE_UNWINDING; |
| break; |
| |
| default: |
| abort(); |
| } |
| actions |= state & _US_FORCE_UNWIND; |
| |
| is_foreign = 0; |
| |
| /* The dwarf unwinder assumes the context structure holds things like the |
| function and LSDA pointers. The ARM implementation caches these in |
| the exception header (UCB). To avoid rewriting everything we make the |
| virtual IP register point at the UCB. */ |
| ip = (_Unwind_Ptr) ue_header; |
| _Unwind_SetGR (context, 12, ip); |
| #else |
| if (version != 1) |
| return _URC_FATAL_PHASE1_ERROR; |
| |
| is_foreign = exception_class != __go_exception_class; |
| #endif |
| |
| language_specific_data = (const unsigned char *) |
| _Unwind_GetLanguageSpecificData (context); |
| |
| /* If no LSDA, then there are no handlers or cleanups. */ |
| if (! language_specific_data) |
| CONTINUE_UNWINDING; |
| |
| /* Parse the LSDA header. */ |
| p = parse_lsda_header (context, language_specific_data, &info); |
| #ifdef HAVE_GETIPINFO |
| ip = _Unwind_GetIPInfo (context, &ip_before_insn); |
| #else |
| ip = _Unwind_GetIP (context); |
| #endif |
| if (! ip_before_insn) |
| --ip; |
| landing_pad = 0; |
| action_record = NULL; |
| |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| /* The given "IP" is an index into the call-site table, with two |
| exceptions -- -1 means no-action, and 0 means terminate. But |
| since we're using uleb128 values, we've not got random access |
| to the array. */ |
| if ((int) ip <= 0) |
| return _URC_CONTINUE_UNWIND; |
| else |
| { |
| _uleb128_t cs_lp, cs_action; |
| do |
| { |
| p = read_uleb128 (p, &cs_lp); |
| p = read_uleb128 (p, &cs_action); |
| } |
| while (--ip); |
| |
| /* Can never have null landing pad for sjlj -- that would have |
| been indicated by a -1 call site index. */ |
| landing_pad = (_Unwind_Ptr)cs_lp + 1; |
| if (cs_action) |
| action_record = info.action_table + cs_action - 1; |
| goto found_something; |
| } |
| #else |
| /* Search the call-site table for the action associated with this IP. */ |
| while (p < info.action_table) |
| { |
| _Unwind_Ptr cs_start, cs_len, cs_lp; |
| _uleb128_t cs_action; |
| |
| /* Note that all call-site encodings are "absolute" displacements. */ |
| p = read_encoded_value (0, info.call_site_encoding, p, &cs_start); |
| p = read_encoded_value (0, info.call_site_encoding, p, &cs_len); |
| p = read_encoded_value (0, info.call_site_encoding, p, &cs_lp); |
| p = read_uleb128 (p, &cs_action); |
| |
| /* The table is sorted, so if we've passed the ip, stop. */ |
| if (ip < info.Start + cs_start) |
| p = info.action_table; |
| else if (ip < info.Start + cs_start + cs_len) |
| { |
| if (cs_lp) |
| landing_pad = info.LPStart + cs_lp; |
| if (cs_action) |
| action_record = info.action_table + cs_action - 1; |
| goto found_something; |
| } |
| } |
| #endif |
| |
| /* IP is not in table. No associated cleanups. */ |
| CONTINUE_UNWINDING; |
| |
| found_something: |
| if (landing_pad == 0) |
| { |
| /* IP is present, but has a null landing pad. |
| No handler to be run. */ |
| CONTINUE_UNWINDING; |
| } |
| |
| if (actions & _UA_SEARCH_PHASE) |
| { |
| if (action_record == 0) |
| { |
| /* This indicates a cleanup rather than an exception |
| handler. */ |
| CONTINUE_UNWINDING; |
| } |
| |
| return _URC_HANDLER_FOUND; |
| } |
| |
| /* It's possible for g to be NULL here for an exception thrown by a |
| language other than Go. */ |
| g = runtime_g (); |
| if (g == NULL) |
| { |
| if (!is_foreign) |
| abort (); |
| } |
| else |
| { |
| g->exception = ue_header; |
| g->isforeign = is_foreign; |
| } |
| |
| _Unwind_SetGR (context, __builtin_eh_return_data_regno (0), |
| (_Unwind_Ptr) ue_header); |
| _Unwind_SetGR (context, __builtin_eh_return_data_regno (1), 0); |
| _Unwind_SetIP (context, landing_pad); |
| return _URC_INSTALL_CONTEXT; |
| } |