blob: da280a09a1ad9fe9862777c0e139fb79f0c143f6 [file] [log] [blame]
// Copyright 2014 Google Inc. 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 "syzygy/refinery/unittest_util.h"
#include <windows.h> // NOLINT
#include <dbghelp.h>
#include <cstring>
#include <vector>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/numerics/safe_math.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "gtest/gtest.h"
#include "syzygy/common/com_utils.h"
#include "testing/multiprocess_func_list.h"
namespace testing {
namespace {
void PopulateContext(std::string* context_bytes, int base_offset) {
DCHECK(context_bytes != nullptr);
context_bytes->resize(sizeof(CONTEXT));
CONTEXT* ctx = reinterpret_cast<CONTEXT*>(&context_bytes->at(0));
ctx->ContextFlags = CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_CONTROL;
ctx->SegGs = base_offset + 1;
ctx->SegFs = base_offset + 2;
ctx->SegEs = base_offset + 3;
ctx->SegDs = base_offset + 4;
ctx->Edi = base_offset + 11;
ctx->Esi = base_offset + 12;
ctx->Ebx = base_offset + 13;
ctx->Edx = base_offset + 14;
ctx->Ecx = base_offset + 15;
ctx->Eax = base_offset + 16;
ctx->Ebp = base_offset + 21;
ctx->Eip = base_offset + 22;
ctx->SegCs = base_offset + 23;
ctx->EFlags = base_offset + 24;
ctx->Esp = base_offset + 25;
ctx->SegSs = base_offset + 26;
}
using MemorySpecification = MinidumpSpecification::MemorySpecification;
using ThreadSpecification = MinidumpSpecification::ThreadSpecification;
using ExceptionSpecification = MinidumpSpecification::ExceptionSpecification;
using ModuleSpecification = MinidumpSpecification::ModuleSpecification;
// TODO(manzagop): ensure on destruction or finalizing that the allocations are
// actually reflected in the file. At this point, allocating without writing
// would leave the file short.
class MinidumpSerializer {
public:
typedef RVA Position;
MinidumpSerializer();
// @pre @p dir must be a valid directory.
bool Initialize(const base::ScopedTempDir& dir);
bool SerializeThreads(const std::vector<ThreadSpecification>& threads);
bool SerializeMemory(const std::vector<MemorySpecification>& regions);
bool SerializeModules(const std::vector<ModuleSpecification>& modules);
bool SerializeExceptions(
const std::vector<ExceptionSpecification>& exceptions);
bool Finalize();
const base::FilePath& path() const { return path_; }
private:
static const Position kHeaderPos = 0U;
void SerializeDirectory();
void SerializeHeader();
Position Allocate(size_t size_bytes);
// Allocate new space and write to it.
template <class DataType>
Position Append(const DataType& data);
// @pre @p data must not be empty.
template <class DataType>
Position AppendVec(const std::vector<DataType>& data);
// @pre @p elements must not be empty.
template <class DataType>
Position AppendListStream(MINIDUMP_STREAM_TYPE type,
const std::vector<DataType>& elements);
Position AppendBytes(base::StringPiece data);
Position AppendMinidumpString(base::StringPiece utf8);
// Write to already allocated space.
template <class DataType>
void Write(Position pos, const DataType& data);
void WriteBytes(Position pos, size_t size_bytes, const void* data);
bool IncrementCursor(size_t size_bytes);
// Gets the position of an address range which is fully contained in a
// serialized range.
// @pre is_serialize_memory_invoked_ must be true.
// @param range the range for which to get the rva.
// @param pos the returned position.
// returns true on success, false otherwise.
bool GetPos(const refinery::AddressRange& range, Position* pos) const;
void AddDirectoryEntry(MINIDUMP_STREAM_TYPE type, Position pos, size_t size);
bool succeeded() const { return !failed_; }
bool failed_;
bool is_serialize_memory_invoked_;
std::vector<MINIDUMP_DIRECTORY> directory_;
Position cursor_; // The next allocatable position.
base::FilePath path_;
base::ScopedFILE file_;
std::map<refinery::AddressRange, Position> memory_positions_;
};
MinidumpSerializer::MinidumpSerializer()
: failed_(true), is_serialize_memory_invoked_(false), cursor_(0U) {
}
bool MinidumpSerializer::Initialize(const base::ScopedTempDir& dir) {
DCHECK(dir.IsValid());
failed_ = false;
is_serialize_memory_invoked_ = false;
directory_.clear();
// Create the backing file.
cursor_ = 0U;
if (!CreateTemporaryFileInDir(dir.path(), &path_)) {
failed_ = true;
return false;
}
file_.reset(base::OpenFile(path_, "wb"));
if (file_.get() == nullptr)
failed_ = true;
// Allocate the header.
Position pos = Allocate(sizeof(MINIDUMP_HEADER));
DCHECK_EQ(kHeaderPos, pos);
return succeeded();
}
bool MinidumpSerializer::SerializeThreads(
const std::vector<ThreadSpecification>& specs) {
if (specs.empty())
return succeeded();
std::vector<MINIDUMP_THREAD> threads;
threads.resize(specs.size());
for (int i = 0; i < specs.size(); ++i) {
const ThreadSpecification& spec = specs[i];
// Write the context.
DCHECK_EQ(sizeof(CONTEXT), spec.context_data.size());
Position pos = AppendBytes(spec.context_data);
// Copy thread to vector for more efficient serialization, then set Rvas.
DCHECK_EQ(sizeof(MINIDUMP_THREAD), spec.thread_data.size());
memcpy(&threads.at(i), spec.thread_data.c_str(), sizeof(MINIDUMP_THREAD));
MINIDUMP_THREAD& thread = threads[i];
refinery::AddressRange stack_range(thread.Stack.StartOfMemoryRange,
thread.Stack.Memory.DataSize);
if (!GetPos(stack_range, &thread.Stack.Memory.Rva))
failed_ = true;
thread.ThreadContext.Rva = pos;
}
AppendListStream(ThreadListStream, threads);
return succeeded();
}
bool MinidumpSerializer::SerializeMemory(
const std::vector<MemorySpecification>& regions) {
// Signal that memory serialization has occured, and regions now have
// associated positions in the minidump.
is_serialize_memory_invoked_ = true;
if (regions.empty())
return succeeded();
// Write bytes data and create the memory descriptors.
std::vector<MINIDUMP_MEMORY_DESCRIPTOR> memory_descriptors;
memory_descriptors.resize(regions.size());
size_t idx = 0;
for (const auto& region : regions) {
refinery::AddressRange range(region.address, region.buffer.size());
DCHECK(range.IsValid());
Position pos = AppendBytes(region.buffer);
auto inserted = memory_positions_.insert(std::make_pair(range, pos));
DCHECK_EQ(true, inserted.second);
memory_descriptors[idx].StartOfMemoryRange = range.start();
memory_descriptors[idx].Memory.DataSize = range.size();
memory_descriptors[idx].Memory.Rva = pos;
++idx;
}
// Write descriptors and create directory entry.
AppendListStream(MemoryListStream, memory_descriptors);
return succeeded();
}
bool MinidumpSerializer::SerializeModules(
const std::vector<ModuleSpecification>& module_specs) {
if (module_specs.empty())
return succeeded();
std::vector<MINIDUMP_MODULE> modules;
modules.resize(module_specs.size());
for (int i = 0; i < module_specs.size(); ++i) {
modules[i].BaseOfImage = module_specs[i].addr;
modules[i].SizeOfImage = module_specs[i].size;
modules[i].CheckSum = module_specs[i].checksum;
modules[i].TimeDateStamp = module_specs[i].timestamp;
modules[i].ModuleNameRva = AppendMinidumpString(module_specs[i].name);
}
AppendListStream(ModuleListStream, modules);
return succeeded();
}
bool MinidumpSerializer::SerializeExceptions(
const std::vector<ExceptionSpecification>& exception_specs) {
if (exception_specs.empty())
return succeeded();
for (const ExceptionSpecification& spec : exception_specs) {
// Write the context.
DCHECK_EQ(sizeof(CONTEXT), spec.context_data.size());
Position pos = AppendBytes(spec.context_data);
// Write the MINIDUMP_EXCEPTION_STREAM.
ULONG32 thread_id = spec.thread_id;
Position rva = Append(thread_id);
ULONG32 dummy_alignment = 0U;
Append(dummy_alignment);
MINIDUMP_EXCEPTION exception = {0};
exception.ExceptionCode = spec.exception_code;
exception.ExceptionFlags = spec.exception_flags;
exception.ExceptionRecord = spec.exception_record;
exception.ExceptionAddress = spec.exception_address;
exception.NumberParameters = spec.exception_information.size();
DCHECK(exception.NumberParameters <= EXCEPTION_MAXIMUM_PARAMETERS);
for (size_t i = 0; i < spec.exception_information.size(); ++i) {
exception.ExceptionInformation[i] = spec.exception_information[i];
}
Append(exception);
MINIDUMP_LOCATION_DESCRIPTOR context_loc = {};
context_loc.DataSize = sizeof(CONTEXT);
context_loc.Rva = pos;
Append(context_loc);
AddDirectoryEntry(ExceptionStream, rva, sizeof(MINIDUMP_EXCEPTION_STREAM));
}
return succeeded();
}
bool MinidumpSerializer::Finalize() {
// Serialize the directory.
Position pos = AppendVec(directory_);
// Serialize the header.
MINIDUMP_HEADER hdr;
hdr.Signature = MINIDUMP_SIGNATURE;
hdr.NumberOfStreams = directory_.size();
hdr.StreamDirectoryRva = pos;
Write(kHeaderPos, hdr);
return succeeded();
}
MinidumpSerializer::Position MinidumpSerializer::Allocate(size_t size_bytes) {
Position pos = cursor_;
if (!IncrementCursor(size_bytes))
failed_ = true;
return pos;
}
template <class DataType>
MinidumpSerializer::Position MinidumpSerializer::Append(const DataType& data) {
Position pos = Allocate(sizeof(data));
Write(pos, data);
return pos;
}
template <class DataType>
MinidumpSerializer::Position MinidumpSerializer::AppendVec(
const std::vector<DataType>& data) {
DCHECK(!data.empty());
size_t size_bytes = sizeof(DataType) * data.size();
Position pos = Allocate(size_bytes);
WriteBytes(pos, size_bytes, &data.at(0));
return pos;
}
template <class DataType>
MinidumpSerializer::Position MinidumpSerializer::AppendListStream(
MINIDUMP_STREAM_TYPE type,
const std::vector<DataType>& elements) {
DCHECK(!elements.empty());
// Append the stream
ULONG32 num_elements = elements.size();
Position pos = Append(num_elements);
AppendVec(elements);
// Create its directory entry.
size_t size_bytes = sizeof(ULONG32) + elements.size() * sizeof(DataType);
AddDirectoryEntry(type, pos, size_bytes);
return pos;
}
MinidumpSerializer::Position MinidumpSerializer::AppendBytes(
base::StringPiece data) {
Position pos = Allocate(data.length());
WriteBytes(pos, data.length(), data.data());
return pos;
}
MinidumpSerializer::Position MinidumpSerializer::AppendMinidumpString(
base::StringPiece utf8) {
std::wstring wide = base::UTF8ToWide(utf8);
ULONG32 size_bytes = wide.length() * sizeof(std::wstring::value_type);
Position pos = Append(size_bytes);
// Note: write the null termination character.
size_bytes += sizeof(std::wstring::value_type);
Position string_pos = Allocate(size_bytes);
WriteBytes(string_pos, size_bytes, wide.c_str());
return pos;
}
template <class DataType>
void MinidumpSerializer::Write(Position pos, const DataType& data) {
WriteBytes(pos, sizeof(data), &data);
}
void MinidumpSerializer::WriteBytes(Position pos,
size_t size_bytes,
const void* data) {
if (failed_)
return;
// Validate the write does not go past the cursor.
base::CheckedNumeric<Position> pos_end = pos;
pos_end += size_bytes;
DCHECK(pos_end.IsValid());
DCHECK(pos_end.ValueOrDie() <= cursor_);
DCHECK(file_.get() != nullptr);
// Seek and write.
if (fseek(file_.get(), pos, SEEK_SET) != 0) {
failed_ = true;
return;
}
if (fwrite(data, sizeof(char), size_bytes, file_.get()) != size_bytes) {
failed_ = true;
return;
}
}
bool MinidumpSerializer::IncrementCursor(size_t size_bytes) {
base::CheckedNumeric<Position> cur = cursor_;
cur += size_bytes;
if (!cur.IsValid())
return false;
cursor_ += size_bytes;
return true;
}
bool MinidumpSerializer::GetPos(const refinery::AddressRange& range,
Position* pos) const {
DCHECK(range.IsValid());
DCHECK(pos != nullptr);
DCHECK(is_serialize_memory_invoked_);
auto it = memory_positions_.upper_bound(range);
if (it == memory_positions_.begin())
return false;
// Note: given that memory ranges do not overlap, only the immediate
// predecessor is a candidate match.
--it;
if (it->first.Contains(range)) {
*pos = it->second;
return true;
}
return false;
}
void MinidumpSerializer::AddDirectoryEntry(MINIDUMP_STREAM_TYPE type,
Position pos,
size_t size_bytes) {
MINIDUMP_DIRECTORY directory = {0};
directory.StreamType = type;
directory.Location.Rva = pos;
directory.Location.DataSize = size_bytes;
directory_.push_back(directory);
}
} // namespace
const uint32_t ScopedMinidump::kMinidumpWithStacks =
MiniDumpWithProcessThreadData | // Get PEB and TEB.
MiniDumpWithUnloadedModules; // Get unloaded modules when available.
const uint32_t ScopedMinidump::kMinidumpWithData =
kMinidumpWithStacks |
MiniDumpWithIndirectlyReferencedMemory; // Get referenced memory.
MinidumpSpecification::MinidumpSpecification() : allow_memory_overlap_(false) {
}
MinidumpSpecification::MinidumpSpecification(AllowMemoryOverlap dummy)
: allow_memory_overlap_(true) {
}
bool MinidumpSpecification::AddThread(const ThreadSpecification& spec) {
DCHECK_EQ(sizeof(MINIDUMP_THREAD), spec.thread_data.size());
DCHECK_EQ(sizeof(CONTEXT), spec.context_data.size());
DCHECK_GT(spec.thread_data.size(), 0U);
DCHECK_GT(spec.context_data.size(), 0U);
threads_.push_back(spec);
return true;
}
bool MinidumpSpecification::AddMemoryRegion(const MemorySpecification& spec) {
refinery::Address address = spec.address;
refinery::Size size_bytes = spec.buffer.size();
// Ensure range validity.
refinery::AddressRange range(address, size_bytes);
if (!range.IsValid())
return false;
if (!allow_memory_overlap_) {
// Overlap is not allowed in this instance - check with successor and
// predecessor.
auto it = region_sizes_.upper_bound(address);
if (it != region_sizes_.end()) {
refinery::AddressRange post_range(it->first, it->second);
DCHECK(post_range.IsValid());
if (range.Intersects(post_range))
return false;
}
if (it != region_sizes_.begin()) {
--it;
refinery::AddressRange pre_range(it->first, it->second);
DCHECK(pre_range.IsValid());
if (range.Intersects(pre_range))
return false;
}
// Add the specification.
auto inserted = region_sizes_.insert(std::make_pair(address, size_bytes));
if (!inserted.second)
return false;
}
memory_regions_.push_back(spec);
return true;
}
bool MinidumpSpecification::AddModule(const ModuleSpecification& module) {
modules_.push_back(module);
return true;
}
bool MinidumpSpecification::AddException(
const ExceptionSpecification& exception) {
exceptions_.push_back(exception);
return true;
}
bool MinidumpSpecification::Serialize(const base::ScopedTempDir& dir,
base::FilePath* path) const {
MinidumpSerializer serializer;
bool success = serializer.Initialize(dir) &&
serializer.SerializeMemory(memory_regions_) &&
serializer.SerializeThreads(threads_) &&
serializer.SerializeModules(modules_) &&
serializer.SerializeExceptions(exceptions_) &&
serializer.Finalize();
*path = serializer.path();
return success;
}
MinidumpSpecification::MemorySpecification::MemorySpecification()
: address(0ULL) {
}
MinidumpSpecification::MemorySpecification::MemorySpecification(
refinery::Address addr,
base::StringPiece data)
: address(addr) {
data.CopyToString(&buffer);
}
MinidumpSpecification::ThreadSpecification::ThreadSpecification(
size_t thread_id,
refinery::Address stack_address,
refinery::Size stack_size) {
// Generate the MINIDUMP_THREAD.
thread_data.resize(sizeof(MINIDUMP_THREAD));
MINIDUMP_THREAD* thread =
reinterpret_cast<MINIDUMP_THREAD*>(&thread_data.at(0));
thread->ThreadId = thread_id;
thread->SuspendCount = 2;
thread->PriorityClass = 3;
thread->Priority = 4;
thread->Teb = 5U;
thread->Stack.StartOfMemoryRange = stack_address;
thread->Stack.Memory.DataSize = stack_size;
thread->ThreadContext.DataSize = sizeof(CONTEXT);
// Note: thread.Stack.Memory.Rva and thread.ThreadContext.Rva are set during
// serialization.
PopulateContext(&context_data, 0);
}
void MinidumpSpecification::ThreadSpecification::SetTebAddress(
refinery::Address addr) {
DCHECK(thread_data.size() == sizeof(MINIDUMP_THREAD));
MINIDUMP_THREAD* thread =
reinterpret_cast<MINIDUMP_THREAD*>(&thread_data.at(0));
thread->Teb = addr;
}
void MinidumpSpecification::ThreadSpecification::FillStackMemorySpecification(
MinidumpSpecification::MemorySpecification* spec) const {
DCHECK(spec);
DCHECK_EQ(sizeof(MINIDUMP_THREAD), thread_data.size());
const MINIDUMP_THREAD* thread =
reinterpret_cast<const MINIDUMP_THREAD*>(&thread_data.at(0));
// The stack is a range of 'S' padded at either end with a single 'P'.
DCHECK_GT(thread->Stack.StartOfMemoryRange, 0U);
const ULONG32 kStackMaxSize = static_cast<ULONG32>(-1) - 1;
DCHECK(thread->Stack.Memory.DataSize < kStackMaxSize);
spec->address = thread->Stack.StartOfMemoryRange - 1;
spec->buffer.resize(thread->Stack.Memory.DataSize + 2, 'S');
spec->buffer[0] = 'P';
spec->buffer[thread->Stack.Memory.DataSize + 1] = 'P';
}
MinidumpSpecification::ExceptionSpecification::ExceptionSpecification(
uint32_t thread_identifier) {
thread_id = thread_identifier;
exception_code = EXCEPTION_ACCESS_VIOLATION;
exception_flags = EXCEPTION_NONCONTINUABLE;
exception_record = 0ULL;
exception_address = 1111ULL;
exception_information.push_back(1);
exception_information.push_back(2222ULL);
PopulateContext(&context_data, 100);
}
MinidumpSpecification::ModuleSpecification::ModuleSpecification() {
addr = 12345ULL;
size = 75U;
checksum = 23U;
timestamp = 42U;
name = "someModule";
}
namespace {
// Minidump.
const wchar_t kMinidumpFileName[] = L"minidump.dmp";
const char kSwitchExceptionPtrs[] = "exception-ptrs";
const char kSwitchPid[] = "dump-pid";
const char kSwitchMinidumpPath[] = "dump-path";
const char kSwitchTid[] = "exception-thread-id";
const char kSwitchMinidumpType[] = "minidump-type";
__declspec(noinline) DWORD GetEip() {
return reinterpret_cast<DWORD>(_ReturnAddress());
}
} // namespace
MULTIPROCESS_TEST_MAIN(MinidumpDumperProcess) {
// Retrieve information from the command line.
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (!cmd_line->HasSwitch(kSwitchPid) || !cmd_line->HasSwitch(kSwitchTid) ||
!cmd_line->HasSwitch(kSwitchExceptionPtrs) ||
!cmd_line->HasSwitch(kSwitchMinidumpPath)) {
return 1;
}
std::string pid_string = cmd_line->GetSwitchValueASCII(kSwitchPid);
unsigned pid_uint = 0U;
if (!base::StringToUint(pid_string, &pid_uint))
return 1;
base::ProcessId pid = static_cast<base::ProcessId>(pid_uint);
std::string thread_id_string = cmd_line->GetSwitchValueASCII(kSwitchTid);
unsigned thread_id = 0U;
if (!base::StringToUint(thread_id_string, &thread_id))
return 1;
std::string exception_ptrs_string =
cmd_line->GetSwitchValueASCII(kSwitchExceptionPtrs);
unsigned exception_ptrs = 0ULL;
if (!base::StringToUint(exception_ptrs_string, &exception_ptrs))
return 1;
std::string minidump_type_string =
cmd_line->GetSwitchValueASCII(kSwitchMinidumpType);
uint32_t minidump_type = 0U;
if (!base::StringToUint(minidump_type_string, &minidump_type))
return 1;
base::FilePath minidump_path =
cmd_line->GetSwitchValuePath(kSwitchMinidumpPath);
// Get handles to dumpee and dump file.
base::Process dumpee_process = base::Process::OpenWithAccess(
pid, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ);
if (!dumpee_process.IsValid()) {
LOG(ERROR) << "Failed to open process: " << ::common::LogWe() << ".";
return 1;
}
base::File minidump_file(minidump_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
if (!minidump_file.IsValid()) {
LOG(ERROR) << "Failed to create minidump file: " << minidump_path.value();
return 1;
}
// Build the dump related information.
MINIDUMP_EXCEPTION_INFORMATION exception_information = {};
exception_information.ThreadId = static_cast<DWORD>(thread_id);
exception_information.ExceptionPointers =
reinterpret_cast<PEXCEPTION_POINTERS>(exception_ptrs);
exception_information.ClientPointers = true;
MINIDUMP_USER_STREAM_INFORMATION* user_info = nullptr;
MINIDUMP_CALLBACK_INFORMATION* callback_info = nullptr;
// Take the minidump.
if (::MiniDumpWriteDump(
dumpee_process.Handle(), pid, minidump_file.GetPlatformFile(),
static_cast<MINIDUMP_TYPE>(minidump_type), &exception_information,
user_info, callback_info) == FALSE) {
LOG(ERROR) << "MiniDumpWriteDump failed: " << ::common::LogWe() << ".";
return 1;
}
return 0;
}
bool ScopedMinidump::GenerateMinidump(uint32_t minidump_type) {
// Determine minidump path.
if (!temp_dir_.CreateUniqueTempDir())
return false;
minidump_path_ = temp_dir_.path().Append(kMinidumpFileName);
// Grab a context. RtlCaptureContext sets the instruction pointer, stack
// pointer and base pointer to values from this function's callee (similar
// to _ReturnAddress). Override them so they actually match the context.
// TODO(manzagop): package this to a utility function.
CONTEXT context = {};
::RtlCaptureContext(&context);
__asm {
mov context.Ebp, ebp
mov context.Esp, esp
}
context.Eip = GetEip();
// Build the exception information.
EXCEPTION_RECORD exception = {};
exception.ExceptionCode = 0xCAFEBABE; // Note: a random error code.
exception.ExceptionAddress = reinterpret_cast<PVOID>(context.Eip);
EXCEPTION_POINTERS exception_pointers = {&exception, &context};
// Build the dumper's command line.
base::CommandLine dumper_command_line(
base::GetMultiProcessTestChildBaseCommandLine());
dumper_command_line.AppendSwitchASCII(switches::kTestChildProcess,
"MinidumpDumperProcess");
base::Process current_process = base::Process::Current();
dumper_command_line.AppendSwitchASCII(
kSwitchPid, base::UintToString(current_process.Pid()));
dumper_command_line.AppendSwitchASCII(
kSwitchTid, base::UintToString(::GetCurrentThreadId()));
unsigned exception_pointers_uint =
reinterpret_cast<unsigned>(&exception_pointers);
dumper_command_line.AppendSwitchASCII(
kSwitchExceptionPtrs, base::UintToString(exception_pointers_uint));
dumper_command_line.AppendSwitchASCII(kSwitchMinidumpType,
base::UintToString(minidump_type));
dumper_command_line.AppendSwitchPath(kSwitchMinidumpPath, minidump_path());
// Launch the dumper.
base::Process dumper_process =
base::LaunchProcess(dumper_command_line, base::LaunchOptions());
int exit_code = 0;
bool success = dumper_process.WaitForExitWithTimeout(
TestTimeouts::action_timeout(), &exit_code);
if (!success) {
dumper_process.Terminate(0, true);
return false;
}
return exit_code == 0;
}
ScopedHeap::ScopedHeap() : heap_(nullptr) {
}
ScopedHeap::~ScopedHeap() {
if (heap_ != nullptr) {
EXPECT_TRUE(::HeapDestroy(heap_));
heap_ = nullptr;
}
}
bool ScopedHeap::Create() {
CHECK(heap_ == nullptr);
heap_ = ::HeapCreate(0, 0, 0);
return heap_ != nullptr;
}
void* ScopedHeap::Allocate(size_t block_size) {
CHECK(heap_ != nullptr);
return ::HeapAlloc(heap_, 0, block_size);
}
bool ScopedHeap::Free(void* block) {
CHECK(heap_ != nullptr);
return ::HeapFree(heap_, 0, block);
}
bool ScopedHeap::IsLFHBlock(const void* block) {
const uint32_t* ptr = reinterpret_cast<const uint32_t*>(block);
__try {
// Search back a bounded distance for the LFH bin signature.
for (size_t i = 0; i < 32; ++i) {
const uint32_t kLFHBinSignature = 0xF0E0D0C0;
if (*ptr-- == kLFHBinSignature)
return true;
}
} __except (EXCEPTION_EXECUTE_HANDLER) { // NOLINT
// Note that git cl format yields the above, which is then contrary to
// our presubmit checks.
// On exception, we conclude this isn't an LFH block.
}
return false;
}
refinery::Address ToAddress(const void* ptr) {
return static_cast<refinery::Address>(reinterpret_cast<uintptr_t>(ptr));
}
bool IsAppVerifierActive() {
// TODO(siggi): This seems like a solid proxy for verifier present.
return ::GetModuleHandle(L"verifier.dll") != nullptr;
}
} // namespace testing