1 //===-- MachException.cpp ---------------------------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // Created by Greg Clayton on 6/18/07.
11 //===----------------------------------------------------------------------===//
13 #include "MachException.h"
17 #include "MachProcess.h"
18 #include "PThreadMutex.h"
19 #include "SysSignal.h"
21 #include <sys/ptrace.h>
22 #include <sys/types.h>
24 // Routine mach_exception_raise
25 extern "C" kern_return_t
26 catch_mach_exception_raise(mach_port_t exception_port
, mach_port_t thread
,
27 mach_port_t task
, exception_type_t exception
,
28 mach_exception_data_t code
,
29 mach_msg_type_number_t codeCnt
);
31 extern "C" kern_return_t
catch_mach_exception_raise_state(
32 mach_port_t exception_port
, exception_type_t exception
,
33 const mach_exception_data_t code
, mach_msg_type_number_t codeCnt
,
34 int *flavor
, const thread_state_t old_state
,
35 mach_msg_type_number_t old_stateCnt
, thread_state_t new_state
,
36 mach_msg_type_number_t
*new_stateCnt
);
38 // Routine mach_exception_raise_state_identity
39 extern "C" kern_return_t
catch_mach_exception_raise_state_identity(
40 mach_port_t exception_port
, mach_port_t thread
, mach_port_t task
,
41 exception_type_t exception
, mach_exception_data_t code
,
42 mach_msg_type_number_t codeCnt
, int *flavor
, thread_state_t old_state
,
43 mach_msg_type_number_t old_stateCnt
, thread_state_t new_state
,
44 mach_msg_type_number_t
*new_stateCnt
);
46 extern "C" boolean_t
mach_exc_server(mach_msg_header_t
*InHeadP
,
47 mach_msg_header_t
*OutHeadP
);
49 // Note: g_message points to the storage allocated to catch the data from
50 // catching the current exception raise. It's populated when we catch a raised
51 // exception which can't immediately be replied to.
53 // If it becomes possible to catch exceptions from multiple threads
54 // simultaneously, accesses to g_message would need to be mutually exclusive.
55 static MachException::Data
*g_message
= NULL
;
57 extern "C" kern_return_t
catch_mach_exception_raise_state(
58 mach_port_t exc_port
, exception_type_t exc_type
,
59 const mach_exception_data_t exc_data
, mach_msg_type_number_t exc_data_count
,
60 int *flavor
, const thread_state_t old_state
,
61 mach_msg_type_number_t old_stateCnt
, thread_state_t new_state
,
62 mach_msg_type_number_t
*new_stateCnt
) {
63 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
)) {
64 DNBLogThreaded("::%s ( exc_port = 0x%4.4x, exc_type = %d ( %s ), exc_data "
65 "= 0x%llx, exc_data_count = %d)",
66 __FUNCTION__
, exc_port
, exc_type
,
67 MachException::Name(exc_type
), (uint64_t)exc_data
,
73 extern "C" kern_return_t
catch_mach_exception_raise_state_identity(
74 mach_port_t exc_port
, mach_port_t thread_port
, mach_port_t task_port
,
75 exception_type_t exc_type
, mach_exception_data_t exc_data
,
76 mach_msg_type_number_t exc_data_count
, int *flavor
,
77 thread_state_t old_state
, mach_msg_type_number_t old_stateCnt
,
78 thread_state_t new_state
, mach_msg_type_number_t
*new_stateCnt
) {
79 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
)) {
80 DNBLogThreaded("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = "
81 "0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { 0x%llx, "
83 __FUNCTION__
, exc_port
, thread_port
, task_port
, exc_type
,
84 MachException::Name(exc_type
), exc_data_count
,
85 (uint64_t)(exc_data_count
> 0 ? exc_data
[0] : 0xBADDBADD),
86 (uint64_t)(exc_data_count
> 1 ? exc_data
[1] : 0xBADDBADD));
92 extern "C" kern_return_t
93 catch_mach_exception_raise(mach_port_t exc_port
, mach_port_t thread_port
,
94 mach_port_t task_port
, exception_type_t exc_type
,
95 mach_exception_data_t exc_data
,
96 mach_msg_type_number_t exc_data_count
) {
97 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
)) {
98 DNBLogThreaded("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = "
99 "0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { 0x%llx, "
101 __FUNCTION__
, exc_port
, thread_port
, task_port
, exc_type
,
102 MachException::Name(exc_type
), exc_data_count
,
103 (uint64_t)(exc_data_count
> 0 ? exc_data
[0] : 0xBADDBADD),
104 (uint64_t)(exc_data_count
> 1 ? exc_data
[1] : 0xBADDBADD));
106 g_message
->exc_type
= 0;
107 g_message
->exc_data
.clear();
109 if (task_port
== g_message
->task_port
) {
110 g_message
->task_port
= task_port
;
111 g_message
->thread_port
= thread_port
;
112 g_message
->exc_type
= exc_type
;
113 g_message
->AppendExceptionData(exc_data
, exc_data_count
);
115 } else if (!MachTask::IsValid(g_message
->task_port
)) {
116 // Our original exception port isn't valid anymore check for a SIGTRAP
117 if (exc_type
== EXC_SOFTWARE
&& exc_data_count
== 2 &&
118 exc_data
[0] == EXC_SOFT_SIGNAL
&& exc_data
[1] == SIGTRAP
) {
119 // We got a SIGTRAP which indicates we might have exec'ed and possibly
120 // lost our old task port during the exec, so we just need to switch over
121 // to using this new task port
122 g_message
->task_port
= task_port
;
123 g_message
->thread_port
= thread_port
;
124 g_message
->exc_type
= exc_type
;
125 g_message
->AppendExceptionData(exc_data
, exc_data_count
);
132 void MachException::Message::Dump() const {
133 DNBLogThreadedIf(LOG_EXCEPTIONS
, " exc_msg { bits = 0x%8.8x size = 0x%8.8x "
134 "remote-port = 0x%8.8x local-port = 0x%8.8x "
135 "reserved = 0x%8.8x id = 0x%8.8x } ",
136 exc_msg
.hdr
.msgh_bits
, exc_msg
.hdr
.msgh_size
,
137 exc_msg
.hdr
.msgh_remote_port
, exc_msg
.hdr
.msgh_local_port
,
138 exc_msg
.hdr
.msgh_reserved
, exc_msg
.hdr
.msgh_id
);
140 DNBLogThreadedIf(LOG_EXCEPTIONS
, "reply_msg { bits = 0x%8.8x size = 0x%8.8x "
141 "remote-port = 0x%8.8x local-port = 0x%8.8x "
142 "reserved = 0x%8.8x id = 0x%8.8x }",
143 reply_msg
.hdr
.msgh_bits
, reply_msg
.hdr
.msgh_size
,
144 reply_msg
.hdr
.msgh_remote_port
,
145 reply_msg
.hdr
.msgh_local_port
, reply_msg
.hdr
.msgh_reserved
,
146 reply_msg
.hdr
.msgh_id
);
151 bool MachException::Data::GetStopInfo(
152 struct DNBThreadStopInfo
*stop_info
) const {
153 // Zero out the structure.
154 memset(stop_info
, 0, sizeof(struct DNBThreadStopInfo
));
157 stop_info
->reason
= eStopTypeInvalid
;
161 // We always stop with a mach exceptions
162 stop_info
->reason
= eStopTypeException
;
163 // Save the EXC_XXXX exception type
164 stop_info
->details
.exception
.type
= exc_type
;
166 // Fill in a text description
167 const char *exc_name
= MachException::Name(exc_type
);
168 char *desc
= stop_info
->description
;
169 const char *end_desc
= desc
+ DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH
;
172 snprintf(desc
, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH
, "%s", exc_name
);
175 snprintf(desc
, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH
, "%i", exc_type
);
177 stop_info
->details
.exception
.data_count
= exc_data
.size();
179 int soft_signal
= SoftSignal();
181 if (desc
< end_desc
) {
182 const char *sig_str
= SysSignal::Name(soft_signal
);
183 snprintf(desc
, end_desc
- desc
, " EXC_SOFT_SIGNAL( %i ( %s ))",
184 soft_signal
, sig_str
? sig_str
: "unknown signal");
187 // No special disassembly for exception data, just
189 if (desc
< end_desc
) {
190 desc
+= snprintf(desc
, end_desc
- desc
, " data[%llu] = {",
191 (uint64_t)stop_info
->details
.exception
.data_count
);
194 desc
< end_desc
&& idx
< stop_info
->details
.exception
.data_count
;
197 desc
, end_desc
- desc
, "0x%llx%c", (uint64_t)exc_data
[idx
],
198 ((idx
+ 1 == stop_info
->details
.exception
.data_count
) ? '}' : ','));
202 // Copy the exception data
204 for (i
= 0; i
< stop_info
->details
.exception
.data_count
; i
++)
205 stop_info
->details
.exception
.data
[i
] = exc_data
[i
];
210 void MachException::Data::DumpStopReason() const {
211 int soft_signal
= SoftSignal();
213 const char *signal_str
= SysSignal::Name(soft_signal
);
215 DNBLog("signal(%s)", signal_str
);
217 DNBLog("signal(%i)", soft_signal
);
220 DNBLog("%s", Name(exc_type
));
223 kern_return_t
MachException::Message::Receive(mach_port_t port
,
224 mach_msg_option_t options
,
225 mach_msg_timeout_t timeout
,
226 mach_port_t notify_port
) {
228 const bool log_exceptions
= DNBLogCheckLogBit(LOG_EXCEPTIONS
);
229 mach_msg_timeout_t mach_msg_timeout
=
230 options
& MACH_RCV_TIMEOUT
? timeout
: 0;
231 if (log_exceptions
&& ((options
& MACH_RCV_TIMEOUT
) == 0)) {
232 // Dump this log message if we have no timeout in case it never returns
233 DNBLogThreaded("::mach_msg ( msg->{bits = %#x, size = %u remote_port = "
234 "%#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option "
235 "= %#x, send_size = 0, rcv_size = %llu, rcv_name = %#x, "
236 "timeout = %u, notify = %#x)",
237 exc_msg
.hdr
.msgh_bits
, exc_msg
.hdr
.msgh_size
,
238 exc_msg
.hdr
.msgh_remote_port
, exc_msg
.hdr
.msgh_local_port
,
239 exc_msg
.hdr
.msgh_reserved
, exc_msg
.hdr
.msgh_id
, options
,
240 (uint64_t)sizeof(exc_msg
.data
), port
, mach_msg_timeout
,
244 err
= ::mach_msg(&exc_msg
.hdr
,
247 sizeof(exc_msg
.data
), // Receive size
248 port
, // exception port to watch for exception on
249 mach_msg_timeout
, // timeout in msec (obeyed only if
250 // MACH_RCV_TIMEOUT is ORed into the
251 // options parameter)
254 // Dump any errors we get
255 if (log_exceptions
) {
256 err
.LogThreaded("::mach_msg ( msg->{bits = %#x, size = %u remote_port = "
257 "%#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, "
258 "option = %#x, send_size = %u, rcv_size = %u, rcv_name = "
259 "%#x, timeout = %u, notify = %#x)",
260 exc_msg
.hdr
.msgh_bits
, exc_msg
.hdr
.msgh_size
,
261 exc_msg
.hdr
.msgh_remote_port
, exc_msg
.hdr
.msgh_local_port
,
262 exc_msg
.hdr
.msgh_reserved
, exc_msg
.hdr
.msgh_id
, options
, 0,
263 sizeof(exc_msg
.data
), port
, mach_msg_timeout
, notify_port
);
268 bool MachException::Message::CatchExceptionRaise(task_t task
) {
269 bool success
= false;
270 state
.task_port
= task
;
272 // The exc_server function is the MIG generated server handling function
273 // to handle messages from the kernel relating to the occurrence of an
274 // exception in a thread. Such messages are delivered to the exception port
275 // set via thread_set_exception_ports or task_set_exception_ports. When an
276 // exception occurs in a thread, the thread sends an exception message to
277 // its exception port, blocking in the kernel waiting for the receipt of a
278 // reply. The exc_server function performs all necessary argument handling
279 // for this kernel message and calls catch_exception_raise,
280 // catch_exception_raise_state or catch_exception_raise_state_identity,
281 // which should handle the exception. If the called routine returns
282 // KERN_SUCCESS, a reply message will be sent, allowing the thread to
283 // continue from the point of the exception; otherwise, no reply message
284 // is sent and the called routine must have dealt with the exception
286 if (mach_exc_server(&exc_msg
.hdr
, &reply_msg
.hdr
)) {
288 } else if (DNBLogCheckLogBit(LOG_EXCEPTIONS
)) {
289 DNBLogThreaded("mach_exc_server returned zero...");
295 kern_return_t
MachException::Message::Reply(MachProcess
*process
, int signal
) {
296 // Reply to the exception...
299 // If we had a soft signal, we need to update the thread first so it can
300 // continue without signaling
301 int soft_signal
= state
.SoftSignal();
304 if (process
->Task().TaskPort() == state
.task_port
) {
305 // This is our task, so we can update the signal to send to it
306 state_pid
= process
->ProcessID();
307 soft_signal
= signal
;
309 err
= ::pid_for_task(state
.task_port
, &state_pid
);
312 assert(state_pid
!= -1);
313 if (state_pid
!= -1) {
315 if (::ptrace(PT_THUPDATE
, state_pid
,
316 (caddr_t
)((uintptr_t)state
.thread_port
), soft_signal
) != 0)
317 err
.SetError(errno
, DNBError::POSIX
);
321 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
) || err
.Fail())
322 err
.LogThreaded("::ptrace (request = PT_THUPDATE, pid = 0x%4.4x, tid = "
323 "0x%4.4x, signal = %i)",
324 state_pid
, state
.thread_port
, soft_signal
);
329 LOG_EXCEPTIONS
, "::mach_msg ( msg->{bits = %#x, size = %u, remote_port = "
330 "%#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, "
331 "option = %#x, send_size = %u, rcv_size = %u, rcv_name = "
332 "%#x, timeout = %u, notify = %#x)",
333 reply_msg
.hdr
.msgh_bits
, reply_msg
.hdr
.msgh_size
,
334 reply_msg
.hdr
.msgh_remote_port
, reply_msg
.hdr
.msgh_local_port
,
335 reply_msg
.hdr
.msgh_reserved
, reply_msg
.hdr
.msgh_id
,
336 MACH_SEND_MSG
| MACH_SEND_INTERRUPT
, reply_msg
.hdr
.msgh_size
, 0,
337 MACH_PORT_NULL
, MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
);
339 err
= ::mach_msg(&reply_msg
.hdr
, MACH_SEND_MSG
| MACH_SEND_INTERRUPT
,
340 reply_msg
.hdr
.msgh_size
, 0, MACH_PORT_NULL
,
341 MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
);
344 if (err
.Status() == MACH_SEND_INTERRUPTED
) {
345 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
))
346 err
.LogThreaded("::mach_msg() - send interrupted");
347 // TODO: keep retrying to reply???
349 if (state
.task_port
== process
->Task().TaskPort()) {
350 DNBLogThreaded("error: mach_msg() returned an error when replying to a "
351 "mach exception: error = %u",
354 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
))
355 err
.LogThreaded("::mach_msg() - failed (child of task)");
363 void MachException::Data::Dump() const {
364 const char *exc_type_name
= MachException::Name(exc_type
);
366 LOG_EXCEPTIONS
, " state { task_port = 0x%4.4x, thread_port = "
367 "0x%4.4x, exc_type = %i (%s) ...",
368 task_port
, thread_port
, exc_type
, exc_type_name
? exc_type_name
: "???");
370 const size_t exc_data_count
= exc_data
.size();
371 // Dump any special exception data contents
372 int soft_signal
= SoftSignal();
373 if (soft_signal
!= 0) {
374 const char *sig_str
= SysSignal::Name(soft_signal
);
375 DNBLogThreadedIf(LOG_EXCEPTIONS
,
376 " exc_data: EXC_SOFT_SIGNAL (%i (%s))",
377 soft_signal
, sig_str
? sig_str
: "unknown signal");
379 // No special disassembly for this data, just dump the data
381 for (idx
= 0; idx
< exc_data_count
; ++idx
) {
382 DNBLogThreadedIf(LOG_EXCEPTIONS
, " exc_data[%llu]: 0x%llx",
383 (uint64_t)idx
, (uint64_t)exc_data
[idx
]);
388 // The EXC_MASK_ALL value hard-coded here so that lldb can be built
389 // on a new OS with an older deployment target . The new OS may have
390 // an addition to its EXC_MASK_ALL that the old OS will not recognize -
391 // <mach/exception_types.h> doesn't vary the value based on the deployment
392 // target. So we need a known set of masks that can be assumed to be
393 // valid when running on an older OS. We'll fall back to trying
394 // PREV_EXC_MASK_ALL if the EXC_MASK_ALL value lldb was compiled with is
397 #define PREV_EXC_MASK_ALL (EXC_MASK_BAD_ACCESS | \
398 EXC_MASK_BAD_INSTRUCTION | \
399 EXC_MASK_ARITHMETIC | \
400 EXC_MASK_EMULATION | \
401 EXC_MASK_SOFTWARE | \
402 EXC_MASK_BREAKPOINT | \
404 EXC_MASK_MACH_SYSCALL | \
405 EXC_MASK_RPC_ALERT | \
406 EXC_MASK_RESOURCE | \
410 #define LLDB_EXC_MASK EXC_MASK_ALL
412 kern_return_t
MachException::PortInfo::Save(task_t task
) {
413 DNBLogThreadedIf(LOG_EXCEPTIONS
| LOG_VERBOSE
,
414 "MachException::PortInfo::Save ( task = 0x%4.4x )", task
);
415 // Be careful to be able to have debugserver built on a newer OS than what
416 // it is currently running on by being able to start with all exceptions
417 // and back off to just what is supported on the current system
420 mask
= LLDB_EXC_MASK
;
422 count
= (sizeof(ports
) / sizeof(ports
[0]));
423 err
= ::task_get_exception_ports(task
, mask
, masks
, &count
, ports
, behaviors
,
425 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
) || err
.Fail())
426 err
.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, "
427 "maskCnt => %u, ports, behaviors, flavors )",
430 if (err
.Status() == KERN_INVALID_ARGUMENT
&& mask
!= PREV_EXC_MASK_ALL
) {
431 mask
= PREV_EXC_MASK_ALL
;
432 count
= (sizeof(ports
) / sizeof(ports
[0]));
433 err
= ::task_get_exception_ports(task
, mask
, masks
, &count
, ports
,
435 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
) || err
.Fail())
436 err
.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = "
437 "0x%x, maskCnt => %u, ports, behaviors, flavors )",
447 kern_return_t
MachException::PortInfo::Restore(task_t task
) {
448 DNBLogThreadedIf(LOG_EXCEPTIONS
| LOG_VERBOSE
,
449 "MachException::PortInfo::Restore( task = 0x%4.4x )", task
);
453 for (i
= 0; i
< count
; i
++) {
454 err
= ::task_set_exception_ports(task
, masks
[i
], ports
[i
], behaviors
[i
],
456 if (DNBLogCheckLogBit(LOG_EXCEPTIONS
) || err
.Fail()) {
457 err
.LogThreaded("::task_set_exception_ports ( task = 0x%4.4x, "
458 "exception_mask = 0x%8.8x, new_port = 0x%4.4x, "
459 "behavior = 0x%8.8x, new_flavor = 0x%8.8x )",
460 task
, masks
[i
], ports
[i
], behaviors
[i
], flavors
[i
]);
461 // Bail if we encounter any errors
472 const char *MachException::Name(exception_type_t exc_type
) {
475 return "EXC_BAD_ACCESS";
476 case EXC_BAD_INSTRUCTION
:
477 return "EXC_BAD_INSTRUCTION";
479 return "EXC_ARITHMETIC";
481 return "EXC_EMULATION";
483 return "EXC_SOFTWARE";
485 return "EXC_BREAKPOINT";
487 return "EXC_SYSCALL";
488 case EXC_MACH_SYSCALL
:
489 return "EXC_MACH_SYSCALL";
491 return "EXC_RPC_ALERT";
497 return "EXC_RESOURCE";
502 #ifdef EXC_CORPSE_NOTIFY
503 case EXC_CORPSE_NOTIFY
:
504 return "EXC_CORPSE_NOTIFY";
506 #ifdef EXC_CORPSE_VARIANT_BIT
507 case EXC_CORPSE_VARIANT_BIT
:
508 return "EXC_CORPSE_VARIANT_BIT";