1 //===-- MachException.cpp ---------------------------------------*- C++ -*-===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // Created by Greg Clayton on 6/18/07.
11 //
12 //===----------------------------------------------------------------------===//
13
14 #include "MachException.h"
15 #include "MachProcess.h"
16 #include "DNB.h"
17 #include "DNBError.h"
18 #include <sys/types.h>
19 #include "DNBLog.h"
20 #include "PThreadMutex.h"
21 #include "SysSignal.h"
22 #include <errno.h>
23 #include <sys/ptrace.h>
24
25 // Routine mach_exception_raise
26 extern "C"
27 kern_return_t catch_mach_exception_raise
28 (
29 mach_port_t exception_port,
30 mach_port_t thread,
31 mach_port_t task,
32 exception_type_t exception,
33 mach_exception_data_t code,
34 mach_msg_type_number_t codeCnt
35 );
36
37 extern "C"
38 kern_return_t catch_mach_exception_raise_state
39 (
40 mach_port_t exception_port,
41 exception_type_t exception,
42 const mach_exception_data_t code,
43 mach_msg_type_number_t codeCnt,
44 int *flavor,
45 const thread_state_t old_state,
46 mach_msg_type_number_t old_stateCnt,
47 thread_state_t new_state,
48 mach_msg_type_number_t *new_stateCnt
49 );
50
51 // Routine mach_exception_raise_state_identity
52 extern "C"
53 kern_return_t catch_mach_exception_raise_state_identity
54 (
55 mach_port_t exception_port,
56 mach_port_t thread,
57 mach_port_t task,
58 exception_type_t exception,
59 mach_exception_data_t code,
60 mach_msg_type_number_t codeCnt,
61 int *flavor,
62 thread_state_t old_state,
63 mach_msg_type_number_t old_stateCnt,
64 thread_state_t new_state,
65 mach_msg_type_number_t *new_stateCnt
66 );
67
68 extern "C" boolean_t mach_exc_server(
69 mach_msg_header_t *InHeadP,
70 mach_msg_header_t *OutHeadP);
71
72 // Any access to the g_message variable should be done by locking the
73 // g_message_mutex first, using the g_message variable, then unlocking
74 // the g_message_mutex. See MachException::Message::CatchExceptionRaise()
75 // for sample code.
76
77 static MachException::Data *g_message = NULL;
78 //static pthread_mutex_t g_message_mutex = PTHREAD_MUTEX_INITIALIZER;
79
80
81 extern "C"
82 kern_return_t
catch_mach_exception_raise_state(mach_port_t exc_port,exception_type_t exc_type,const mach_exception_data_t exc_data,mach_msg_type_number_t exc_data_count,int * flavor,const thread_state_t old_state,mach_msg_type_number_t old_stateCnt,thread_state_t new_state,mach_msg_type_number_t * new_stateCnt)83 catch_mach_exception_raise_state
84 (
85 mach_port_t exc_port,
86 exception_type_t exc_type,
87 const mach_exception_data_t exc_data,
88 mach_msg_type_number_t exc_data_count,
89 int * flavor,
90 const thread_state_t old_state,
91 mach_msg_type_number_t old_stateCnt,
92 thread_state_t new_state,
93 mach_msg_type_number_t * new_stateCnt
94 )
95 {
96 if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
97 {
98 DNBLogThreaded ("::%s ( exc_port = 0x%4.4x, exc_type = %d ( %s ), exc_data = 0x%llx, exc_data_count = %d)",
99 __FUNCTION__,
100 exc_port,
101 exc_type, MachException::Name(exc_type),
102 (uint64_t)exc_data,
103 exc_data_count);
104 }
105 return KERN_FAILURE;
106 }
107
108 extern "C"
109 kern_return_t
catch_mach_exception_raise_state_identity(mach_port_t exc_port,mach_port_t thread_port,mach_port_t task_port,exception_type_t exc_type,mach_exception_data_t exc_data,mach_msg_type_number_t exc_data_count,int * flavor,thread_state_t old_state,mach_msg_type_number_t old_stateCnt,thread_state_t new_state,mach_msg_type_number_t * new_stateCnt)110 catch_mach_exception_raise_state_identity
111 (
112 mach_port_t exc_port,
113 mach_port_t thread_port,
114 mach_port_t task_port,
115 exception_type_t exc_type,
116 mach_exception_data_t exc_data,
117 mach_msg_type_number_t exc_data_count,
118 int * flavor,
119 thread_state_t old_state,
120 mach_msg_type_number_t old_stateCnt,
121 thread_state_t new_state,
122 mach_msg_type_number_t *new_stateCnt
123 )
124 {
125 if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
126 {
127 DNBLogThreaded("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { 0x%llx, 0x%llx })",
128 __FUNCTION__,
129 exc_port,
130 thread_port,
131 task_port,
132 exc_type, MachException::Name(exc_type),
133 exc_data_count,
134 (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD),
135 (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD));
136 }
137 mach_port_deallocate (mach_task_self (), task_port);
138 mach_port_deallocate (mach_task_self (), thread_port);
139
140 return KERN_FAILURE;
141 }
142
143 extern "C"
144 kern_return_t
catch_mach_exception_raise(mach_port_t exc_port,mach_port_t thread_port,mach_port_t task_port,exception_type_t exc_type,mach_exception_data_t exc_data,mach_msg_type_number_t exc_data_count)145 catch_mach_exception_raise
146 (
147 mach_port_t exc_port,
148 mach_port_t thread_port,
149 mach_port_t task_port,
150 exception_type_t exc_type,
151 mach_exception_data_t exc_data,
152 mach_msg_type_number_t exc_data_count)
153 {
154 if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
155 {
156 DNBLogThreaded ("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { 0x%llx, 0x%llx })",
157 __FUNCTION__,
158 exc_port,
159 thread_port,
160 task_port,
161 exc_type, MachException::Name(exc_type),
162 exc_data_count,
163 (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD),
164 (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD));
165 }
166
167 if (task_port == g_message->task_port)
168 {
169 g_message->task_port = task_port;
170 g_message->thread_port = thread_port;
171 g_message->exc_type = exc_type;
172 g_message->exc_data.resize(exc_data_count);
173 ::memcpy (&g_message->exc_data[0], exc_data, g_message->exc_data.size() * sizeof (mach_exception_data_type_t));
174 return KERN_SUCCESS;
175 }
176 return KERN_FAILURE;
177 }
178
179
180 void
Dump() const181 MachException::Message::Dump() const
182 {
183 DNBLogThreadedIf(LOG_EXCEPTIONS,
184 " exc_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = 0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = 0x%8.8x } ",
185 exc_msg.hdr.msgh_bits,
186 exc_msg.hdr.msgh_size,
187 exc_msg.hdr.msgh_remote_port,
188 exc_msg.hdr.msgh_local_port,
189 exc_msg.hdr.msgh_reserved,
190 exc_msg.hdr.msgh_id);
191
192 DNBLogThreadedIf(LOG_EXCEPTIONS,
193 "reply_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = 0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = 0x%8.8x }",
194 reply_msg.hdr.msgh_bits,
195 reply_msg.hdr.msgh_size,
196 reply_msg.hdr.msgh_remote_port,
197 reply_msg.hdr.msgh_local_port,
198 reply_msg.hdr.msgh_reserved,
199 reply_msg.hdr.msgh_id);
200
201 state.Dump();
202 }
203
204 bool
GetStopInfo(struct DNBThreadStopInfo * stop_info) const205 MachException::Data::GetStopInfo(struct DNBThreadStopInfo *stop_info) const
206 {
207 // Zero out the structure.
208 memset(stop_info, 0, sizeof(struct DNBThreadStopInfo));
209 // We always stop with a mach exceptions
210 stop_info->reason = eStopTypeException;
211 // Save the EXC_XXXX exception type
212 stop_info->details.exception.type = exc_type;
213
214 // Fill in a text description
215 const char * exc_name = MachException::Name(exc_type);
216 char *desc = stop_info->description;
217 const char *end_desc = desc + DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH;
218 if (exc_name)
219 desc += snprintf(desc, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH, "%s", exc_name);
220 else
221 desc += snprintf(desc, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH, "%i", exc_type);
222
223 stop_info->details.exception.data_count = exc_data.size();
224
225 int soft_signal = SoftSignal();
226 if (soft_signal)
227 {
228 if (desc < end_desc)
229 {
230 const char *sig_str = SysSignal::Name(soft_signal);
231 snprintf(desc, end_desc - desc, " EXC_SOFT_SIGNAL( %i ( %s ))", soft_signal, sig_str ? sig_str : "unknown signal");
232 }
233 }
234 else
235 {
236 // No special disassembly for exception data, just
237 size_t idx;
238 if (desc < end_desc)
239 {
240 desc += snprintf(desc, end_desc - desc, " data[%llu] = {", (uint64_t)stop_info->details.exception.data_count);
241
242 for (idx = 0; desc < end_desc && idx < stop_info->details.exception.data_count; ++idx)
243 desc += snprintf(desc, end_desc - desc, "0x%llx%c", (uint64_t)exc_data[idx], ((idx + 1 == stop_info->details.exception.data_count) ? '}' : ','));
244 }
245 }
246
247 // Copy the exception data
248 size_t i;
249 for (i=0; i<stop_info->details.exception.data_count; i++)
250 stop_info->details.exception.data[i] = exc_data[i];
251
252 return true;
253 }
254
255
256 void
DumpStopReason() const257 MachException::Data::DumpStopReason() const
258 {
259 int soft_signal = SoftSignal();
260 if (soft_signal)
261 {
262 const char *signal_str = SysSignal::Name(soft_signal);
263 if (signal_str)
264 DNBLog("signal(%s)", signal_str);
265 else
266 DNBLog("signal(%i)", soft_signal);
267 return;
268 }
269 DNBLog("%s", Name(exc_type));
270 }
271
272 kern_return_t
Receive(mach_port_t port,mach_msg_option_t options,mach_msg_timeout_t timeout,mach_port_t notify_port)273 MachException::Message::Receive(mach_port_t port, mach_msg_option_t options, mach_msg_timeout_t timeout, mach_port_t notify_port)
274 {
275 DNBError err;
276 const bool log_exceptions = DNBLogCheckLogBit(LOG_EXCEPTIONS);
277 mach_msg_timeout_t mach_msg_timeout = options & MACH_RCV_TIMEOUT ? timeout : 0;
278 if (log_exceptions && ((options & MACH_RCV_TIMEOUT) == 0))
279 {
280 // Dump this log message if we have no timeout in case it never returns
281 DNBLogThreaded ("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = 0, rcv_size = %llu, rcv_name = %#x, timeout = %u, notify = %#x)",
282 exc_msg.hdr.msgh_bits,
283 exc_msg.hdr.msgh_size,
284 exc_msg.hdr.msgh_remote_port,
285 exc_msg.hdr.msgh_local_port,
286 exc_msg.hdr.msgh_reserved,
287 exc_msg.hdr.msgh_id,
288 options,
289 (uint64_t)sizeof (exc_msg.data),
290 port,
291 mach_msg_timeout,
292 notify_port);
293 }
294
295 err = ::mach_msg (&exc_msg.hdr,
296 options, // options
297 0, // Send size
298 sizeof (exc_msg.data), // Receive size
299 port, // exception port to watch for exception on
300 mach_msg_timeout, // timeout in msec (obeyed only if MACH_RCV_TIMEOUT is ORed into the options parameter)
301 notify_port);
302
303 // Dump any errors we get
304 if (log_exceptions)
305 {
306 err.LogThreaded("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)",
307 exc_msg.hdr.msgh_bits,
308 exc_msg.hdr.msgh_size,
309 exc_msg.hdr.msgh_remote_port,
310 exc_msg.hdr.msgh_local_port,
311 exc_msg.hdr.msgh_reserved,
312 exc_msg.hdr.msgh_id,
313 options,
314 0,
315 sizeof (exc_msg.data),
316 port,
317 mach_msg_timeout,
318 notify_port);
319 }
320 return err.Error();
321 }
322
323 bool
CatchExceptionRaise(task_t task)324 MachException::Message::CatchExceptionRaise(task_t task)
325 {
326 bool success = false;
327 // locker will keep a mutex locked until it goes out of scope
328 // PThreadMutex::Locker locker(&g_message_mutex);
329 // DNBLogThreaded("calling mach_exc_server");
330 state.task_port = task;
331 g_message = &state;
332 // The exc_server function is the MIG generated server handling function
333 // to handle messages from the kernel relating to the occurrence of an
334 // exception in a thread. Such messages are delivered to the exception port
335 // set via thread_set_exception_ports or task_set_exception_ports. When an
336 // exception occurs in a thread, the thread sends an exception message to
337 // its exception port, blocking in the kernel waiting for the receipt of a
338 // reply. The exc_server function performs all necessary argument handling
339 // for this kernel message and calls catch_exception_raise,
340 // catch_exception_raise_state or catch_exception_raise_state_identity,
341 // which should handle the exception. If the called routine returns
342 // KERN_SUCCESS, a reply message will be sent, allowing the thread to
343 // continue from the point of the exception; otherwise, no reply message
344 // is sent and the called routine must have dealt with the exception
345 // thread directly.
346 if (mach_exc_server (&exc_msg.hdr, &reply_msg.hdr))
347 {
348 success = true;
349 }
350 else if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
351 {
352 DNBLogThreaded("mach_exc_server returned zero...");
353 }
354 g_message = NULL;
355 return success;
356 }
357
358
359
360 kern_return_t
Reply(MachProcess * process,int signal)361 MachException::Message::Reply(MachProcess *process, int signal)
362 {
363 // Reply to the exception...
364 DNBError err;
365
366 // If we had a soft signal, we need to update the thread first so it can
367 // continue without signaling
368 int soft_signal = state.SoftSignal();
369 if (soft_signal)
370 {
371 int state_pid = -1;
372 if (process->Task().TaskPort() == state.task_port)
373 {
374 // This is our task, so we can update the signal to send to it
375 state_pid = process->ProcessID();
376 soft_signal = signal;
377 }
378 else
379 {
380 err = ::pid_for_task(state.task_port, &state_pid);
381 }
382
383 assert (state_pid != -1);
384 if (state_pid != -1)
385 {
386 errno = 0;
387 if (::ptrace (PT_THUPDATE, state_pid, (caddr_t)((uintptr_t)state.thread_port), soft_signal) != 0)
388 err.SetError(errno, DNBError::POSIX);
389 else
390 err.Clear();
391
392 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail())
393 err.LogThreaded("::ptrace (request = PT_THUPDATE, pid = 0x%4.4x, tid = 0x%4.4x, signal = %i)", state_pid, state.thread_port, soft_signal);
394 }
395 }
396
397 DNBLogThreadedIf(LOG_EXCEPTIONS, "::mach_msg ( msg->{bits = %#x, size = %u, remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)",
398 reply_msg.hdr.msgh_bits,
399 reply_msg.hdr.msgh_size,
400 reply_msg.hdr.msgh_remote_port,
401 reply_msg.hdr.msgh_local_port,
402 reply_msg.hdr.msgh_reserved,
403 reply_msg.hdr.msgh_id,
404 MACH_SEND_MSG | MACH_SEND_INTERRUPT,
405 reply_msg.hdr.msgh_size,
406 0,
407 MACH_PORT_NULL,
408 MACH_MSG_TIMEOUT_NONE,
409 MACH_PORT_NULL);
410
411 err = ::mach_msg ( &reply_msg.hdr,
412 MACH_SEND_MSG | MACH_SEND_INTERRUPT,
413 reply_msg.hdr.msgh_size,
414 0,
415 MACH_PORT_NULL,
416 MACH_MSG_TIMEOUT_NONE,
417 MACH_PORT_NULL);
418
419 if (err.Fail())
420 {
421 if (err.Error() == MACH_SEND_INTERRUPTED)
422 {
423 if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
424 err.LogThreaded("::mach_msg() - send interrupted");
425 // TODO: keep retrying to reply???
426 }
427 else
428 {
429 if (state.task_port == process->Task().TaskPort())
430 {
431 if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
432 err.LogThreaded("::mach_msg() - failed (task)");
433 abort ();
434 }
435 else
436 {
437 if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
438 err.LogThreaded("::mach_msg() - failed (child of task)");
439 }
440 }
441 }
442
443 return err.Error();
444 }
445
446
447 void
Dump() const448 MachException::Data::Dump() const
449 {
450 const char *exc_type_name = MachException::Name(exc_type);
451 DNBLogThreadedIf(LOG_EXCEPTIONS, " state { task_port = 0x%4.4x, thread_port = 0x%4.4x, exc_type = %i (%s) ...", task_port, thread_port, exc_type, exc_type_name ? exc_type_name : "???");
452
453 const size_t exc_data_count = exc_data.size();
454 // Dump any special exception data contents
455 int soft_signal = SoftSignal();
456 if (soft_signal != 0)
457 {
458 const char *sig_str = SysSignal::Name(soft_signal);
459 DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data: EXC_SOFT_SIGNAL (%i (%s))", soft_signal, sig_str ? sig_str : "unknown signal");
460 }
461 else
462 {
463 // No special disassembly for this data, just dump the data
464 size_t idx;
465 for (idx = 0; idx < exc_data_count; ++idx)
466 {
467 DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data[%llu]: 0x%llx", (uint64_t)idx, (uint64_t)exc_data[idx]);
468 }
469 }
470 }
471
472 #define PREV_EXC_MASK_ALL (EXC_MASK_BAD_ACCESS | \
473 EXC_MASK_BAD_INSTRUCTION | \
474 EXC_MASK_ARITHMETIC | \
475 EXC_MASK_EMULATION | \
476 EXC_MASK_SOFTWARE | \
477 EXC_MASK_BREAKPOINT | \
478 EXC_MASK_SYSCALL | \
479 EXC_MASK_MACH_SYSCALL | \
480 EXC_MASK_RPC_ALERT | \
481 EXC_MASK_MACHINE)
482
483 // Don't listen for EXC_RESOURCE, it should really get handled by the system handler.
484
485 #ifndef EXC_RESOURCE
486 #define EXC_RESOURCE 11
487 #endif
488
489 #ifndef EXC_MASK_RESOURCE
490 #define EXC_MASK_RESOURCE (1 << EXC_RESOURCE)
491 #endif
492
493 #define LLDB_EXC_MASK (EXC_MASK_ALL & ~EXC_MASK_RESOURCE)
494
495 kern_return_t
Save(task_t task)496 MachException::PortInfo::Save (task_t task)
497 {
498 DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Save ( task = 0x%4.4x )", task);
499 // Be careful to be able to have debugserver built on a newer OS than what
500 // it is currently running on by being able to start with all exceptions
501 // and back off to just what is supported on the current system
502 DNBError err;
503
504 mask = LLDB_EXC_MASK;
505
506 count = (sizeof (ports) / sizeof (ports[0]));
507 err = ::task_get_exception_ports (task, mask, masks, &count, ports, behaviors, flavors);
508 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail())
509 err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, mask, count);
510
511 if (err.Error() == KERN_INVALID_ARGUMENT && mask != PREV_EXC_MASK_ALL)
512 {
513 mask = PREV_EXC_MASK_ALL;
514 count = (sizeof (ports) / sizeof (ports[0]));
515 err = ::task_get_exception_ports (task, mask, masks, &count, ports, behaviors, flavors);
516 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail())
517 err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, mask, count);
518 }
519 if (err.Fail())
520 {
521 mask = 0;
522 count = 0;
523 }
524 return err.Error();
525 }
526
527 kern_return_t
Restore(task_t task)528 MachException::PortInfo::Restore (task_t task)
529 {
530 DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Restore( task = 0x%4.4x )", task);
531 uint32_t i = 0;
532 DNBError err;
533 if (count > 0)
534 {
535 for (i = 0; i < count; i++)
536 {
537 err = ::task_set_exception_ports (task, masks[i], ports[i], behaviors[i], flavors[i]);
538 if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail())
539 {
540 err.LogThreaded("::task_set_exception_ports ( task = 0x%4.4x, exception_mask = 0x%8.8x, new_port = 0x%4.4x, behavior = 0x%8.8x, new_flavor = 0x%8.8x )", task, masks[i], ports[i], behaviors[i], flavors[i]);
541 // Bail if we encounter any errors
542 }
543
544 if (err.Fail())
545 break;
546 }
547 }
548 count = 0;
549 return err.Error();
550 }
551
552 const char *
Name(exception_type_t exc_type)553 MachException::Name(exception_type_t exc_type)
554 {
555 switch (exc_type)
556 {
557 case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS";
558 case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION";
559 case EXC_ARITHMETIC: return "EXC_ARITHMETIC";
560 case EXC_EMULATION: return "EXC_EMULATION";
561 case EXC_SOFTWARE: return "EXC_SOFTWARE";
562 case EXC_BREAKPOINT: return "EXC_BREAKPOINT";
563 case EXC_SYSCALL: return "EXC_SYSCALL";
564 case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL";
565 case EXC_RPC_ALERT: return "EXC_RPC_ALERT";
566 #ifdef EXC_CRASH
567 case EXC_CRASH: return "EXC_CRASH";
568 #endif
569 default:
570 break;
571 }
572 return NULL;
573 }
574
575
576
577