// Copyright 2017 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 #include #include #include #include #include #include "base/files/file_path.h" #include "client/crash_report_database.h" #include "client/crashpad_client.h" #include "gtest/gtest.h" #include "minidump/test/minidump_file_writer_test_util.h" #include "test/test_paths.h" #include "test/win/win_multiprocess_with_temp_dir.h" #include "util/file/file_reader.h" #include "util/misc/capture_context.h" namespace crashpad { namespace test { namespace { constexpr DWORD kExpectedExitCode = 0x1CEB00DA; void StartAndCrashWithExtendedHandler(const base::FilePath& temp_dir) { base::FilePath handler_path = TestPaths::BuildArtifact( L"handler", L"extended_handler", TestPaths::FileType::kExecutable); CrashpadClient client; ASSERT_TRUE(client.StartHandler(handler_path, temp_dir, base::FilePath(), "", std::map(), std::vector(), false, false)); // It appears that the GoogleTest fixture will catch and handle exceptions // from here. Hence the fabricated crash in favor of raising an exception. EXCEPTION_RECORD exception_record = {kExpectedExitCode, EXCEPTION_NONCONTINUABLE}; CONTEXT context; CaptureContext(&context); EXCEPTION_POINTERS exception_pointers = {&exception_record, &context}; CrashpadClient::DumpAndCrash(&exception_pointers); } class CrashWithExtendedHandler final : public WinMultiprocessWithTempDir { public: CrashWithExtendedHandler() : WinMultiprocessWithTempDir() {} ~CrashWithExtendedHandler() {} private: void ValidateGeneratedDump(); void WinMultiprocessParent() override { SetExpectedChildExitCode(kExpectedExitCode); } void WinMultiprocessChild() override { StartAndCrashWithExtendedHandler(GetTempDirPath()); } void WinMultiprocessParentAfterChild(HANDLE child) override { // At this point the child has exited, which means the crash report should // have been written. ValidateGeneratedDump(); // Delegate the cleanup to the superclass. WinMultiprocessWithTempDir::WinMultiprocessParentAfterChild(child); } }; void CrashWithExtendedHandler::ValidateGeneratedDump() { // Open the database and find the sole dump that should have been created. std::unique_ptr database( CrashReportDatabase::Initialize(GetTempDirPath())); ASSERT_TRUE(database); std::vector reports; ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); // Open the dump and validate that it has the extension stream with the // expected contents. FileReader reader; ASSERT_TRUE(reader.Open(reports[0].file_path)); // Read the header. MINIDUMP_HEADER header = {}; ASSERT_TRUE(reader.ReadExactly(&header, sizeof(header))); // Read the directory. std::vector directory(header.NumberOfStreams); ASSERT_TRUE(reader.SeekSet(header.StreamDirectoryRva)); ASSERT_TRUE(reader.ReadExactly(directory.data(), directory.size() * sizeof(directory[0]))); // Search for the extension stream. size_t found_extension_streams = 0; for (const auto& entry : directory) { if (entry.StreamType == 0xCAFEBABE) { ++found_extension_streams; ASSERT_TRUE(reader.SeekSet(entry.Location.Rva)); std::vector data; data.resize(entry.Location.DataSize); ASSERT_TRUE(reader.ReadExactly(data.data(), data.size())); static constexpr char kExpectedData[] = "Injected extension stream!"; EXPECT_EQ(memcmp(kExpectedData, data.data(), sizeof(kExpectedData)), 0); } } EXPECT_EQ(found_extension_streams, 1u); } TEST(CrashpadHandler, ExtensibilityCalloutsWork) { WinMultiprocessWithTempDir::Run(); } } // namespace } // namespace test } // namespace crashpad