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