xref: /xnu-12377.61.12/tests/exc_helpers.c (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8) !
1 /*
2  * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #include "exc_helpers.h"
30 
31 #include <darwintest.h>
32 #include <ptrauth.h>
33 #include <stdbool.h>
34 #include <stdlib.h>
35 
36 #if __arm64__
37 #define EXCEPTION_THREAD_STATE          ARM_THREAD_STATE64
38 #define EXCEPTION_THREAD_STATE_COUNT    ARM_THREAD_STATE64_COUNT
39 #elif __x86_64__
40 #define EXCEPTION_THREAD_STATE          x86_THREAD_STATE64
41 #define EXCEPTION_THREAD_STATE_COUNT    x86_THREAD_STATE64_COUNT
42 #else
43 #error Unsupported architecture
44 #endif
45 
46 #define EXCEPTION_IDENTITY_PROTECTED 4
47 
48 bool verbose_exc_helper = true;
49 
50 #define LOG_VERBOSE(format, ...)                        \
51 	do {                                            \
52 	        if (verbose_exc_helper) {               \
53 	                T_LOG(format, ##__VA_ARGS__);   \
54 	        }                                       \
55 	} while (0)
56 
57 /**
58  * mach_exc_server() is a MIG-generated function that verifies the message
59  * that was received is indeed a mach exception and then calls
60  * catch_mach_exception_raise_state() to handle the exception.
61  */
62 extern boolean_t mach_exc_server(mach_msg_header_t *, mach_msg_header_t *);
63 
64 extern kern_return_t
65 catch_mach_exception_raise(
66 	mach_port_t exception_port,
67 	mach_port_t thread,
68 	mach_port_t task,
69 	exception_type_t type,
70 	exception_data_t codes,
71 	mach_msg_type_number_t code_count);
72 
73 extern kern_return_t
74 catch_mach_exception_raise_identity_protected(
75 	__unused mach_port_t      exception_port,
76 	uint64_t                  thread_id,
77 	mach_port_t               task_id_token,
78 	exception_type_t          exception,
79 	mach_exception_data_t     codes,
80 	mach_msg_type_number_t    codeCnt);
81 
82 extern kern_return_t
83 catch_mach_exception_raise_backtrace(
84 	__unused mach_port_t exception_port,
85 	mach_port_t kcdata_object,
86 	exception_type_t exception,
87 	mach_exception_data_t codes,
88 	__unused mach_msg_type_number_t codeCnt);
89 
90 extern kern_return_t
91 catch_mach_exception_raise_state(
92 	mach_port_t exception_port,
93 	exception_type_t type,
94 	exception_data_t codes,
95 	mach_msg_type_number_t code_count,
96 	int *flavor,
97 	thread_state_t in_state,
98 	mach_msg_type_number_t in_state_count,
99 	thread_state_t out_state,
100 	mach_msg_type_number_t *out_state_count);
101 
102 extern kern_return_t
103 catch_mach_exception_raise_state_identity(
104 	mach_port_t exception_port,
105 	mach_port_t thread,
106 	mach_port_t task,
107 	exception_type_t type,
108 	exception_data_t codes,
109 	mach_msg_type_number_t code_count,
110 	int *flavor,
111 	thread_state_t in_state,
112 	mach_msg_type_number_t in_state_count,
113 	thread_state_t out_state,
114 	mach_msg_type_number_t *out_state_count);
115 
116 /* Thread-local storage for exception server threads. */
117 
118 struct exc_handler_callbacks {
119 	exc_handler_callback_t state_callback;
120 	exc_handler_protected_callback_t protected_callback;
121 	exc_handler_state_protected_callback_t state_protected_callback;
122 	exc_handler_backtrace_callback_t backtrace_callback;
123 };
124 
125 static __thread struct exc_handler_callbacks tls_callbacks;
126 
127 /*
128  * Return the (ptrauth-stripped) PC from the
129  * thread state passed to an exception handler.
130  */
131 static uint64_t
get_exception_pc(thread_state_t in_state)132 get_exception_pc(thread_state_t in_state)
133 {
134 #if __arm64__
135 	arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)in_state;
136 	return arm_thread_state64_get_pc(*state);
137 #elif __x86_64__
138 	x86_thread_state64_t *state = (x86_thread_state64_t*)(void *)in_state;
139 	return state->__rip;
140 #else
141 	T_FAIL("unknown architecture");
142 	__builtin_unreachable();
143 #endif
144 }
145 
146 /*
147  * Increment the PC in thread state `out_state` by `advance_pc` bytes.
148  */
149 static void
advance_exception_pc(size_t advance_pc,thread_state_t out_state)150 advance_exception_pc(
151 	size_t advance_pc,
152 	thread_state_t out_state)
153 {
154 	/* disallow the sentinel value used by the exception handlers */
155 	assert(advance_pc != EXC_HELPER_HALT);
156 
157 #if __arm64__
158 	arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)out_state;
159 
160 	void *pc = (void*)(arm_thread_state64_get_pc(*state) + advance_pc);
161 	/* Have to sign the new PC value when pointer authentication is enabled. */
162 	pc = ptrauth_sign_unauthenticated(pc, ptrauth_key_function_pointer, 0);
163 	arm_thread_state64_set_pc_fptr(*state, pc);
164 #elif __x86_64__
165 	x86_thread_state64_t *state = (x86_thread_state64_t*)(void *)out_state;
166 	state->__rip += advance_pc;
167 #else
168 	(void)advance_pc;
169 	T_FAIL("unknown architecture");
170 	__builtin_unreachable();
171 #endif
172 }
173 
174 /**
175  * This has to be defined for linking purposes, but it's unused.
176  */
177 kern_return_t
catch_mach_exception_raise(mach_port_t exception_port,mach_port_t thread,mach_port_t task,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count)178 catch_mach_exception_raise(
179 	mach_port_t exception_port,
180 	mach_port_t thread,
181 	mach_port_t task,
182 	exception_type_t type,
183 	exception_data_t codes,
184 	mach_msg_type_number_t code_count)
185 {
186 #pragma unused(exception_port, thread, task, type, codes, code_count)
187 	T_FAIL("Triggered catch_mach_exception_raise() which shouldn't happen...");
188 	__builtin_unreachable();
189 }
190 
191 kern_return_t
catch_mach_exception_raise_state_identity_protected(mach_port_t exception_port __unused,uint64_t thread_id,mach_port_t task_id_token,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count,int * flavor,thread_state_t in_state,mach_msg_type_number_t in_state_count,thread_state_t out_state,mach_msg_type_number_t * out_state_count)192 catch_mach_exception_raise_state_identity_protected(
193 	mach_port_t exception_port __unused,
194 	uint64_t                  thread_id,
195 	mach_port_t               task_id_token,
196 	exception_type_t type,
197 	exception_data_t codes,
198 	mach_msg_type_number_t code_count,
199 	int *flavor,
200 	thread_state_t in_state,
201 	mach_msg_type_number_t in_state_count,
202 	thread_state_t out_state,
203 	mach_msg_type_number_t *out_state_count)
204 {
205 	LOG_VERBOSE("Caught a mach exception!\n");
206 
207 	/* There should only be two code values. */
208 	T_QUIET; T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
209 
210 	/**
211 	 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
212 	 * when setting the exception port.
213 	 */
214 	mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
215 	LOG_VERBOSE("Mach exception type %d, codes[0]: %#llx, codes[1]: %#llx\n",
216 	    type, codes_64[0], codes_64[1]);
217 
218 	/* Verify that we're receiving the expected thread state flavor. */
219 	T_QUIET; T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
220 	T_QUIET; T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
221 
222 	*out_state_count = in_state_count; /* size of state object in 32-bit words */
223 	memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
224 
225 	size_t advance_pc = tls_callbacks.state_protected_callback(
226 		task_id_token, thread_id, type, codes_64, in_state,
227 		in_state_count, out_state, out_state_count);
228 
229 	if (advance_pc == EXC_HELPER_HALT) {
230 		/* Exception handler callback says we can't continue. */
231 		LOG_VERBOSE("Halting after exception");
232 		return KERN_FAILURE;
233 	}
234 
235 	if (advance_pc != 0) {
236 		T_FAIL("unimplemented PC change from EXCEPTION_STATE_IDENTITY_PROTECTED callback");
237 		return KERN_FAILURE;
238 	}
239 
240 	/* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
241 	return KERN_SUCCESS;
242 }
243 
244 
245 kern_return_t
catch_mach_exception_raise_identity_protected(__unused mach_port_t exception_port,uint64_t thread_id,mach_port_t task_id_token,exception_type_t exception,mach_exception_data_t codes,mach_msg_type_number_t codeCnt)246 catch_mach_exception_raise_identity_protected(
247 	__unused mach_port_t      exception_port,
248 	uint64_t                  thread_id,
249 	mach_port_t               task_id_token,
250 	exception_type_t          exception,
251 	mach_exception_data_t     codes,
252 	mach_msg_type_number_t    codeCnt)
253 {
254 	LOG_VERBOSE("Caught a mach exception!\n");
255 
256 	/* There should only be two code values. */
257 	T_QUIET; T_ASSERT_EQ(codeCnt, 2, "Two code values were provided with the mach exception");
258 
259 	/**
260 	 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
261 	 * when setting the exception port.
262 	 */
263 	mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
264 	LOG_VERBOSE("Mach exception type %d, codes[0]: %#llx, codes[1]: %#llx\n",
265 	    exception, codes_64[0], codes_64[1]);
266 
267 	size_t advance_pc = tls_callbacks.protected_callback(
268 		task_id_token, thread_id, exception, codes_64);
269 
270 	if (advance_pc == EXC_HELPER_HALT) {
271 		/* Exception handler callback says we can't continue. */
272 		LOG_VERBOSE("Halting after exception");
273 		return KERN_FAILURE;
274 	}
275 
276 	if (advance_pc != 0) {
277 		T_FAIL("unimplemented PC change from EXCEPTION_IDENTITY_PROTECTED callback");
278 		return KERN_FAILURE;
279 	}
280 
281 	/* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
282 	return KERN_SUCCESS;
283 }
284 
285 /**
286  * This has to be defined for linking purposes, but it's unused.
287  */
288 kern_return_t
catch_mach_exception_raise_state(mach_port_t exception_port,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count,int * flavor,thread_state_t in_state,mach_msg_type_number_t in_state_count,thread_state_t out_state,mach_msg_type_number_t * out_state_count)289 catch_mach_exception_raise_state(
290 	mach_port_t exception_port,
291 	exception_type_t type,
292 	exception_data_t codes,
293 	mach_msg_type_number_t code_count,
294 	int *flavor,
295 	thread_state_t in_state,
296 	mach_msg_type_number_t in_state_count,
297 	thread_state_t out_state,
298 	mach_msg_type_number_t *out_state_count)
299 {
300 #pragma unused(exception_port, type, codes, code_count, flavor, in_state, in_state_count, out_state, out_state_count)
301 	T_FAIL("Triggered catch_mach_exception_raise_state() which shouldn't happen...");
302 	__builtin_unreachable();
303 }
304 
305 /**
306  * Called by mach_exc_server() to handle the exception. This will call the
307  * test's exception-handler callback and will then modify
308  * the thread state to move to the next instruction.
309  */
310 kern_return_t
catch_mach_exception_raise_state_identity(mach_port_t exception_port __unused,mach_port_t thread,mach_port_t task,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count,int * flavor,thread_state_t in_state,mach_msg_type_number_t in_state_count,thread_state_t out_state,mach_msg_type_number_t * out_state_count)311 catch_mach_exception_raise_state_identity(
312 	mach_port_t exception_port __unused,
313 	mach_port_t thread,
314 	mach_port_t task,
315 	exception_type_t type,
316 	exception_data_t codes,
317 	mach_msg_type_number_t code_count,
318 	int *flavor,
319 	thread_state_t in_state,
320 	mach_msg_type_number_t in_state_count,
321 	thread_state_t out_state,
322 	mach_msg_type_number_t *out_state_count)
323 {
324 	LOG_VERBOSE("Caught a mach exception!\n");
325 
326 	/* There should only be two code values. */
327 	T_QUIET; T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
328 
329 	/**
330 	 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
331 	 * when setting the exception port.
332 	 */
333 	mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
334 	LOG_VERBOSE("Mach exception type %d, codes[0]: %#llx, codes[1]: %#llx\n",
335 	    type, codes_64[0], codes_64[1]);
336 
337 	/* Verify that we're receiving the expected thread state flavor. */
338 	T_QUIET; T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
339 	T_QUIET; T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
340 
341 	uint64_t exception_pc = get_exception_pc(in_state);
342 
343 	size_t advance_pc = tls_callbacks.state_callback(
344 		task, thread, type, codes_64, exception_pc);
345 
346 	if (advance_pc == EXC_HELPER_HALT) {
347 		/* Exception handler callback says we can't continue. */
348 		LOG_VERBOSE("Halting after exception");
349 		return KERN_FAILURE;
350 	}
351 
352 	/**
353 	 * Copy in_state to out_state, then increment the PC by the requested
354 	 * amount so the thread doesn't cause another exception when it resumes.
355 	 */
356 	*out_state_count = in_state_count; /* size of state object in 32-bit words */
357 	memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
358 	assert(0 == memcmp(in_state, out_state, in_state_count * 4));
359 	if (advance_pc != 0) {
360 		advance_exception_pc(advance_pc, out_state);
361 		LOG_VERBOSE("Continuing after exception at a new PC");
362 	} else {
363 		LOG_VERBOSE("Continuing after exception");
364 	}
365 
366 	/* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
367 	return KERN_SUCCESS;
368 }
369 
370 kern_return_t
catch_mach_exception_raise_backtrace(__unused mach_port_t exception_port,mach_port_t kcdata_object,exception_type_t exception,mach_exception_data_t codes,__unused mach_msg_type_number_t codeCnt)371 catch_mach_exception_raise_backtrace(
372 	__unused mach_port_t exception_port,
373 	mach_port_t kcdata_object,
374 	exception_type_t exception,
375 	mach_exception_data_t codes,
376 	__unused mach_msg_type_number_t codeCnt)
377 {
378 	return tls_callbacks.backtrace_callback(kcdata_object, exception, codes);
379 }
380 
381 mach_port_t
create_exception_port(exception_mask_t exception_mask)382 create_exception_port(exception_mask_t exception_mask)
383 {
384 	return create_exception_port_behavior64(exception_mask, EXCEPTION_STATE_IDENTITY);
385 }
386 
387 void
set_thread_exception_port(mach_port_t exc_port,exception_mask_t exception_mask)388 set_thread_exception_port(mach_port_t exc_port, exception_mask_t exception_mask)
389 {
390 	set_thread_exception_port_behavior64(exc_port, exception_mask, EXCEPTION_STATE_IDENTITY);
391 }
392 
393 void
set_thread_exception_port_behavior64(exception_port_t exc_port,exception_mask_t exception_mask,exception_behavior_t behavior)394 set_thread_exception_port_behavior64(exception_port_t exc_port, exception_mask_t exception_mask, exception_behavior_t behavior)
395 {
396 	mach_port_t thread = mach_thread_self();
397 	kern_return_t kr;
398 
399 	if (((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY &&
400 	    ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_IDENTITY_PROTECTED) {
401 		T_FAIL("Passed behavior (%d) is not supported by exc_helpers.", behavior);
402 	}
403 
404 	behavior |= MACH_EXCEPTION_CODES;
405 
406 	/* Tell the kernel what port to send exceptions to. */
407 	kr = thread_set_exception_ports(
408 		thread,
409 		exception_mask,
410 		exc_port,
411 		(exception_behavior_t)((unsigned int)behavior),
412 		EXCEPTION_THREAD_STATE);
413 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Set the exception port to my custom handler");
414 }
415 
416 mach_port_t
create_exception_port_behavior64(exception_mask_t exception_mask,exception_behavior_t behavior)417 create_exception_port_behavior64(exception_mask_t exception_mask, exception_behavior_t behavior)
418 {
419 	mach_port_t exc_port = MACH_PORT_NULL;
420 	mach_port_t task = mach_task_self();
421 	kern_return_t kr = KERN_SUCCESS;
422 
423 	/* Create the mach port the exception messages will be sent to. */
424 	kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exc_port);
425 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Allocated mach exception port");
426 
427 	/**
428 	 * Insert a send right into the exception port that the kernel will use to
429 	 * send the exception thread the exception messages.
430 	 */
431 	kr = mach_port_insert_right(task, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
432 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Inserted a SEND right into the exception port");
433 
434 	set_thread_exception_port_behavior64(exc_port, exception_mask, behavior);
435 	return exc_port;
436 }
437 
438 struct thread_params {
439 	mach_port_t exc_port;
440 	bool run_once;
441 
442 	struct exc_handler_callbacks callbacks;
443 };
444 
445 /**
446  * Thread to handle the mach exception.
447  *
448  * @param arg The exception port to wait for a message on.
449  */
450 static void *
exc_server_thread(void * arg)451 exc_server_thread(void *arg)
452 {
453 	struct thread_params *params = arg;
454 	mach_port_t exc_port = params->exc_port;
455 	bool run_once = params->run_once;
456 
457 	/*
458 	 * Save callbacks to thread-local storage so the
459 	 * catch_mach_exception_raise_* functions can get them.
460 	 */
461 	tls_callbacks = params->callbacks;
462 
463 	free(params);
464 	params = NULL;
465 
466 	/**
467 	 * mach_msg_server_once is a helper function provided by libsyscall that
468 	 * handles creating mach messages, blocks waiting for a message on the
469 	 * exception port, calls mach_exc_server() to handle the exception, and
470 	 * sends a reply based on the return value of mach_exc_server().
471 	 */
472 #define MACH_MSG_REPLY_SIZE 4096
473 	kern_return_t kr;
474 	if (run_once) {
475 		kr = mach_msg_server_once(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
476 	} else {
477 		kr = mach_msg_server(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
478 	}
479 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Received mach exception message");
480 
481 	pthread_exit((void*)0);
482 	__builtin_unreachable();
483 }
484 
485 static void
_run_exception_handler(mach_port_t exc_port,void * preferred_callback,void * callback,bool run_once,exception_behavior_t behavior)486 _run_exception_handler(mach_port_t exc_port, void *preferred_callback, void *callback, bool run_once, exception_behavior_t behavior)
487 {
488 	/* Set parameters for the exception server's thread. */
489 	struct thread_params *params = calloc(1, sizeof(*params));
490 	params->exc_port = exc_port;
491 	params->run_once = run_once;
492 
493 	if (behavior & MACH_EXCEPTION_BACKTRACE_PREFERRED) {
494 		T_QUIET; T_ASSERT_NE(NULL, preferred_callback, "Require a preferred callback");
495 		params->callbacks.backtrace_callback = (exc_handler_backtrace_callback_t)preferred_callback;
496 	}
497 
498 	behavior &= ~MACH_EXCEPTION_MASK;
499 
500 	switch (behavior) {
501 	case EXCEPTION_STATE_IDENTITY:
502 		params->callbacks.state_callback = (exc_handler_callback_t)callback;
503 		break;
504 	case EXCEPTION_STATE_IDENTITY_PROTECTED:
505 		params->callbacks.state_protected_callback = (exc_handler_state_protected_callback_t)callback;
506 		break;
507 	case EXCEPTION_IDENTITY_PROTECTED:
508 		params->callbacks.protected_callback = (exc_handler_protected_callback_t)callback;
509 		break;
510 	default:
511 		T_FAIL("Unsupported behavior");
512 		break;
513 	}
514 
515 	/* Spawn the exception server's thread. */
516 	pthread_t exc_thread;
517 	int err = pthread_create(&exc_thread, (pthread_attr_t*)0, exc_server_thread, params);
518 	T_QUIET; T_ASSERT_POSIX_ZERO(err, "Spawned exception server thread");
519 
520 	/* No need to wait for the exception server to be joined when it exits. */
521 	pthread_detach(exc_thread);
522 }
523 
524 void
run_exception_handler(mach_port_t exc_port,exc_handler_callback_t callback)525 run_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
526 {
527 	run_exception_handler_behavior64(exc_port, NULL, (void *)callback, EXCEPTION_STATE_IDENTITY, true);
528 }
529 
530 void
run_exception_handler_behavior64(mach_port_t exc_port,void * preferred_callback,void * callback,exception_behavior_t behavior,bool run_once)531 run_exception_handler_behavior64(mach_port_t exc_port, void *preferred_callback,
532     void *callback, exception_behavior_t behavior, bool run_once)
533 {
534 	if (((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY &&
535 	    ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_IDENTITY_PROTECTED &&
536 	    ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY_PROTECTED) {
537 		T_FAIL("Passed behavior (%d) is not supported by exc_helpers.", behavior);
538 	}
539 
540 	_run_exception_handler(exc_port, (void *)preferred_callback, (void *)callback, run_once, behavior);
541 }
542 
543 void
repeat_exception_handler(mach_port_t exc_port,exc_handler_callback_t callback)544 repeat_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
545 {
546 	_run_exception_handler(exc_port, NULL, (void *)callback, false, EXCEPTION_STATE_IDENTITY);
547 }
548