|
| 1 | +// Copyright 2015 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +// Emulation of the Unix signal SIGSEGV. |
| 6 | +// |
| 7 | +// On iOS, Go tests and apps under development are run by lldb. |
| 8 | +// The debugger uses a task-level exception handler to intercept signals. |
| 9 | +// Despite having a 'handle' mechanism like gdb, lldb will not allow a |
| 10 | +// SIGSEGV to pass to the running program. For Go, this means we cannot |
| 11 | +// generate a panic, which cannot be recovered, and so tests fail. |
| 12 | +// |
| 13 | +// We work around this by registering a thread-level mach exception handler |
| 14 | +// and intercepting EXC_BAD_ACCESS. The kernel offers thread handlers a |
| 15 | +// chance to resolve exceptions before the task handler, so we can generate |
| 16 | +// the panic and avoid lldb's SIGSEGV handler. |
| 17 | +// |
| 18 | +// If you want to debug a segfault under lldb, compile the standard library |
| 19 | +// with the build tag lldb: |
| 20 | +// |
| 21 | +// go test -tags lldb -installsuffix lldb |
| 22 | + |
| 23 | +// +build darwin,arm,!lldb |
| 24 | + |
| 25 | +// TODO(crawshaw): darwin,arm64,!lldb |
| 26 | + |
| 27 | +#include <limits.h> |
| 28 | +#include <pthread.h> |
| 29 | +#include <stdio.h> |
| 30 | +#include <signal.h> |
| 31 | +#include <stdlib.h> |
| 32 | +#include <unistd.h> |
| 33 | + |
| 34 | +#include <mach/arm/thread_status.h> |
| 35 | +#include <mach/exception_types.h> |
| 36 | +#include <mach/mach.h> |
| 37 | +#include <mach/mach_init.h> |
| 38 | +#include <mach/mach_port.h> |
| 39 | +#include <mach/thread_act.h> |
| 40 | +#include <mach/thread_status.h> |
| 41 | + |
| 42 | +#include "libcgo.h" |
| 43 | + |
| 44 | +uintptr_t x_cgo_panicmem; |
| 45 | + |
| 46 | +static pthread_mutex_t mach_exception_handler_port_set_mu; |
| 47 | +static mach_port_t mach_exception_handler_port_set = MACH_PORT_NULL; |
| 48 | + |
| 49 | +kern_return_t |
| 50 | +catch_exception_raise( |
| 51 | + mach_port_t exception_port, |
| 52 | + mach_port_t thread, |
| 53 | + mach_port_t task, |
| 54 | + exception_type_t exception, |
| 55 | + exception_data_t code_vector, |
| 56 | + mach_msg_type_number_t code_count) |
| 57 | +{ |
| 58 | + kern_return_t ret; |
| 59 | + arm_unified_thread_state_t thread_state; |
| 60 | + mach_msg_type_number_t state_count = ARM_UNIFIED_THREAD_STATE_COUNT; |
| 61 | + |
| 62 | + // Returning KERN_SUCCESS intercepts the exception. |
| 63 | + // |
| 64 | + // Returning KERN_FAILURE lets the exception fall through to the |
| 65 | + // next handler, which is the standard signal emulation code |
| 66 | + // registered on the task port. |
| 67 | + |
| 68 | + if (exception != EXC_BAD_ACCESS) { |
| 69 | + return KERN_FAILURE; |
| 70 | + } |
| 71 | + |
| 72 | + ret = thread_get_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t)&thread_state, &state_count); |
| 73 | + if (ret) { |
| 74 | + fprintf(stderr, "runtime/cgo: thread_get_state failed: %d\n", ret); |
| 75 | + abort(); |
| 76 | + } |
| 77 | + |
| 78 | + // Bounce call to sigpanic through asm that makes it look like |
| 79 | + // we call sigpanic directly from the faulting code. |
| 80 | + thread_state.ts_32.__r[1] = thread_state.ts_32.__lr; |
| 81 | + thread_state.ts_32.__r[2] = thread_state.ts_32.__pc; |
| 82 | + thread_state.ts_32.__pc = x_cgo_panicmem; |
| 83 | + |
| 84 | + ret = thread_set_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t)&thread_state, state_count); |
| 85 | + if (ret) { |
| 86 | + fprintf(stderr, "runtime/cgo: thread_set_state failed: %d\n", ret); |
| 87 | + abort(); |
| 88 | + } |
| 89 | + |
| 90 | + return KERN_SUCCESS; |
| 91 | +} |
| 92 | + |
| 93 | +void |
| 94 | +darwin_arm_init_thread_exception_port() |
| 95 | +{ |
| 96 | + // Called by each new OS thread to bind its EXC_BAD_ACCESS exception |
| 97 | + // to mach_exception_handler_port_set. |
| 98 | + int ret; |
| 99 | + mach_port_t port = MACH_PORT_NULL; |
| 100 | + |
| 101 | + ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); |
| 102 | + if (ret) { |
| 103 | + fprintf(stderr, "runtime/cgo: mach_port_allocate failed: %d\n", ret); |
| 104 | + abort(); |
| 105 | + } |
| 106 | + ret = mach_port_insert_right( |
| 107 | + mach_task_self(), |
| 108 | + port, |
| 109 | + port, |
| 110 | + MACH_MSG_TYPE_MAKE_SEND); |
| 111 | + if (ret) { |
| 112 | + fprintf(stderr, "runtime/cgo: mach_port_insert_right failed: %d\n", ret); |
| 113 | + abort(); |
| 114 | + } |
| 115 | + |
| 116 | + ret = thread_set_exception_ports( |
| 117 | + mach_thread_self(), |
| 118 | + EXC_MASK_BAD_ACCESS, |
| 119 | + port, |
| 120 | + EXCEPTION_DEFAULT, |
| 121 | + THREAD_STATE_NONE); |
| 122 | + if (ret) { |
| 123 | + fprintf(stderr, "runtime/cgo: thread_set_exception_ports failed: %d\n", ret); |
| 124 | + abort(); |
| 125 | + } |
| 126 | + |
| 127 | + ret = pthread_mutex_lock(&mach_exception_handler_port_set_mu); |
| 128 | + if (ret) { |
| 129 | + fprintf(stderr, "runtime/cgo: pthread_mutex_lock failed: %d\n", ret); |
| 130 | + abort(); |
| 131 | + } |
| 132 | + ret = mach_port_move_member( |
| 133 | + mach_task_self(), |
| 134 | + port, |
| 135 | + mach_exception_handler_port_set); |
| 136 | + if (ret) { |
| 137 | + fprintf(stderr, "runtime/cgo: mach_port_move_member failed: %d\n", ret); |
| 138 | + abort(); |
| 139 | + } |
| 140 | + ret = pthread_mutex_unlock(&mach_exception_handler_port_set_mu); |
| 141 | + if (ret) { |
| 142 | + fprintf(stderr, "runtime/cgo: pthread_mutex_unlock failed: %d\n", ret); |
| 143 | + abort(); |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +static void* |
| 148 | +mach_exception_handler(void *port) |
| 149 | +{ |
| 150 | + // Calls catch_exception_raise. |
| 151 | + extern boolean_t exc_server(); |
| 152 | + mach_msg_server(exc_server, 2048, (mach_port_t)port, 0); |
| 153 | + abort(); // never returns |
| 154 | +} |
| 155 | + |
| 156 | +void |
| 157 | +darwin_arm_init_mach_exception_handler() |
| 158 | +{ |
| 159 | + pthread_mutex_init(&mach_exception_handler_port_set_mu, NULL); |
| 160 | + |
| 161 | + // Called once per process to initialize a mach port server, listening |
| 162 | + // for EXC_BAD_ACCESS thread exceptions. |
| 163 | + int ret; |
| 164 | + pthread_t thr = NULL; |
| 165 | + pthread_attr_t attr; |
| 166 | + |
| 167 | + ret = mach_port_allocate( |
| 168 | + mach_task_self(), |
| 169 | + MACH_PORT_RIGHT_PORT_SET, |
| 170 | + &mach_exception_handler_port_set); |
| 171 | + if (ret) { |
| 172 | + fprintf(stderr, "runtime/cgo: mach_port_allocate failed for port_set: %d\n", ret); |
| 173 | + abort(); |
| 174 | + } |
| 175 | + |
| 176 | + // Start a thread to handle exceptions. |
| 177 | + pthread_attr_init(&attr); |
| 178 | + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| 179 | + ret = pthread_create(&thr, &attr, mach_exception_handler, (void*)mach_exception_handler_port_set); |
| 180 | + if (ret) { |
| 181 | + fprintf(stderr, "runtime/cgo: pthread_create failed: %d\n", ret); |
| 182 | + abort(); |
| 183 | + } |
| 184 | + pthread_attr_destroy(&attr); |
| 185 | +} |
0 commit comments