win: support x64 reading x86 (wow64)

Removes the bitness-specific targets in favour of pulling binaries from
the other build directory. This is to avoid the added complexity of
duplicating all the targets for the x86 in x64 build.

Overall, mostly templatizing more functions to support the
wow64-flavoured structures. The only additional functionality required
is reading the x86 TEB that's chained from the x64 TEB when running
as WOW64.

The crashing child test was switched to a manual CreateProcess because
it needs to launch a binary other than itself.

R=mark@chromium.org
BUG=crashpad:50

Review URL: https://codereview.chromium.org/1349313003 .
This commit is contained in:
Scott Graham 2015-09-18 16:06:05 -07:00
parent 00a422357d
commit bf556829d9
15 changed files with 404 additions and 271 deletions

View File

@ -24,6 +24,7 @@ sys.path.insert(
import gyp
def main(args):
if 'GYP_GENERATORS' not in os.environ:
os.environ['GYP_GENERATORS'] = 'ninja'
@ -31,16 +32,25 @@ def main(args):
crashpad_dir_or_dot = crashpad_dir if crashpad_dir is not '' else '.'
args.extend(['-D', 'crashpad_standalone=1'])
args.extend(['--include', os.path.join(crashpad_dir,
'third_party',
'mini_chromium',
'mini_chromium',
'build',
'common.gypi')])
args.extend(['--include',
os.path.join(crashpad_dir, 'third_party', 'mini_chromium',
'mini_chromium', 'build', 'common.gypi')])
args.extend(['--depth', crashpad_dir_or_dot])
args.append(os.path.join(crashpad_dir, 'crashpad.gyp'))
return gyp.main(args)
result = gyp.main(args)
if result != 0:
return result
if sys.platform == 'win32':
# Also generate the x86 build.
result = gyp.main(args + ['-D', 'target_arch=ia32', '-G', 'config=Debug'])
if result != 0:
return result
result = gyp.main(args + ['-D', 'target_arch=ia32', '-G', 'config=Release'])
return result
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -25,8 +25,9 @@ import sys
# location in the recipe.
def main(args):
if len(args) != 1:
print >>sys.stderr, 'usage: run_tests.py {Debug|Release}'
return 1;
print >> sys.stderr, \
'usage: run_tests.py {Debug|Release|Debug_x64|Release_x64}'
return 1
crashpad_dir = \
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)

View File

@ -93,6 +93,11 @@
],
},
}],
['OS=="win"', {
'dependencies': [
'crashpad_snapshot_test_crashing_child',
],
}],
],
},
{
@ -129,5 +134,22 @@
},
],
}],
['OS=="win"', {
'targets': [
{
'target_name': 'crashpad_snapshot_test_crashing_child',
'type': 'executable',
'dependencies': [
'../client/client.gyp:crashpad_client',
'../compat/compat.gyp:crashpad_compat',
'../third_party/mini_chromium/mini_chromium.gyp:base',
'../util/util.gyp:crashpad_util',
],
'sources': [
'win/crashpad_snapshot_test_crashing_child.cc',
],
},
],
}],
],
}

View File

@ -21,10 +21,70 @@
namespace crashpad {
namespace {
template <class T>
void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
LOG_IF(ERROR, !(context.ContextFlags & WOW64_CONTEXT_i386))
<< "non-x86 context";
memset(out, 0, sizeof(*out));
// We assume in this function that the WOW64_CONTEXT_* and x86 CONTEXT_*
// values for ContextFlags are identical.
if (context.ContextFlags & WOW64_CONTEXT_CONTROL) {
out->ebp = context.Ebp;
out->eip = context.Eip;
out->cs = static_cast<uint16_t>(context.SegCs);
out->eflags = context.EFlags;
out->esp = context.Esp;
out->ss = static_cast<uint16_t>(context.SegSs);
}
if (context.ContextFlags & WOW64_CONTEXT_INTEGER) {
out->eax = context.Eax;
out->ebx = context.Ebx;
out->ecx = context.Ecx;
out->edx = context.Edx;
out->edi = context.Edi;
out->esi = context.Esi;
}
if (context.ContextFlags & WOW64_CONTEXT_SEGMENTS) {
out->ds = static_cast<uint16_t>(context.SegDs);
out->es = static_cast<uint16_t>(context.SegEs);
out->fs = static_cast<uint16_t>(context.SegFs);
out->gs = static_cast<uint16_t>(context.SegGs);
}
if (context.ContextFlags & WOW64_CONTEXT_DEBUG_REGISTERS) {
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
// https://en.wikipedia.org/wiki/X86_debug_register.
out->dr4 = context.Dr6;
out->dr5 = context.Dr7;
out->dr6 = context.Dr6;
out->dr7 = context.Dr7;
}
if (context.ContextFlags & WOW64_CONTEXT_EXTENDED_REGISTERS) {
static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters),
"types must be equivalent");
memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave));
} else if (context.ContextFlags & WOW64_CONTEXT_FLOATING_POINT) {
CHECK(false) << "TODO(scottmg): extract x87 data";
}
}
} // namespace
#if defined(ARCH_CPU_64_BITS)
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
CommonInitializeX86Context(context, out);
}
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
@ -88,55 +148,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
#else // ARCH_CPU_64_BITS
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) {
memset(out, 0, sizeof(*out));
LOG_IF(ERROR, !(context.ContextFlags & CONTEXT_i386)) << "non-x86 context";
if (context.ContextFlags & CONTEXT_CONTROL) {
out->ebp = context.Ebp;
out->eip = context.Eip;
out->cs = static_cast<uint16_t>(context.SegCs);
out->eflags = context.EFlags;
out->esp = context.Esp;
out->ss = static_cast<uint16_t>(context.SegSs);
}
if (context.ContextFlags & CONTEXT_INTEGER) {
out->eax = context.Eax;
out->ebx = context.Ebx;
out->ecx = context.Ecx;
out->edx = context.Edx;
out->edi = context.Edi;
out->esi = context.Esi;
}
if (context.ContextFlags & CONTEXT_SEGMENTS) {
out->ds = static_cast<uint16_t>(context.SegDs);
out->es = static_cast<uint16_t>(context.SegEs);
out->fs = static_cast<uint16_t>(context.SegFs);
out->gs = static_cast<uint16_t>(context.SegGs);
}
if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS) {
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
// https://en.wikipedia.org/wiki/X86_debug_register.
out->dr4 = context.Dr6;
out->dr5 = context.Dr7;
out->dr6 = context.Dr6;
out->dr7 = context.Dr7;
}
if (context.ContextFlags & CONTEXT_EXTENDED_REGISTERS) {
static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters),
"types must be equivalent");
memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave));
} else if (context.ContextFlags & CONTEXT_FLOATING_POINT) {
CHECK(false) << "TODO(scottmg): extract x87 data";
}
CommonInitializeX86Context(context, out);
}
#endif // ARCH_CPU_64_BITS

View File

@ -0,0 +1,43 @@
// 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 <stdint.h>
#include <stdlib.h>
#include <windows.h>
#include "base/logging.h"
#include "client/crashpad_client.h"
#include "util/file/file_io.h"
#include "util/win/address_types.h"
__declspec(noinline) crashpad::WinVMAddress CurrentAddress() {
return reinterpret_cast<crashpad::WinVMAddress>(_ReturnAddress());
}
int main(int argc, char* argv[]) {
CHECK_EQ(argc, 2);
crashpad::CrashpadClient client;
CHECK(client.SetHandler(argv[1]));
CHECK(client.UseHandler());
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
CHECK_NE(out, INVALID_HANDLE_VALUE);
crashpad::WinVMAddress break_address = CurrentAddress();
crashpad::CheckedWriteFile(out, &break_address, sizeof(break_address));
__debugbreak();
return 0;
}

View File

@ -17,7 +17,6 @@
#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 {
@ -51,48 +50,41 @@ bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
}
if (!found_thread) {
LOG(ERROR) << "thread ID " << thread_id << "not found in process";
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 defined(ARCH_CPU_64_BITS)
if (process_reader->Is64Bit()) {
#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_reader, exception_pointers, &context_record)) {
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD64,
process_types::EXCEPTION_POINTERS64>(
*process_reader, exception_pointers_address, &context_record)) {
return false;
}
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
InitializeX64Context(context_record, context_.x86_64);
} else {
CHECK(false) << "TODO(scottmg) WOW64";
return false;
}
#else
CONTEXT context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32>(
*process_reader, exception_pointers, &context_record)) {
#endif
if (!is_64_bit) {
Context32 context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32,
process_types::EXCEPTION_POINTERS32>(
*process_reader, exception_pointers_address, &context_record)) {
return false;
}
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(context_record, context_.x86);
#endif // ARCH_CPU_64_BITS
}
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
@ -128,14 +120,28 @@ const std::vector<uint64_t>& ExceptionSnapshotWin::Codes() const {
return codes_;
}
template <class ExceptionRecordType, class ContextType>
template <class ExceptionRecordType,
class ExceptionPointersType,
class ContextType>
bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
const ProcessReaderWin& process_reader,
const EXCEPTION_POINTERS& exception_pointers,
WinVMAddress exception_pointers_address,
ContextType* context_record) {
ExceptionPointersType 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;
}
ExceptionRecordType first_record;
if (!process_reader.ReadMemory(
reinterpret_cast<WinVMAddress>(exception_pointers.ExceptionRecord),
static_cast<WinVMAddress>(exception_pointers.ExceptionRecord),
sizeof(first_record),
&first_record)) {
LOG(ERROR) << "ExceptionRecord";
@ -152,7 +158,7 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
}
if (!process_reader.ReadMemory(
reinterpret_cast<WinVMAddress>(exception_pointers.ContextRecord),
static_cast<WinVMAddress>(exception_pointers.ContextRecord),
sizeof(*context_record),
context_record)) {
LOG(ERROR) << "ContextRecord";

View File

@ -24,6 +24,7 @@
#include "snapshot/exception_snapshot.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/win/address_types.h"
#include "util/win/process_structs.h"
namespace crashpad {
@ -61,10 +62,11 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot {
const std::vector<uint64_t>& Codes() const override;
private:
template <class ExceptionRecordType, class ContextType>
bool InitializeFromExceptionPointers(
const ProcessReaderWin& process_reader,
const EXCEPTION_POINTERS& exception_pointers,
template <class ExceptionRecordType,
class ExceptionPointersType,
class ContextType>
bool InitializeFromExceptionPointers(const ProcessReaderWin& process_reader,
WinVMAddress exception_pointers_address,
ContextType* context_record);
#if defined(ARCH_CPU_X86_FAMILY)

View File

@ -16,11 +16,14 @@
#include <string>
#include "base/files/file_path.h"
#include "base/strings/stringprintf.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "client/crashpad_client.h"
#include "gtest/gtest.h"
#include "snapshot/win/process_snapshot_win.h"
#include "test/paths.h"
#include "test/win/win_child_process.h"
#include "util/thread/thread.h"
#include "util/win/exception_handler_server.h"
@ -46,17 +49,15 @@ HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
return nullptr;
}
class ExceptionSnapshotWinTest : public testing::Test {
public:
class Delegate : public ExceptionHandlerServer::Delegate {
class Delegate : public ExceptionHandlerServer::Delegate {
public:
Delegate(HANDLE server_ready, HANDLE completed_test_event)
: server_ready_(server_ready),
completed_test_event_(completed_test_event),
break_near_(nullptr) {}
break_near_(0) {}
~Delegate() override {}
void set_break_near(void* break_near) { break_near_ = break_near; }
void set_break_near(WinVMAddress break_near) { break_near_ = break_near; }
void ExceptionHandlerServerStarted() override { SetEvent(server_ready_); }
@ -76,10 +77,9 @@ class ExceptionSnapshotWinTest : public testing::Test {
// 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_GT(snapshot.Exception()->ExceptionAddress(), break_near_);
EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
reinterpret_cast<uint64_t>(break_near_) + kAllowedOffset);
break_near_ + kAllowedOffset);
SetEvent(completed_test_event_);
@ -89,13 +89,9 @@ class ExceptionSnapshotWinTest : public testing::Test {
private:
HANDLE server_ready_; // weak
HANDLE completed_test_event_; // weak
void* break_near_;
WinVMAddress break_near_;
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
private:
ScopedKernelHANDLE exception_happened_;
};
// Runs the ExceptionHandlerServer on a background thread.
@ -136,51 +132,7 @@ class ScopedStopServerAndJoinThread {
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) {
// Spawn a child process that will immediately crash (once we let it
// run below by telling it what to connect to).
WinChildProcess::EntryPoint<CrashingChildProcess>();
scoped_ptr<WinChildProcess::Handles> handle = WinChildProcess::Launch();
void TestCrashingChild(const base::string16& directory_modification) {
// Set up the registration server on a background thread.
std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
base::StringPrintf("%08x", GetCurrentProcessId());
@ -196,19 +148,74 @@ TEST_F(ExceptionSnapshotWinTest, ChildCrash) {
&exception_handler_server, &server_thread);
WaitForSingleObject(server_ready.get(), INFINITE);
// Allow the child to continue and tell it where to connect to.
WriteString(handle->write.get(), pipe_name);
// Spawn a child process, passing it the pipe name to connect to.
base::FilePath test_executable = Paths::Executable();
std::wstring child_test_executable =
test_executable.DirName()
.Append(directory_modification)
.Append(test_executable.BaseName().RemoveFinalExtension().value() +
L"_crashing_child.exe")
.value();
// Create a pipe for the stdout of the child.
SECURITY_ATTRIBUTES security_attributes = {0};
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = true;
HANDLE stdout_read;
HANDLE stdout_write;
ASSERT_TRUE(CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0));
ScopedFileHANDLE read_handle(stdout_read);
ScopedFileHANDLE write_handle(stdout_write);
ASSERT_TRUE(SetHandleInformation(read_handle.get(), HANDLE_FLAG_INHERIT, 0));
std::wstring command_line =
child_test_executable + L" " + base::UTF8ToUTF16(pipe_name);
STARTUPINFO startup_info = {0};
startup_info.cb = sizeof(startup_info);
startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startup_info.hStdOutput = write_handle.get();
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startup_info.dwFlags = STARTF_USESTDHANDLES;
PROCESS_INFORMATION process_information;
ASSERT_TRUE(CreateProcess(child_test_executable.c_str(),
&command_line[0],
nullptr,
nullptr,
true,
0,
nullptr,
nullptr,
&startup_info,
&process_information));
// Take ownership of the two process handles returned.
ScopedKernelHANDLE process_main_thread_handle(process_information.hThread);
ScopedKernelHANDLE process_handle(process_information.hProcess);
// The child tells us (approximately) where it will crash.
void* break_near_address;
WinVMAddress break_near_address;
LoggingReadFile(
handle->read.get(), &break_near_address, sizeof(break_near_address));
read_handle.get(), &break_near_address, sizeof(break_near_address));
delegate.set_break_near(break_near_address);
// Wait for the child to crash and the exception information to be validated.
WaitForSingleObject(completed.get(), INFINITE);
}
TEST(ExceptionSnapshotWinTest, ChildCrash) {
TestCrashingChild(FILE_PATH_LITERAL("."));
}
#if defined(ARCH_CPU_64_BITS)
TEST(ExceptionSnapshotWinTest, ChildCrashWOW64) {
#ifndef NDEBUG
TestCrashingChild(FILE_PATH_LITERAL("..\\..\\out\\Debug"));
#else
TestCrashingChild(FILE_PATH_LITERAL("..\\..\\out\\Release"));
#endif
}
#endif // ARCH_CPU_64_BITS
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -116,23 +116,23 @@ HANDLE OpenThread(
template <class Traits>
bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
ProcessReaderWin::Thread* thread,
ProcessSuspensionState suspension_state) {
ProcessSuspensionState suspension_state,
bool is_64_reading_32) {
// Don't suspend the thread if it's this thread. This is really only for test
// binaries, as we won't be walking ourselves, in general.
bool is_current_thread = thread->id ==
reinterpret_cast<process_types::TEB<Traits>*>(
NtCurrentTeb())->ClientId.UniqueThread;
// TODO(scottmg): Handle cross-bitness in this function.
if (is_current_thread) {
DCHECK(suspension_state == ProcessSuspensionState::kRunning);
thread->suspend_count = 0;
RtlCaptureContext(&thread->context);
DCHECK(!is_64_reading_32);
RtlCaptureContext(&thread->context.native);
} else {
DWORD previous_suspend_count = SuspendThread(thread_handle);
if (previous_suspend_count == -1) {
PLOG(ERROR) << "SuspendThread failed";
PLOG(ERROR) << "SuspendThread";
return false;
}
DCHECK(previous_suspend_count > 0 ||
@ -142,14 +142,28 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
(suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0);
memset(&thread->context, 0, sizeof(thread->context));
thread->context.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, &thread->context)) {
PLOG(ERROR) << "GetThreadContext failed";
#if defined(ARCH_CPU_32_BITS)
const bool is_native = true;
#elif defined(ARCH_CPU_64_BITS)
const bool is_native = !is_64_reading_32;
if (is_64_reading_32) {
thread->context.wow64.ContextFlags = CONTEXT_ALL;
if (!Wow64GetThreadContext(thread_handle, &thread->context.wow64)) {
PLOG(ERROR) << "Wow64GetThreadContext";
return false;
}
}
#endif
if (is_native) {
thread->context.native.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, &thread->context.native)) {
PLOG(ERROR) << "GetThreadContext";
return false;
}
}
if (!ResumeThread(thread_handle)) {
PLOG(ERROR) << "ResumeThread failed";
PLOG(ERROR) << "ResumeThread";
return false;
}
}
@ -242,10 +256,11 @@ const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() {
initialized_threads_ = true;
if (process_info_.Is64Bit())
ReadThreadData<process_types::internal::Traits64>();
else
ReadThreadData<process_types::internal::Traits32>();
#if defined(ARCH_CPU_64_BITS)
ReadThreadData<process_types::internal::Traits64>(process_info_.IsWow64());
#else
ReadThreadData<process_types::internal::Traits32>(false);
#endif
return threads_;
}
@ -261,7 +276,7 @@ const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() {
}
template <class Traits>
void ProcessReaderWin::ReadThreadData() {
void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) {
DCHECK(threads_.empty());
scoped_ptr<uint8_t[]> buffer;
@ -280,8 +295,10 @@ void ProcessReaderWin::ReadThreadData() {
if (!thread_handle.is_valid())
continue;
if (!FillThreadContextAndSuspendCount<Traits>(
thread_handle.get(), &thread, suspension_state_)) {
if (!FillThreadContextAndSuspendCount<Traits>(thread_handle.get(),
&thread,
suspension_state_,
is_64_reading_32)) {
continue;
}
@ -309,15 +326,32 @@ void ProcessReaderWin::ReadThreadData() {
// Read the TIB (Thread Information Block) which is the first element of the
// TEB, for its stack fields.
process_types::NT_TIB<Traits> tib;
if (ReadMemory(thread_basic_info.TebBaseAddress, sizeof(tib), &tib)) {
thread.teb = thread_basic_info.TebBaseAddress;
if (ReadMemory(thread.teb, sizeof(tib), &tib)) {
WinVMAddress base = 0;
WinVMAddress limit = 0;
// If we're reading a WOW64 process, then the TIB we just retrieved is the
// x64 one. The first word of the x64 TIB points at the x86 TIB. See
// https://msdn.microsoft.com/en-us/library/dn424783.aspx
if (is_64_reading_32) {
process_types::NT_TIB<process_types::internal::Traits32> tib32;
thread.teb = tib.Wow64Teb;
if (ReadMemory(thread.teb, sizeof(tib32), &tib32)) {
base = tib32.StackBase;
limit = tib32.StackLimit;
}
} else {
base = tib.StackBase;
limit = tib.StackLimit;
}
// Note, "backwards" because of direction of stack growth.
thread.stack_region_address = tib.StackLimit;
if (tib.StackLimit > tib.StackBase) {
LOG(ERROR) << "invalid stack range: " << tib.StackBase << " - "
<< tib.StackLimit;
thread.stack_region_address = limit;
if (limit > base) {
LOG(ERROR) << "invalid stack range: " << base << " - " << limit;
thread.stack_region_size = 0;
} else {
thread.stack_region_size = tib.StackBase - tib.StackLimit;
thread.stack_region_size = base - limit;
}
}
threads_.push_back(thread);

View File

@ -20,6 +20,7 @@
#include <vector>
#include "build/build_config.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/win/address_types.h"
#include "util/win/process_info.h"
@ -43,7 +44,12 @@ class ProcessReaderWin {
Thread();
~Thread() {}
CONTEXT context;
union {
CONTEXT native;
#if defined(ARCH_CPU_64_BITS)
WOW64_CONTEXT wow64;
#endif;
} context;
uint64_t id;
WinVMAddress teb;
WinVMAddress stack_region_address;
@ -108,7 +114,7 @@ class ProcessReaderWin {
private:
template <class Traits>
void ReadThreadData();
void ReadThreadData(bool is_64_reading_32);
HANDLE process_;
ProcessInfo process_info_;

View File

@ -108,9 +108,9 @@ TEST(ProcessReaderWin, SelfOneThread) {
EXPECT_EQ(GetCurrentThreadId(), threads[0].id);
#if defined(ARCH_CPU_64_BITS)
EXPECT_NE(0, threads[0].context.Rip);
EXPECT_NE(0, threads[0].context.native.Rip);
#else
EXPECT_NE(0u, threads[0].context.Eip);
EXPECT_NE(0u, threads[0].context.native.Eip);
#endif
EXPECT_EQ(0, threads[0].suspend_count);

View File

@ -41,20 +41,16 @@ bool ThreadSnapshotWin::Initialize(
if (process_reader->Is64Bit()) {
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
InitializeX64Context(process_reader_thread.context, context_.x86_64);
InitializeX64Context(process_reader_thread.context.native, context_.x86_64);
} else {
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(
*reinterpret_cast<const WOW64_CONTEXT*>(&process_reader_thread.context),
context_.x86);
InitializeX86Context(process_reader_thread.context.wow64, context_.x86);
}
#else
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(
*reinterpret_cast<const CONTEXT*>(&process_reader_thread.context),
context_.x86);
InitializeX86Context(process_reader_thread.context.native, context_.x86);
#endif // ARCH_CPU_X86_64
INITIALIZATION_STATE_SET_VALID(initialized_);

View File

@ -94,8 +94,7 @@
}],
['OS=="win"', {
'dependencies': [
'crashpad_util_test_process_info_test_child_x64',
'crashpad_util_test_process_info_test_child_x86',
'crashpad_util_test_process_info_test_child',
],
'link_settings': {
'libraries': [
@ -111,12 +110,11 @@
['OS=="win"', {
'targets': [
{
'target_name': 'crashpad_util_test_process_info_test_child_x64',
'target_name': 'crashpad_util_test_process_info_test_child',
'type': 'executable',
'sources': [
'win/process_info_test_child.cc',
],
'msvs_configuration_platform': 'x64',
# Set an unusually high load address to make sure that the main
# executable still appears as the first element in
# ProcessInfo::Modules().
@ -126,29 +124,6 @@
'/BASE:0x78000000',
'/FIXED',
],
'MinimumRequiredVersion': '5.02', # Server 2003.
'TargetMachine': '17', # x64.
},
},
},
{
# Same as above, but explicitly x86 to test 64->32 access.
'target_name': 'crashpad_util_test_process_info_test_child_x86',
'type': 'executable',
'sources': [
'win/process_info_test_child.cc',
],
'msvs_configuration_platform': 'x86',
# Set an unusually high load address to make sure that the main
# executable still appears as the first element in
# ProcessInfo::Modules().
'msvs_settings': {
'VCLinkerTool': {
'AdditionalOptions': [
'/BASE:0x78000000',
'/FIXED',
],
'TargetMachine': '1', # x86.
},
},
},

View File

@ -103,7 +103,7 @@ TEST(ProcessInfo, Self) {
EXPECT_GT(modules[1].timestamp, 0);
}
void TestOtherProcess(const std::wstring& child_name_suffix) {
void TestOtherProcess(const base::string16& directory_modification) {
ProcessInfo process_info;
::UUID system_uuid;
@ -120,9 +120,13 @@ void TestOtherProcess(const std::wstring& child_name_suffix) {
ASSERT_TRUE(done.get());
base::FilePath test_executable = Paths::Executable();
std::wstring child_test_executable =
test_executable.RemoveFinalExtension().value() +
L"_process_info_test_child_" + child_name_suffix + L".exe";
test_executable.DirName()
.Append(directory_modification)
.Append(test_executable.BaseName().RemoveFinalExtension().value() +
L"_process_info_test_child.exe")
.value();
// TODO(scottmg): Command line escaping utility.
std::wstring command_line = child_test_executable + L" " +
started_uuid.ToString16() + L" " +
@ -155,8 +159,7 @@ void TestOtherProcess(const std::wstring& child_name_suffix) {
std::vector<ProcessInfo::Module> modules;
EXPECT_TRUE(process_info.Modules(&modules));
ASSERT_GE(modules.size(), 3u);
std::wstring child_name = L"\\crashpad_util_test_process_info_test_child_" +
child_name_suffix + L".exe";
std::wstring child_name = L"\\crashpad_util_test_process_info_test_child.exe";
ASSERT_GE(modules[0].name.size(), child_name.size());
EXPECT_EQ(child_name,
modules[0].name.substr(modules[0].name.size() - child_name.size()));
@ -173,19 +176,20 @@ void TestOtherProcess(const std::wstring& child_name_suffix) {
wcslen(kLz32dllName)));
}
// This test can't run the child if the host OS is x86, and can't read from the
// child if it is x86 and the child is x64, so it only makes sense to run this
// if we built as x64.
TEST(ProcessInfo, OtherProcess) {
TestOtherProcess(FILE_PATH_LITERAL("."));
}
#if defined(ARCH_CPU_64_BITS)
TEST(ProcessInfo, OtherProcessX64) {
TestOtherProcess(L"x64");
TEST(ProcessInfo, OtherProcessWOW64) {
#ifndef NDEBUG
TestOtherProcess(FILE_PATH_LITERAL("..\\..\\out\\Debug"));
#else
TestOtherProcess(FILE_PATH_LITERAL("..\\..\\out\\Release"));
#endif
}
#endif // ARCH_CPU_64_BITS
TEST(ProcessInfo, OtherProcessX86) {
TestOtherProcess(L"x86");
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -283,6 +283,10 @@ struct PEB {
template <class Traits>
struct NT_TIB {
union {
// See https://msdn.microsoft.com/en-us/library/dn424783.aspx.
typename Traits::Pointer Wow64Teb;
struct {
typename Traits::Pointer ExceptionList;
typename Traits::Pointer StackBase;
typename Traits::Pointer StackLimit;
@ -293,6 +297,8 @@ struct NT_TIB {
};
typename Traits::Pointer ArbitraryUserPointer;
typename Traits::Pointer Self;
};
};
};
// See https://msdn.microsoft.com/en-us/library/gg750647.aspx.
@ -417,7 +423,7 @@ struct SYSTEM_PROCESS_INFORMATION {
template <class Traits>
struct THREAD_BASIC_INFORMATION {
union {
NTSTATUS ExitStatus;
LONG ExitStatus;
typename Traits::Pad padding_for_x64_0;
};
typename Traits::Pointer TebBaseAddress;
@ -427,6 +433,15 @@ struct THREAD_BASIC_INFORMATION {
LONG BasePriority;
};
template <class Traits>
struct EXCEPTION_POINTERS {
typename Traits::Pointer ExceptionRecord;
typename Traits::Pointer ContextRecord;
};
using EXCEPTION_POINTERS32 = EXCEPTION_POINTERS<internal::Traits32>;
using EXCEPTION_POINTERS64 = EXCEPTION_POINTERS<internal::Traits64>;
#pragma pack(pop)
//! \}