diff --git a/handler/win/crash_report_exception_handler.cc b/handler/win/crash_report_exception_handler.cc index faf03671..69df244f 100644 --- a/handler/win/crash_report_exception_handler.cc +++ b/handler/win/crash_report_exception_handler.cc @@ -21,6 +21,7 @@ #include "snapshot/win/process_snapshot_win.h" #include "util/file/file_writer.h" #include "util/win/registration_protocol_win.h" +#include "util/win/scoped_process_suspend.h" namespace crashpad { @@ -44,7 +45,7 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException( WinVMAddress exception_information_address) { const unsigned int kFailedTerminationCode = 0xffff7002; - // TODO(scottmg): ScopedProcessSuspend + ScopedProcessSuspend suspend(process); ProcessSnapshotWin process_snapshot; if (!process_snapshot.Initialize(process)) { diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc index 5da9f044..f68c17c2 100644 --- a/tools/generate_dump.cc +++ b/tools/generate_dump.cc @@ -41,6 +41,7 @@ #elif defined(OS_WIN) #include "base/strings/utf_string_conversions.h" #include "snapshot/win/process_snapshot_win.h" +#include "util/win/scoped_process_suspend.h" #endif // OS_MACOSX namespace crashpad { @@ -168,9 +169,9 @@ int GenerateDumpMain(int argc, char* argv[]) { suspend.reset(new ScopedTaskSuspend(task)); } #elif defined(OS_WIN) + scoped_ptr suspend; if (options.suspend) { - LOG(ERROR) << "TODO(scottmg): --no-suspend is required for now."; - return EXIT_FAILURE; + suspend.reset(new ScopedProcessSuspend(process.get())); } #endif // OS_MACOSX diff --git a/util/util.gyp b/util/util.gyp index 60b79235..95a263c9 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -162,6 +162,8 @@ 'win/registration_protocol_win.h', 'win/scoped_handle.cc', 'win/scoped_handle.h', + 'win/scoped_process_suspend.cc', + 'win/scoped_process_suspend.h', 'win/time.cc', 'win/time.h', ], diff --git a/util/util_test.gyp b/util/util_test.gyp index e7bc9402..98879eb6 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -81,6 +81,7 @@ 'thread/thread_test.cc', 'win/exception_handler_server_test.cc', 'win/process_info_test.cc', + 'win/scoped_process_suspend_test.cc', 'win/time_test.cc', ], 'conditions': [ diff --git a/util/win/scoped_process_suspend.cc b/util/win/scoped_process_suspend.cc new file mode 100644 index 00000000..4450efb5 --- /dev/null +++ b/util/win/scoped_process_suspend.cc @@ -0,0 +1,41 @@ +// 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 "util/win/scoped_process_suspend.h" + +#include + +#include "base/logging.h" + +namespace crashpad { + +ScopedProcessSuspend::ScopedProcessSuspend(HANDLE process) : process_(process) { + typedef NTSTATUS(__stdcall * NtSuspendProcessFunc)(HANDLE); + static NtSuspendProcessFunc func = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtSuspendProcess")); + NTSTATUS status = func(process_); + if (status) + LOG(ERROR) << "NtSuspendProcess, ntstatus=" << status; +} + +ScopedProcessSuspend::~ScopedProcessSuspend() { + typedef NTSTATUS(__stdcall * NtResumeProcessFunc)(HANDLE); + static NtResumeProcessFunc func = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtResumeProcess")); + NTSTATUS status = func(process_); + if (status) + LOG(ERROR) << "NtResumeProcess, ntstatus=" << status; +} + +} // namespace crashpad diff --git a/util/win/scoped_process_suspend.h b/util/win/scoped_process_suspend.h new file mode 100644 index 00000000..dad2e29f --- /dev/null +++ b/util/win/scoped_process_suspend.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_PROCESS_SUSPEND_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_PROCESS_SUSPEND_H_ + +#include + +#include "base/basictypes.h" + +namespace crashpad { + +//! \brief Manages the suspension of another process. +//! +//! While an object of this class exists, the other process will be suspended. +//! Once the object is destroyed, the other process will become eligible for +//! resumption. +//! +//! If this process crashes while this object exists, there is no guarantee that +//! the other process will be resumed. +class ScopedProcessSuspend { + public: + //! Does not take ownership of \a process. + explicit ScopedProcessSuspend(HANDLE process); + ~ScopedProcessSuspend(); + + private: + HANDLE process_; + + DISALLOW_COPY_AND_ASSIGN(ScopedProcessSuspend); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_PROCESS_SUSPEND_H_ diff --git a/util/win/scoped_process_suspend_test.cc b/util/win/scoped_process_suspend_test.cc new file mode 100644 index 00000000..98f06433 --- /dev/null +++ b/util/win/scoped_process_suspend_test.cc @@ -0,0 +1,106 @@ +// 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 "util/win/scoped_process_suspend.h" + +#include + +#include +#include + +#include "gtest/gtest.h" +#include "test/win/win_child_process.h" + +namespace crashpad { +namespace test { +namespace { + +// There is no per-process suspend count on Windows, only a per-thread suspend +// count. NtSuspendProcess just suspends all threads of a given process. So, +// verify that all thread's suspend counts match the desired suspend count. +bool SuspendCountMatches(HANDLE process, DWORD desired_suspend_count) { + DWORD process_id = GetProcessId(process); + + ScopedKernelHANDLE snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)); + if (!snapshot.is_valid()) + return false; + + THREADENTRY32 te; + te.dwSize = sizeof(te); + if (!Thread32First(snapshot.get(), &te)) + return false; + do { + if (te.dwSize >= offsetof(THREADENTRY32, th32OwnerProcessID) + + sizeof(te.th32OwnerProcessID) && + te.th32OwnerProcessID == process_id) { + ScopedKernelHANDLE thread( + OpenThread(THREAD_ALL_ACCESS, false, te.th32ThreadID)); + DWORD result = SuspendThread(thread.get()); + EXPECT_NE(result, static_cast(-1)); + if (result != static_cast(-1)) + ResumeThread(thread.get()); + if (result != desired_suspend_count) + return false; + } + te.dwSize = sizeof(te); + } while (Thread32Next(snapshot.get(), &te)); + + return true; +} + +class ScopedProcessSuspendTest final : public WinChildProcess { + public: + ScopedProcessSuspendTest() : WinChildProcess() {} + ~ScopedProcessSuspendTest() {} + + private: + int Run() override { + char c; + // Wait for notification from parent. + EXPECT_TRUE(LoggingReadFile(ReadPipeHandle(), &c, sizeof(c))); + EXPECT_EQ(' ', c); + return EXIT_SUCCESS; + } + + DISALLOW_COPY_AND_ASSIGN(ScopedProcessSuspendTest); +}; + +TEST(ScopedProcessSuspend, ScopedProcessSuspend) { + WinChildProcess::EntryPoint(); + scoped_ptr handles = WinChildProcess::Launch(); + + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 0)); + + { + ScopedProcessSuspend suspend(handles->process.get()); + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 1)); + + { + ScopedProcessSuspend suspend(handles->process.get()); + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 2)); + } + + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 1)); + } + + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 0)); + + // Tell the child it's OK to terminate. + char c = ' '; + EXPECT_TRUE(WriteFile(handles->write.get(), &c, sizeof(c))); +} + +} // namespace +} // namespace test +} // namespace crashpad