Home | History | Annotate | Download | only in cgo
      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 // The dist tool enables this by build flag when testing.
     19 
     20 // +build lldb
     21 // +build darwin
     22 // +build arm arm64
     23 
     24 #include <limits.h>
     25 #include <pthread.h>
     26 #include <stdio.h>
     27 #include <signal.h>
     28 #include <stdlib.h>
     29 #include <unistd.h>
     30 
     31 #include <mach/arm/thread_status.h>
     32 #include <mach/exception_types.h>
     33 #include <mach/mach.h>
     34 #include <mach/mach_init.h>
     35 #include <mach/mach_port.h>
     36 #include <mach/thread_act.h>
     37 #include <mach/thread_status.h>
     38 
     39 #include "libcgo.h"
     40 #include "libcgo_unix.h"
     41 
     42 void xx_cgo_panicmem(void);
     43 uintptr_t x_cgo_panicmem = (uintptr_t)xx_cgo_panicmem;
     44 
     45 static pthread_mutex_t mach_exception_handler_port_set_mu;
     46 static mach_port_t mach_exception_handler_port_set = MACH_PORT_NULL;
     47 
     48 kern_return_t
     49 catch_exception_raise(
     50 	mach_port_t exception_port,
     51 	mach_port_t thread,
     52 	mach_port_t task,
     53 	exception_type_t exception,
     54 	exception_data_t code_vector,
     55 	mach_msg_type_number_t code_count)
     56 {
     57 	kern_return_t ret;
     58 	arm_unified_thread_state_t thread_state;
     59 	mach_msg_type_number_t state_count = ARM_UNIFIED_THREAD_STATE_COUNT;
     60 
     61 	// Returning KERN_SUCCESS intercepts the exception.
     62 	//
     63 	// Returning KERN_FAILURE lets the exception fall through to the
     64 	// next handler, which is the standard signal emulation code
     65 	// registered on the task port.
     66 
     67 	if (exception != EXC_BAD_ACCESS) {
     68 		return KERN_FAILURE;
     69 	}
     70 
     71 	ret = thread_get_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t)&thread_state, &state_count);
     72 	if (ret) {
     73 		fprintf(stderr, "runtime/cgo: thread_get_state failed: %d\n", ret);
     74 		abort();
     75 	}
     76 
     77 	// Bounce call to sigpanic through asm that makes it look like
     78 	// we call sigpanic directly from the faulting code.
     79 #ifdef __arm64__
     80 	thread_state.ts_64.__x[1] = thread_state.ts_64.__lr;
     81 	thread_state.ts_64.__x[2] = thread_state.ts_64.__pc;
     82 	thread_state.ts_64.__pc = x_cgo_panicmem;
     83 #else
     84 	thread_state.ts_32.__r[1] = thread_state.ts_32.__lr;
     85 	thread_state.ts_32.__r[2] = thread_state.ts_32.__pc;
     86 	thread_state.ts_32.__pc = x_cgo_panicmem;
     87 #endif
     88 
     89 	if (0) {
     90 		// Useful debugging logic when panicmem is broken.
     91 		//
     92 		// Sends the first SIGSEGV and lets lldb catch the
     93 		// second one, avoiding a loop that locks up iOS
     94 		// devices requiring a hard reboot.
     95 		fprintf(stderr, "runtime/cgo: caught exc_bad_access\n");
     96 		fprintf(stderr, "__lr = %llx\n", thread_state.ts_64.__lr);
     97 		fprintf(stderr, "__pc = %llx\n", thread_state.ts_64.__pc);
     98 		static int pass1 = 0;
     99 		if (pass1) {
    100 			return KERN_FAILURE;
    101 		}
    102 		pass1 = 1;
    103 	}
    104 
    105 	ret = thread_set_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t)&thread_state, state_count);
    106 	if (ret) {
    107 		fprintf(stderr, "runtime/cgo: thread_set_state failed: %d\n", ret);
    108 		abort();
    109 	}
    110 
    111 	return KERN_SUCCESS;
    112 }
    113 
    114 void
    115 darwin_arm_init_thread_exception_port()
    116 {
    117 	// Called by each new OS thread to bind its EXC_BAD_ACCESS exception
    118 	// to mach_exception_handler_port_set.
    119 	int ret;
    120 	mach_port_t port = MACH_PORT_NULL;
    121 
    122 	ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
    123 	if (ret) {
    124 		fprintf(stderr, "runtime/cgo: mach_port_allocate failed: %d\n", ret);
    125 		abort();
    126 	}
    127 	ret = mach_port_insert_right(
    128 		mach_task_self(),
    129 		port,
    130 		port,
    131 		MACH_MSG_TYPE_MAKE_SEND);
    132 	if (ret) {
    133 		fprintf(stderr, "runtime/cgo: mach_port_insert_right failed: %d\n", ret);
    134 		abort();
    135 	}
    136 
    137 	ret = thread_set_exception_ports(
    138 		mach_thread_self(),
    139 		EXC_MASK_BAD_ACCESS,
    140 		port,
    141 		EXCEPTION_DEFAULT,
    142 		THREAD_STATE_NONE);
    143 	if (ret) {
    144 		fprintf(stderr, "runtime/cgo: thread_set_exception_ports failed: %d\n", ret);
    145 		abort();
    146 	}
    147 
    148 	ret = pthread_mutex_lock(&mach_exception_handler_port_set_mu);
    149 	if (ret) {
    150 		fprintf(stderr, "runtime/cgo: pthread_mutex_lock failed: %d\n", ret);
    151 		abort();
    152 	}
    153 	ret = mach_port_move_member(
    154 		mach_task_self(),
    155 		port,
    156 		mach_exception_handler_port_set);
    157 	if (ret) {
    158 		fprintf(stderr, "runtime/cgo: mach_port_move_member failed: %d\n", ret);
    159 		abort();
    160 	}
    161 	ret = pthread_mutex_unlock(&mach_exception_handler_port_set_mu);
    162 	if (ret) {
    163 		fprintf(stderr, "runtime/cgo: pthread_mutex_unlock failed: %d\n", ret);
    164 		abort();
    165 	}
    166 }
    167 
    168 static void*
    169 mach_exception_handler(void *port)
    170 {
    171 	// Calls catch_exception_raise.
    172 	extern boolean_t exc_server();
    173 	mach_msg_server(exc_server, 2048, (mach_port_t)port, 0);
    174 	abort(); // never returns
    175 }
    176 
    177 void
    178 darwin_arm_init_mach_exception_handler()
    179 {
    180 	pthread_mutex_init(&mach_exception_handler_port_set_mu, NULL);
    181 
    182 	// Called once per process to initialize a mach port server, listening
    183 	// for EXC_BAD_ACCESS thread exceptions.
    184 	int ret;
    185 	pthread_t thr = NULL;
    186 	pthread_attr_t attr;
    187 	sigset_t ign, oset;
    188 
    189 	ret = mach_port_allocate(
    190 		mach_task_self(),
    191 		MACH_PORT_RIGHT_PORT_SET,
    192 		&mach_exception_handler_port_set);
    193 	if (ret) {
    194 		fprintf(stderr, "runtime/cgo: mach_port_allocate failed for port_set: %d\n", ret);
    195 		abort();
    196 	}
    197 
    198 	// Block all signals to the exception handler thread
    199 	sigfillset(&ign);
    200 	pthread_sigmask(SIG_SETMASK, &ign, &oset);
    201 
    202 	// Start a thread to handle exceptions.
    203 	uintptr_t port_set = (uintptr_t)mach_exception_handler_port_set;
    204 	pthread_attr_init(&attr);
    205 	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    206 	ret = _cgo_try_pthread_create(&thr, &attr, mach_exception_handler, (void*)port_set);
    207 
    208 	pthread_sigmask(SIG_SETMASK, &oset, nil);
    209 
    210 	if (ret) {
    211 		fprintf(stderr, "runtime/cgo: pthread_create failed: %d\n", ret);
    212 		abort();
    213 	}
    214 	pthread_attr_destroy(&attr);
    215 }
    216