Text file src/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