summaryrefslogtreecommitdiffstats
path: root/src/runtime/cgo/gcc_signal_ios_arm64.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/cgo/gcc_signal_ios_arm64.c')
-rw-r--r--src/runtime/cgo/gcc_signal_ios_arm64.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/runtime/cgo/gcc_signal_ios_arm64.c b/src/runtime/cgo/gcc_signal_ios_arm64.c
new file mode 100644
index 0000000..6519edd
--- /dev/null
+++ b/src/runtime/cgo/gcc_signal_ios_arm64.c
@@ -0,0 +1,213 @@
+// Copyright 2015 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.
+
+// Emulation of the Unix signal SIGSEGV.
+//
+// On iOS, Go tests and apps under development are run by lldb.
+// The debugger uses a task-level exception handler to intercept signals.
+// Despite having a 'handle' mechanism like gdb, lldb will not allow a
+// SIGSEGV to pass to the running program. For Go, this means we cannot
+// generate a panic, which cannot be recovered, and so tests fail.
+//
+// We work around this by registering a thread-level mach exception handler
+// and intercepting EXC_BAD_ACCESS. The kernel offers thread handlers a
+// chance to resolve exceptions before the task handler, so we can generate
+// the panic and avoid lldb's SIGSEGV handler.
+//
+// The dist tool enables this by build flag when testing.
+
+// +build lldb
+
+#include <limits.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <mach/arm/thread_status.h>
+#include <mach/exception_types.h>
+#include <mach/mach.h>
+#include <mach/mach_init.h>
+#include <mach/mach_port.h>
+#include <mach/thread_act.h>
+#include <mach/thread_status.h>
+
+#include "libcgo.h"
+#include "libcgo_unix.h"
+
+void xx_cgo_panicmem(void);
+uintptr_t x_cgo_panicmem = (uintptr_t)xx_cgo_panicmem;
+
+static pthread_mutex_t mach_exception_handler_port_set_mu;
+static mach_port_t mach_exception_handler_port_set = MACH_PORT_NULL;
+
+kern_return_t
+catch_exception_raise(
+ mach_port_t exception_port,
+ mach_port_t thread,
+ mach_port_t task,
+ exception_type_t exception,
+ exception_data_t code_vector,
+ mach_msg_type_number_t code_count)
+{
+ kern_return_t ret;
+ arm_unified_thread_state_t thread_state;
+ mach_msg_type_number_t state_count = ARM_UNIFIED_THREAD_STATE_COUNT;
+
+ // Returning KERN_SUCCESS intercepts the exception.
+ //
+ // Returning KERN_FAILURE lets the exception fall through to the
+ // next handler, which is the standard signal emulation code
+ // registered on the task port.
+
+ if (exception != EXC_BAD_ACCESS) {
+ return KERN_FAILURE;
+ }
+
+ ret = thread_get_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t)&thread_state, &state_count);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: thread_get_state failed: %d\n", ret);
+ abort();
+ }
+
+ // Bounce call to sigpanic through asm that makes it look like
+ // we call sigpanic directly from the faulting code.
+#ifdef __arm64__
+ thread_state.ts_64.__x[1] = thread_state.ts_64.__lr;
+ thread_state.ts_64.__x[2] = thread_state.ts_64.__pc;
+ thread_state.ts_64.__pc = x_cgo_panicmem;
+#else
+ thread_state.ts_32.__r[1] = thread_state.ts_32.__lr;
+ thread_state.ts_32.__r[2] = thread_state.ts_32.__pc;
+ thread_state.ts_32.__pc = x_cgo_panicmem;
+#endif
+
+ if (0) {
+ // Useful debugging logic when panicmem is broken.
+ //
+ // Sends the first SIGSEGV and lets lldb catch the
+ // second one, avoiding a loop that locks up iOS
+ // devices requiring a hard reboot.
+ fprintf(stderr, "runtime/cgo: caught exc_bad_access\n");
+ fprintf(stderr, "__lr = %llx\n", thread_state.ts_64.__lr);
+ fprintf(stderr, "__pc = %llx\n", thread_state.ts_64.__pc);
+ static int pass1 = 0;
+ if (pass1) {
+ return KERN_FAILURE;
+ }
+ pass1 = 1;
+ }
+
+ ret = thread_set_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t)&thread_state, state_count);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: thread_set_state failed: %d\n", ret);
+ abort();
+ }
+
+ return KERN_SUCCESS;
+}
+
+void
+darwin_arm_init_thread_exception_port()
+{
+ // Called by each new OS thread to bind its EXC_BAD_ACCESS exception
+ // to mach_exception_handler_port_set.
+ int ret;
+ mach_port_t port = MACH_PORT_NULL;
+
+ ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: mach_port_allocate failed: %d\n", ret);
+ abort();
+ }
+ ret = mach_port_insert_right(
+ mach_task_self(),
+ port,
+ port,
+ MACH_MSG_TYPE_MAKE_SEND);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: mach_port_insert_right failed: %d\n", ret);
+ abort();
+ }
+
+ ret = thread_set_exception_ports(
+ mach_thread_self(),
+ EXC_MASK_BAD_ACCESS,
+ port,
+ EXCEPTION_DEFAULT,
+ THREAD_STATE_NONE);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: thread_set_exception_ports failed: %d\n", ret);
+ abort();
+ }
+
+ ret = pthread_mutex_lock(&mach_exception_handler_port_set_mu);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: pthread_mutex_lock failed: %d\n", ret);
+ abort();
+ }
+ ret = mach_port_move_member(
+ mach_task_self(),
+ port,
+ mach_exception_handler_port_set);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: mach_port_move_member failed: %d\n", ret);
+ abort();
+ }
+ ret = pthread_mutex_unlock(&mach_exception_handler_port_set_mu);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: pthread_mutex_unlock failed: %d\n", ret);
+ abort();
+ }
+}
+
+static void*
+mach_exception_handler(void *port)
+{
+ // Calls catch_exception_raise.
+ extern boolean_t exc_server();
+ mach_msg_server(exc_server, 2048, (mach_port_t)port, 0);
+ abort(); // never returns
+}
+
+void
+darwin_arm_init_mach_exception_handler()
+{
+ pthread_mutex_init(&mach_exception_handler_port_set_mu, NULL);
+
+ // Called once per process to initialize a mach port server, listening
+ // for EXC_BAD_ACCESS thread exceptions.
+ int ret;
+ pthread_t thr = NULL;
+ pthread_attr_t attr;
+ sigset_t ign, oset;
+
+ ret = mach_port_allocate(
+ mach_task_self(),
+ MACH_PORT_RIGHT_PORT_SET,
+ &mach_exception_handler_port_set);
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: mach_port_allocate failed for port_set: %d\n", ret);
+ abort();
+ }
+
+ // Block all signals to the exception handler thread
+ sigfillset(&ign);
+ pthread_sigmask(SIG_SETMASK, &ign, &oset);
+
+ // Start a thread to handle exceptions.
+ uintptr_t port_set = (uintptr_t)mach_exception_handler_port_set;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ret = _cgo_try_pthread_create(&thr, &attr, mach_exception_handler, (void*)port_set);
+
+ pthread_sigmask(SIG_SETMASK, &oset, nil);
+
+ if (ret) {
+ fprintf(stderr, "runtime/cgo: pthread_create failed: %d\n", ret);
+ abort();
+ }
+ pthread_attr_destroy(&attr);
+}