win: Make reading CrashpadInfo work across bitness

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

Review URL: https://codereview.chromium.org/1355503005 .
This commit is contained in:
Scott Graham 2015-09-22 10:37:11 -07:00
parent 5165c48b3a
commit bd9bc07625
13 changed files with 266 additions and 118 deletions

View File

@ -99,7 +99,13 @@ CrashpadInfo::CrashpadInfo()
crashpad_handler_behavior_(TriState::kUnset),
system_crash_reporter_forwarding_(TriState::kUnset),
padding_0_(0),
simple_annotations_(nullptr) {
simple_annotations_(nullptr)
#if !defined(NDEBUG) && defined(OS_WIN)
,
invalid_read_detection_(0xbadc0de)
#endif
{
}
} // namespace crashpad

View File

@ -121,6 +121,10 @@ struct CrashpadInfo {
uint16_t padding_0_;
SimpleStringDictionary* simple_annotations_; // weak
#if !defined(NDEBUG) && defined(OS_WIN)
uint32_t invalid_read_detection_;
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

View File

@ -179,6 +179,18 @@
'NoImportLibrary': 'true',
},
},
{
'target_name': 'crashpad_snapshot_test_simple_annotations',
'type': 'executable',
'dependencies': [
'../client/client.gyp:crashpad_client',
'../compat/compat.gyp:crashpad_compat',
'../third_party/mini_chromium/mini_chromium.gyp:base',
],
'sources': [
'win/crashpad_snapshot_test_simple_annotations.cc',
],
},
],
}],
],

View File

@ -0,0 +1,53 @@
// 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 "base/logging.h"
#include "client/crashpad_info.h"
#include "util/file/file_io.h"
int wmain(int argc, wchar_t* argv[]) {
crashpad::CrashpadInfo* crashpad_info =
crashpad::CrashpadInfo::GetCrashpadInfo();
// This is "leaked" to crashpad_info.
crashpad::SimpleStringDictionary* simple_annotations =
new crashpad::SimpleStringDictionary();
simple_annotations->SetKeyValue("#TEST# pad", "break");
simple_annotations->SetKeyValue("#TEST# key", "value");
simple_annotations->SetKeyValue("#TEST# pad", "crash");
simple_annotations->SetKeyValue("#TEST# x", "y");
simple_annotations->SetKeyValue("#TEST# longer", "shorter");
simple_annotations->SetKeyValue("#TEST# empty_value", "");
crashpad_info->set_simple_annotations(simple_annotations);
// Tell the parent that the environment has been set up.
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
PCHECK(out != INVALID_HANDLE_VALUE) << "GetStdHandle";
char c = ' ';
crashpad::CheckedWriteFile(out, &c, sizeof(c));
HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
PCHECK(in != INVALID_HANDLE_VALUE) << "GetStdHandle";
crashpad::CheckedReadFile(in, &c, sizeof(c));
CHECK(c == 'd' || c == ' ');
// If 'd' we crash with a debug break, otherwise exit normally.
if (c == 'd')
__debugbreak();
return 0;
}

View File

@ -57,21 +57,10 @@ bool ModuleSnapshotWin::Initialize(
void ModuleSnapshotWin::GetCrashpadOptions(CrashpadInfoClientOptions* options) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
process_types::CrashpadInfo crashpad_info;
if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) {
options->crashpad_handler_behavior = TriState::kUnset;
options->system_crash_reporter_forwarding = TriState::kUnset;
return;
}
options->crashpad_handler_behavior =
CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
crashpad_info.crashpad_handler_behavior);
options->system_crash_reporter_forwarding =
CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
crashpad_info.system_crash_reporter_forwarding);
if (process_reader_->Is64Bit())
GetCrashpadOptionsInternal<process_types::internal::Traits64>(options);
else
GetCrashpadOptionsInternal<process_types::internal::Traits32>(options);
}
std::string ModuleSnapshotWin::Name() const {
@ -171,5 +160,24 @@ std::map<std::string, std::string> ModuleSnapshotWin::AnnotationsSimpleMap()
return annotations_reader.SimpleMap();
}
template <class Traits>
void ModuleSnapshotWin::GetCrashpadOptionsInternal(
CrashpadInfoClientOptions* options) {
process_types::CrashpadInfo<Traits> crashpad_info;
if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) {
options->crashpad_handler_behavior = TriState::kUnset;
options->system_crash_reporter_forwarding = TriState::kUnset;
return;
}
options->crashpad_handler_behavior =
CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
crashpad_info.crashpad_handler_behavior);
options->system_crash_reporter_forwarding =
CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
crashpad_info.system_crash_reporter_forwarding);
}
} // namespace internal
} // namespace crashpad

View File

@ -85,6 +85,9 @@ class ModuleSnapshotWin final : public ModuleSnapshot {
std::map<std::string, std::string> AnnotationsSimpleMap() const override;
private:
template <class Traits>
void GetCrashpadOptionsInternal(CrashpadInfoClientOptions* options);
std::wstring name_;
time_t timestamp_;
scoped_ptr<PEImageReader> pe_image_reader_;

View File

@ -20,6 +20,7 @@
#include "client/simple_string_dictionary.h"
#include "snapshot/win/pe_image_reader.h"
#include "snapshot/win/process_reader_win.h"
#include "util/win/process_structs.h"
namespace crashpad {
@ -34,13 +35,20 @@ PEImageAnnotationsReader::PEImageAnnotationsReader(
std::map<std::string, std::string> PEImageAnnotationsReader::SimpleMap() const {
std::map<std::string, std::string> simple_map_annotations;
ReadCrashpadSimpleAnnotations(&simple_map_annotations);
if (process_reader_->Is64Bit()) {
ReadCrashpadSimpleAnnotations<process_types::internal::Traits64>(
&simple_map_annotations);
} else {
ReadCrashpadSimpleAnnotations<process_types::internal::Traits32>(
&simple_map_annotations);
}
return simple_map_annotations;
}
template <class Traits>
void PEImageAnnotationsReader::ReadCrashpadSimpleAnnotations(
std::map<std::string, std::string>* simple_map_annotations) const {
process_types::CrashpadInfo crashpad_info;
process_types::CrashpadInfo<Traits> crashpad_info;
if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info))
return;

View File

@ -56,6 +56,7 @@ class PEImageAnnotationsReader {
private:
// Reads CrashpadInfo::simple_annotations_ on behalf of SimpleMap().
template <class Traits>
void ReadCrashpadSimpleAnnotations(
std::map<std::string, std::string>* simple_map_annotations) const;

View File

@ -22,12 +22,15 @@
#include <vector>
#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/strings/utf_string_conversions.h"
#include "client/crashpad_info.h"
#include "client/simple_string_dictionary.h"
#include "gtest/gtest.h"
#include "snapshot/win/pe_image_reader.h"
#include "snapshot/win/process_reader_win.h"
#include "test/paths.h"
#include "test/win/child_launcher.h"
#include "test/win/win_multiprocess.h"
#include "util/file/file_io.h"
#include "util/win/process_info.h"
@ -44,98 +47,99 @@ enum TestType {
kCrashDebugBreak,
};
template <TestType Type>
class TestPEImageAnnotationsReader final : public WinMultiprocess {
public:
TestPEImageAnnotationsReader() {}
~TestPEImageAnnotationsReader() {}
void TestAnnotationsOnCrash(TestType type,
const base::string16& directory_modification) {
// 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"_simple_annotations.exe")
.value();
ChildLauncher child(child_test_executable, L"");
child.Start();
private:
// WinMultiprocess:
// Wait for the child process to indicate that it's done setting up its
// annotations via the CrashpadInfo interface.
char c;
CheckedReadFile(child.stdout_read_handle(), &c, sizeof(c));
void WinMultiprocessParent() override {
ProcessReaderWin process_reader;
ASSERT_TRUE(process_reader.Initialize(ChildProcess(),
ProcessSuspensionState::kRunning));
ProcessReaderWin process_reader;
ASSERT_TRUE(process_reader.Initialize(child.process_handle(),
ProcessSuspensionState::kRunning));
// Wait for the child process to indicate that it's done setting up its
// annotations via the CrashpadInfo interface.
char c;
CheckedReadFile(ReadPipeHandle(), &c, sizeof(c));
// Verify the "simple map" annotations set via the CrashpadInfo interface.
const std::vector<ProcessInfo::Module>& modules = process_reader.Modules();
std::map<std::string, std::string> all_annotations_simple_map;
for (const ProcessInfo::Module& module : modules) {
PEImageReader pe_image_reader;
pe_image_reader.Initialize(&process_reader,
module.dll_base,
module.size,
base::UTF16ToUTF8(module.name));
PEImageAnnotationsReader module_annotations_reader(
&process_reader, &pe_image_reader, module.name);
std::map<std::string, std::string> module_annotations_simple_map =
module_annotations_reader.SimpleMap();
all_annotations_simple_map.insert(module_annotations_simple_map.begin(),
module_annotations_simple_map.end());
}
EXPECT_GE(all_annotations_simple_map.size(), 5u);
EXPECT_EQ("crash", all_annotations_simple_map["#TEST# pad"]);
EXPECT_EQ("value", all_annotations_simple_map["#TEST# key"]);
EXPECT_EQ("y", all_annotations_simple_map["#TEST# x"]);
EXPECT_EQ("shorter", all_annotations_simple_map["#TEST# longer"]);
EXPECT_EQ("", all_annotations_simple_map["#TEST# empty_value"]);
if (Type == kCrashDebugBreak)
SetExpectedChildExitCode(STATUS_BREAKPOINT);
// Tell the child process to continue.
CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
// Verify the "simple map" annotations set via the CrashpadInfo interface.
const std::vector<ProcessInfo::Module>& modules = process_reader.Modules();
std::map<std::string, std::string> all_annotations_simple_map;
for (const ProcessInfo::Module& module : modules) {
PEImageReader pe_image_reader;
pe_image_reader.Initialize(&process_reader,
module.dll_base,
module.size,
base::UTF16ToUTF8(module.name));
PEImageAnnotationsReader module_annotations_reader(
&process_reader, &pe_image_reader, module.name);
std::map<std::string, std::string> module_annotations_simple_map =
module_annotations_reader.SimpleMap();
all_annotations_simple_map.insert(module_annotations_simple_map.begin(),
module_annotations_simple_map.end());
}
void WinMultiprocessChild() override {
CrashpadInfo* crashpad_info = CrashpadInfo::GetCrashpadInfo();
EXPECT_GE(all_annotations_simple_map.size(), 5u);
EXPECT_EQ("crash", all_annotations_simple_map["#TEST# pad"]);
EXPECT_EQ("value", all_annotations_simple_map["#TEST# key"]);
EXPECT_EQ("y", all_annotations_simple_map["#TEST# x"]);
EXPECT_EQ("shorter", all_annotations_simple_map["#TEST# longer"]);
EXPECT_EQ("", all_annotations_simple_map["#TEST# empty_value"]);
// This is "leaked" to crashpad_info.
SimpleStringDictionary* simple_annotations = new SimpleStringDictionary();
simple_annotations->SetKeyValue("#TEST# pad", "break");
simple_annotations->SetKeyValue("#TEST# key", "value");
simple_annotations->SetKeyValue("#TEST# pad", "crash");
simple_annotations->SetKeyValue("#TEST# x", "y");
simple_annotations->SetKeyValue("#TEST# longer", "shorter");
simple_annotations->SetKeyValue("#TEST# empty_value", "");
crashpad_info->set_simple_annotations(simple_annotations);
// Tell the parent that the environment has been set up.
char c = '\0';
CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
// Wait for the parent to indicate that it's safe to continue/crash.
CheckedReadFile(ReadPipeHandle(), &c, sizeof(c));
switch (Type) {
case kDontCrash:
break;
case kCrashDebugBreak:
__debugbreak();
break;
}
// Tell the child process to continue.
DWORD expected_exit_code;
switch (type) {
case kDontCrash:
c = ' ';
expected_exit_code = 0;
break;
case kCrashDebugBreak:
c = 'd';
expected_exit_code = STATUS_BREAKPOINT;
break;
default:
FAIL();
}
CheckedWriteFile(child.stdin_write_handle(), &c, sizeof(c));
DISALLOW_COPY_AND_ASSIGN(TestPEImageAnnotationsReader);
};
EXPECT_EQ(expected_exit_code, child.WaitForExit());
}
TEST(PEImageAnnotationsReader, DontCrash) {
WinMultiprocess::Run<TestPEImageAnnotationsReader<kDontCrash>>();
TestAnnotationsOnCrash(kDontCrash, FILE_PATH_LITERAL("."));
}
TEST(PEImageAnnotationsReader, CrashDebugBreak) {
WinMultiprocess::Run<TestPEImageAnnotationsReader<kCrashDebugBreak>>();
TestAnnotationsOnCrash(kCrashDebugBreak, FILE_PATH_LITERAL("."));
}
#if defined(ARCH_CPU_64_BITS)
TEST(PEImageAnnotationsReader, DontCrashWOW64) {
#ifndef NDEBUG
TestAnnotationsOnCrash(kDontCrash, FILE_PATH_LITERAL("..\\..\\out\\Debug"));
#else
TestAnnotationsOnCrash(kDontCrash, FILE_PATH_LITERAL("..\\..\\out\\Release"));
#endif
}
TEST(PEImageAnnotationsReader, CrashDebugBreakWOW64) {
#ifndef NDEBUG
TestAnnotationsOnCrash(kCrashDebugBreak,
FILE_PATH_LITERAL("..\\..\\out\\Debug"));
#else
TestAnnotationsOnCrash(kCrashDebugBreak,
FILE_PATH_LITERAL("..\\..\\out\\Release"));
#endif
}
#endif // ARCH_CPU_64_BITS
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -22,6 +22,7 @@
#include "client/crashpad_info.h"
#include "snapshot/win/process_reader_win.h"
#include "util/misc/pdb_structures.h"
#include "util/win/process_structs.h"
namespace crashpad {
@ -34,6 +35,20 @@ std::string RangeToString(const CheckedWinAddressRange& range) {
range.Is64Bit() ? "64" : "32");
}
// Map from Traits to an IMAGE_NT_HEADERSxx.
template <class Traits>
struct NtHeadersForTraits;
template <>
struct NtHeadersForTraits<process_types::internal::Traits32> {
using type = IMAGE_NT_HEADERS32;
};
template <>
struct NtHeadersForTraits<process_types::internal::Traits64> {
using type = IMAGE_NT_HEADERS64;
};
} // namespace
PEImageReader::PEImageReader()
@ -65,20 +80,16 @@ bool PEImageReader::Initialize(ProcessReaderWin* process_reader,
return true;
}
template <class Traits>
bool PEImageReader::GetCrashpadInfo(
process_types::CrashpadInfo* crashpad_info) const {
process_types::CrashpadInfo<Traits>* crashpad_info) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
IMAGE_SECTION_HEADER section;
if (process_reader_->Is64Bit()) {
if (!GetSectionByName<IMAGE_NT_HEADERS64>("CPADinfo", &section))
return false;
} else {
if (!GetSectionByName<IMAGE_NT_HEADERS32>("CPADinfo", &section))
return false;
}
if (!GetSectionByName<NtHeadersForTraits<Traits>::type>("CPADinfo", &section))
return false;
if (section.Misc.VirtualSize < sizeof(process_types::CrashpadInfo)) {
if (section.Misc.VirtualSize < sizeof(process_types::CrashpadInfo<Traits>)) {
LOG(WARNING) << "small crashpad info section size "
<< section.Misc.VirtualSize << ", " << module_name_;
return false;
@ -100,9 +111,8 @@ bool PEImageReader::GetCrashpadInfo(
return false;
}
// TODO(scottmg): process_types for cross-bitness.
if (!process_reader_->ReadMemory(crashpad_info_address,
sizeof(process_types::CrashpadInfo),
sizeof(process_types::CrashpadInfo<Traits>),
crashpad_info)) {
LOG(WARNING) << "could not read crashpad info " << module_name_;
return false;
@ -270,4 +280,13 @@ bool PEImageReader::CheckedReadMemory(WinVMAddress address,
return process_reader_->ReadMemory(address, size, into);
}
// Explicit instantiations with the only 2 valid template arguments to avoid
// putting the body of the function in the header.
template bool PEImageReader::GetCrashpadInfo<process_types::internal::Traits32>(
process_types::CrashpadInfo<process_types::internal::Traits32>*
crashpad_info) const;
template bool PEImageReader::GetCrashpadInfo<process_types::internal::Traits64>(
process_types::CrashpadInfo<process_types::internal::Traits64>*
crashpad_info) const;
} // namespace crashpad

View File

@ -24,6 +24,7 @@
#include "util/misc/uuid.h"
#include "util/win/address_types.h"
#include "util/win/checked_win_address_range.h"
#include "util/win/process_structs.h"
namespace crashpad {
@ -31,7 +32,7 @@ class ProcessReaderWin;
namespace process_types {
// TODO(scottmg): Genericize and/or? move process_types out of mac/.
template <class Traits>
struct CrashpadInfo {
uint32_t signature;
uint32_t size;
@ -39,10 +40,7 @@ struct CrashpadInfo {
uint8_t crashpad_handler_behavior; // TriState.
uint8_t system_crash_reporter_forwarding; // TriState.
uint16_t padding_0;
uint64_t simple_annotations; // TODO(scottmg): x86/64.
};
struct Section {
typename Traits::Pointer simple_annotations;
};
} // namespace process_types
@ -92,7 +90,9 @@ class PEImageReader {
//! \return `true` on success, `false` on failure. If the module does not have
//! a `CPADinfo` section, this will return `false` without logging any
//! messages. Other failures will result in messages being logged.
bool GetCrashpadInfo(process_types::CrashpadInfo* crashpad_info) const;
template <class Traits>
bool GetCrashpadInfo(
process_types::CrashpadInfo<Traits>* crashpad_info) const;
//! \brief Obtains information from the module's debug directory, if any.
//!

View File

@ -25,12 +25,13 @@ ChildLauncher::ChildLauncher(const std::wstring& executable,
command_line_(command_line),
process_handle_(),
main_thread_handle_(),
stdout_read_handle_() {
stdout_read_handle_(),
stdin_write_handle_() {
}
ChildLauncher::~ChildLauncher() {
EXPECT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(process_handle_.get(), INFINITE));
if (process_handle_.is_valid())
WaitForExit();
}
void ChildLauncher::Start() {
@ -38,10 +39,11 @@ void ChildLauncher::Start() {
ASSERT_FALSE(main_thread_handle_.is_valid());
ASSERT_FALSE(stdout_read_handle_.is_valid());
// Create a pipe for the stdout of the child.
// Create pipes for the stdin/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));
@ -50,9 +52,17 @@ void ChildLauncher::Start() {
ASSERT_TRUE(
SetHandleInformation(stdout_read_handle_.get(), HANDLE_FLAG_INHERIT, 0));
HANDLE stdin_read;
HANDLE stdin_write;
ASSERT_TRUE(CreatePipe(&stdin_read, &stdin_write, &security_attributes, 0));
stdin_write_handle_.reset(stdin_write);
ScopedFileHANDLE read_handle(stdin_read);
ASSERT_TRUE(
SetHandleInformation(stdin_write_handle_.get(), HANDLE_FLAG_INHERIT, 0));
STARTUPINFO startup_info = {0};
startup_info.cb = sizeof(startup_info);
startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startup_info.hStdInput = read_handle.get();
startup_info.hStdOutput = write_handle.get();
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startup_info.dwFlags = STARTF_USESTDHANDLES;
@ -76,6 +86,16 @@ void ChildLauncher::Start() {
process_handle_.reset(process_information.hProcess);
}
DWORD ChildLauncher::WaitForExit() {
EXPECT_TRUE(process_handle_.is_valid());
EXPECT_EQ(WAIT_OBJECT_0,
WaitForSingleObject(process_handle_.get(), INFINITE));
DWORD exit_code = 0;
EXPECT_TRUE(GetExitCodeProcess(process_handle_.get(), &exit_code));
process_handle_.reset();
return exit_code;
}
// Ref: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
void AppendCommandLineArgument(const std::wstring& argument,
std::wstring* command_line) {

View File

@ -26,7 +26,8 @@ namespace test {
//! \brief Creates a child process for testing. Uses gtest `ASSERT_*` to
//! indicate failure. The child's output is passed through a pipe and is
//! available via stdout_read_handle().
//! available via stdout_read_handle(), and the child's input is attached to
//! a second pipe available via stdin_write_handle().
class ChildLauncher {
public:
//! \brief Creates the object. \a executable will be escaped and prepended to
@ -40,6 +41,11 @@ class ChildLauncher {
//! will be valid.
void Start();
//! \brief Waits for the child process to exit.
//!
//! \return The process exit code.
DWORD WaitForExit();
//! \brief The child process's `HANDLE`.
HANDLE process_handle() const { return process_handle_.get(); }
@ -49,12 +55,16 @@ class ChildLauncher {
//! \brief The read end of a pipe attached to the child's stdout.
HANDLE stdout_read_handle() const { return stdout_read_handle_.get(); }
//! \brief The write end of a pipe attached to the child's stdin.
HANDLE stdin_write_handle() const { return stdin_write_handle_.get(); }
private:
std::wstring executable_;
std::wstring command_line_;
ScopedKernelHANDLE process_handle_;
ScopedKernelHANDLE main_thread_handle_;
ScopedFileHANDLE stdout_read_handle_;
ScopedFileHANDLE stdin_write_handle_;
};
//! \brief Utility function for building escaped command lines.