...

Text file src/pkg/runtime/cgo/gcc_signal_darwin_armx.c

     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	}

View as plain text