1103d8f40f11f6a1dd1c2bb6302fd26a45e9e990
[sbcl.git] / src / runtime / darwin-os.c
1 /*
2  * This is the Darwin incarnation of OS-dependent routines. See also
3  * "bsd-os.c".
4  */
5
6 /*
7  * This software is part of the SBCL system. See the README file for
8  * more information.
9  *
10  * This software is derived from the CMU CL system, which was
11  * written at Carnegie Mellon University and released into the
12  * public domain. The software is in the public domain and is
13  * provided with absolutely no warranty. See the COPYING and CREDITS
14  * files for more information.
15  */
16
17 #include "thread.h"
18 #include "sbcl.h"
19 #include "globals.h"
20 #include "runtime.h"
21 #include <signal.h>
22 #include <limits.h>
23 #include <mach-o/dyld.h>
24 #include <stdio.h>
25 #include <errno.h>
26 #include <dlfcn.h>
27
28 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
29 #include <mach/mach.h>
30 #include <libkern/OSAtomic.h>
31 #include <stdlib.h>
32 #endif
33
34 char *
35 os_get_runtime_executable_path(int external)
36 {
37     char path[PATH_MAX + 1];
38     uint32_t size = sizeof(path);
39
40     if (_NSGetExecutablePath(path, &size) == -1)
41         return NULL;
42
43     return copied_string(path);
44 }
45
46 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
47
48 /* exc_server handles mach exception messages from the kernel and
49  * calls catch exception raise. We use the system-provided
50  * mach_msg_server, which, I assume, calls exc_server in a loop.
51  *
52  */
53 extern boolean_t exc_server();
54
55 void *
56 mach_exception_handler(void *port)
57 {
58   mach_msg_server(exc_server, 2048, (mach_port_t) port, 0);
59   /* mach_msg_server should never return, but it should dispatch mach
60    * exceptions to our catch_exception_raise function
61    */
62   lose("mach_msg_server returned");
63 }
64
65 /* Sets up the thread that will listen for mach exceptions. note that
66    the exception handlers will be run on this thread. This is
67    different from the BSD-style signal handling situation in which the
68    signal handlers run in the relevant thread directly. */
69
70 mach_port_t mach_exception_handler_port_set = MACH_PORT_NULL;
71 mach_port_t current_mach_task = MACH_PORT_NULL;
72
73 pthread_t
74 setup_mach_exception_handling_thread()
75 {
76     kern_return_t ret;
77     pthread_t mach_exception_handling_thread = NULL;
78     pthread_attr_t attr;
79
80     current_mach_task = mach_task_self();
81
82     /* allocate a mach_port for this process */
83     ret = mach_port_allocate(current_mach_task,
84                              MACH_PORT_RIGHT_PORT_SET,
85                              &mach_exception_handler_port_set);
86
87     /* create the thread that will receive the mach exceptions */
88
89     FSHOW((stderr, "Creating mach_exception_handler thread!\n"));
90
91     pthread_attr_init(&attr);
92     pthread_create(&mach_exception_handling_thread,
93                    &attr,
94                    mach_exception_handler,
95                    (void*) mach_exception_handler_port_set);
96     pthread_attr_destroy(&attr);
97
98     return mach_exception_handling_thread;
99 }
100
101 struct exception_port_record
102 {
103     struct thread * thread;
104     struct exception_port_record * next;
105 };
106
107 static OSQueueHead free_records = OS_ATOMIC_QUEUE_INIT;
108
109 /* We can't depend on arbitrary addresses to be accepted as mach port
110  * names, particularly not on 64-bit platforms.  Instead, we allocate
111  * records that point to the thread struct, and loop until one is accepted
112  * as a port name.
113  *
114  * Threads are mapped to exception ports with a slot in the thread struct,
115  * and exception ports are casted to records that point to the corresponding
116  * thread.
117  *
118  * The lock-free free-list above is used as a cheap fast path.
119  */
120 static mach_port_t
121 find_receive_port(struct thread * thread)
122 {
123     mach_port_t ret;
124     struct exception_port_record * curr, * to_free = NULL;
125     unsigned long i;
126     for (i = 1;; i++) {
127         curr = OSAtomicDequeue(&free_records, offsetof(struct exception_port_record, next));
128         if (curr == NULL) {
129             curr = calloc(1, sizeof(struct exception_port_record));
130             if (curr == NULL)
131                 lose("unable to allocate exception_port_record\n");
132         }
133 #ifdef LISP_FEATURE_X86_64
134         if ((mach_port_t)curr != (unsigned long)curr)
135             goto skip;
136 #endif
137
138         if (mach_port_allocate_name(current_mach_task,
139                                     MACH_PORT_RIGHT_RECEIVE,
140                                     (mach_port_t)curr))
141             goto skip;
142         curr->thread = thread;
143         ret = (mach_port_t)curr;
144         break;
145         skip:
146         curr->next = to_free;
147         to_free = curr;
148         if ((i % 1024) == 0)
149             FSHOW((stderr, "Looped %lu times trying to allocate an exception port\n"));
150     }
151     while (to_free != NULL) {
152         struct exception_port_record * current = to_free;
153         to_free = to_free->next;
154         free(current);
155     }
156
157     FSHOW((stderr, "Allocated exception port %x for thread %p\n", ret, thread));
158
159     return ret;
160 }
161
162 /* tell the kernel that we want EXC_BAD_ACCESS exceptions sent to the
163    exception port (which is being listened to do by the mach
164    exception handling thread). */
165 kern_return_t
166 mach_lisp_thread_init(struct thread * thread)
167 {
168     kern_return_t ret;
169     mach_port_t current_mach_thread, thread_exception_port;
170
171     /* allocate a named port for the thread */
172     thread_exception_port
173         = thread->mach_port_name
174         = find_receive_port(thread);
175
176     /* establish the right for the thread_exception_port to send messages */
177     ret = mach_port_insert_right(current_mach_task,
178                                  thread_exception_port,
179                                  thread_exception_port,
180                                  MACH_MSG_TYPE_MAKE_SEND);
181     if (ret) {
182         lose("mach_port_insert_right failed with return_code %d\n", ret);
183     }
184
185     current_mach_thread = mach_thread_self();
186     ret = thread_set_exception_ports(current_mach_thread,
187                                      EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
188                                      thread_exception_port,
189                                      EXCEPTION_DEFAULT,
190                                      THREAD_STATE_NONE);
191     if (ret) {
192         lose("thread_set_exception_ports failed with return_code %d\n", ret);
193     }
194
195     ret = mach_port_deallocate (current_mach_task, current_mach_thread);
196     if (ret) {
197         lose("mach_port_deallocate failed with return_code %d\n", ret);
198     }
199
200     ret = mach_port_move_member(current_mach_task,
201                                 thread_exception_port,
202                                 mach_exception_handler_port_set);
203     if (ret) {
204         lose("mach_port_move_member failed with return_code %d\n", ret);
205     }
206
207     return ret;
208 }
209
210 kern_return_t
211 mach_lisp_thread_destroy(struct thread *thread) {
212     kern_return_t ret;
213     mach_port_t port = thread->mach_port_name;
214     FSHOW((stderr, "Deallocating mach port %x\n", port));
215     mach_port_move_member(current_mach_task, port, MACH_PORT_NULL);
216     mach_port_deallocate(current_mach_task, port);
217
218     ret = mach_port_destroy(current_mach_task, port);
219     ((struct exception_port_record*)port)->thread = NULL;
220     OSAtomicEnqueue(&free_records, (void*)port, offsetof(struct exception_port_record, next));
221
222     return ret;
223 }
224
225 void
226 setup_mach_exceptions() {
227     setup_mach_exception_handling_thread();
228     mach_lisp_thread_init(all_threads);
229 }
230
231 pid_t
232 mach_fork() {
233     pid_t pid = fork();
234     if (pid == 0) {
235         setup_mach_exceptions();
236         return pid;
237     } else {
238         return pid;
239     }
240 }
241 #endif
242
243 void darwin_init(void)
244 {
245 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
246     setup_mach_exception_handling_thread();
247 #endif
248 }
249
250
251 #ifdef LISP_FEATURE_SB_THREAD
252
253 inline void
254 os_sem_init(os_sem_t *sem, unsigned int value)
255 {
256     if (KERN_SUCCESS!=semaphore_create(current_mach_task, sem, SYNC_POLICY_FIFO, (int)value))
257         lose("os_sem_init(%p): %s", sem, strerror(errno));
258 }
259
260 inline void
261 os_sem_wait(os_sem_t *sem, char *what)
262 {
263     kern_return_t ret;
264   restart:
265     FSHOW((stderr, "%s: os_sem_wait(%p)\n", what, sem));
266     ret = semaphore_wait(*sem);
267     FSHOW((stderr, "%s: os_sem_wait(%p) => %s\n", what, sem,
268            KERN_SUCCESS==ret ? "ok" : strerror(errno)));
269     switch (ret) {
270     case KERN_SUCCESS:
271         return;
272         /* It is unclear just when we can get this, but a sufficiently
273          * long wait seems to do that, at least sometimes.
274          *
275          * However, a wait that long is definitely abnormal for the
276          * GC, so we complain before retrying.
277          */
278     case KERN_OPERATION_TIMED_OUT:
279         fprintf(stderr, "%s: os_sem_wait(%p): %s", what, sem, strerror(errno));
280         /* This is analogous to POSIX EINTR. */
281     case KERN_ABORTED:
282         goto restart;
283     default:
284         lose("%s: os_sem_wait(%p): %lu, %s", what, sem, ret, strerror(errno));
285     }
286 }
287
288 void
289 os_sem_post(os_sem_t *sem, char *what)
290 {
291     if (KERN_SUCCESS!=semaphore_signal(*sem))
292         lose("%s: os_sem_post(%p): %s", what, sem, strerror(errno));
293     FSHOW((stderr, "%s: os_sem_post(%p) ok\n", what, sem));
294 }
295
296 void
297 os_sem_destroy(os_sem_t *sem)
298 {
299     if (-1==semaphore_destroy(current_mach_task, *sem))
300         lose("os_sem_destroy(%p): %s", sem, strerror(errno));
301 }
302
303 #endif