mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-17 08:33:54 +00:00
win: Implement exception snapshot
Refactor some of the NT internals helpers and cpu_context to share between the thread and exception snapshot code. Add test that runs crashing child and validates the exception in the snapshot. R=mark@chromium.org, cpu@chromium.org, rsesek@chromium.org BUG=crashpad:1 Review URL: https://codereview.chromium.org/1126413008 .
This commit is contained in:
parent
1a770c8237
commit
a691448ffb
@ -56,17 +56,19 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
|||||||
// contention here is very unlikely, and we'll still have a stack that's
|
// contention here is very unlikely, and we'll still have a stack that's
|
||||||
// blocked at this location.
|
// blocked at this location.
|
||||||
if (base::subtle::Barrier_AtomicIncrement(&g_have_crashed, 1) > 1) {
|
if (base::subtle::Barrier_AtomicIncrement(&g_have_crashed, 1) > 1) {
|
||||||
SleepEx(false, INFINITE);
|
SleepEx(INFINITE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we're the first thread, so record the exception pointer and
|
// Otherwise, we're the first thread, so record the exception pointer and
|
||||||
// signal the crash handler.
|
// signal the crash handler.
|
||||||
|
crashpad::CrashpadInfo::GetCrashpadInfo()->set_thread_id(
|
||||||
|
GetCurrentThreadId());
|
||||||
crashpad::CrashpadInfo::GetCrashpadInfo()->set_exception_pointers(
|
crashpad::CrashpadInfo::GetCrashpadInfo()->set_exception_pointers(
|
||||||
exception_pointers);
|
exception_pointers);
|
||||||
DWORD rv = SignalObjectAndWait(g_signal_exception,
|
DWORD rv = SignalObjectAndWait(g_signal_exception,
|
||||||
g_wait_termination,
|
g_wait_termination,
|
||||||
kMillisecondsUntilTerminate,
|
kMillisecondsUntilTerminate,
|
||||||
FALSE);
|
false);
|
||||||
if (rv != WAIT_OBJECT_0) {
|
if (rv != WAIT_OBJECT_0) {
|
||||||
// Something went wrong. It is likely that a dump has not been created.
|
// Something went wrong. It is likely that a dump has not been created.
|
||||||
if (rv == WAIT_TIMEOUT) {
|
if (rv == WAIT_TIMEOUT) {
|
||||||
|
@ -89,7 +89,13 @@ CrashpadInfo::CrashpadInfo()
|
|||||||
crashpad_handler_behavior_(TriState::kUnset),
|
crashpad_handler_behavior_(TriState::kUnset),
|
||||||
system_crash_reporter_forwarding_(TriState::kUnset),
|
system_crash_reporter_forwarding_(TriState::kUnset),
|
||||||
padding_0_(0),
|
padding_0_(0),
|
||||||
simple_annotations_(nullptr) {
|
simple_annotations_(nullptr)
|
||||||
|
#if defined(OS_WIN)
|
||||||
|
,
|
||||||
|
exception_pointers_(nullptr),
|
||||||
|
thread_id_(0)
|
||||||
|
#endif // OS_WIN
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace crashpad
|
} // namespace crashpad
|
||||||
|
@ -99,10 +99,15 @@ struct CrashpadInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
//! \brief Save an EXCEPTION_POINTERS record for the crash handler.
|
//! \brief Save the crashing thread ID for the crash handler.
|
||||||
|
void set_thread_id(DWORD thread_id) { thread_id_ = thread_id; }
|
||||||
|
DWORD thread_id() const { return thread_id_; }
|
||||||
|
|
||||||
|
//! \brief Save an `EXCEPTION_POINTERS` record for the crash handler.
|
||||||
void set_exception_pointers(EXCEPTION_POINTERS* exception_pointers) {
|
void set_exception_pointers(EXCEPTION_POINTERS* exception_pointers) {
|
||||||
exception_pointers_ = exception_pointers;
|
exception_pointers_ = exception_pointers;
|
||||||
}
|
}
|
||||||
|
EXCEPTION_POINTERS* exception_pointers() const { return exception_pointers_; }
|
||||||
#endif // OS_WIN
|
#endif // OS_WIN
|
||||||
|
|
||||||
enum : uint32_t {
|
enum : uint32_t {
|
||||||
@ -130,6 +135,7 @@ struct CrashpadInfo {
|
|||||||
|
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
EXCEPTION_POINTERS* exception_pointers_;
|
EXCEPTION_POINTERS* exception_pointers_;
|
||||||
|
DWORD thread_id_;
|
||||||
#endif // OS_WIN
|
#endif // OS_WIN
|
||||||
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
|
@ -410,6 +410,9 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION {
|
|||||||
//! the original exception code will appear instead. The exception type as it
|
//! the original exception code will appear instead. The exception type as it
|
||||||
//! was received will appear at index 0 of #ExceptionInformation.
|
//! was received will appear at index 0 of #ExceptionInformation.
|
||||||
//!
|
//!
|
||||||
|
//! For Windows minidumps, this will be an \ref EXCEPTION_x "EXCEPTION_*"
|
||||||
|
//! exception type, such as `EXCEPTION_ACCESS_VIOLATION`.
|
||||||
|
//!
|
||||||
//! \note This field is named ExceptionCode, but what is known as the
|
//! \note This field is named ExceptionCode, but what is known as the
|
||||||
//! “exception code” on Mac OS X/Mach is actually stored in the
|
//! “exception code” on Mac OS X/Mach is actually stored in the
|
||||||
//! #ExceptionFlags field of a minidump file.
|
//! #ExceptionFlags field of a minidump file.
|
||||||
@ -432,6 +435,10 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION {
|
|||||||
//! In all cases for Mac OS X minidumps, the code as it was received by the
|
//! In all cases for Mac OS X minidumps, the code as it was received by the
|
||||||
//! Mach exception handler will appear at index 1 of #ExceptionInformation.
|
//! Mach exception handler will appear at index 1 of #ExceptionInformation.
|
||||||
//!
|
//!
|
||||||
|
//! For Windows minidumps, this will either be `0` if the exception is
|
||||||
|
//! continuable, or `EXCEPTION_NONCONTINUABLE` to indicate a noncontinuable
|
||||||
|
//! exception.
|
||||||
|
//!
|
||||||
//! \todo Document the possible values by OS. There may be OS-specific enums
|
//! \todo Document the possible values by OS. There may be OS-specific enums
|
||||||
//! in minidump_extensions.h.
|
//! in minidump_extensions.h.
|
||||||
uint32_t ExceptionFlags;
|
uint32_t ExceptionFlags;
|
||||||
@ -461,6 +468,9 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION {
|
|||||||
//! exception handler. Unlike #ExceptionCode and #ExceptionFlags, the values
|
//! exception handler. Unlike #ExceptionCode and #ExceptionFlags, the values
|
||||||
//! received by a Mach exception handler are used directly here even for the
|
//! received by a Mach exception handler are used directly here even for the
|
||||||
//! `EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD` exception types.
|
//! `EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD` exception types.
|
||||||
|
|
||||||
|
//! For Windows, these are additional arguments (if any) as provided to
|
||||||
|
//! `RaiseException()`.
|
||||||
uint64_t ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
|
uint64_t ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@ class ExceptionSnapshot {
|
|||||||
//! processed as `EXC_CRASH` when generated from another preceding exception:
|
//! processed as `EXC_CRASH` when generated from another preceding exception:
|
||||||
//! the original exception code will appear instead. The exception type as it
|
//! the original exception code will appear instead. The exception type as it
|
||||||
//! was received will appear at index 0 of Codes().
|
//! was received will appear at index 0 of Codes().
|
||||||
|
//!
|
||||||
|
//! For Windows, this will be an \ref EXCEPTION_x "EXCEPTION_*" exception type
|
||||||
|
//! such as `EXCEPTION_ACCESS_VIOLATION`.
|
||||||
virtual uint32_t Exception() const = 0;
|
virtual uint32_t Exception() const = 0;
|
||||||
|
|
||||||
//! \brief Returns the second-level exception code identifying the exception.
|
//! \brief Returns the second-level exception code identifying the exception.
|
||||||
@ -69,6 +72,9 @@ class ExceptionSnapshot {
|
|||||||
//!
|
//!
|
||||||
//! In all cases on Mac OS X, the full exception code at index 0 as it was
|
//! In all cases on Mac OS X, the full exception code at index 0 as it was
|
||||||
//! received will appear at index 1 of Codes().
|
//! received will appear at index 1 of Codes().
|
||||||
|
//!
|
||||||
|
//! On Windows, this will either be `0` if the exception is continuable, or
|
||||||
|
//! `EXCEPTION_NONCONTINUABLE` to indicate a noncontinuable exception.
|
||||||
virtual uint32_t ExceptionInfo() const = 0;
|
virtual uint32_t ExceptionInfo() const = 0;
|
||||||
|
|
||||||
//! \brief Returns the address that triggered the exception.
|
//! \brief Returns the address that triggered the exception.
|
||||||
@ -92,6 +98,10 @@ class ExceptionSnapshot {
|
|||||||
//! For Mac OS X, this will be a vector containing the original exception type
|
//! For Mac OS X, this will be a vector containing the original exception type
|
||||||
//! and the values of `code[0]` and `code[1]` as received by a Mach exception
|
//! and the values of `code[0]` and `code[1]` as received by a Mach exception
|
||||||
//! handler.
|
//! handler.
|
||||||
|
//!
|
||||||
|
//! For Windows, these are additional arguments (if any) as provided to
|
||||||
|
//! `RaiseException()`. See the documentation for `ExceptionInformation` in
|
||||||
|
//! `EXCEPTION_RECORD`.
|
||||||
virtual const std::vector<uint64_t>& Codes() const = 0;
|
virtual const std::vector<uint64_t>& Codes() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "snapshot/mac/process_snapshot_mac.h"
|
#include "snapshot/mac/process_snapshot_mac.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
#include "util/misc/tri_state.h"
|
#include "util/misc/tri_state.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
'mac/system_snapshot_mac.h',
|
'mac/system_snapshot_mac.h',
|
||||||
'mac/thread_snapshot_mac.cc',
|
'mac/thread_snapshot_mac.cc',
|
||||||
'mac/thread_snapshot_mac.h',
|
'mac/thread_snapshot_mac.h',
|
||||||
|
'memory_snapshot.h',
|
||||||
'minidump/minidump_simple_string_dictionary_reader.cc',
|
'minidump/minidump_simple_string_dictionary_reader.cc',
|
||||||
'minidump/minidump_simple_string_dictionary_reader.h',
|
'minidump/minidump_simple_string_dictionary_reader.h',
|
||||||
'minidump/minidump_string_list_reader.cc',
|
'minidump/minidump_string_list_reader.cc',
|
||||||
@ -82,11 +83,14 @@
|
|||||||
'minidump/module_snapshot_minidump.h',
|
'minidump/module_snapshot_minidump.h',
|
||||||
'minidump/process_snapshot_minidump.cc',
|
'minidump/process_snapshot_minidump.cc',
|
||||||
'minidump/process_snapshot_minidump.h',
|
'minidump/process_snapshot_minidump.h',
|
||||||
'memory_snapshot.h',
|
|
||||||
'module_snapshot.h',
|
'module_snapshot.h',
|
||||||
'process_snapshot.h',
|
'process_snapshot.h',
|
||||||
'system_snapshot.h',
|
'system_snapshot.h',
|
||||||
'thread_snapshot.h',
|
'thread_snapshot.h',
|
||||||
|
'win/cpu_context_win.cc',
|
||||||
|
'win/cpu_context_win.h',
|
||||||
|
'win/exception_snapshot_win.cc',
|
||||||
|
'win/exception_snapshot_win.h',
|
||||||
'win/memory_snapshot_win.cc',
|
'win/memory_snapshot_win.cc',
|
||||||
'win/memory_snapshot_win.h',
|
'win/memory_snapshot_win.h',
|
||||||
'win/module_snapshot_win.cc',
|
'win/module_snapshot_win.cc',
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
'snapshot.gyp:crashpad_snapshot',
|
'snapshot.gyp:crashpad_snapshot',
|
||||||
'../client/client.gyp:crashpad_client',
|
'../client/client.gyp:crashpad_client',
|
||||||
'../compat/compat.gyp:crashpad_compat',
|
'../compat/compat.gyp:crashpad_compat',
|
||||||
|
'../handler/handler.gyp:crashpad_handler',
|
||||||
'../test/test.gyp:crashpad_test',
|
'../test/test.gyp:crashpad_test',
|
||||||
'../third_party/gtest/gtest.gyp:gtest',
|
'../third_party/gtest/gtest.gyp:gtest',
|
||||||
'../third_party/gtest/gtest.gyp:gtest_main',
|
'../third_party/gtest/gtest.gyp:gtest_main',
|
||||||
@ -74,6 +75,8 @@
|
|||||||
'mac/process_types_test.cc',
|
'mac/process_types_test.cc',
|
||||||
'mac/system_snapshot_mac_test.cc',
|
'mac/system_snapshot_mac_test.cc',
|
||||||
'minidump/process_snapshot_minidump_test.cc',
|
'minidump/process_snapshot_minidump_test.cc',
|
||||||
|
'win/cpu_context_win_test.cc',
|
||||||
|
'win/exception_snapshot_win_test.cc',
|
||||||
'win/pe_image_annotations_reader_test.cc',
|
'win/pe_image_annotations_reader_test.cc',
|
||||||
'win/process_reader_win_test.cc',
|
'win/process_reader_win_test.cc',
|
||||||
'win/system_snapshot_win_test.cc',
|
'win/system_snapshot_win_test.cc',
|
||||||
|
77
snapshot/win/cpu_context_win.cc
Normal file
77
snapshot/win/cpu_context_win.cc
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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/cpu_context_win.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
#include "snapshot/cpu_context.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
#if defined(ARCH_CPU_64_BITS)
|
||||||
|
|
||||||
|
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
|
||||||
|
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
|
||||||
|
out->rax = context.Rax;
|
||||||
|
out->rbx = context.Rbx;
|
||||||
|
out->rcx = context.Rcx;
|
||||||
|
out->rdx = context.Rdx;
|
||||||
|
out->rdi = context.Rdi;
|
||||||
|
out->rsi = context.Rsi;
|
||||||
|
out->rbp = context.Rbp;
|
||||||
|
out->rsp = context.Rsp;
|
||||||
|
out->r8 = context.R8;
|
||||||
|
out->r9 = context.R9;
|
||||||
|
out->r10 = context.R10;
|
||||||
|
out->r11 = context.R11;
|
||||||
|
out->r12 = context.R12;
|
||||||
|
out->r13 = context.R13;
|
||||||
|
out->r14 = context.R14;
|
||||||
|
out->r15 = context.R15;
|
||||||
|
out->rip = context.Rip;
|
||||||
|
out->rflags = context.EFlags;
|
||||||
|
out->cs = context.SegCs;
|
||||||
|
out->fs = context.SegFs;
|
||||||
|
out->gs = context.SegGs;
|
||||||
|
|
||||||
|
out->dr0 = context.Dr0;
|
||||||
|
out->dr1 = context.Dr1;
|
||||||
|
out->dr2 = context.Dr2;
|
||||||
|
out->dr3 = context.Dr3;
|
||||||
|
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
|
||||||
|
// http://en.wikipedia.org/wiki/X86_debug_register.
|
||||||
|
out->dr4 = context.Dr6;
|
||||||
|
out->dr5 = context.Dr7;
|
||||||
|
out->dr6 = context.Dr6;
|
||||||
|
out->dr7 = context.Dr7;
|
||||||
|
|
||||||
|
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
|
||||||
|
"types must be equivalent");
|
||||||
|
memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave));
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // ARCH_CPU_64_BITS
|
||||||
|
|
||||||
|
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) {
|
||||||
|
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ARCH_CPU_64_BITS
|
||||||
|
|
||||||
|
} // namespace crashpad
|
47
snapshot/win/cpu_context_win.h
Normal file
47
snapshot/win/cpu_context_win.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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_SNAPSHOT_WIN_CPU_CONTEXT_WIN_H_
|
||||||
|
#define CRASHPAD_SNAPSHOT_WIN_CPU_CONTEXT_WIN_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "build/build_config.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
struct CPUContextX86;
|
||||||
|
struct CPUContextX86_64;
|
||||||
|
|
||||||
|
#if defined(ARCH_CPU_64_BITS) || DOXYGEN
|
||||||
|
|
||||||
|
//! \brief Initializes a CPUContextX86 structure from a native context structure
|
||||||
|
//! on Windows.
|
||||||
|
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out);
|
||||||
|
|
||||||
|
//! \brief Initializes a CPUContextX86_64 structure from a native context
|
||||||
|
//! structure on Windows.
|
||||||
|
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out);
|
||||||
|
|
||||||
|
#else // ARCH_CPU_64_BITS
|
||||||
|
|
||||||
|
//! \brief Initializes a CPUContextX86 structure from a native context structure
|
||||||
|
//! on Windows.
|
||||||
|
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out);
|
||||||
|
|
||||||
|
#endif // ARCH_CPU_64_BITS
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_SNAPSHOT_WIN_CPU_CONTEXT_WIN_H_
|
54
snapshot/win/cpu_context_win_test.cc
Normal file
54
snapshot/win/cpu_context_win_test.cc
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2014 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/cpu_context_win.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "build/build_config.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "snapshot/cpu_context.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#if defined(ARCH_CPU_X86_64)
|
||||||
|
|
||||||
|
TEST(CPUContextWin, InitializeX64Context) {
|
||||||
|
CONTEXT context;
|
||||||
|
context.Rax = 10;
|
||||||
|
context.FltSave.TagWord = 11;
|
||||||
|
context.Dr0 = 12;
|
||||||
|
|
||||||
|
// Test the simple case, where everything in the CPUContextX86_64 argument is
|
||||||
|
// set directly from the supplied thread, float, and debug state parameters.
|
||||||
|
{
|
||||||
|
CPUContextX86_64 cpu_context_x86_64 = {};
|
||||||
|
InitializeX64Context(context, &cpu_context_x86_64);
|
||||||
|
EXPECT_EQ(10u, cpu_context_x86_64.rax);
|
||||||
|
EXPECT_EQ(11u, cpu_context_x86_64.fxsave.ftw);
|
||||||
|
EXPECT_EQ(12u, cpu_context_x86_64.dr0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // ARCH_CPU_X86_64
|
||||||
|
|
||||||
|
#error ARCH_CPU_X86
|
||||||
|
|
||||||
|
#endif // ARCH_CPU_X86_64
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
144
snapshot/win/exception_snapshot_win.cc
Normal file
144
snapshot/win/exception_snapshot_win.cc
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// 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 "snapshot/win/cpu_context_win.h"
|
||||||
|
#include "snapshot/win/process_reader_win.h"
|
||||||
|
#include "util/win/nt_internals.h"
|
||||||
|
#include "util/win/process_structs.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
ExceptionSnapshotWin::ExceptionSnapshotWin()
|
||||||
|
: ExceptionSnapshot(),
|
||||||
|
context_union_(),
|
||||||
|
context_(),
|
||||||
|
codes_(),
|
||||||
|
thread_id_(0),
|
||||||
|
exception_address_(0),
|
||||||
|
exception_flags_(0),
|
||||||
|
exception_code_(0),
|
||||||
|
initialized_() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ExceptionSnapshotWin::~ExceptionSnapshotWin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
|
||||||
|
DWORD thread_id,
|
||||||
|
WinVMAddress exception_pointers_address) {
|
||||||
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||||
|
|
||||||
|
bool found_thread = false;
|
||||||
|
for (const auto& loop_thread : process_reader->Threads()) {
|
||||||
|
if (thread_id == loop_thread.id) {
|
||||||
|
found_thread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_thread) {
|
||||||
|
LOG(ERROR) << "thread ID " << thread_id << "not found in process";
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
thread_id_ = thread_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXCEPTION_POINTERS exception_pointers;
|
||||||
|
if (!process_reader->ReadMemory(exception_pointers_address,
|
||||||
|
sizeof(EXCEPTION_POINTERS),
|
||||||
|
&exception_pointers)) {
|
||||||
|
LOG(ERROR) << "EXCEPTION_POINTERS read failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!exception_pointers.ExceptionRecord) {
|
||||||
|
LOG(ERROR) << "null ExceptionRecord";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process_reader->Is64Bit()) {
|
||||||
|
EXCEPTION_RECORD64 first_record;
|
||||||
|
if (!process_reader->ReadMemory(
|
||||||
|
reinterpret_cast<WinVMAddress>(exception_pointers.ExceptionRecord),
|
||||||
|
sizeof(first_record),
|
||||||
|
&first_record)) {
|
||||||
|
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://code.google.com/p/crashpad/issues/detail?id=43
|
||||||
|
LOG(WARNING) << "dropping chained ExceptionRecord";
|
||||||
|
}
|
||||||
|
|
||||||
|
context_.architecture = kCPUArchitectureX86_64;
|
||||||
|
context_.x86_64 = &context_union_.x86_64;
|
||||||
|
// We assume 64-on-64 here in that we're relying on the CONTEXT definition
|
||||||
|
// to be the x64 one.
|
||||||
|
CONTEXT context_record;
|
||||||
|
if (!process_reader->ReadMemory(
|
||||||
|
reinterpret_cast<WinVMAddress>(exception_pointers.ContextRecord),
|
||||||
|
sizeof(context_record),
|
||||||
|
&context_record)) {
|
||||||
|
LOG(ERROR) << "ContextRecord";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InitializeX64Context(context_record, context_.x86_64);
|
||||||
|
} else {
|
||||||
|
CHECK(false) << "TODO(scottmg) x86";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CPUContext* ExceptionSnapshotWin::Context() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return &context_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ExceptionSnapshotWin::ThreadID() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return thread_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ExceptionSnapshotWin::Exception() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return exception_code_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ExceptionSnapshotWin::ExceptionInfo() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return exception_flags_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ExceptionSnapshotWin::ExceptionAddress() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return exception_address_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<uint64_t>& ExceptionSnapshotWin::Codes() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return codes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace crashpad
|
84
snapshot/win/exception_snapshot_win.h
Normal file
84
snapshot/win/exception_snapshot_win.h
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 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_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_
|
||||||
|
#define CRASHPAD_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "base/basictypes.h"
|
||||||
|
#include "build/build_config.h"
|
||||||
|
#include "snapshot/cpu_context.h"
|
||||||
|
#include "snapshot/exception_snapshot.h"
|
||||||
|
#include "util/misc/initialization_state_dcheck.h"
|
||||||
|
#include "util/win/address_types.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
class ProcessReaderWin;
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
class ExceptionSnapshotWin final : public ExceptionSnapshot {
|
||||||
|
public:
|
||||||
|
ExceptionSnapshotWin();
|
||||||
|
~ExceptionSnapshotWin() override;
|
||||||
|
|
||||||
|
//! \brief Initializes the object.
|
||||||
|
//!
|
||||||
|
//! \param[in] process_reader A ProcessReader for the process that sustained
|
||||||
|
//! the exception.
|
||||||
|
//! \param[in] thread_id The thread ID in which the exception occurred.
|
||||||
|
//! \param[in] exception_pointers_address The address of an
|
||||||
|
//! `EXCEPTION_POINTERS` record in the target process, passed through from
|
||||||
|
//! the exception handler.
|
||||||
|
//!
|
||||||
|
//! \return `true` if the snapshot could be created, `false` otherwise with
|
||||||
|
//! an appropriate message logged.
|
||||||
|
bool Initialize(ProcessReaderWin* process_reader,
|
||||||
|
DWORD thread_id,
|
||||||
|
WinVMAddress exception_pointers);
|
||||||
|
|
||||||
|
// ExceptionSnapshot:
|
||||||
|
|
||||||
|
const CPUContext* Context() const override;
|
||||||
|
uint64_t ThreadID() const override;
|
||||||
|
uint32_t Exception() const override;
|
||||||
|
uint32_t ExceptionInfo() const override;
|
||||||
|
uint64_t ExceptionAddress() const override;
|
||||||
|
const std::vector<uint64_t>& Codes() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(ARCH_CPU_X86_FAMILY)
|
||||||
|
union {
|
||||||
|
CPUContextX86 x86;
|
||||||
|
CPUContextX86_64 x86_64;
|
||||||
|
} context_union_;
|
||||||
|
#endif
|
||||||
|
CPUContext context_;
|
||||||
|
std::vector<uint64_t> codes_;
|
||||||
|
uint64_t thread_id_;
|
||||||
|
uint64_t exception_address_;
|
||||||
|
uint32_t exception_flags_;
|
||||||
|
DWORD exception_code_;
|
||||||
|
InitializationStateDcheck initialized_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotWin);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_
|
243
snapshot/win/exception_snapshot_win_test.cc
Normal file
243
snapshot/win/exception_snapshot_win_test.cc
Normal file
@ -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
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "base/memory/scoped_ptr.h"
|
#include "base/memory/scoped_ptr.h"
|
||||||
#include "base/numerics/safe_conversions.h"
|
#include "base/numerics/safe_conversions.h"
|
||||||
|
#include "util/win/nt_internals.h"
|
||||||
#include "util/win/process_structs.h"
|
#include "util/win/process_structs.h"
|
||||||
#include "util/win/scoped_handle.h"
|
#include "util/win/scoped_handle.h"
|
||||||
#include "util/win/time.h"
|
#include "util/win/time.h"
|
||||||
@ -26,49 +27,6 @@ namespace crashpad {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
NTSTATUS NtQuerySystemInformation(
|
|
||||||
SYSTEM_INFORMATION_CLASS system_information_class,
|
|
||||||
PVOID system_information,
|
|
||||||
ULONG system_information_length,
|
|
||||||
PULONG return_length) {
|
|
||||||
static decltype(::NtQuerySystemInformation)* nt_query_system_information =
|
|
||||||
reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetProcAddress(
|
|
||||||
LoadLibrary(L"ntdll.dll"), "NtQuerySystemInformation"));
|
|
||||||
DCHECK(nt_query_system_information);
|
|
||||||
return nt_query_system_information(system_information_class,
|
|
||||||
system_information,
|
|
||||||
system_information_length,
|
|
||||||
return_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The 4th argument is CLIENT_ID*, but as we can't typedef that, we simply cast
|
|
||||||
// to void* here.
|
|
||||||
typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(
|
|
||||||
PHANDLE ThreadHandle,
|
|
||||||
ACCESS_MASK DesiredAccess,
|
|
||||||
POBJECT_ATTRIBUTES ObjectAttributes,
|
|
||||||
const void* ClientId);
|
|
||||||
|
|
||||||
template <class Traits>
|
|
||||||
NTSTATUS NtOpenThread(PHANDLE thread_handle,
|
|
||||||
ACCESS_MASK desired_access,
|
|
||||||
POBJECT_ATTRIBUTES object_attributes,
|
|
||||||
const process_types::CLIENT_ID<Traits>* client_id) {
|
|
||||||
static NtOpenThreadFunction nt_open_thread =
|
|
||||||
reinterpret_cast<NtOpenThreadFunction>(
|
|
||||||
GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread"));
|
|
||||||
DCHECK(nt_open_thread);
|
|
||||||
return nt_open_thread(thread_handle,
|
|
||||||
desired_access,
|
|
||||||
object_attributes,
|
|
||||||
static_cast<const void*>(client_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of
|
|
||||||
// ntstatus.h.
|
|
||||||
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
|
|
||||||
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
|
|
||||||
|
|
||||||
// Gets a pointer to the process information structure after a given one, or
|
// Gets a pointer to the process information structure after a given one, or
|
||||||
// null when iteration is complete, assuming they've been retrieved in a block
|
// null when iteration is complete, assuming they've been retrieved in a block
|
||||||
// via NtQuerySystemInformation().
|
// via NtQuerySystemInformation().
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "snapshot/win/process_snapshot_win.h"
|
#include "snapshot/win/process_snapshot_win.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
#include "snapshot/win/module_snapshot_win.h"
|
#include "snapshot/win/module_snapshot_win.h"
|
||||||
#include "util/win/time.h"
|
#include "util/win/time.h"
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ ProcessSnapshotWin::ProcessSnapshotWin()
|
|||||||
system_(),
|
system_(),
|
||||||
threads_(),
|
threads_(),
|
||||||
modules_(),
|
modules_(),
|
||||||
// TODO(scottmg): exception_(),
|
exception_(),
|
||||||
process_reader_(),
|
process_reader_(),
|
||||||
report_id_(),
|
report_id_(),
|
||||||
client_id_(),
|
client_id_(),
|
||||||
@ -53,6 +54,22 @@ bool ProcessSnapshotWin::Initialize(HANDLE process) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ProcessSnapshotWin::InitializeException(
|
||||||
|
DWORD thread_id,
|
||||||
|
WinVMAddress exception_pointers) {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
DCHECK(!exception_);
|
||||||
|
|
||||||
|
exception_.reset(new internal::ExceptionSnapshotWin());
|
||||||
|
if (!exception_->Initialize(
|
||||||
|
&process_reader_, thread_id, exception_pointers)) {
|
||||||
|
exception_.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessSnapshotWin::GetCrashpadOptions(
|
void ProcessSnapshotWin::GetCrashpadOptions(
|
||||||
CrashpadInfoClientOptions* options) {
|
CrashpadInfoClientOptions* options) {
|
||||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
@ -149,8 +166,7 @@ std::vector<const ModuleSnapshot*> ProcessSnapshotWin::Modules() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ExceptionSnapshot* ProcessSnapshotWin::Exception() const {
|
const ExceptionSnapshot* ProcessSnapshotWin::Exception() const {
|
||||||
CHECK(false) << "TODO(scottmg): Exception()";
|
return exception_.get();
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessSnapshotWin::InitializeThreads() {
|
void ProcessSnapshotWin::InitializeThreads() {
|
||||||
|
@ -31,11 +31,13 @@
|
|||||||
#include "snapshot/process_snapshot.h"
|
#include "snapshot/process_snapshot.h"
|
||||||
#include "snapshot/system_snapshot.h"
|
#include "snapshot/system_snapshot.h"
|
||||||
#include "snapshot/thread_snapshot.h"
|
#include "snapshot/thread_snapshot.h"
|
||||||
|
#include "snapshot/win/exception_snapshot_win.h"
|
||||||
#include "snapshot/win/module_snapshot_win.h"
|
#include "snapshot/win/module_snapshot_win.h"
|
||||||
#include "snapshot/win/system_snapshot_win.h"
|
#include "snapshot/win/system_snapshot_win.h"
|
||||||
#include "snapshot/win/thread_snapshot_win.h"
|
#include "snapshot/win/thread_snapshot_win.h"
|
||||||
#include "util/misc/initialization_state_dcheck.h"
|
#include "util/misc/initialization_state_dcheck.h"
|
||||||
#include "util/misc/uuid.h"
|
#include "util/misc/uuid.h"
|
||||||
|
#include "util/win/address_types.h"
|
||||||
#include "util/stdlib/pointer_container.h"
|
#include "util/stdlib/pointer_container.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
@ -55,6 +57,20 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
|
|||||||
//! an appropriate message logged.
|
//! an appropriate message logged.
|
||||||
bool Initialize(HANDLE process);
|
bool Initialize(HANDLE process);
|
||||||
|
|
||||||
|
//! \brief Initializes the object's exception.
|
||||||
|
//!
|
||||||
|
//! This populates the data to be returned by Exception(). The parameters may
|
||||||
|
//! be passed directly through from a Windows exception handler.
|
||||||
|
//!
|
||||||
|
//! This method must not be called until after a successful call to
|
||||||
|
//! Initialize().
|
||||||
|
//!
|
||||||
|
//! \return `true` if the exception information could be initialized, `false`
|
||||||
|
//! otherwise with an appropriate message logged. When this method returns
|
||||||
|
//! `false`, the ProcessSnapshotWin object's validity remains unchanged.
|
||||||
|
bool InitializeException(DWORD thread_id,
|
||||||
|
WinVMAddress exception_pointers);
|
||||||
|
|
||||||
//! \brief Sets the value to be returned by ReportID().
|
//! \brief Sets the value to be returned by ReportID().
|
||||||
//!
|
//!
|
||||||
//! The crash report ID is under the control of the snapshot producer, which
|
//! The crash report ID is under the control of the snapshot producer, which
|
||||||
@ -113,7 +129,7 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
|
|||||||
internal::SystemSnapshotWin system_;
|
internal::SystemSnapshotWin system_;
|
||||||
PointerVector<internal::ThreadSnapshotWin> threads_;
|
PointerVector<internal::ThreadSnapshotWin> threads_;
|
||||||
PointerVector<internal::ModuleSnapshotWin> modules_;
|
PointerVector<internal::ModuleSnapshotWin> modules_;
|
||||||
// TODO(scottmg): scoped_ptr<internal::ExceptionSnapshotWin> exception_;
|
scoped_ptr<internal::ExceptionSnapshotWin> exception_;
|
||||||
ProcessReaderWin process_reader_;
|
ProcessReaderWin process_reader_;
|
||||||
UUID report_id_;
|
UUID report_id_;
|
||||||
UUID client_id_;
|
UUID client_id_;
|
||||||
|
@ -15,60 +15,12 @@
|
|||||||
#include "snapshot/win/thread_snapshot_win.h"
|
#include "snapshot/win/thread_snapshot_win.h"
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
#include "snapshot/win/cpu_context_win.h"
|
||||||
#include "snapshot/win/process_reader_win.h"
|
#include "snapshot/win/process_reader_win.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
void InitializeX64Context(const CONTEXT& context,
|
|
||||||
CPUContextX86_64* out) {
|
|
||||||
out->rax = context.Rax;
|
|
||||||
out->rbx = context.Rbx;
|
|
||||||
out->rcx = context.Rcx;
|
|
||||||
out->rdx = context.Rdx;
|
|
||||||
out->rdi = context.Rdi;
|
|
||||||
out->rsi = context.Rsi;
|
|
||||||
out->rbp = context.Rbp;
|
|
||||||
out->rsp = context.Rsp;
|
|
||||||
out->r8 = context.R8;
|
|
||||||
out->r9 = context.R9;
|
|
||||||
out->r10 = context.R10;
|
|
||||||
out->r11 = context.R11;
|
|
||||||
out->r12 = context.R12;
|
|
||||||
out->r13 = context.R13;
|
|
||||||
out->r14 = context.R14;
|
|
||||||
out->r15 = context.R15;
|
|
||||||
out->rip = context.Rip;
|
|
||||||
out->rflags = context.EFlags;
|
|
||||||
out->cs = context.SegCs;
|
|
||||||
out->fs = context.SegFs;
|
|
||||||
out->gs = context.SegGs;
|
|
||||||
|
|
||||||
out->dr0 = context.Dr0;
|
|
||||||
out->dr1 = context.Dr1;
|
|
||||||
out->dr2 = context.Dr2;
|
|
||||||
out->dr3 = context.Dr3;
|
|
||||||
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
|
|
||||||
// http://en.wikipedia.org/wiki/X86_debug_register.
|
|
||||||
out->dr4 = context.Dr6;
|
|
||||||
out->dr5 = context.Dr7;
|
|
||||||
out->dr6 = context.Dr6;
|
|
||||||
out->dr7 = context.Dr7;
|
|
||||||
|
|
||||||
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
|
|
||||||
"types must be equivalent");
|
|
||||||
memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave));
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeX86Context(const CONTEXT& context,
|
|
||||||
CPUContextX86* out) {
|
|
||||||
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
ThreadSnapshotWin::ThreadSnapshotWin()
|
ThreadSnapshotWin::ThreadSnapshotWin()
|
||||||
: ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() {
|
: ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() {
|
||||||
}
|
}
|
||||||
@ -85,7 +37,7 @@ bool ThreadSnapshotWin::Initialize(
|
|||||||
stack_.Initialize(
|
stack_.Initialize(
|
||||||
process_reader, thread_.stack_region_address, thread_.stack_region_size);
|
process_reader, thread_.stack_region_address, thread_.stack_region_size);
|
||||||
|
|
||||||
#if defined(ARCH_CPU_X86_FAMILY)
|
#if defined(ARCH_CPU_X86_64)
|
||||||
if (process_reader->Is64Bit()) {
|
if (process_reader->Is64Bit()) {
|
||||||
context_.architecture = kCPUArchitectureX86_64;
|
context_.architecture = kCPUArchitectureX86_64;
|
||||||
context_.x86_64 = &context_union_.x86_64;
|
context_.x86_64 = &context_union_.x86_64;
|
||||||
@ -93,9 +45,13 @@ bool ThreadSnapshotWin::Initialize(
|
|||||||
} else {
|
} else {
|
||||||
context_.architecture = kCPUArchitectureX86;
|
context_.architecture = kCPUArchitectureX86;
|
||||||
context_.x86 = &context_union_.x86;
|
context_.x86 = &context_union_.x86;
|
||||||
InitializeX86Context(process_reader_thread.context, context_.x86);
|
InitializeX86Context(
|
||||||
|
*reinterpret_cast<const WOW64_CONTEXT*>(&process_reader_thread.context),
|
||||||
|
context_.x86);
|
||||||
}
|
}
|
||||||
#endif
|
#else
|
||||||
|
#error ARCH_CPU_X86
|
||||||
|
#endif // ARCH_CPU_X86_64
|
||||||
|
|
||||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||||
return true;
|
return true;
|
||||||
|
@ -149,6 +149,8 @@
|
|||||||
'win/checked_win_address_range.h',
|
'win/checked_win_address_range.h',
|
||||||
'win/module_version.cc',
|
'win/module_version.cc',
|
||||||
'win/module_version.h',
|
'win/module_version.h',
|
||||||
|
'win/nt_internals.cc',
|
||||||
|
'win/nt_internals.h',
|
||||||
'win/process_info.cc',
|
'win/process_info.cc',
|
||||||
'win/process_info.h',
|
'win/process_info.h',
|
||||||
'win/process_structs.h',
|
'win/process_structs.h',
|
||||||
|
91
util/win/nt_internals.cc
Normal file
91
util/win/nt_internals.cc
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// 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/nt_internals.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
NTSTATUS NtQuerySystemInformation(
|
||||||
|
SYSTEM_INFORMATION_CLASS system_information_class,
|
||||||
|
PVOID system_information,
|
||||||
|
ULONG system_information_length,
|
||||||
|
PULONG return_length) {
|
||||||
|
static decltype(::NtQuerySystemInformation)* nt_query_system_information =
|
||||||
|
reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetProcAddress(
|
||||||
|
LoadLibrary(L"ntdll.dll"), "NtQuerySystemInformation"));
|
||||||
|
DCHECK(nt_query_system_information);
|
||||||
|
return nt_query_system_information(system_information_class,
|
||||||
|
system_information,
|
||||||
|
system_information_length,
|
||||||
|
return_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS NtQueryInformationThread(HANDLE thread_handle,
|
||||||
|
THREADINFOCLASS thread_information_class,
|
||||||
|
PVOID thread_information,
|
||||||
|
ULONG thread_information_length,
|
||||||
|
PULONG return_length) {
|
||||||
|
static decltype(::NtQueryInformationThread)* nt_query_information_thread =
|
||||||
|
reinterpret_cast<decltype(::NtQueryInformationThread)*>(GetProcAddress(
|
||||||
|
LoadLibrary(L"ntdll.dll"), "NtQueryInformationThread"));
|
||||||
|
DCHECK(nt_query_information_thread);
|
||||||
|
return nt_query_information_thread(thread_handle,
|
||||||
|
thread_information_class,
|
||||||
|
thread_information,
|
||||||
|
thread_information_length,
|
||||||
|
return_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 4th argument is CLIENT_ID*, but as we can't typedef that, we simply cast
|
||||||
|
// to void* here.
|
||||||
|
typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(
|
||||||
|
PHANDLE ThreadHandle,
|
||||||
|
ACCESS_MASK DesiredAccess,
|
||||||
|
POBJECT_ATTRIBUTES ObjectAttributes,
|
||||||
|
const void* ClientId);
|
||||||
|
|
||||||
|
template <class Traits>
|
||||||
|
NTSTATUS NtOpenThread(PHANDLE thread_handle,
|
||||||
|
ACCESS_MASK desired_access,
|
||||||
|
POBJECT_ATTRIBUTES object_attributes,
|
||||||
|
const process_types::CLIENT_ID<Traits>* client_id) {
|
||||||
|
static NtOpenThreadFunction nt_open_thread =
|
||||||
|
reinterpret_cast<NtOpenThreadFunction>(
|
||||||
|
GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread"));
|
||||||
|
DCHECK(nt_open_thread);
|
||||||
|
return nt_open_thread(thread_handle,
|
||||||
|
desired_access,
|
||||||
|
object_attributes,
|
||||||
|
static_cast<const void*>(client_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit instantiations with the only 2 valid template arguments to avoid
|
||||||
|
// putting the body of the function in the header.
|
||||||
|
template NTSTATUS NtOpenThread<process_types::internal::Traits32>(
|
||||||
|
PHANDLE thread_handle,
|
||||||
|
ACCESS_MASK desired_access,
|
||||||
|
POBJECT_ATTRIBUTES object_attributes,
|
||||||
|
const process_types::CLIENT_ID<process_types::internal::Traits32>*
|
||||||
|
client_id);
|
||||||
|
|
||||||
|
template NTSTATUS NtOpenThread<process_types::internal::Traits64>(
|
||||||
|
PHANDLE thread_handle,
|
||||||
|
ACCESS_MASK desired_access,
|
||||||
|
POBJECT_ATTRIBUTES object_attributes,
|
||||||
|
const process_types::CLIENT_ID<process_types::internal::Traits64>*
|
||||||
|
client_id);
|
||||||
|
|
||||||
|
} // namespace crashpad
|
48
util/win/nt_internals.h
Normal file
48
util/win/nt_internals.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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 <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
|
||||||
|
#include "util/win/process_structs.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of
|
||||||
|
// ntstatus.h.
|
||||||
|
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
|
||||||
|
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
|
||||||
|
|
||||||
|
// winternal.h defines THREADINFOCLASS, but not all members.
|
||||||
|
enum { ThreadBasicInformation = 0 };
|
||||||
|
|
||||||
|
NTSTATUS NtQuerySystemInformation(
|
||||||
|
SYSTEM_INFORMATION_CLASS system_information_class,
|
||||||
|
PVOID system_information,
|
||||||
|
ULONG system_information_length,
|
||||||
|
PULONG return_length);
|
||||||
|
|
||||||
|
NTSTATUS NtQueryInformationThread(HANDLE thread_handle,
|
||||||
|
THREADINFOCLASS thread_information_class,
|
||||||
|
PVOID thread_information,
|
||||||
|
ULONG thread_information_length,
|
||||||
|
PULONG return_length);
|
||||||
|
|
||||||
|
template <class Traits>
|
||||||
|
NTSTATUS NtOpenThread(PHANDLE thread_handle,
|
||||||
|
ACCESS_MASK desired_access,
|
||||||
|
POBJECT_ATTRIBUTES object_attributes,
|
||||||
|
const process_types::CLIENT_ID<Traits>* client_id);
|
||||||
|
|
||||||
|
} // namespace crashpad
|
Loading…
x
Reference in New Issue
Block a user