From f130822b9f4b99cb7d6aa24947b522c2ef6a78d2 Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Thu, 22 Feb 2018 13:29:05 -0800 Subject: [PATCH] linux: Add CrashpadClient tests Bug: crashpad:30 Change-Id: Ie2bea049d8c47c09e53e76601ed45817591f2e28 Reviewed-on: https://chromium-review.googlesource.com/927795 Commit-Queue: Joshua Peraza Reviewed-by: Mark Mentovai --- client/BUILD.gn | 4 + client/client_test.gyp | 11 +- client/crashpad_client_linux_test.cc | 303 +++++++++++++++++++++++++++ 3 files changed, 315 insertions(+), 3 deletions(-) create mode 100644 client/crashpad_client_linux_test.cc diff --git a/client/BUILD.gn b/client/BUILD.gn index 9a2f66ea..cb4161f5 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -107,6 +107,10 @@ source_set("client_test") { sources += [ "crashpad_client_win_test.cc" ] } + if (crashpad_is_linux || crashpad_is_android) { + sources += [ "crashpad_client_linux_test.cc" ] + } + deps = [ ":client", "../compat", diff --git a/client/client_test.gyp b/client/client_test.gyp index ab2626a0..61a4a7e5 100644 --- a/client/client_test.gyp +++ b/client/client_test.gyp @@ -39,6 +39,7 @@ 'annotation_list_test.cc', 'crash_report_database_test.cc', 'crashpad_client_win_test.cc', + 'crashpad_client_linux_test.cc', 'prune_crash_reports_test.cc', 'settings_test.cc', 'simple_address_range_bag_test.cc', @@ -51,9 +52,13 @@ '../handler/handler.gyp:crashpad_handler_console', ], }], - ['OS=="linux" or OS=="android"', - {'dependencies!': ['../handler/handler.gyp:crashpad_handler']}, - ], + ], + 'target_conditions': [ + ['OS=="android"', { + 'sources/': [ + ['include', '^crashpad_client_linux_test\\.cc$'], + ], + }], ], }, ], diff --git a/client/crashpad_client_linux_test.cc b/client/crashpad_client_linux_test.cc new file mode 100644 index 00000000..1e07394a --- /dev/null +++ b/client/crashpad_client_linux_test.cc @@ -0,0 +1,303 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client/crashpad_client.h" + +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "client/crash_report_database.h" +#include "client/simulate_crash.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "test/multiprocess.h" +#include "test/scoped_temp_dir.h" +#include "test/test_paths.h" +#include "util/file/file_io.h" +#include "util/linux/exception_handler_client.h" +#include "util/linux/exception_information.h" +#include "util/misc/address_types.h" +#include "util/misc/from_pointer_cast.h" +#include "util/posix/signals.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(CrashpadClient, SimulateCrash) { + ScopedTempDir temp_dir; + + base::FilePath handler_path = TestPaths::Executable().DirName().Append( + FILE_PATH_LITERAL("crashpad_handler")); + + crashpad::CrashpadClient client; + ASSERT_TRUE(client.StartHandlerAtCrash(handler_path, + base::FilePath(temp_dir.path()), + base::FilePath(), + "", + std::map(), + std::vector())); + + CRASHPAD_SIMULATE_CRASH(); + + auto database = + CrashReportDatabase::InitializeWithoutCreating(temp_dir.path()); + ASSERT_TRUE(database); + + std::vector reports; + ASSERT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 0u); + + reports.clear(); + ASSERT_EQ(database->GetCompletedReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 1u); +} + +CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { + FileHandle in = StdioFileHandle(StdioStream::kStandardInput); + + VMSize temp_dir_length; + CheckedReadFileExactly(in, &temp_dir_length, sizeof(temp_dir_length)); + + std::string temp_dir(temp_dir_length, '\0'); + CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); + + base::FilePath handler_path = TestPaths::Executable().DirName().Append( + FILE_PATH_LITERAL("crashpad_handler")); + + crashpad::CrashpadClient client; + if (!client.StartHandlerAtCrash(handler_path, + base::FilePath(temp_dir), + base::FilePath(), + "", + std::map(), + std::vector())) { + return EXIT_FAILURE; + } + + __builtin_trap(); + + NOTREACHED(); + return EXIT_SUCCESS; +} + +class StartHandlerAtCrashTest : public MultiprocessExec { + public: + StartHandlerAtCrashTest() : MultiprocessExec() { + SetChildTestMainFunction("StartHandlerAtCrashChild"); + SetExpectedChildTerminationBuiltinTrap(); + } + + private: + void MultiprocessParent() override { + ScopedTempDir temp_dir; + VMSize temp_dir_length = temp_dir.path().value().size(); + ASSERT_TRUE(LoggingWriteFile( + WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); + ASSERT_TRUE(LoggingWriteFile( + WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); + + // Wait for child to finish. + CheckedReadFileAtEOF(ReadPipeHandle()); + + auto database = CrashReportDatabase::Initialize(temp_dir.path()); + ASSERT_TRUE(database); + + std::vector reports; + ASSERT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 0u); + + ASSERT_EQ(database->GetCompletedReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 1u); + } + + DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest); +}; + +TEST(CrashpadClient, StartHandlerAtCrash) { + StartHandlerAtCrashTest test; + test.Run(); +} + +// Test state for starting the handler for another process. +class StartHandlerForClientTest { + public: + StartHandlerForClientTest() = default; + ~StartHandlerForClientTest() = default; + + bool Initialize() { + int socks[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) { + PLOG(ERROR) << "socketpair"; + return false; + } + client_sock_.reset(socks[0]); + server_sock_.reset(socks[1]); + + return true; + } + + bool StartHandlerOnDemand() { + char c; + if (!LoggingReadFileExactly(server_sock_.get(), &c, sizeof(c))) { + ADD_FAILURE(); + return false; + } + + base::FilePath handler_path = TestPaths::Executable().DirName().Append( + FILE_PATH_LITERAL("crashpad_handler")); + + CrashpadClient client; + if (!client.StartHandlerForClient(handler_path, + temp_dir_.path(), + base::FilePath(), + "", + std::map(), + std::vector(), + server_sock_.get())) { + ADD_FAILURE(); + return false; + } + + return true; + } + + void ExpectReport() { + auto database = + CrashReportDatabase::InitializeWithoutCreating(temp_dir_.path()); + ASSERT_TRUE(database); + + std::vector reports; + ASSERT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 0u); + + ASSERT_EQ(database->GetCompletedReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 1u); + } + + bool InstallHandler() { + auto signal_handler = SandboxedHandler::Get(); + return signal_handler->Initialize(client_sock_.get()); + } + + private: + // A signal handler that defers handler process startup to another, presumably + // more privileged, process. + class SandboxedHandler { + public: + static SandboxedHandler* Get() { + static SandboxedHandler* instance = new SandboxedHandler(); + return instance; + } + + bool Initialize(FileHandle client_sock) { + client_sock_ = client_sock; + return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); + } + + private: + SandboxedHandler() = default; + ~SandboxedHandler() = delete; + + static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { + auto state = Get(); + + char c; + CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c))); + + ExceptionInformation exception_information; + exception_information.siginfo_address = + FromPointerCast( + siginfo); + exception_information.context_address = + FromPointerCast( + context); + exception_information.thread_id = syscall(SYS_gettid); + + ClientInformation info = {}; + info.exception_information_address = + FromPointerCast( + &exception_information); + + ExceptionHandlerClient handler_client(state->client_sock_); + CHECK_EQ(handler_client.RequestCrashDump(info), 0); + + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); + } + + FileHandle client_sock_; + + DISALLOW_COPY_AND_ASSIGN(SandboxedHandler); + }; + + ScopedTempDir temp_dir_; + ScopedFileHandle client_sock_; + ScopedFileHandle server_sock_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForClientTest); +}; + +// Tests starting the handler for a child process. +class StartHandlerForChildTest : public Multiprocess { + public: + StartHandlerForChildTest() = default; + ~StartHandlerForChildTest() = default; + + bool Initialize() { + SetExpectedChildTerminationBuiltinTrap(); + return test_state_.Initialize(); + } + + private: + void MultiprocessParent() { + ASSERT_TRUE(test_state_.StartHandlerOnDemand()); + + // Wait for chlid to finish. + CheckedReadFileAtEOF(ReadPipeHandle()); + + test_state_.ExpectReport(); + } + + void MultiprocessChild() { + CHECK(test_state_.InstallHandler()); + + __builtin_trap(); + + NOTREACHED(); + } + + StartHandlerForClientTest test_state_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForChildTest); +}; + +TEST(CrashpadClient, StartHandlerForChild) { + StartHandlerForChildTest test; + ASSERT_TRUE(test.Initialize()); + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad