1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "crash-handler.h"
6
7 #include <inttypes.h>
8
9 #include <zircon/process.h>
10 #include <zircon/status.h>
11 #include <zircon/syscalls/exception.h>
12 #include <zircon/syscalls/port.h>
13 #include <zircon/threads.h>
14
15 #define EXCEPTION_PORT_KEY 1
16 // The test completed without the test thread crashing.
17 #define TEST_ENDED_EVENT_KEY 2
18 // The test thread had a registered crash.
19 #define TEST_THREAD_TERMINATED_KEY 3
20
21 // Signals sent from the test thread to the crash handler port to indicate
22 // the test result.
23 #define TEST_PASSED_SIGNAL ZX_USER_SIGNAL_0
24 #define TEST_FAILED_SIGNAL ZX_USER_SIGNAL_1
25
26 /**
27 * Kills the crashing process or thread found in the registered list matching
28 * the exception report. Processes or threads are registered in tests via
29 * REGISTER_CRASH if a crash is expected.
30 *
31 * If killing failed, the test will be terminated.
32 *
33 * If the crash was not registered, it will be bubbled up to the crashlogger,
34 * and then the test will be terminated.
35 */
process_exception(crash_list_t crash_list,const zx_port_packet_t * packet,zx_handle_t exception_port)36 static void process_exception(crash_list_t crash_list, const zx_port_packet_t* packet,
37 zx_handle_t exception_port) {
38 const zx_packet_exception_t* exception = &packet->exception;
39
40 // Check for exceptions from registered processes that are not really crashes.
41 switch (packet->type) {
42 case ZX_EXCP_THREAD_STARTING:
43 case ZX_EXCP_THREAD_EXITING: {
44 zx_handle_t process = crash_list_lookup_koid(crash_list, exception->pid);
45 zx_handle_t thread = ZX_HANDLE_INVALID;
46 if (process == ZX_HANDLE_INVALID) {
47 // The test may have registered a thread handle instead.
48 thread = crash_list_lookup_koid(crash_list, exception->tid);
49 }
50 if (process != ZX_HANDLE_INVALID || thread != ZX_HANDLE_INVALID) {
51 zx_status_t status;
52 if (thread == ZX_HANDLE_INVALID) {
53 status =
54 zx_object_get_child(process, exception->tid, ZX_RIGHT_SAME_RIGHTS, &thread);
55 if (status != ZX_OK) {
56 UNITTEST_FAIL_TRACEF(
57 "FATAL: failed to get a handle to [%" PRIu64 "%." PRIu64 "] : error %s\n",
58 exception->pid, exception->tid, zx_status_get_string(status));
59 exit(ZX_ERR_INTERNAL);
60 }
61 }
62 status = zx_task_resume_from_exception(thread, exception_port, 0);
63 if (status != ZX_OK) {
64 UNITTEST_FAIL_TRACEF("FATAL: failed to resume [%" PRIu64 ".%" PRIu64
65 "] : error %s\n",
66 exception->pid, exception->tid, zx_status_get_string(status));
67 exit(ZX_ERR_INTERNAL);
68 }
69 return;
70 }
71 break;
72 }
73 default:
74 break;
75 }
76
77 // Check if the crashed process is in the registered list and remove
78 // it if so.
79 zx_handle_t match = crash_list_delete_koid(crash_list, exception->pid);
80 if (match == ZX_HANDLE_INVALID) {
81 // The test may have registered a thread handle instead.
82 match = crash_list_delete_koid(crash_list, exception->tid);
83 }
84
85 // The crash was not registered. We should let crashlogger print out the
86 // details and then fail the test.
87 if (match == ZX_HANDLE_INVALID) {
88 UNITTEST_FAIL_TRACEF("FATAL: [%" PRIu64 ".%" PRIu64
89 "] crashed with exception 0x%x but was not registered\n",
90 exception->pid, exception->tid, packet->type);
91 zx_handle_t job = zx_job_default();
92 if (job == ZX_HANDLE_INVALID) {
93 UNITTEST_FAIL_TRACEF("FATAL: Unexpected environment. Tests should have a "
94 "default job available\n");
95 exit(ZX_ERR_INTERNAL);
96
97 }
98 zx_handle_t process;
99 zx_status_t status =
100 zx_object_get_child(job, exception->pid, ZX_RIGHT_SAME_RIGHTS, &process);
101 if (status != ZX_OK) {
102 UNITTEST_FAIL_TRACEF("FATAL: failed to get a handle to [%" PRIu64 "] : error %s\n",
103 exception->pid, zx_status_get_string(status));
104 exit(ZX_ERR_INTERNAL);
105 }
106 zx_handle_t thread;
107 status = zx_object_get_child(process, exception->tid, ZX_RIGHT_SAME_RIGHTS, &thread);
108 if (status != ZX_OK) {
109 UNITTEST_FAIL_TRACEF("FATAL: failed to get a handle to [%" PRIu64 ".%" PRIu64
110 "] : error %s\n",
111 exception->pid, exception->tid, zx_status_get_string(status));
112 zx_handle_close(process);
113 exit(ZX_ERR_INTERNAL);
114 }
115 // Pass the exception up to crashlogger.
116 status = zx_task_resume_from_exception(thread, exception_port, ZX_RESUME_TRY_NEXT);
117 if (status == ZX_OK) {
118 // Give crashlogger a little time to print info about the crashed
119 // thread.
120 zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
121 } else {
122 UNITTEST_FAIL_TRACEF("FATAL: could not pass exception from [%" PRIu64 ".%" PRIu64
123 "] : error %s\n",
124 exception->pid, exception->tid, zx_status_get_string(status));
125 }
126 // This may not be reached if the test process itself crashed,
127 // as crashlogger will kill the crashed process.
128 zx_handle_close(process);
129 zx_handle_close(thread);
130 // TODO: fail the test more gracefully.
131 exit(ZX_ERR_INTERNAL);
132 }
133 zx_status_t status = zx_task_kill(match);
134 if (status != ZX_OK) {
135 UNITTEST_FAIL_TRACEF("FATAL: failed to kill [%" PRIu64 ".%" PRIu64 "] : error %s\n",
136 exception->pid, exception->tid, zx_status_get_string(status));
137 exit(ZX_ERR_INTERNAL);
138 }
139
140 // The exception is still unprocessed. We should wait for termination so
141 // there is no race condition with when we unbind the exception port.
142 status = zx_object_wait_one(match, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, NULL);
143 if (status != ZX_OK) {
144 UNITTEST_FAIL_TRACEF("FATAL: failed to wait for termination : error %s\n",
145 zx_status_get_string(status));
146 exit(ZX_ERR_INTERNAL);
147 }
148 zx_handle_close(match);
149 }
150
151 // Returns the test result if it completes, else true if the test thread
152 // had a registered crash.
watch_test_thread(zx_handle_t port,crash_list_t crash_list)153 static test_result_t watch_test_thread(zx_handle_t port, crash_list_t crash_list) {
154 zx_port_packet_t packet;
155 while (true) {
156 zx_status_t status = zx_port_wait(port, ZX_TIME_INFINITE, &packet);
157 if (status != ZX_OK) {
158 UNITTEST_FAIL_TRACEF("failed to wait on port: error %s\n",
159 zx_status_get_string(status));
160 exit(ZX_ERR_INTERNAL);
161 }
162 switch (packet.key) {
163 case EXCEPTION_PORT_KEY:
164 process_exception(crash_list, &packet, port);
165 break;
166 case TEST_ENDED_EVENT_KEY:
167 if (packet.signal.observed & TEST_PASSED_SIGNAL) {
168 return TEST_PASSED;
169 } else if (packet.signal.observed & TEST_FAILED_SIGNAL) {
170 return TEST_FAILED;
171 } else {
172 UNITTEST_FAIL_TRACEF("unknown test ended event signal: %u\n",
173 packet.signal.observed);
174 exit(ZX_ERR_INTERNAL);
175 }
176 case TEST_THREAD_TERMINATED_KEY:
177 // The test thread exited without sending the
178 // TEST_ENDED_EVENT_KEY packet, so we must have killed the crashing
179 // thread. If it was an unregistered crash, we would have exited
180 // and failed the test already, so this must be a registered crash.
181 return TEST_CRASHED;
182 }
183 }
184 __UNREACHABLE;
185 }
186
187 struct test_data_t {
188 // The test function to call.
189 bool (*test_function)(void*);
190 void* test_function_arg;
191
192 // For signaling TEST_PASSED_SIGNAL or TEST_FAILED_SIGNAL.
193 zx_handle_t test_ended_event;
194 // For registering test termination.
195 zx_handle_t port;
196
197 // For registering the test thread, if it is expected to crash.
198 crash_list_t crash_list;
199 // Whether to bind to the thread exception port.
200 bool bind_to_thread;
201 };
202
203 // This is run as a separate thread, so exit() is used instead of returning
204 // status values.
run_test(void * arg)205 static int run_test(void* arg) {
206 test_data_t* data = (test_data_t*)arg;
207 zx_handle_t self = zx_thread_self();
208
209 // We need to register for thread termination here instead of the main
210 // thread. The main thread can't get a handle to this thread before it has
211 // started, at which point the test may have run and crashed already,
212 // leading to an invalid handle.
213 zx_status_t status = zx_object_wait_async(self, data->port, TEST_THREAD_TERMINATED_KEY,
214 ZX_THREAD_TERMINATED, ZX_WAIT_ASYNC_ONCE);
215 if (status != ZX_OK) {
216 UNITTEST_FAIL_TRACEF("FATAL: failed to wait on test thread termination : error %s\n",
217 zx_status_get_string(status));
218 exit(ZX_ERR_INTERNAL);
219 }
220
221 // We also can't do this in the main thread as we wouldn't have the
222 // thread handle yet.
223 if (data->bind_to_thread) {
224 status = zx_task_bind_exception_port(self, data->port, EXCEPTION_PORT_KEY, 0);
225 if (status != ZX_OK) {
226 UNITTEST_FAIL_TRACEF("FATAL: failed to bind to exception port: error %s\n",
227 zx_status_get_string(status));
228 exit(ZX_ERR_INTERNAL);
229 }
230 crash_list_register(data->crash_list, self);
231 }
232
233 bool test_result = data->test_function(data->test_function_arg);
234
235 // Notify the crash handler of the test result before returning.
236 // We can't just return the test result as the test thread could
237 // be registered to crash, so the crash handler can't use thrd_join.
238 uint32_t signal = test_result ? TEST_PASSED_SIGNAL : TEST_FAILED_SIGNAL;
239 status = zx_object_signal(data->test_ended_event, 0, signal);
240 if (status != ZX_OK) {
241 UNITTEST_FAIL_TRACEF("FATAL: failed to signal test result : error %s\n",
242 zx_status_get_string(status));
243 exit(ZX_ERR_INTERNAL);
244 }
245 return 0;
246 }
247
248 // Runs the function in a separate thread with the given argument,
249 // catching any crashes.
250 // If bind_to_job is true, this will bind to the job exception port
251 // before starting the test.
252 // If false, this will bind to the test thread's exception port once started
253 // and add the thread to the expected crashes list.
run_with_crash_handler(crash_list_t crash_list,bool (* fn_to_run)(void *),void * arg,bool bind_to_job,test_result_t * test_result)254 static zx_status_t run_with_crash_handler(crash_list_t crash_list,
255 bool (*fn_to_run)(void*), void* arg,
256 bool bind_to_job,
257 test_result_t* test_result) {
258 zx_handle_t port;
259 zx_status_t status = zx_port_create(0, &port);
260 if (status != ZX_OK) {
261 UNITTEST_FAIL_TRACEF("failed to create port: error %s\n", zx_status_get_string(status));
262 return status;
263 }
264 if (bind_to_job) {
265 status = zx_task_bind_exception_port(zx_job_default(), port, EXCEPTION_PORT_KEY, 0);
266 if (status != ZX_OK) {
267 UNITTEST_FAIL_TRACEF("failed to bind to exception port: error %s\n",
268 zx_status_get_string(status));
269 zx_handle_close(port);
270 return status;
271 }
272 }
273
274 zx_handle_t test_ended_event;
275 status = zx_event_create(0, &test_ended_event);
276 if (status != ZX_OK) {
277 UNITTEST_FAIL_TRACEF("failed to create event: error %s\n", zx_status_get_string(status));
278 zx_handle_close(port);
279 return status;
280 }
281 status = zx_object_wait_async(test_ended_event, port, TEST_ENDED_EVENT_KEY,
282 TEST_PASSED_SIGNAL | TEST_FAILED_SIGNAL, ZX_WAIT_ASYNC_ONCE);
283 if (status != ZX_OK) {
284 UNITTEST_FAIL_TRACEF("failed to wait on test_ended_event: error %s\n",
285 zx_status_get_string(status));
286 zx_handle_close(port);
287 zx_handle_close(test_ended_event);
288 return status;
289 }
290
291 // Run the test in a separate thread in case it crashes.
292 thrd_t test_thread;
293 test_data_t test_data = {.test_function = fn_to_run,
294 .test_function_arg = arg,
295 .test_ended_event = test_ended_event,
296 .port = port,
297 .crash_list = crash_list,
298 .bind_to_thread = !bind_to_job};
299
300 int thrd_res = thrd_create(&test_thread, run_test, (void*)&test_data);
301 if (thrd_res != thrd_success) {
302 UNITTEST_FAIL_TRACEF("failed to create test thread\n");
303 zx_handle_close(port);
304 zx_handle_close(test_ended_event);
305 return thrd_status_to_zx_status(thrd_res);
306 }
307
308 // The test thread will signal on the test_ended event when it completes,
309 // or the crash handler will catch it crashing.
310 *test_result = watch_test_thread(port, crash_list);
311
312 zx_handle_close(port);
313 zx_handle_close(test_ended_event);
314
315 return ZX_OK;
316 }
317
318 struct test_wrapper_arg_t {
319 bool (*fn)(void);
320 };
321
test_wrapper(void * arg)322 static bool test_wrapper(void* arg) {
323 return static_cast<test_wrapper_arg_t*>(arg)->fn();
324 }
325
run_test_with_crash_handler(crash_list_t crash_list,bool (* test_to_run)(),test_result_t * test_result)326 zx_status_t run_test_with_crash_handler(crash_list_t crash_list, bool (*test_to_run)(),
327 test_result_t* test_result) {
328 test_wrapper_arg_t twarg = {.fn = test_to_run};
329
330 return run_with_crash_handler(crash_list, test_wrapper, &twarg, true, test_result);
331 }
332
333 struct crash_fn_wrapper_arg_t {
334 void (*fn)(void*);
335 void* arg;
336 };
337
crash_fn_wrapper(void * arg)338 static bool crash_fn_wrapper(void* arg) {
339 crash_fn_wrapper_arg_t* cfwarg = static_cast<crash_fn_wrapper_arg_t*>(arg);
340 cfwarg->fn(cfwarg->arg);
341 // The function is expected to crash and shouldn't get to here.
342 return false;
343 }
344
run_fn_with_crash_handler(void (* fn_to_run)(void *),void * arg,test_result_t * test_result)345 zx_status_t run_fn_with_crash_handler(void (*fn_to_run)(void*), void* arg,
346 test_result_t* test_result) {
347 crash_list_t crash_list = crash_list_new();
348 crash_fn_wrapper_arg_t cfwarg = {.fn = fn_to_run, .arg = arg};
349
350 zx_status_t status =
351 run_with_crash_handler(crash_list, crash_fn_wrapper, &cfwarg, false, test_result);
352
353 crash_list_delete(crash_list);
354
355 return status;
356 }
357