| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdio.h> |
| #include <regex.h> |
| #include <unistd.h> |
| |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/unique_ptr.h> |
| #include <launchpad/launchpad.h> |
| #include <unittest/unittest.h> |
| |
| static const char* g_executable_filename; |
| |
| // This should match the value used by crashlogger. |
| static const uint64_t kSysExceptionKey = 1166444u; |
| |
| // Helper class for using POSIX regular expressions. |
| class RegEx { |
| public: |
| RegEx(const char* regex_str) { |
| int err = regcomp(®ex_, regex_str, REG_EXTENDED); |
| if (err != 0) { |
| char msg[100]; |
| msg[0] = '\0'; |
| regerror(err, ®ex_, msg, sizeof(msg)); |
| fprintf(stderr, "Regex compilation failed: %s\n", msg); |
| abort(); |
| } |
| } |
| |
| ~RegEx() { |
| regfree(®ex_); |
| } |
| |
| bool Matches(const char* str) { |
| return regexec(®ex_, str, 0, NULL, 0) == 0; |
| } |
| |
| private: |
| regex_t regex_; |
| }; |
| |
| _Pragma("GCC diagnostic push") |
| // Tell GCC not to warn about "-Winfinite-recursion" being unknown. |
| _Pragma("GCC diagnostic ignored \"-Wpragmas\"") |
| // Tell Clang not to warn about recursion in the following function. |
| _Pragma("GCC diagnostic ignored \"-Winfinite-recursion\"") |
| |
| void stack_overflow(volatile int* ptr) { |
| // To stop the compiler from making the function call a tail call, |
| // allocate a variable on the stack and take its address. To stop the |
| // compiler optimizing the variable away, do a volatile write to it. |
| if (ptr) |
| *ptr = 0; |
| volatile int x; |
| stack_overflow(&x); |
| } |
| |
| _Pragma("GCC diagnostic pop") |
| |
| void handle_crash_arg(int argc, char** argv) { |
| if (argc >= 2 && !strcmp(argv[1], "--crash")) { |
| if (argc == 3 && !strcmp(argv[2], "write_to_zero")) { |
| *(volatile int*) 0 = 0x12345678; |
| exit(1); |
| } |
| if (argc == 3 && !strcmp(argv[2], "stack_overflow")) { |
| stack_overflow(nullptr); |
| exit(1); |
| } |
| fprintf(stderr, "Unrecognized arguments"); |
| exit(1); |
| } |
| } |
| |
| // This tests the output of crashlogger given a process that crashes. It |
| // launches a test instance of crashlogger in order to capture its output. |
| bool test_crash(const char* crash_arg) { |
| const char* argv[] = { g_executable_filename, "--crash", crash_arg }; |
| launchpad_t* crasher_lp; |
| launchpad_create(0, "crash-test", &crasher_lp); |
| |
| // Make sure we bind an exception port to the process before we start |
| // it running. |
| zx_handle_t crasher_proc = launchpad_get_process_handle(crasher_lp); |
| zx_handle_t exception_port; |
| ASSERT_EQ(zx_port_create(0, &exception_port), ZX_OK); |
| ASSERT_EQ(zx_task_bind_exception_port(crasher_proc, exception_port, |
| kSysExceptionKey, 0), ZX_OK); |
| |
| // Launch the crasher process. |
| launchpad_load_from_file(crasher_lp, argv[0]); |
| launchpad_clone(crasher_lp, LP_CLONE_ALL); |
| launchpad_set_args(crasher_lp, fbl::count_of(argv), argv); |
| const char* errmsg; |
| ASSERT_EQ(launchpad_go(crasher_lp, &crasher_proc, &errmsg), ZX_OK); |
| |
| // Launch a test instance of crashlogger. |
| const char* crashlogger_argv[] = { "/boot/bin/crashlogger" }; |
| launchpad_t* crashlogger_lp; |
| launchpad_create(0, "crashlogger-test-instance", &crashlogger_lp); |
| launchpad_load_from_file(crashlogger_lp, crashlogger_argv[0]); |
| launchpad_clone(crasher_lp, LP_CLONE_ALL); |
| launchpad_set_args(crashlogger_lp, fbl::count_of(crashlogger_argv), |
| crashlogger_argv); |
| zx_handle_t handles[] = { exception_port }; |
| uint32_t handle_types[] = { PA_HND(PA_USER0, 0) }; |
| launchpad_add_handles(crashlogger_lp, fbl::count_of(handles), handles, |
| handle_types); |
| int pipe_fd; |
| launchpad_add_pipe(crashlogger_lp, &pipe_fd, STDOUT_FILENO); |
| zx_handle_t crashlogger_proc; |
| ASSERT_EQ(launchpad_go(crashlogger_lp, &crashlogger_proc, &errmsg), ZX_OK); |
| |
| // Read crashlogger's output into a buffer. Stop reading when we get |
| // an end-of-backtrace line which matches the following regular |
| // expression. |
| RegEx end_regex("^bt#\\d+: end"); |
| FILE* fp = fdopen(pipe_fd, "r"); |
| uint32_t output_size = 10000; |
| uint32_t size_read = 0; |
| fbl::unique_ptr<char[]> output(new char[output_size]); |
| for (;;) { |
| char* line_buf = &output[size_read]; |
| int32_t size_remaining = output_size - size_read; |
| ASSERT_GT(size_remaining, 1); |
| if (!fgets(line_buf, size_remaining, fp)) |
| break; |
| size_read += (uint32_t)strlen(line_buf); |
| if (end_regex.Matches(line_buf)) |
| break; |
| } |
| fclose(fp); |
| |
| // Check that the output contains backtrace info. |
| RegEx overall_regex( |
| "arch: .*\n" |
| "(dso: id=.* base=.* name=.*\n)+" |
| "(bt#\\d+: pc 0x.* sp 0x.* \\(.*,0x.*\\))+"); |
| ASSERT_TRUE(overall_regex.Matches(output.get())); |
| |
| // Clean up. |
| ASSERT_EQ(zx_object_wait_one(crasher_proc, ZX_PROCESS_TERMINATED, |
| ZX_TIME_INFINITE, NULL), ZX_OK); |
| ASSERT_EQ(zx_handle_close(crasher_proc), ZX_OK); |
| ASSERT_EQ(zx_task_kill(crashlogger_proc), ZX_OK); |
| ASSERT_EQ(zx_object_wait_one(crashlogger_proc, ZX_PROCESS_TERMINATED, |
| ZX_TIME_INFINITE, NULL), ZX_OK); |
| ASSERT_EQ(zx_handle_close(crashlogger_proc), ZX_OK); |
| return true; |
| } |
| |
| bool test_crash_write0() { |
| BEGIN_TEST; |
| test_crash("write_to_zero"); |
| END_TEST; |
| } |
| |
| bool test_crash_stack_overflow() { |
| BEGIN_TEST; |
| test_crash("stack_overflow"); |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(crashlogger_tests) |
| RUN_TEST(test_crash_write0) |
| RUN_TEST(test_crash_stack_overflow) |
| END_TEST_CASE(crashlogger_tests) |
| |
| int main(int argc, char** argv) { |
| g_executable_filename = argv[0]; |
| handle_crash_arg(argc, argv); |
| |
| bool success = unittest_run_all_tests(argc, argv); |
| return success ? 0 : -1; |
| } |