Scott Graham has submitted this change and it was merged.
Change subject: win: Support dumping another process by causing it to crash
......................................................................
win: Support dumping another process by causing it to crash
Adds a new client API which allows causing an exception in another
process. This is accomplished by injecting a thread that calls
RaiseException(). A special exception code is used that indicates to the
handler that the exception arguments contain a thread id and exception
code, which are in turn used to fabricate an exception record. This is
so that the API can allow the client to "blame" a particular thread in
the target process.
The target process must also be a registered Crashpad client, as the
normal exception mechanism is used to handle the exception.
The injection of a thread is used instead of DebugBreakProcess() which
does not cause the UnhandledExceptionFilter() to be executed.
NtCreateThreadEx() is used in lieu of CreateRemoteThread() as it allows
passing of a flag which avoids calling DllMain()s. This is necessary to
allow thread creation to succeed even when the target process is
deadlocked on the loader lock.
BUG=crashpad:103
Change-Id: I797007bd2b1e3416afe3f37a6566c0cdb259b106
Reviewed-on:
https://chromium-review.googlesource.com/339263
Reviewed-by: Mark Mentovai <
ma...@chromium.org>
---
M client/crashpad_client.h
M client/crashpad_client_win.cc
M handler/handler.gyp
A handler/win/crash_other_program.cc
A handler/win/hanging_program.cc
A handler/win/loader_lock_dll.cc
M snapshot/win/end_to_end_test.py
M snapshot/win/exception_snapshot_win.cc
M snapshot/win/exception_snapshot_win.h
M snapshot/win/process_snapshot_win.cc
M util/win/nt_internals.cc
M util/win/nt_internals.h
12 files changed, 782 insertions(+), 44 deletions(-)
Approvals:
Mark Mentovai: Looks good to me, approved
diff --git a/client/crashpad_client.h b/client/crashpad_client.h
index 28a87a3..61338a3 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -19,6 +19,8 @@
#include <string>
#include <vector>
+#include <stdint.h>
+
#include "base/files/file_path.h"
#include "base/macros.h"
#include "build/build_config.h"
@@ -152,6 +154,41 @@
//! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would
generally
//! passed to an unhandled exception filter.
static void DumpAndCrash(EXCEPTION_POINTERS* exception_pointers);
+
+ //! \brief Requests that the handler capture a dump of a different
process.
+ //!
+ //! The target process must be an already-registered Crashpad client. An
+ //! exception will be triggered in the target process, and the regular
dump
+ //! mechanism used. This function will block until the exception in the
target
+ //! process has been handled by the Crashpad handler.
+ //!
+ //! This function is unavailable when running on Windows XP and will
return
+ //! `false`.
+ //!
+ //! \param[in] process A `HANDLE` identifying the process to be dumped.
+ //! \param[in] blame_thread If non-null, a `HANDLE` valid in the caller's
+ //! process, referring to a thread in the target process. If this is
+ //! supplied, instead of the exception referring to the location
where the
+ //! exception was injected, an exception record will be fabricated
that
+ //! refers to the current location of the given thread.
+ //! \param[in] exception_code If \a blame_thread is non-null, this will
be
+ //! used as the exception code in the exception record.
+ //!
+ //! \return `true` if the exception was triggered successfully.
+ bool DumpAndCrashTargetProcess(HANDLE process,
+ HANDLE blame_thread,
+ DWORD exception_code) const;
+
+ enum : uint32_t {
+ //! \brief The exception code (roughly "Client called") used when
+ //! DumpAndCrashTargetProcess() triggers an exception in a target
+ //! process.
+ //!
+ //! \note This value does not have any bits of the top nibble set, to
avoid
+ //! confusion with real exception codes which tend to have those
bits
+ //! set.
+ kTriggeredExceptionCode = 0xcca11ed,
+ };
#endif
//! \brief Configures the process to direct its crashes to a Crashpad
handler.
diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc
index 1f10686..b0fca53 100644
--- a/client/crashpad_client_win.cc
+++ b/client/crashpad_client_win.cc
@@ -27,12 +27,17 @@
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "util/file/file_io.h"
+#include "util/win/address_types.h"
#include "util/win/command_line.h"
#include "util/win/critical_section_with_debug_info.h"
#include "util/win/get_function.h"
#include "util/win/handle.h"
+#include "util/win/nt_internals.h"
+#include "util/win/ntstatus_logging.h"
+#include "util/win/process_info.h"
#include "util/win/registration_protocol_win.h"
#include "util/win/scoped_handle.h"
+#include "util/win/scoped_process_suspend.h"
namespace {
@@ -165,6 +170,19 @@
handle_list->end()) {
handle_list->push_back(handle);
}
+}
+
+void AddUint32(std::vector<unsigned char>* data_vector, uint32_t data) {
+ data_vector->push_back(static_cast<unsigned char>(data & 0xff));
+ data_vector->push_back(static_cast<unsigned char>((data & 0xff00) >> 8));
+ data_vector->push_back(static_cast<unsigned char>((data & 0xff0000) >>
16));
+ data_vector->push_back(static_cast<unsigned char>((data & 0xff000000) >>
24));
+}
+
+void AddUint64(std::vector<unsigned char>* data_vector, uint64_t data) {
+ AddUint32(data_vector, static_cast<uint32_t>(data & 0xffffffffULL));
+ AddUint32(data_vector,
+ static_cast<uint32_t>((data & 0xffffffff00000000ULL) >> 32));
}
} // namespace
@@ -469,4 +487,247 @@
UnhandledExceptionHandler(exception_pointers);
}
+bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process,
+ HANDLE blame_thread,
+ DWORD exception_code) const
{
+ // Confirm we're on Vista or later.
+ const DWORD version = GetVersion();
+ const DWORD major_version = LOBYTE(LOWORD(version));
+ if (major_version < 6) {
+ LOG(ERROR) << "unavailable before Vista";
+ return false;
+ }
+
+ // Confirm that our bitness is the same as the process we're crashing.
+ ProcessInfo process_info;
+ if (!process_info.Initialize(process)) {
+ LOG(ERROR) << "ProcessInfo::Initialize";
+ return false;
+ }
+#if defined(ARCH_CPU_64_BITS)
+ if (!process_info.Is64Bit()) {
+ LOG(ERROR) << "DumpAndCrashTargetProcess currently not supported
x64->x86";
+ return false;
+ }
+#endif // ARCH_CPU_64_BITS
+
+ ScopedProcessSuspend suspend(process);
+
+ // If no thread handle was provided, or the thread has already exited,
we pass
+ // 0 to the handler, which indicates no fake exception record to be
created.
+ DWORD thread_id = 0;
+ if (blame_thread) {
+ // Now that we've suspended the process, if our thread hasn't exited,
we
+ // know we're relatively safe to pass the thread id through.
+ if (WaitForSingleObject(blame_thread, 0) == WAIT_TIMEOUT) {
+ static const auto get_thread_id =
+ GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetThreadId);
+ thread_id = get_thread_id(blame_thread);
+ }
+ }
+
+ const size_t kInjectBufferSize = 4 * 1024;
+ WinVMAddress inject_memory =
+ reinterpret_cast<WinVMAddress>(VirtualAllocEx(process,
+ nullptr,
+ kInjectBufferSize,
+ MEM_RESERVE |
MEM_COMMIT,
+ PAGE_READWRITE));
+ if (!inject_memory) {
+ PLOG(ERROR) << "VirtualAllocEx";
+ return false;
+ }
+
+ // Because we're the same bitness as our target, we can rely kernel32
being
+ // loaded at the same address in our process as the target, and just
look up
+ // its address here.
+ WinVMAddress raise_exception_address =
+ reinterpret_cast<WinVMAddress>(&RaiseException);
+
+ WinVMAddress code_entry_point = 0;
+ std::vector<unsigned char> data_to_write;
+ if (process_info.Is64Bit()) {
+ // Data written is first, the data for the 4th argument (lpArguments)
to
+ // RaiseException(). A two element array:
+ //
+ // DWORD64: thread_id
+ // DWORD64: exception_code
+ //
+ // Following that, code which sets the arguments to RaiseException()
and
+ // then calls it:
+ //
+ // mov r9, <data_array_address>
+ // mov r8d, 2 ; nNumberOfArguments
+ // mov edx, 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE
+ // mov ecx, 0xcca11ed ; dwExceptionCode, interpreted specially by the
+ // ; handler.
+ // jmp <address_of_RaiseException>
+ //
+ // Note that the first three arguments to RaiseException() are DWORDs
even
+ // on x64, so only the 4th argument (a pointer) is a full-width
register.
+ //
+ // We also don't need to set up a stack or use call, since the only
+ // registers modified are volatile ones, and we can just jmp straight
to
+ // RaiseException().
+
+ // The data array.
+ AddUint64(&data_to_write, thread_id);
+ AddUint64(&data_to_write, exception_code);
+
+ // The thread entry point.
+ code_entry_point = inject_memory + data_to_write.size();
+
+ // r9 = pointer to data.
+ data_to_write.push_back(0x49);
+ data_to_write.push_back(0xb9);
+ AddUint64(&data_to_write, inject_memory);
+
+ // r8d = 2 for nNumberOfArguments.
+ data_to_write.push_back(0x41);
+ data_to_write.push_back(0xb8);
+ AddUint32(&data_to_write, 2);
+
+ // edx = 1 for dwExceptionFlags.
+ data_to_write.push_back(0xba);
+ AddUint32(&data_to_write, 1);
+
+ // ecx = kTriggeredExceptionCode for dwExceptionCode.
+ data_to_write.push_back(0xb9);
+ AddUint32(&data_to_write, kTriggeredExceptionCode);
+
+ // jmp to RaiseException() via rax.
+ data_to_write.push_back(0x48); // mov rax, imm.
+ data_to_write.push_back(0xb8);
+ AddUint64(&data_to_write, raise_exception_address);
+ data_to_write.push_back(0xff); // jmp rax.
+ data_to_write.push_back(0xe0);
+ } else {
+ // Data written is first, the data for the 4th argument (lpArguments)
to
+ // RaiseException(). A two element array:
+ //
+ // DWORD: thread_id
+ // DWORD: exception_code
+ //
+ // Following that, code which pushes our arguments to RaiseException()
and
+ // then calls it:
+ //
+ // push <data_array_address>
+ // push 2 ; nNumberOfArguments
+ // push 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE
+ // push 0xcca11ed ; dwExceptionCode, interpreted specially by the
handler.
+ // call <address_of_RaiseException>
+ // ud2 ; Generate invalid opcode to make sure we still crash if we
return
+ // ; for some reason.
+ //
+ // No need to clean up the stack, as RaiseException() is __stdcall.
+
+ // The data array.
+ AddUint32(&data_to_write, thread_id);
+ AddUint32(&data_to_write, exception_code);
+
+ // The thread entry point.
+ code_entry_point = inject_memory + data_to_write.size();
+
+ // Push data address.
+ data_to_write.push_back(0x68);
+ AddUint32(&data_to_write, static_cast<uint32_t>(inject_memory));
+
+ // Push 2 for nNumberOfArguments.
+ data_to_write.push_back(0x6a);
+ data_to_write.push_back(2);
+
+ // Push 1 for dwExceptionCode.
+ data_to_write.push_back(0x6a);
+ data_to_write.push_back(1);
+
+ // Push dwExceptionFlags.
+ data_to_write.push_back(0x68);
+ AddUint32(&data_to_write, kTriggeredExceptionCode);
+
+ // Relative call to RaiseException().
+ int64_t relative_address_to_raise_exception =
+ raise_exception_address - (inject_memory + data_to_write.size() +
5);
+ data_to_write.push_back(0xe8);
+ AddUint32(&data_to_write,
+ static_cast<uint32_t>(relative_address_to_raise_exception));
+
+ // ud2.
+ data_to_write.push_back(0x0f);
+ data_to_write.push_back(0x0b);
+ }
+
+ DCHECK_LT(data_to_write.size(), kInjectBufferSize);
+
+ SIZE_T bytes_written;
+ if (!WriteProcessMemory(process,
+ reinterpret_cast<void*>(inject_memory),
+ data_to_write.data(),
+ data_to_write.size(),
+ &bytes_written)) {
+ PLOG(ERROR) << "WriteProcessMemory";
+ return false;
+ }
+
+ if (bytes_written != data_to_write.size()) {
+ LOG(ERROR) << "WriteProcessMemory unexpected number of bytes";
+ return false;
+ }
+
+ if (!FlushInstructionCache(
+ process, reinterpret_cast<void*>(inject_memory), bytes_written))
{
+ PLOG(ERROR) << "FlushInstructionCache";
+ return false;
+ }
+
+ DWORD old_protect;
+ if (!VirtualProtectEx(process,
+ reinterpret_cast<void*>(inject_memory),
+ kInjectBufferSize,
+ PAGE_EXECUTE_READ,
+ &old_protect)) {
+ PLOG(ERROR) << "VirtualProtectEx";
+ return false;
+ }
+
+ // Cause an exception in the target process by creating a thread which
calls
+ // RaiseException with our arguments above. Note that we cannot get away
with
+ // using DebugBreakProcess() (nothing happens unless a debugger is
attached)
+ // and we cannot get away with CreateRemoteThread() because it doesn't
work if
+ // the target is hung waiting for the loader lock. We use
NtCreateThreadEx()
+ // with the SKIP_THREAD_ATTACH flag, which skips various notifications,
+ // letting this cause an exception, even when the target is stuck in the
+ // loader lock.
+ HANDLE injected_thread;
+ const size_t kStackSize = 0x4000; // This is what DebugBreakProcess()
uses.
+ NTSTATUS status = NtCreateThreadEx(&injected_thread,
+ STANDARD_RIGHTS_ALL |
SPECIFIC_RIGHTS_ALL,
+ nullptr,
+ process,
+
reinterpret_cast<void*>(code_entry_point),
+ nullptr,
+
THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH,
+ 0,
+ kStackSize,
+ 0,
+ nullptr);
+ if (!NT_SUCCESS(status)) {
+ NTSTATUS_LOG(ERROR, status) << "NtCreateThreadEx";
+ return false;
+ }
+
+ bool result = true;
+ if (WaitForSingleObject(injected_thread, 60 * 1000) != WAIT_OBJECT_0) {
+ PLOG(ERROR) << "WaitForSingleObject";
+ result = false;
+ }
+
+ status = NtClose(injected_thread);
+ if (!NT_SUCCESS(status)) {
+ NTSTATUS_LOG(ERROR, status) << "NtClose";
+ result = false;
+ }
+
+ return result;
+}
+
} // namespace crashpad
diff --git a/handler/handler.gyp b/handler/handler.gyp
index 40d5977..bbb2ce4 100644
--- a/handler/handler.gyp
+++ b/handler/handler.gyp
@@ -111,6 +111,40 @@
],
},
{
+ 'target_name': 'crash_other_program',
+ 'type': 'executable',
+ 'dependencies': [
+ '../client/client.gyp:crashpad_client',
+ '../test/test.gyp:crashpad_test',
+ '../third_party/mini_chromium/mini_chromium.gyp:base',
+ '../util/util.gyp:crashpad_util',
+ ],
+ 'sources': [
+ 'win/crash_other_program.cc',
+ ],
+ },
+ {
+ 'target_name': 'hanging_program',
+ 'type': 'executable',
+ 'dependencies': [
+ '../client/client.gyp:crashpad_client',
+ '../third_party/mini_chromium/mini_chromium.gyp:base',
+ ],
+ 'sources': [
+ 'win/hanging_program.cc',
+ ],
+ },
+ {
+ 'target_name': 'loader_lock_dll',
+ 'type': 'loadable_module',
+ 'sources': [
+ 'win/loader_lock_dll.cc',
+ ],
+ 'msvs_settings': {
+ 'NoImportLibrary': 'true',
+ },
+ },
+ {
'target_name': 'self_destroying_program',
'type': 'executable',
'dependencies': [
diff --git a/handler/win/crash_other_program.cc
b/handler/win/crash_other_program.cc
new file mode 100644
index 0000000..db532bb
--- /dev/null
+++ b/handler/win/crash_other_program.cc
@@ -0,0 +1,120 @@
+// Copyright 2016 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 <stdlib.h>
+#include <windows.h>
+#include <tlhelp32.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "client/crashpad_client.h"
+#include "test/paths.h"
+#include "test/win/child_launcher.h"
+#include "util/file/file_io.h"
+#include "util/win/scoped_handle.h"
+#include "util/win/xp_compat.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) {
+ DWORD target_pid = GetProcessId(process);
+
+ HANDLE thread_snap_raw = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (thread_snap_raw == INVALID_HANDLE_VALUE) {
+ LOG(ERROR) << "CreateToolhelp32Snapshot";
+ return false;
+ }
+ ScopedFileHANDLE thread_snap(thread_snap_raw);
+
+ THREADENTRY32 te32;
+ te32.dwSize = sizeof(THREADENTRY32);
+ if (!Thread32First(thread_snap.get(), &te32)) {
+ LOG(ERROR) << "Thread32First";
+ return false;
+ }
+
+ int thread_count = 0;
+ do {
+ if (te32.th32OwnerProcessID == target_pid) {
+ thread_count++;
+ if (thread_count == 2) {
+ // Nominate this lucky thread as our blamee, and dump it. This
will be
+ // "Thread1" in the child.
+ ScopedKernelHANDLE thread(
+ OpenThread(kXPThreadAllAccess, false, te32.th32ThreadID));
+ if (!client.DumpAndCrashTargetProcess(
+ process, thread.get(), 0xdeadbea7)) {
+ LOG(ERROR) << "DumpAndCrashTargetProcess failed";
+ return false;
+ }
+ return true;
+ }
+ }
+ } while (Thread32Next(thread_snap.get(), &te32));
+
+ return false;
+}
+
+int CrashOtherProgram(int argc, wchar_t* argv[]) {
+ CrashpadClient client;
+
+ if (argc == 2 || argc == 3) {
+ if (!client.SetHandlerIPCPipe(argv[1])) {
+ LOG(ERROR) << "SetHandler";
+ return EXIT_FAILURE;
+ }
+ } else {
+ fprintf(stderr, "Usage: %ls <server_pipe_name> [noexception]\n",
argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (!client.UseHandler()) {
+ LOG(ERROR) << "UseHandler";
+ return EXIT_FAILURE;
+ }
+
+ // Launch another process that hangs.
+ base::FilePath test_executable = Paths::Executable();
+ std::wstring child_test_executable =
+ test_executable.DirName().Append(L"hanging_program.exe").value();
+ ChildLauncher child(child_test_executable, argv[1]);
+ child.Start();
+
+ // Wait until it's ready.
+ char c;
+ if (!LoggingReadFile(child.stdout_read_handle(), &c, sizeof(c)) ||
c != ' ') {
+ LOG(ERROR) << "failed child communication";
+ return EXIT_FAILURE;
+ }
+
+ if (argc == 3 && wcscmp(argv[2], L"noexception") == 0) {
+ client.DumpAndCrashTargetProcess(child.process_handle(), 0, 0);
+ return EXIT_SUCCESS;
+ } else {
+ if (CrashAndDumpTarget(client, child.process_handle()))
+ return EXIT_SUCCESS;
+ }
+
+ return EXIT_FAILURE;
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad
+
+int wmain(int argc, wchar_t* argv[]) {
+ return crashpad::test::CrashOtherProgram(argc, argv);
+}
diff --git a/handler/win/hanging_program.cc b/handler/win/hanging_program.cc
new file mode 100644
index 0000000..877578e
--- /dev/null
+++ b/handler/win/hanging_program.cc
@@ -0,0 +1,86 @@
+// Copyright 2016 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 <stdio.h>
+#include <windows.h>
+
+#include "base/debug/alias.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "client/crashpad_client.h"
+#include "client/crashpad_info.h"
+
+DWORD WINAPI Thread1(LPVOID dummy) {
+ Sleep(INFINITE);
+ return 0;
+}
+
+DWORD WINAPI Thread2(LPVOID dummy) {
+ Sleep(INFINITE);
+ return 0;
+}
+
+DWORD WINAPI Thread3(LPVOID dummy) {
+ HMODULE dll = LoadLibrary(L"loader_lock_dll.dll");
+ if (!dll)
+ PLOG(ERROR) << "LoadLibrary";
+
+ // This call is not expected to return.
+ if (!FreeLibrary(dll))
+ PLOG(ERROR) << "FreeLibrary";
+
+ return 0;
+}
+
+int wmain(int argc, wchar_t* argv[]) {
+ crashpad::CrashpadClient client;
+
+ if (argc == 2) {
+ if (!client.SetHandlerIPCPipe(argv[1])) {
+ LOG(ERROR) << "SetHandler";
+ return EXIT_FAILURE;
+ }
+ } else {
+ fprintf(stderr, "Usage: %ls <server_pipe_name>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (!client.UseHandler()) {
+ LOG(ERROR) << "UseHandler";
+ return EXIT_FAILURE;
+ }
+
+ // Make sure this module has a CrashpadInfo structure.
+ crashpad::CrashpadInfo* crashpad_info =
+ crashpad::CrashpadInfo::GetCrashpadInfo();
+ base::debug::Alias(crashpad_info);
+
+ HANDLE threads[3];
+ threads[0] = CreateThread(nullptr, 0, Thread1, nullptr, 0, nullptr);
+ threads[1] = CreateThread(nullptr, 0, Thread2, nullptr, 0, nullptr);
+ threads[2] = CreateThread(nullptr, 0, Thread3, nullptr, 0, nullptr);
+
+ // Our whole process is going to hang when the loaded DLL hangs in its
+ // DllMain(), so we can't signal to our parent that we're "ready". So,
use a
+ // hokey delay of 1s after we spawn the threads, and hope that we make
it to
+ // the FreeLibrary call by then.
+ Sleep(1000);
+
+ fprintf(stdout, " ");
+ fflush(stdout);
+
+ WaitForMultipleObjects(ARRAYSIZE(threads), threads, true, INFINITE);
+
+ return 0;
+}
diff --git a/handler/win/loader_lock_dll.cc b/handler/win/loader_lock_dll.cc
new file mode 100644
index 0000000..0a05af3
--- /dev/null
+++ b/handler/win/loader_lock_dll.cc
@@ -0,0 +1,28 @@
+// Copyright 2016 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 <windows.h>
+
+// This program intentionally blocks in DllMain which is executed with the
+// loader lock locked. This allows us to test that
+// CrashpadClient::DumpAndCrashTargetProcess() can still dump the target
in this
+// case.
+BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) {
+ switch (reason) {
+ case DLL_PROCESS_DETACH:
+ case DLL_THREAD_DETACH:
+ Sleep(INFINITE);
+ }
+ return TRUE;
+}
diff --git a/snapshot/win/end_to_end_test.py
b/snapshot/win/end_to_end_test.py
index 53d1b07..a5540e5 100755
--- a/snapshot/win/end_to_end_test.py
+++ b/snapshot/win/end_to_end_test.py
@@ -79,11 +79,12 @@
return None
-def GetDumpFromProgram(out_dir, pipe_name, executable_name):
+def GetDumpFromProgram(out_dir, pipe_name, executable_name, *args):
"""Initialize a crash database, and run |executable_name| connecting to a
crash handler. If pipe_name is set, crashpad_handler will be started
first. If
pipe_name is empty, the executable is responsible for starting
- crashpad_handler. Returns the minidump generated by crashpad_handler for
+ crashpad_handler. *args will be passed after other arguments to
+ executable_name. Returns the minidump generated by crashpad_handler for
further testing.
"""
test_database = MakeTempDir()
@@ -111,11 +112,12 @@
printed = True
time.sleep(0.1)
- subprocess.call([os.path.join(out_dir, executable_name), pipe_name])
+ subprocess.call([os.path.join(out_dir, executable_name), pipe_name] +
+ list(args))
else:
subprocess.call([os.path.join(out_dir, executable_name),
os.path.join(out_dir, 'crashpad_handler.exe'),
- test_database])
+ test_database] + list(args))
out = subprocess.check_output([
os.path.join(out_dir, 'crashpad_database_util.exe'),
@@ -133,6 +135,11 @@
def GetDumpFromCrashyProgram(out_dir, pipe_name):
return GetDumpFromProgram(out_dir, pipe_name, 'crashy_program.exe')
+
+
+def GetDumpFromOtherProgram(out_dir, pipe_name, *args):
+ return GetDumpFromProgram(out_dir, pipe_name, 'crash_other_program.exe',
+ *args)
def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name):
@@ -182,6 +189,8 @@
start_handler_dump_path,
destroyed_dump_path,
z7_dump_path,
+ other_program_path,
+ other_program_no_exception_path,
pipe_name):
"""Runs various tests in sequence. Runs a new cdb instance on the dump
for
each block of tests to reduce the chances that output from one command is
@@ -311,6 +320,18 @@
out.Check(r'z7_test C \(codeview symbols\) z7_test.dll',
'expected non-pdb symbol format')
+ out = CdbRun(cdb_path, other_program_path, '.ecxr;k;~')
+ out.Check('Unknown exception - code deadbea7',
+ 'other program dump exception code')
+ out.Check('!Sleep', 'other program reasonable location')
+ out.Check('hanging_program!Thread1', 'other program dump right thread')
+ out.Check('\. 1 Id', 'other program exception on correct thread')
+
+ out = CdbRun(cdb_path, other_program_no_exception_path, '.ecxr;k')
+ out.Check('Unknown exception - code 0cca11ed',
+ 'other program with no exception given')
+ out.Check('!RaiseException', 'other program in RaiseException()')
+
def main(args):
try:
@@ -352,11 +373,22 @@
if not z7_dump_path:
return 1
+ other_program_path = GetDumpFromOtherProgram(args[0], pipe_name)
+ if not other_program_path:
+ return 1
+
+ other_program_no_exception_path = GetDumpFromOtherProgram(
+ args[0], pipe_name, 'noexception')
+ if not other_program_no_exception_path:
+ return 1
+
RunTests(cdb_path,
crashy_dump_path,
start_handler_dump_path,
destroyed_dump_path,
z7_dump_path,
+ other_program_path,
+ other_program_no_exception_path,
pipe_name)
return 0
diff --git a/snapshot/win/exception_snapshot_win.cc
b/snapshot/win/exception_snapshot_win.cc
index 2abe6d0..ca29e1f 100644
--- a/snapshot/win/exception_snapshot_win.cc
+++ b/snapshot/win/exception_snapshot_win.cc
@@ -14,6 +14,7 @@
#include "snapshot/win/exception_snapshot_win.h"
+#include "client/crashpad_client.h"
#include "snapshot/capture_memory.h"
#include "snapshot/memory_snapshot.h"
#include "snapshot/win/cpu_context_win.h"
@@ -24,6 +25,34 @@
namespace crashpad {
namespace internal {
+
+namespace {
+
+#if defined(ARCH_CPU_32_BITS)
+using Context32 = CONTEXT;
+#elif defined(ARCH_CPU_64_BITS)
+using Context32 = WOW64_CONTEXT;
+#endif
+
+#if defined(ARCH_CPU_64_BITS)
+void NativeContextToCPUContext64(const CONTEXT& context_record,
+ CPUContext* context,
+ CPUContextUnion* context_union) {
+ context->architecture = kCPUArchitectureX86_64;
+ context->x86_64 = &context_union->x86_64;
+ InitializeX64Context(context_record, context->x86_64);
+}
+#endif
+
+void NativeContextToCPUContext32(const Context32& context_record,
+ CPUContext* context,
+ CPUContextUnion* context_union) {
+ context->architecture = kCPUArchitectureX86;
+ context->x86 = &context_union->x86;
+ InitializeX86Context(context_record, context->x86);
+}
+
+} // namespace
ExceptionSnapshotWin::ExceptionSnapshotWin()
: ExceptionSnapshot(),
@@ -40,9 +69,11 @@
ExceptionSnapshotWin::~ExceptionSnapshotWin() {
}
-bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
- DWORD thread_id,
- WinVMAddress
exception_pointers_address) {
+bool ExceptionSnapshotWin::Initialize(
+ ProcessReaderWin* process_reader,
+ DWORD thread_id,
+ WinVMAddress exception_pointers_address,
+ const PointerVector<internal::ThreadSnapshotWin>& threads) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
const ProcessReaderWin::Thread* thread = nullptr;
@@ -62,32 +93,28 @@
#if defined(ARCH_CPU_32_BITS)
const bool is_64_bit = false;
- using Context32 = CONTEXT;
#elif defined(ARCH_CPU_64_BITS)
const bool is_64_bit = process_reader->Is64Bit();
- using Context32 = WOW64_CONTEXT;
if (is_64_bit) {
- CONTEXT context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD64,
process_types::EXCEPTION_POINTERS64>(
- *process_reader, exception_pointers_address, &context_record))
{
+ *process_reader,
+ exception_pointers_address,
+ threads,
+ &NativeContextToCPUContext64)) {
return false;
}
- context_.architecture = kCPUArchitectureX86_64;
- context_.x86_64 = &context_union_.x86_64;
- InitializeX64Context(context_record, context_.x86_64);
}
#endif
if (!is_64_bit) {
- Context32 context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32,
process_types::EXCEPTION_POINTERS32>(
- *process_reader, exception_pointers_address, &context_record))
{
+ *process_reader,
+ exception_pointers_address,
+ threads,
+ &NativeContextToCPUContext32)) {
return false;
}
- context_.architecture = kCPUArchitectureX86;
- context_.x86 = &context_union_.x86;
- InitializeX86Context(context_record, context_.x86);
}
CaptureMemoryDelegateWin capture_memory_delegate(
@@ -143,7 +170,10 @@
bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
const ProcessReaderWin& process_reader,
WinVMAddress exception_pointers_address,
- ContextType* context_record) {
+ const PointerVector<internal::ThreadSnapshotWin>& threads,
+ void (*native_to_cpu_context)(const ContextType& context_record,
+ CPUContext* context,
+ CPUContextUnion* context_union)) {
ExceptionPointersType exception_pointers;
if (!process_reader.ReadMemory(exception_pointers_address,
sizeof(exception_pointers),
@@ -164,22 +194,60 @@
LOG(ERROR) << "ExceptionRecord";
return false;
}
- exception_code_ = first_record.ExceptionCode;
- exception_flags_ = first_record.ExceptionFlags;
- exception_address_ = first_record.ExceptionAddress;
- for (DWORD i = 0; i < first_record.NumberParameters; ++i)
- codes_.push_back(first_record.ExceptionInformation[i]);
- if (first_record.ExceptionRecord) {
- //
https://crashpad.chromium.org/bug/43
- LOG(WARNING) << "dropping chained ExceptionRecord";
- }
- if (!process_reader.ReadMemory(
- static_cast<WinVMAddress>(exception_pointers.ContextRecord),
- sizeof(*context_record),
- context_record)) {
- LOG(ERROR) << "ContextRecord";
- return false;
+ if (first_record.ExceptionCode ==
CrashpadClient::kTriggeredExceptionCode &&
+ first_record.NumberParameters == 2 &&
+ first_record.ExceptionInformation[0] != 0) {
+ // This special exception code indicates that the target was crashed by
+ // another client calling CrashpadClient::DumpAndCrashTargetProcess().
In
+ // this case the parameters are a thread id and an exception code
which we
+ // use to fabricate a new exception record.
+ using ArgumentType = decltype(first_record.ExceptionInformation[0]);
+ const ArgumentType thread_id = first_record.ExceptionInformation[0];
+ exception_code_ =
static_cast<DWORD>(first_record.ExceptionInformation[1]);
+ exception_flags_ = EXCEPTION_NONCONTINUABLE;
+ for (const auto* thread : threads) {
+ if (thread->ThreadID() == thread_id) {
+ thread_id_ = thread_id;
+ exception_address_ = thread->Context()->InstructionPointer();
+ context_.architecture = thread->Context()->architecture;
+ if (context_.architecture == kCPUArchitectureX86_64) {
+ context_union_.x86_64 = *thread->Context()->x86_64;
+ context_.x86_64 = &context_union_.x86_64;
+ } else {
+ context_union_.x86 = *thread->Context()->x86;
+ context_.x86 = &context_union_.x86;
+ }
+ break;
+ }
+ }
+
+ if (exception_address_ == 0) {
+ LOG(WARNING) << "thread " << thread_id << " not found";
+ return false;
+ }
+ } else {
+ // Normal case.
+ exception_code_ = first_record.ExceptionCode;
+ exception_flags_ = first_record.ExceptionFlags;
+ exception_address_ = first_record.ExceptionAddress;
+ for (DWORD i = 0; i < first_record.NumberParameters; ++i)
+ codes_.push_back(first_record.ExceptionInformation[i]);
+ if (first_record.ExceptionRecord) {
+ //
https://crashpad.chromium.org/bug/43
+ LOG(WARNING) << "dropping chained ExceptionRecord";
+ }
+
+ ContextType context_record;
+ if (!process_reader.ReadMemory(
+ static_cast<WinVMAddress>(exception_pointers.ContextRecord),
+ sizeof(context_record),
+ &context_record)) {
+ LOG(ERROR) << "ContextRecord";
+ return false;
+ }
+
+ native_to_cpu_context(context_record, &context_, &context_union_);
}
return true;
diff --git a/snapshot/win/exception_snapshot_win.h
b/snapshot/win/exception_snapshot_win.h
index 3e66dd2..8ff7d38 100644
--- a/snapshot/win/exception_snapshot_win.h
+++ b/snapshot/win/exception_snapshot_win.h
@@ -22,6 +22,7 @@
#include "build/build_config.h"
#include "snapshot/cpu_context.h"
#include "snapshot/exception_snapshot.h"
+#include "snapshot/win/thread_snapshot_win.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/stdlib/pointer_container.h"
#include "util/win/address_types.h"
@@ -34,6 +35,13 @@
namespace internal {
class MemorySnapshotWin;
+
+#if defined(ARCH_CPU_X86_FAMILY)
+union CPUContextUnion {
+ CPUContextX86 x86;
+ CPUContextX86_64 x86_64;
+};
+#endif
class ExceptionSnapshotWin final : public ExceptionSnapshot {
public:
@@ -53,7 +61,8 @@
//! an appropriate message logged.
bool Initialize(ProcessReaderWin* process_reader,
DWORD thread_id,
- WinVMAddress exception_pointers);
+ WinVMAddress exception_pointers,
+ const PointerVector<internal::ThreadSnapshotWin>&
threads);
// ExceptionSnapshot:
@@ -69,15 +78,16 @@
template <class ExceptionRecordType,
class ExceptionPointersType,
class ContextType>
- bool InitializeFromExceptionPointers(const ProcessReaderWin&
process_reader,
- WinVMAddress
exception_pointers_address,
- ContextType* context_record);
+ bool InitializeFromExceptionPointers(
+ const ProcessReaderWin& process_reader,
+ WinVMAddress exception_pointers_address,
+ const PointerVector<internal::ThreadSnapshotWin>& threads,
+ void (*native_to_cpu_context)(const ContextType& context_record,
+ CPUContext* context,
+ CPUContextUnion* context_union));
#if defined(ARCH_CPU_X86_FAMILY)
- union {
- CPUContextX86 x86;
- CPUContextX86_64 x86_64;
- } context_union_;
+ CPUContextUnion context_union_;
#endif
CPUContext context_;
std::vector<uint64_t> codes_;
diff --git a/snapshot/win/process_snapshot_win.cc
b/snapshot/win/process_snapshot_win.cc
index 6af6f20..888a3e0 100644
--- a/snapshot/win/process_snapshot_win.cc
+++ b/snapshot/win/process_snapshot_win.cc
@@ -107,7 +107,8 @@
exception_.reset(new internal::ExceptionSnapshotWin());
if (!exception_->Initialize(&process_reader_,
exception_information.thread_id,
- exception_information.exception_pointers)) {
+ exception_information.exception_pointers,
+ threads_)) {
exception_.reset();
return false;
}
diff --git a/util/win/nt_internals.cc b/util/win/nt_internals.cc
index 6fc0999..8cf96a4 100644
--- a/util/win/nt_internals.cc
+++ b/util/win/nt_internals.cc
@@ -21,6 +21,18 @@
struct CLIENT_ID;
+NTSTATUS NTAPI NtCreateThreadEx(PHANDLE ThreadHandle,
+ ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ HANDLE ProcessHandle,
+ PVOID StartRoutine,
+ PVOID Argument,
+ ULONG CreateFlags,
+ SIZE_T ZeroBits,
+ SIZE_T StackSize,
+ SIZE_T MaximumStackSize,
+ PVOID /*PPS_ATTRIBUTE_LIST*/
AttributeList);
+
NTSTATUS NTAPI NtOpenThread(HANDLE* ThreadHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES* ObjectAttributes,
@@ -30,6 +42,38 @@
namespace crashpad {
+NTSTATUS NtClose(HANDLE handle) {
+ static const auto nt_close =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtClose);
+ return nt_close(handle);
+}
+
+NTSTATUS
+NtCreateThreadEx(PHANDLE thread_handle,
+ ACCESS_MASK desired_access,
+ POBJECT_ATTRIBUTES object_attributes,
+ HANDLE process_handle,
+ PVOID start_routine,
+ PVOID argument,
+ ULONG create_flags,
+ SIZE_T zero_bits,
+ SIZE_T stack_size,
+ SIZE_T maximum_stack_size,
+ PVOID attribute_list) {
+ static const auto nt_create_thread_ex =
+ GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtCreateThreadEx);
+ return nt_create_thread_ex(thread_handle,
+ desired_access,
+ object_attributes,
+ process_handle,
+ start_routine,
+ argument,
+ create_flags,
+ zero_bits,
+ stack_size,
+ maximum_stack_size,
+ attribute_list);
+}
+
NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS system_information_class,
PVOID system_information,
diff --git a/util/win/nt_internals.h b/util/win/nt_internals.h
index b622101..3ced5cc 100644
--- a/util/win/nt_internals.h
+++ b/util/win/nt_internals.h
@@ -19,6 +19,23 @@
namespace crashpad {
+NTSTATUS NtClose(HANDLE handle);
+
+//
http://processhacker.sourceforge.net/doc/ntpsapi_8h_source.html
+#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
+NTSTATUS
+NtCreateThreadEx(PHANDLE thread_handle,
+ ACCESS_MASK desired_access,
+ POBJECT_ATTRIBUTES object_attributes,
+ HANDLE process_handle,
+ PVOID start_routine,
+ PVOID argument,
+ ULONG create_flags,
+ SIZE_T zero_bits,
+ SIZE_T stack_size,
+ SIZE_T maximum_stack_size,
+ PVOID /*PPS_ATTRIBUTE_LIST*/ attribute_list);
+
// Copied from ntstatus.h because um/winnt.h conflicts with general
inclusion of
// ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
Gerrit-MessageType: merged
Gerrit-Change-Id: I797007bd2b1e3416afe3f37a6566c0cdb259b106
Gerrit-PatchSet: 21