]>
Commit | Line | Data |
---|---|---|
70ad1dc8 A |
1 | /* |
2 | * Copyright (c) 2018 Apple Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_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. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | #include "internal.h" | |
24 | ||
25 | #pragma mark Types | |
26 | typedef struct _os_mach_port_disposition { | |
27 | mach_msg_bits_t ompd_bits; | |
28 | const char *const ompd_human; | |
29 | } os_mach_port_disposition_t; | |
30 | ||
31 | #define os_mach_port_disposition_init(d) [d] = { \ | |
32 | .ompd_bits = (d), \ | |
33 | .ompd_human = #d, \ | |
34 | } | |
35 | ||
36 | #pragma mark Top-Level Statics | |
37 | static const os_flagset_t _mach_msgh_bits = { | |
38 | os_flag_init(MACH_MSGH_BITS_COMPLEX), | |
39 | os_flag_init(MACH_MSGH_BITS_RAISEIMP), | |
40 | // MACH_MSGH_BITS_DENAP is deprecated | |
41 | os_flag_init(MACH_MSGH_BITS_IMPHOLDASRT), | |
42 | // MACH_MSGH_BITS_DENAPHOLDASRT is deprecated | |
43 | // MACH_MSGH_BITS_CIRCULAR is kernel-internal | |
44 | }; | |
45 | ||
46 | static const os_mach_port_disposition_t _mach_port_dispositions[] = { | |
47 | os_mach_port_disposition_init(MACH_MSG_TYPE_MOVE_RECEIVE), | |
48 | os_mach_port_disposition_init(MACH_MSG_TYPE_MOVE_SEND), | |
49 | os_mach_port_disposition_init(MACH_MSG_TYPE_MOVE_SEND_ONCE), | |
50 | os_mach_port_disposition_init(MACH_MSG_TYPE_COPY_SEND), | |
51 | os_mach_port_disposition_init(MACH_MSG_TYPE_MAKE_SEND), | |
52 | os_mach_port_disposition_init(MACH_MSG_TYPE_MAKE_SEND_ONCE), | |
53 | // MACH_MSG_TYPE_COPY_RECEIVE is not a valid operation, so unclear why it's | |
54 | // even defined | |
55 | os_mach_port_disposition_init(MACH_MSG_TYPE_DISPOSE_RECEIVE), | |
56 | os_mach_port_disposition_init(MACH_MSG_TYPE_DISPOSE_SEND), | |
57 | os_mach_port_disposition_init(MACH_MSG_TYPE_DISPOSE_SEND_ONCE), | |
58 | }; | |
59 | ||
60 | static inline const char * | |
61 | _mach_port_disposition_string(mach_msg_bits_t d) | |
62 | { | |
63 | if (d < MACH_MSG_TYPE_MOVE_RECEIVE) { | |
64 | return "[invalid]"; | |
65 | } | |
66 | if (d > MACH_MSG_TYPE_DISPOSE_SEND_ONCE) { | |
67 | return "[invalid]"; | |
68 | } | |
69 | return _mach_port_dispositions[d].ompd_human; | |
70 | } | |
71 | ||
72 | static const os_flagset_t _mach_port_rights = { | |
73 | os_flag_init(MACH_PORT_TYPE_SEND), | |
74 | os_flag_init(MACH_PORT_TYPE_RECEIVE), | |
75 | os_flag_init(MACH_PORT_TYPE_SEND_ONCE), | |
76 | os_flag_init(MACH_PORT_TYPE_PORT_SET), | |
77 | os_flag_init(MACH_PORT_TYPE_DEAD_NAME), | |
78 | // MACH_PORT_TYPE_LABELH is obsolete | |
79 | // MACH_PORT_TYPE_DNREQUEST->_mach_port_requests | |
80 | // MACH_PORT_TYPE_SPREQUEST->_mach_port_requests | |
81 | // MACH_PORT_TYPE_SPREQUEST_DELAYED->_mach_port_requests | |
82 | // MACH_PORT_RIGHT_NUMBER is obsolete | |
83 | }; | |
84 | ||
85 | static const os_flagset_t _mach_port_requests = { | |
86 | os_flag_init(MACH_PORT_TYPE_DNREQUEST), | |
87 | os_flag_init(MACH_PORT_TYPE_SPREQUEST), | |
88 | os_flag_init(MACH_PORT_TYPE_SPREQUEST_DELAYED), | |
89 | }; | |
90 | ||
91 | static const os_flagset_t _mach_port_status = { | |
92 | os_flag_init(MACH_PORT_STATUS_FLAG_TEMPOWNER), | |
93 | os_flag_init(MACH_PORT_STATUS_FLAG_GUARDED), | |
94 | os_flag_init(MACH_PORT_STATUS_FLAG_STRICT_GUARD), | |
95 | os_flag_init(MACH_PORT_STATUS_FLAG_IMP_DONATION), | |
96 | // MACH_PORT_STATUS_FLAG_REVIVE is obsolete | |
97 | os_flag_init(MACH_PORT_STATUS_FLAG_TASKPTR), | |
98 | }; | |
99 | ||
100 | static const os_flagset_t _mach_special_bits = { | |
101 | os_flag_init(MACH_MSG_IPC_SPACE), | |
102 | os_flag_init(MACH_MSG_VM_SPACE), | |
103 | os_flag_init(MACH_MSG_IPC_KERNEL), | |
104 | os_flag_init(MACH_MSG_VM_KERNEL), | |
105 | }; | |
106 | ||
107 | #pragma mark API | |
108 | const mach_msg_trailer_t * | |
109 | os_mach_msg_get_trailer(const mach_msg_header_t *hdr) | |
110 | { | |
111 | // The mach_msg() documentation states that the trailer will follow the | |
112 | // message body on the next natural boundary. But when we moved to 64-bit, | |
113 | // we kept the trailer alignment on a 4-byte boundary for compatibility | |
114 | // reasons. Specifically, natural_t is still 32 bits on both 32- and 64-bit | |
115 | // platforms. | |
116 | return (mach_msg_trailer_t *)((uint8_t *)hdr + round_msg(hdr->msgh_size)); | |
117 | } | |
118 | ||
119 | const mach_msg_audit_trailer_t * | |
120 | os_mach_msg_get_audit_trailer(const mach_msg_header_t *hdr) | |
121 | { | |
122 | const mach_msg_trailer_t *tlr = NULL; | |
123 | const mach_msg_audit_trailer_t *audit_tlr = NULL; | |
124 | ||
125 | tlr = os_mach_msg_get_trailer(hdr); | |
126 | if (tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0) { | |
127 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t)) { | |
128 | audit_tlr = (mach_msg_audit_trailer_t *)tlr; | |
129 | } | |
130 | } | |
131 | ||
132 | return audit_tlr; | |
133 | } | |
134 | ||
135 | const mach_msg_context_trailer_t * | |
136 | os_mach_msg_get_context_trailer(const mach_msg_header_t *hdr) | |
137 | { | |
138 | const mach_msg_trailer_t *tlr = NULL; | |
139 | const mach_msg_context_trailer_t *ctx_tlr = NULL; | |
140 | ||
141 | tlr = os_mach_msg_get_trailer(hdr); | |
142 | if (tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0) { | |
143 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_context_trailer_t)) { | |
144 | ctx_tlr = (mach_msg_context_trailer_t *)tlr; | |
145 | } | |
146 | } | |
147 | ||
148 | return ctx_tlr; | |
149 | } | |
150 | ||
151 | char * | |
152 | os_mach_msg_copy_description(const mach_msg_header_t *msg) | |
153 | { | |
154 | int ret = -1; | |
155 | mach_msg_bits_t local = MACH_MSGH_BITS_LOCAL(msg->msgh_bits); | |
156 | mach_msg_bits_t remote = MACH_MSGH_BITS_REMOTE(msg->msgh_bits); | |
157 | mach_msg_bits_t voucher = MACH_MSGH_BITS_VOUCHER(msg->msgh_bits); | |
158 | char *__os_free bits_desc = NULL; | |
159 | const char *local_desc = _mach_port_disposition_string(local); | |
160 | const char *remote_desc = _mach_port_disposition_string(remote); | |
161 | const char *voucher_desc = _mach_port_disposition_string(voucher); | |
162 | char *desc = NULL; | |
163 | mach_msg_size_t ool_cnt = 0; | |
164 | ||
165 | if (msg->msgh_bits & MACH_MSGH_BITS_COMPLEX) { | |
166 | ool_cnt = ((mach_msg_base_t *)msg)->body.msgh_descriptor_count; | |
167 | } | |
168 | ||
169 | bits_desc = os_flagset_copy_string(_mach_msgh_bits, msg->msgh_bits); | |
170 | ret = asprintf(&desc, "id = %#x, size = %u, bits = %s, " | |
171 | "local disp = %s, local port = %#x, " | |
172 | "remote disp = %s, remote port = %#x, " | |
173 | "voucher disp = %s, voucher port = %#x, " | |
174 | "out-of-line descriptor cnt = %u", | |
175 | msg->msgh_id, msg->msgh_size, bits_desc, | |
176 | local_desc, msg->msgh_local_port, | |
177 | remote_desc, msg->msgh_remote_port, | |
178 | voucher_desc, msg->msgh_voucher_port, | |
179 | ool_cnt); | |
180 | posix_assert_zero(ret); | |
181 | ||
182 | return desc; | |
183 | } | |
184 | ||
185 | char * | |
186 | os_mach_msg_trailer_copy_description(const mach_msg_trailer_t *tlr) | |
187 | { | |
188 | union { | |
189 | int r; | |
190 | size_t n; | |
191 | } ret = { | |
192 | .r = -1, | |
193 | }; | |
194 | char *desc = NULL; | |
195 | char buff[512]; | |
196 | char *cursor = buff; | |
197 | size_t left = sizeof(buff); | |
198 | // Yes we do not know the actual size of the trailer yet, so this is | |
199 | // technically unsafe, but we only dereference members after determining | |
200 | // that they are safe to dereference. Just us chickens and all that. | |
201 | const mach_msg_mac_trailer_t *max = (const mach_msg_mac_trailer_t *)tlr; | |
202 | ||
203 | if (tlr->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) { | |
204 | ret.r = asprintf(&desc, "type = %u, size = %u", | |
205 | tlr->msgh_trailer_type, tlr->msgh_trailer_size); | |
206 | os_assert_zero(ret.r); | |
207 | goto __out; | |
208 | } | |
209 | ||
210 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_trailer_t)) { | |
211 | ret.r = snprintf(cursor, left, "format = %u, size = %u", | |
212 | tlr->msgh_trailer_type, tlr->msgh_trailer_size); | |
213 | os_assert_sprintf(ret.r, left); | |
214 | ||
215 | // Safe since the above assert has verified that ret is both positive | |
216 | // and less than or equal to the size of the buffer. | |
217 | cursor += ret.n; | |
218 | left -= ret.n; | |
219 | } | |
220 | ||
221 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_seqno_trailer_t)) { | |
222 | ret.r = snprintf(cursor, left, ", seqno = %u", max->msgh_seqno); | |
223 | os_assert_sprintf(ret.r, left); | |
224 | cursor += ret.n; | |
225 | left -= ret.n; | |
226 | } | |
227 | ||
228 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_security_trailer_t)) { | |
229 | ret.r = snprintf(cursor, left, ", security.uid = %u, security.gid = %u", | |
230 | max->msgh_sender.val[0], max->msgh_sender.val[1]); | |
231 | os_assert_sprintf(ret.r, left); | |
232 | cursor += ret.n; | |
233 | left -= ret.n; | |
234 | } | |
235 | ||
236 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t)) { | |
237 | ret.r = snprintf(cursor, left, ", audit.auid = %u, " | |
238 | "audit.euid = %u, audit.egid = %u, " | |
239 | "audit.ruid = %u, audit.rgid = %u, " | |
240 | "audit.pid = %u, audit.asid = %u, audit.pidvers = %u", | |
241 | max->msgh_audit.val[0], max->msgh_audit.val[1], | |
242 | max->msgh_audit.val[2], max->msgh_audit.val[3], | |
243 | max->msgh_audit.val[4], max->msgh_audit.val[5], | |
244 | max->msgh_audit.val[6], max->msgh_audit.val[7]); | |
245 | os_assert_sprintf(ret.r, left); | |
246 | cursor += ret.n; | |
247 | left -= ret.n; | |
248 | } | |
249 | ||
250 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_context_trailer_t)) { | |
251 | uint64_t ctx = max->msgh_context; | |
252 | ret.r = snprintf(cursor, left, ", context = %#llx", ctx); | |
253 | os_assert_sprintf(ret.r, left); | |
254 | cursor += ret.n; | |
255 | left -= ret.n; | |
256 | } | |
257 | ||
258 | if (tlr->msgh_trailer_size >= sizeof(mach_msg_mac_trailer_t)) { | |
259 | ret.r = snprintf(cursor, left, ", labels.sender = %#x", | |
260 | max->msgh_labels.sender); | |
261 | os_assert_sprintf(ret.r, left); | |
262 | cursor += ret.n; | |
263 | left -= ret.n; | |
264 | } | |
265 | ||
266 | desc = os_strdup(buff); | |
267 | ||
268 | __out: | |
269 | return desc; | |
270 | } | |
271 | ||
272 | char * | |
273 | os_mach_port_copy_description(mach_port_t p) | |
274 | { | |
275 | kern_return_t kr = KERN_FAILURE; | |
276 | mach_port_right_t right = 0; | |
277 | mach_port_type_t type = 0; | |
278 | mach_port_urefs_t urefs = 0; | |
279 | mach_port_status_t status; | |
280 | mach_msg_type_number_t status_size = MACH_PORT_RECEIVE_STATUS_COUNT; | |
281 | char *desc = NULL; | |
282 | char *__os_free rightdesc = NULL; | |
283 | char *__os_free requestdesc = NULL; | |
284 | char *__os_free statusdesc = NULL; | |
285 | char *__os_free urefsdesc = NULL; | |
286 | char *which_urefs = ""; | |
287 | int ret = -1; | |
288 | ||
289 | if (p == MACH_PORT_NULL) { | |
290 | return os_strdup("null"); | |
291 | } | |
292 | if (p == MACH_PORT_DEAD) { | |
293 | return os_strdup("dead-name"); | |
294 | } | |
295 | ||
296 | kr = mach_port_type(mach_task_self(), p, &type); | |
297 | switch (kr) { | |
298 | case KERN_SUCCESS: | |
299 | rightdesc = os_flagset_copy_string(_mach_port_rights, type); | |
300 | requestdesc = os_flagset_copy_string(_mach_port_requests, type); | |
301 | break; | |
302 | default: | |
303 | ret = asprintf(&rightdesc, "[%#x]", kr); | |
304 | posix_assert_zero(ret); | |
305 | } | |
306 | ||
307 | kr = mach_port_get_attributes(mach_task_self(), p, | |
308 | MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_size); | |
309 | switch (kr) { | |
310 | case KERN_SUCCESS: | |
311 | if (status.mps_flags) { | |
312 | statusdesc = os_flagset_copy_string(_mach_port_status, | |
313 | status.mps_flags); | |
314 | } else { | |
315 | statusdesc = os_strdup("[none]"); | |
316 | } | |
317 | ||
318 | break; | |
319 | case KERN_INVALID_RIGHT: | |
320 | if (!(type & MACH_PORT_TYPE_RECEIVE)) { | |
321 | statusdesc = os_strdup("[none]"); | |
322 | break; | |
323 | } | |
324 | default: | |
325 | ret = asprintf(&statusdesc, "[%#x]", kr); | |
326 | posix_assert_zero(ret); | |
327 | } | |
328 | ||
329 | if (type & MACH_PORT_TYPE_SEND) { | |
330 | right = MACH_PORT_RIGHT_SEND; | |
331 | which_urefs = "send"; | |
332 | } else if (type & MACH_PORT_TYPE_DEAD_NAME) { | |
333 | right = MACH_PORT_RIGHT_DEAD_NAME; | |
334 | which_urefs = "dead name"; | |
335 | } | |
336 | ||
337 | if (which_urefs) { | |
338 | kr = mach_port_get_refs(mach_task_self(), p, right, &urefs); | |
339 | switch (kr) { | |
340 | case KERN_SUCCESS: | |
341 | ret = asprintf(&urefsdesc, ", %s urefs = %u", which_urefs, urefs); | |
342 | break; | |
343 | default: | |
344 | ret = asprintf(&urefsdesc, ", %s urefs = [%#x]", | |
345 | which_urefs, kr); | |
346 | break; | |
347 | } | |
348 | } | |
349 | ||
350 | ret = asprintf(&desc, "name = %#x, rights = %s, requests = %s, " | |
351 | "status = %s%s", | |
352 | p, rightdesc, requestdesc, statusdesc, urefsdesc); | |
353 | posix_assert_zero(ret); | |
354 | ||
355 | return desc; | |
356 | } | |
357 | ||
358 | #pragma mark API from <os/assumes.h> | |
359 | // These live here because the implementations uses functionality from | |
360 | // libdarwin, and we don't want to have a circular dependency between Libc and | |
361 | // libsystem_darwin. The long-term plan is to move assumes() and assert() | |
362 | // functionality into libdarwin anyway. | |
363 | void | |
507116e3 | 364 | (os_assert_mach)(const char *op, kern_return_t kr) |
70ad1dc8 A |
365 | { |
366 | kern_return_t real_kr = (kern_return_t)(kr & (~MACH_MSG_MASK)); | |
367 | kern_return_t extra = (kern_return_t)(kr & MACH_MSG_MASK); | |
368 | const char *err_string = NULL; | |
369 | const char *err_type_string = NULL; | |
370 | char err_buff[64]; | |
371 | char code_buff[16]; | |
372 | const char *special_desc = NULL; | |
373 | int sys = err_get_system(real_kr); | |
374 | int sub = err_get_sub(real_kr); | |
375 | int code = err_get_code(real_kr); | |
376 | ||
377 | if (kr == KERN_SUCCESS) { | |
378 | return; | |
379 | } | |
380 | ||
381 | if (kr >= BOOTSTRAP_NOT_PRIVILEGED && kr <= BOOTSTRAP_NO_CHILDREN) { | |
382 | err_string = bootstrap_strerror(kr); | |
383 | snprintf(code_buff, sizeof(code_buff), "%d", kr); | |
384 | err_type_string = "bootstrap"; | |
385 | } else { | |
386 | err_string = mach_error_string(real_kr); | |
387 | if (strcmp(err_string, "unknown error code") == 0) { | |
388 | snprintf(err_buff, sizeof(err_buff), "[%#x|%#x|%#x]", | |
389 | sys, sub, code); | |
390 | err_string = err_buff; | |
391 | err_type_string = "unrecognized"; | |
392 | } else { | |
393 | err_type_string = "mach"; | |
394 | } | |
395 | ||
396 | if (kr <= MIG_TYPE_ERROR && kr >= MIG_TRAILER_ERROR) { | |
397 | snprintf(code_buff, sizeof(code_buff), "%d", kr); | |
398 | } else { | |
399 | snprintf(code_buff, sizeof(code_buff), "%#x", kr); | |
400 | special_desc = os_flagset_copy_string(_mach_special_bits, extra); | |
401 | } | |
402 | } | |
403 | ||
404 | if (special_desc) { | |
405 | os_crash("%s failed: %s error = %s [%s], special bits = %s", | |
406 | op, err_type_string, err_string, code_buff, special_desc); | |
407 | } else { | |
408 | os_crash("%s failed: %s error = %s [%s]", | |
409 | op, err_type_string, err_string, code_buff); | |
410 | } | |
411 | } | |
412 | ||
413 | void | |
414 | os_assert_mach_port_status(const char *desc, mach_port_t p, | |
415 | mach_port_status_t *expected) | |
416 | { | |
417 | kern_return_t kr = KERN_FAILURE; | |
418 | mach_port_status_t status; | |
419 | mach_msg_type_number_t status_cnt = MACH_PORT_RECEIVE_STATUS_COUNT; | |
420 | ||
421 | kr = mach_port_get_attributes(mach_task_self(), p, MACH_PORT_RECEIVE_STATUS, | |
422 | (mach_port_info_t)&status, &status_cnt); | |
423 | os_assert_mach("get status", kr); | |
424 | ||
425 | if (expected->mps_pset != UINT32_MAX) { | |
426 | if (expected->mps_pset != status.mps_pset) { | |
427 | os_crash("port set mismatch: actual = %u, expected = %u", | |
428 | status.mps_pset, expected->mps_pset); | |
429 | } | |
430 | } | |
431 | if (expected->mps_seqno != UINT32_MAX) { | |
432 | if (expected->mps_seqno != status.mps_seqno) { | |
433 | os_crash("sequence number mismatch: actual = %u, expected = %u", | |
434 | status.mps_seqno, expected->mps_seqno); | |
435 | } | |
436 | } | |
437 | if (expected->mps_mscount != UINT32_MAX) { | |
438 | if (expected->mps_mscount != status.mps_mscount) { | |
439 | os_crash("make-send count mismatch: actual = %u, expected = %u", | |
440 | status.mps_mscount, expected->mps_mscount); | |
441 | } | |
442 | } | |
443 | if (expected->mps_qlimit != UINT32_MAX) { | |
444 | if (expected->mps_qlimit != status.mps_qlimit) { | |
445 | os_crash("queue limit mismatch: actual = %u, expected = %u", | |
446 | status.mps_qlimit, expected->mps_qlimit); | |
447 | } | |
448 | } | |
449 | if (expected->mps_msgcount != UINT32_MAX) { | |
450 | if (expected->mps_msgcount != status.mps_msgcount) { | |
451 | os_crash("message count mismatch: actual = %u, expected = %u", | |
452 | status.mps_msgcount, expected->mps_msgcount); | |
453 | } | |
454 | } | |
455 | if (expected->mps_sorights != UINT32_MAX) { | |
456 | if (expected->mps_sorights != status.mps_sorights) { | |
457 | os_crash("send-once rights mismatch: actual = %u, expected = %u", | |
458 | status.mps_sorights, expected->mps_sorights); | |
459 | } | |
460 | } | |
461 | if (expected->mps_srights != INT32_MAX) { | |
462 | if (expected->mps_srights != status.mps_srights) { | |
463 | os_crash("send rights mismatch: actual = %d, expected = %d", | |
464 | status.mps_srights, expected->mps_srights); | |
465 | } | |
466 | } | |
467 | if (expected->mps_pdrequest != INT32_MAX) { | |
468 | if (expected->mps_pdrequest != status.mps_pdrequest) { | |
469 | os_crash("port-destroyed mismatch: actual = %d, expected = %d", | |
470 | status.mps_pdrequest, expected->mps_pdrequest); | |
471 | } | |
472 | } | |
473 | if (expected->mps_nsrequest != INT32_MAX) { | |
474 | if (expected->mps_nsrequest != status.mps_nsrequest) { | |
475 | os_crash("no-senders mismatch: actual = %d, expected = %d", | |
476 | status.mps_nsrequest, expected->mps_nsrequest); | |
477 | } | |
478 | } | |
479 | if (expected->mps_flags) { | |
480 | if (expected->mps_flags != status.mps_flags) { | |
481 | os_crash("flags mismatch: actual = %#x, expected = %#x", | |
482 | status.mps_flags, expected->mps_flags); | |
483 | } | |
484 | } | |
485 | } |