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