| Index: snapshot/win/exception_snapshot_win_test.cc
|
| diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..268d64e4a325c0e6f6397525ebec6cf4e430fe70
|
| --- /dev/null
|
| +++ b/snapshot/win/exception_snapshot_win_test.cc
|
| @@ -0,0 +1,243 @@
|
| +// Copyright 2015 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 "snapshot/win/exception_snapshot_win.h"
|
| +
|
| +#include <string>
|
| +
|
| +#include "base/strings/stringprintf.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| +#include "client/crashpad_client.h"
|
| +#include "client/crashpad_info.h"
|
| +#include "handler/win/registration_server.h"
|
| +#include "gtest/gtest.h"
|
| +#include "snapshot/win/process_reader_win.h"
|
| +#include "snapshot/win/process_snapshot_win.h"
|
| +#include "test/win/win_child_process.h"
|
| +#include "util/thread/thread.h"
|
| +#include "util/win/scoped_handle.h"
|
| +
|
| +namespace crashpad {
|
| +namespace test {
|
| +namespace {
|
| +
|
| +HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
|
| + HANDLE handle;
|
| + if (DuplicateHandle(GetCurrentProcess(),
|
| + event,
|
| + process,
|
| + &handle,
|
| + SYNCHRONIZE | EVENT_MODIFY_STATE,
|
| + false,
|
| + 0)) {
|
| + return handle;
|
| + }
|
| + return nullptr;
|
| +}
|
| +
|
| +class ExceptionSnapshotWinTest : public testing::Test {
|
| + public:
|
| + class Delegate : public RegistrationServer::Delegate {
|
| + public:
|
| + Delegate()
|
| + : crashpad_info_address_(0),
|
| + client_process_(),
|
| + started_event_(CreateEvent(nullptr, false, false, nullptr)),
|
| + request_dump_event_(CreateEvent(nullptr, false, false, nullptr)),
|
| + dump_complete_event_(CreateEvent(nullptr, true, false, nullptr)) {
|
| + EXPECT_TRUE(started_event_.is_valid());
|
| + EXPECT_TRUE(request_dump_event_.is_valid());
|
| + EXPECT_TRUE(dump_complete_event_.is_valid());
|
| + }
|
| +
|
| + ~Delegate() override {
|
| + }
|
| +
|
| + void OnStarted() override {
|
| + EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(started_event_.get(), 0));
|
| + SetEvent(started_event_.get());
|
| + }
|
| +
|
| + bool RegisterClient(ScopedKernelHANDLE client_process,
|
| + WinVMAddress crashpad_info_address,
|
| + HANDLE* request_dump_event,
|
| + HANDLE* dump_complete_event) override {
|
| + client_process_ = client_process.Pass();
|
| + crashpad_info_address_ = crashpad_info_address;
|
| + *request_dump_event =
|
| + DuplicateEvent(client_process_.get(), request_dump_event_.get());
|
| + *dump_complete_event =
|
| + DuplicateEvent(client_process_.get(), dump_complete_event_.get());
|
| + return true;
|
| + }
|
| +
|
| + void WaitForStart() {
|
| + DWORD wait_result = WaitForSingleObject(started_event_.get(), INFINITE);
|
| + if (wait_result == WAIT_FAILED)
|
| + PLOG(ERROR);
|
| + ASSERT_EQ(wait_result, WAIT_OBJECT_0);
|
| + }
|
| +
|
| + void WaitForDumpRequestAndValidateException(void* break_near) {
|
| + // Wait until triggered, and then grab information from the child.
|
| + WaitForSingleObject(request_dump_event_.get(), INFINITE);
|
| +
|
| + // Snapshot the process and exception.
|
| + ProcessReaderWin process_reader;
|
| + ASSERT_TRUE(process_reader.Initialize(client_process_.get()));
|
| + CrashpadInfo crashpad_info;
|
| + ASSERT_TRUE(process_reader.ReadMemory(
|
| + crashpad_info_address_, sizeof(crashpad_info), &crashpad_info));
|
| + ProcessSnapshotWin snapshot;
|
| + snapshot.Initialize(client_process_.get());
|
| + snapshot.InitializeException(
|
| + crashpad_info.thread_id(),
|
| + reinterpret_cast<WinVMAddress>(crashpad_info.exception_pointers()));
|
| +
|
| + // Confirm the exception record was read correctly.
|
| + EXPECT_NE(snapshot.Exception()->ThreadID(), 0u);
|
| + EXPECT_EQ(snapshot.Exception()->Exception(), EXCEPTION_BREAKPOINT);
|
| +
|
| + // Verify the exception happened at the expected location with a bit of
|
| + // slop space to allow for reading the current PC before the exception
|
| + // happens. See CrashingChildProcess::Run().
|
| + const uint64_t kAllowedOffset = 64;
|
| + EXPECT_GT(snapshot.Exception()->ExceptionAddress(),
|
| + reinterpret_cast<uint64_t>(break_near));
|
| + EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
|
| + reinterpret_cast<uint64_t>(break_near) + kAllowedOffset);
|
| +
|
| + // Notify the child that we're done.
|
| + SetEvent(dump_complete_event_.get());
|
| + }
|
| +
|
| + ScopedKernelHANDLE* request_dump_event() { return &request_dump_event_; }
|
| + ScopedKernelHANDLE* dump_complete_event() { return &dump_complete_event_; }
|
| +
|
| + private:
|
| + WinVMAddress crashpad_info_address_;
|
| + ScopedKernelHANDLE client_process_;
|
| + ScopedKernelHANDLE started_event_;
|
| + ScopedKernelHANDLE request_dump_event_;
|
| + ScopedKernelHANDLE dump_complete_event_;
|
| + };
|
| +};
|
| +
|
| +// Runs the RegistrationServer on a background thread.
|
| +class RunServerThread : public Thread {
|
| + public:
|
| + // Instantiates a thread which will invoke server->Run(pipe_name, delegate).
|
| + RunServerThread(RegistrationServer* server,
|
| + const base::string16& pipe_name,
|
| + RegistrationServer::Delegate* delegate)
|
| + : server_(server), pipe_name_(pipe_name), delegate_(delegate) {}
|
| + ~RunServerThread() override {}
|
| +
|
| + private:
|
| + // Thread:
|
| + void ThreadMain() override { server_->Run(pipe_name_, delegate_); }
|
| +
|
| + RegistrationServer* server_;
|
| + base::string16 pipe_name_;
|
| + RegistrationServer::Delegate* delegate_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(RunServerThread);
|
| +};
|
| +
|
| +// During destruction, ensures that the server is stopped and the background
|
| +// thread joined.
|
| +class ScopedStopServerAndJoinThread {
|
| + public:
|
| + explicit ScopedStopServerAndJoinThread(RegistrationServer* server,
|
| + Thread* thread)
|
| + : server_(server), thread_(thread) {}
|
| + ~ScopedStopServerAndJoinThread() {
|
| + server_->Stop();
|
| + thread_->Join();
|
| + }
|
| +
|
| + private:
|
| + RegistrationServer* server_;
|
| + Thread* thread_;
|
| + DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
|
| +};
|
| +
|
| +std::string ReadString(FileHandle handle) {
|
| + size_t length = 0;
|
| + EXPECT_TRUE(LoggingReadFile(handle, &length, sizeof(length)));
|
| + scoped_ptr<char[]> buffer(new char[length]);
|
| + EXPECT_TRUE(LoggingReadFile(handle, &buffer[0], length));
|
| + return std::string(&buffer[0], length);
|
| +}
|
| +
|
| +void WriteString(FileHandle handle, const std::string& str) {
|
| + size_t length = str.size();
|
| + EXPECT_TRUE(LoggingWriteFile(handle, &length, sizeof(length)));
|
| + EXPECT_TRUE(LoggingWriteFile(handle, &str[0], length));
|
| +}
|
| +
|
| +__declspec(noinline) void* CurrentAddress() {
|
| + return _ReturnAddress();
|
| +}
|
| +
|
| +class CrashingChildProcess final : public WinChildProcess {
|
| + public:
|
| + CrashingChildProcess() : WinChildProcess() {}
|
| + ~CrashingChildProcess() {}
|
| +
|
| + private:
|
| + int Run() override {
|
| + std::string pipe_name = ReadString(ReadPipeHandle());
|
| + CrashpadClient client;
|
| + EXPECT_TRUE(client.SetHandler(pipe_name));
|
| + EXPECT_TRUE(client.UseHandler());
|
| + // Save the address where we're about to crash so the exception handler can
|
| + // verify it's in approximately the right location (with a bit of fudge for
|
| + // the code between here and the __debugbreak()).
|
| + void* break_address = CurrentAddress();
|
| + LoggingWriteFile(WritePipeHandle(), &break_address, sizeof(break_address));
|
| + __debugbreak();
|
| + return 0;
|
| + };
|
| +};
|
| +
|
| +TEST_F(ExceptionSnapshotWinTest, ChildCrash) {
|
| + // Set up the registration server on a background thread.
|
| + RegistrationServer server;
|
| + std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
|
| + base::StringPrintf("%08x", GetCurrentProcessId());
|
| + base::string16 pipe_name_16 = base::UTF8ToUTF16(pipe_name);
|
| + Delegate delegate;
|
| + RunServerThread server_thread(&server, pipe_name_16, &delegate);
|
| + server_thread.Start();
|
| + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
| + &server, &server_thread);
|
| + ASSERT_NO_FATAL_FAILURE(delegate.WaitForStart());
|
| +
|
| + // Spawn a child process that immediately crashes.
|
| + WinChildProcess::EntryPoint<CrashingChildProcess>();
|
| + scoped_ptr<WinChildProcess::Handles> handle = WinChildProcess::Launch();
|
| + WriteString(handle->write.get(), pipe_name);
|
| +
|
| + void* break_near_address;
|
| + LoggingReadFile(
|
| + handle->read.get(), &break_near_address, sizeof(break_near_address));
|
| +
|
| + // Verify the exception information is as expected.
|
| + delegate.WaitForDumpRequestAndValidateException(break_near_address);
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace test
|
| +} // namespace crashpad
|
|
|