...

Text file src/runtime/cgo/gcc_signal_ios_arm64.c

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

View as plain text