summaryrefslogtreecommitdiffstats
path: root/src/runtime/cgo/gcc_signal_ios_arm64.c
blob: 87055e9422432a762ad098cbde46de0109c877d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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.

//go: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);
}