crashpad/util/win/process_info.cc
Scott Graham 4860f64923 win: Handle binary with embedded CodeView debug record
I considered writing the CodeView records to the minidump, but I didn't
find a ton of docs and debugging is only lightly supported (e.g.
http://www.debuginfo.com/articles/gendebuginfo.html#debuggersandformats
and it doesn't attempt to load at all on more recent Visual Studios).

As we won't be generating symbols in this format, and we don't expect to
have symbols for any weird modules that get injected into us in the
wild, it seems like we don't lose anything by just ignoring them.

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

Review URL: https://codereview.chromium.org/1430773003 .
2015-10-31 11:45:39 -07:00

693 lines
24 KiB
C++

// 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/process_info.h"
#include <winternl.h>
#include <algorithm>
#include <limits>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/template_util.h"
#include "build/build_config.h"
#include "util/numeric/safe_assignment.h"
#include "util/win/get_function.h"
#include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h"
#include "util/win/process_structs.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
namespace {
NTSTATUS NtQueryInformationProcess(HANDLE process_handle,
PROCESSINFOCLASS process_information_class,
PVOID process_information,
ULONG process_information_length,
PULONG return_length) {
static const auto nt_query_information_process =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryInformationProcess);
return nt_query_information_process(process_handle,
process_information_class,
process_information,
process_information_length,
return_length);
}
bool IsProcessWow64(HANDLE process_handle) {
static const auto is_wow64_process =
GET_FUNCTION(L"kernel32.dll", ::IsWow64Process);
if (!is_wow64_process)
return false;
BOOL is_wow64;
if (!is_wow64_process(process_handle, &is_wow64)) {
PLOG(ERROR) << "IsWow64Process";
return false;
}
return !!is_wow64;
}
template <class T>
bool ReadUnicodeString(HANDLE process,
const process_types::UNICODE_STRING<T>& us,
std::wstring* result) {
if (us.Length == 0) {
result->clear();
return true;
}
DCHECK_EQ(us.Length % sizeof(wchar_t), 0u);
result->resize(us.Length / sizeof(wchar_t));
SIZE_T bytes_read;
if (!ReadProcessMemory(
process,
reinterpret_cast<const void*>(static_cast<uintptr_t>(us.Buffer)),
&result->operator[](0),
us.Length,
&bytes_read)) {
PLOG(ERROR) << "ReadProcessMemory UNICODE_STRING";
return false;
}
if (bytes_read != us.Length) {
LOG(ERROR) << "ReadProcessMemory UNICODE_STRING incorrect size";
return false;
}
return true;
}
template <class T>
bool ReadStruct(HANDLE process, WinVMAddress at, T* into) {
SIZE_T bytes_read;
if (!ReadProcessMemory(process,
reinterpret_cast<const void*>(at),
into,
sizeof(T),
&bytes_read)) {
// We don't have a name for the type we're reading, so include the signature
// to get the type of T.
PLOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__;
return false;
}
if (bytes_read != sizeof(T)) {
LOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__ << " incorrect size";
return false;
}
return true;
}
bool RegionIsAccessible(const MEMORY_BASIC_INFORMATION64& memory_info) {
return memory_info.State == MEM_COMMIT &&
(memory_info.Protect & PAGE_NOACCESS) == 0 &&
(memory_info.Protect & PAGE_GUARD) == 0;
}
MEMORY_BASIC_INFORMATION64 MemoryBasicInformationToMemoryBasicInformation64(
const MEMORY_BASIC_INFORMATION& mbi) {
MEMORY_BASIC_INFORMATION64 mbi64 = {0};
mbi64.BaseAddress = reinterpret_cast<ULONGLONG>(mbi.BaseAddress);
mbi64.AllocationBase = reinterpret_cast<ULONGLONG>(mbi.AllocationBase);
mbi64.AllocationProtect = mbi.AllocationProtect;
mbi64.RegionSize = mbi.RegionSize;
mbi64.State = mbi.State;
mbi64.Protect = mbi.Protect;
mbi64.Type = mbi.Type;
return mbi64;
}
// NtQueryObject with a retry for size mismatch as well as a minimum size to
// retrieve (and expect).
scoped_ptr<uint8_t[]> QueryObject(
HANDLE handle,
OBJECT_INFORMATION_CLASS object_information_class,
ULONG minimum_size) {
ULONG size = minimum_size;
ULONG return_length;
scoped_ptr<uint8_t[]> buffer(new uint8_t[size]);
NTSTATUS status = crashpad::NtQueryObject(
handle, object_information_class, buffer.get(), size, &return_length);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
DCHECK_GT(return_length, size);
size = return_length;
buffer.reset(new uint8_t[size]);
status = crashpad::NtQueryObject(
handle, object_information_class, buffer.get(), size, &return_length);
}
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status) << "NtQueryObject";
return scoped_ptr<uint8_t[]>();
}
DCHECK_GE(return_length, minimum_size);
return buffer.Pass();
}
} // namespace
template <class Traits>
bool GetProcessBasicInformation(HANDLE process,
bool is_wow64,
ProcessInfo* process_info,
WinVMAddress* peb_address,
WinVMSize* peb_size) {
ULONG bytes_returned;
process_types::PROCESS_BASIC_INFORMATION<Traits> process_basic_information;
NTSTATUS status =
crashpad::NtQueryInformationProcess(process,
ProcessBasicInformation,
&process_basic_information,
sizeof(process_basic_information),
&bytes_returned);
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess";
return false;
}
if (bytes_returned != sizeof(process_basic_information)) {
LOG(ERROR) << "NtQueryInformationProcess incorrect size";
return false;
}
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203 on
// 32 bit being the correct size for HANDLEs for proceses, even on Windows
// x64. API functions (e.g. OpenProcess) take only a DWORD, so there's no
// sense in maintaining the top bits.
process_info->process_id_ =
static_cast<DWORD>(process_basic_information.UniqueProcessId);
process_info->inherited_from_process_id_ = static_cast<DWORD>(
process_basic_information.InheritedFromUniqueProcessId);
// We now want to read the PEB to gather the rest of our information. The
// PebBaseAddress as returned above is what we want for 64-on-64 and 32-on-32,
// but for Wow64, we want to read the 32 bit PEB (a Wow64 process has both).
// The address of this is found by a second call to NtQueryInformationProcess.
if (!is_wow64) {
*peb_address = process_basic_information.PebBaseAddress;
*peb_size = sizeof(process_types::PEB<Traits>);
} else {
ULONG_PTR wow64_peb_address;
status = crashpad::NtQueryInformationProcess(process,
ProcessWow64Information,
&wow64_peb_address,
sizeof(wow64_peb_address),
&bytes_returned);
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status), "NtQueryInformationProcess";
return false;
}
if (bytes_returned != sizeof(wow64_peb_address)) {
LOG(ERROR) << "NtQueryInformationProcess incorrect size";
return false;
}
*peb_address = wow64_peb_address;
*peb_size = sizeof(process_types::PEB<process_types::internal::Traits32>);
}
return true;
}
template <class Traits>
bool ReadProcessData(HANDLE process,
WinVMAddress peb_address_vmaddr,
ProcessInfo* process_info) {
Traits::Pointer peb_address;
if (!AssignIfInRange(&peb_address, peb_address_vmaddr)) {
LOG(ERROR) << base::StringPrintf("peb address 0x%x out of range",
peb_address_vmaddr);
return false;
}
// Try to read the process environment block.
process_types::PEB<Traits> peb;
if (!ReadStruct(process, peb_address, &peb))
return false;
process_types::RTL_USER_PROCESS_PARAMETERS<Traits> process_parameters;
if (!ReadStruct(process, peb.ProcessParameters, &process_parameters))
return false;
if (!ReadUnicodeString(process,
process_parameters.CommandLine,
&process_info->command_line_)) {
return false;
}
process_types::PEB_LDR_DATA<Traits> peb_ldr_data;
if (!ReadStruct(process, peb.Ldr, &peb_ldr_data))
return false;
process_types::LDR_DATA_TABLE_ENTRY<Traits> ldr_data_table_entry;
// Include the first module in the memory order list to get our the main
// executable's name, as it's not included in initialization order below.
if (!ReadStruct(process,
static_cast<WinVMAddress>(
peb_ldr_data.InMemoryOrderModuleList.Flink) -
offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>,
InMemoryOrderLinks),
&ldr_data_table_entry)) {
return false;
}
ProcessInfo::Module module;
if (!ReadUnicodeString(
process, ldr_data_table_entry.FullDllName, &module.name)) {
return false;
}
module.dll_base = ldr_data_table_entry.DllBase;
module.size = ldr_data_table_entry.SizeOfImage;
module.timestamp = ldr_data_table_entry.TimeDateStamp;
process_info->modules_.push_back(module);
// Walk the PEB LDR structure (doubly-linked list) to get the list of loaded
// modules. We use this method rather than EnumProcessModules to get the
// modules in initialization order rather than memory order.
Traits::Pointer last = peb_ldr_data.InInitializationOrderModuleList.Blink;
for (Traits::Pointer cur = peb_ldr_data.InInitializationOrderModuleList.Flink;
;
cur = ldr_data_table_entry.InInitializationOrderLinks.Flink) {
// |cur| is the pointer to the LIST_ENTRY embedded in the
// LDR_DATA_TABLE_ENTRY, in the target process's address space. So we need
// to read from the target, and also offset back to the beginning of the
// structure.
if (!ReadStruct(process,
static_cast<WinVMAddress>(cur) -
offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>,
InInitializationOrderLinks),
&ldr_data_table_entry)) {
break;
}
// TODO(scottmg): Capture Checksum, etc. too?
if (!ReadUnicodeString(
process, ldr_data_table_entry.FullDllName, &module.name)) {
break;
}
module.dll_base = ldr_data_table_entry.DllBase;
module.size = ldr_data_table_entry.SizeOfImage;
module.timestamp = ldr_data_table_entry.TimeDateStamp;
process_info->modules_.push_back(module);
if (cur == last)
break;
}
return true;
}
bool ReadMemoryInfo(HANDLE process, bool is_64_bit, ProcessInfo* process_info) {
DCHECK(process_info->memory_info_.empty());
const WinVMAddress min_address = 0;
// We can't use GetSystemInfo() to get the address space range for another
// process. VirtualQueryEx() will fail with ERROR_INVALID_PARAMETER if the
// address is above the highest memory address accessible to the process, so
// we just probe the entire potential range (2^32 for x86, or 2^64 for x64).
const WinVMAddress max_address = is_64_bit
? std::numeric_limits<uint64_t>::max()
: std::numeric_limits<uint32_t>::max();
MEMORY_BASIC_INFORMATION memory_basic_information;
for (WinVMAddress address = min_address; address <= max_address;
address += memory_basic_information.RegionSize) {
size_t result = VirtualQueryEx(process,
reinterpret_cast<void*>(address),
&memory_basic_information,
sizeof(memory_basic_information));
if (result == 0) {
if (GetLastError() == ERROR_INVALID_PARAMETER)
break;
PLOG(ERROR) << "VirtualQueryEx";
return false;
}
process_info->memory_info_.push_back(
MemoryBasicInformationToMemoryBasicInformation64(
memory_basic_information));
if (memory_basic_information.RegionSize == 0) {
LOG(ERROR) << "RegionSize == 0";
return false;
}
}
return true;
}
std::vector<ProcessInfo::Handle> ProcessInfo::BuildHandleVector(
HANDLE process) const {
ULONG buffer_size = 2 * 1024 * 1024;
scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
// Typically if the buffer were too small, STATUS_INFO_LENGTH_MISMATCH would
// return the correct size in the final argument, but it does not for
// SystemExtendedHandleInformation, so we loop and attempt larger sizes.
NTSTATUS status;
ULONG returned_length;
for (int tries = 0; tries < 5; ++tries) {
status = crashpad::NtQuerySystemInformation(
static_cast<SYSTEM_INFORMATION_CLASS>(SystemExtendedHandleInformation),
buffer.get(),
buffer_size,
&returned_length);
if (NT_SUCCESS(status) || status != STATUS_INFO_LENGTH_MISMATCH)
break;
buffer_size *= 2;
buffer.reset();
buffer.reset(new uint8_t[buffer_size]);
}
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status)
<< "NtQuerySystemInformation SystemExtendedHandleInformation";
return std::vector<Handle>();
}
const auto& system_handle_information_ex =
*reinterpret_cast<process_types::SYSTEM_HANDLE_INFORMATION_EX*>(
buffer.get());
DCHECK_LE(offsetof(process_types::SYSTEM_HANDLE_INFORMATION_EX, Handles) +
system_handle_information_ex.NumberOfHandles *
sizeof(system_handle_information_ex.Handles[0]),
returned_length);
std::vector<Handle> handles;
for (size_t i = 0; i < system_handle_information_ex.NumberOfHandles; ++i) {
const auto& handle = system_handle_information_ex.Handles[i];
if (handle.UniqueProcessId != process_id_)
continue;
Handle result_handle;
result_handle.handle =
static_cast<uint32_t>(reinterpret_cast<uintptr_t>(handle.HandleValue));
result_handle.attributes = handle.HandleAttributes;
result_handle.granted_access = handle.GrantedAccess;
// TODO(scottmg): Could special case for self.
HANDLE dup_handle;
if (DuplicateHandle(process,
reinterpret_cast<HANDLE>(handle.HandleValue),
GetCurrentProcess(),
&dup_handle,
0,
false,
DUPLICATE_SAME_ACCESS)) {
// Some handles cannot be duplicated, for example, handles of type
// EtwRegistration. If we fail to duplicate, then we can't gather any more
// information, but include the information that we do have already.
ScopedKernelHANDLE scoped_dup_handle(dup_handle);
scoped_ptr<uint8_t[]> object_basic_information_buffer =
QueryObject(dup_handle,
ObjectBasicInformation,
sizeof(PUBLIC_OBJECT_BASIC_INFORMATION));
if (object_basic_information_buffer) {
PUBLIC_OBJECT_BASIC_INFORMATION* object_basic_information =
reinterpret_cast<PUBLIC_OBJECT_BASIC_INFORMATION*>(
object_basic_information_buffer.get());
// The Attributes and GrantedAccess sometimes differ slightly between
// the data retrieved in SYSTEM_HANDLE_INFORMATION_EX and
// PUBLIC_OBJECT_TYPE_INFORMATION. We prefer the values in
// SYSTEM_HANDLE_INFORMATION_EX because they were retrieved from the
// target process, rather than on the duplicated handle, so don't use
// them here.
// Subtract one to account for our DuplicateHandle() and another for
// NtQueryObject() while the query was being executed.
DCHECK_GT(object_basic_information->PointerCount, 2u);
result_handle.pointer_count =
object_basic_information->PointerCount - 2;
// Subtract one to account for our DuplicateHandle().
DCHECK_GT(object_basic_information->HandleCount, 1u);
result_handle.handle_count = object_basic_information->HandleCount - 1;
}
scoped_ptr<uint8_t[]> object_type_information_buffer =
QueryObject(dup_handle,
ObjectTypeInformation,
sizeof(PUBLIC_OBJECT_TYPE_INFORMATION));
if (object_type_information_buffer) {
PUBLIC_OBJECT_TYPE_INFORMATION* object_type_information =
reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(
object_type_information_buffer.get());
DCHECK_EQ(object_type_information->TypeName.Length %
sizeof(result_handle.type_name[0]),
0u);
result_handle.type_name =
std::wstring(object_type_information->TypeName.Buffer,
object_type_information->TypeName.Length /
sizeof(result_handle.type_name[0]));
}
}
handles.push_back(result_handle);
}
return handles;
}
ProcessInfo::Module::Module() : name(), dll_base(0), size(0), timestamp() {
}
ProcessInfo::Module::~Module() {
}
ProcessInfo::Handle::Handle()
: type_name(),
handle(0),
attributes(0),
granted_access(0),
pointer_count(0),
handle_count(0) {
}
ProcessInfo::Handle::~Handle() {
}
ProcessInfo::ProcessInfo()
: process_id_(),
inherited_from_process_id_(),
process_(),
command_line_(),
peb_address_(0),
peb_size_(0),
modules_(),
memory_info_(),
handles_(),
is_64_bit_(false),
is_wow64_(false),
initialized_() {
}
ProcessInfo::~ProcessInfo() {
}
bool ProcessInfo::Initialize(HANDLE process) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
process_ = process;
is_wow64_ = IsProcessWow64(process);
if (is_wow64_) {
// If it's WoW64, then it's 32-on-64.
is_64_bit_ = false;
} else {
// Otherwise, it's either 32 on 32, or 64 on 64. Use GetSystemInfo() to
// distinguish between these two cases.
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
is_64_bit_ =
system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
}
#if defined(ARCH_CPU_32_BITS)
if (is_64_bit_) {
LOG(ERROR) << "Reading x64 process from x86 process not supported";
return false;
}
#endif // ARCH_CPU_32_BITS
#if defined(ARCH_CPU_64_BITS)
bool result = GetProcessBasicInformation<process_types::internal::Traits64>(
process, is_wow64_, this, &peb_address_, &peb_size_);
#else
bool result = GetProcessBasicInformation<process_types::internal::Traits32>(
process, false, this, &peb_address_, &peb_size_);
#endif // ARCH_CPU_64_BITS
if (!result) {
LOG(ERROR) << "GetProcessBasicInformation failed";
return false;
}
result = is_64_bit_ ? ReadProcessData<process_types::internal::Traits64>(
process, peb_address_, this)
: ReadProcessData<process_types::internal::Traits32>(
process, peb_address_, this);
if (!result) {
LOG(ERROR) << "ReadProcessData failed";
return false;
}
if (!ReadMemoryInfo(process, is_64_bit_, this)) {
LOG(ERROR) << "ReadMemoryInfo failed";
return false;
}
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
bool ProcessInfo::Is64Bit() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return is_64_bit_;
}
bool ProcessInfo::IsWow64() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return is_wow64_;
}
pid_t ProcessInfo::ProcessID() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return process_id_;
}
pid_t ProcessInfo::ParentProcessID() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return inherited_from_process_id_;
}
bool ProcessInfo::CommandLine(std::wstring* command_line) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
*command_line = command_line_;
return true;
}
void ProcessInfo::Peb(WinVMAddress* peb_address, WinVMSize* peb_size) const {
*peb_address = peb_address_;
*peb_size = peb_size_;
}
bool ProcessInfo::Modules(std::vector<Module>* modules) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
*modules = modules_;
return true;
}
const std::vector<MEMORY_BASIC_INFORMATION64>& ProcessInfo::MemoryInfo() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return memory_info_;
}
std::vector<CheckedRange<WinVMAddress, WinVMSize>>
ProcessInfo::GetReadableRanges(
const CheckedRange<WinVMAddress, WinVMSize>& range) const {
return GetReadableRangesOfMemoryMap(range, MemoryInfo());
}
bool ProcessInfo::LoggingRangeIsFullyReadable(
const CheckedRange<WinVMAddress, WinVMSize>& range) const {
const auto ranges = GetReadableRanges(range);
if (ranges.size() != 1) {
LOG(ERROR) << base::StringPrintf(
"range at 0x%llx, size 0x%llx fully unreadable",
range.base(),
range.size());
return false;
}
if (ranges[0].base() != range.base() || ranges[0].size() != range.size()) {
LOG(ERROR) << base::StringPrintf(
"some of range at 0x%llx, size 0x%llx unreadable",
range.base(),
range.size());
return false;
}
return true;
}
const std::vector<ProcessInfo::Handle>& ProcessInfo::Handles() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (handles_.empty())
handles_ = BuildHandleVector(process_);
return handles_;
}
std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap(
const CheckedRange<WinVMAddress, WinVMSize>& range,
const std::vector<MEMORY_BASIC_INFORMATION64>& memory_info) {
using Range = CheckedRange<WinVMAddress, WinVMSize>;
// Find all the ranges that overlap the target range, maintaining their order.
std::vector<MEMORY_BASIC_INFORMATION64> overlapping;
for (const auto& mi : memory_info) {
static_assert(base::is_same<decltype(mi.BaseAddress), WinVMAddress>::value,
"expected range address to be WinVMAddress");
static_assert(base::is_same<decltype(mi.RegionSize), WinVMSize>::value,
"expected range size to be WinVMSize");
if (range.OverlapsRange(Range(mi.BaseAddress, mi.RegionSize)))
overlapping.push_back(mi);
}
if (overlapping.empty())
return std::vector<Range>();
// For the first and last, trim to the boundary of the incoming range.
MEMORY_BASIC_INFORMATION64& front = overlapping.front();
WinVMAddress original_front_base_address = front.BaseAddress;
front.BaseAddress = std::max(front.BaseAddress, range.base());
front.RegionSize =
(original_front_base_address + front.RegionSize) - front.BaseAddress;
MEMORY_BASIC_INFORMATION64& back = overlapping.back();
WinVMAddress back_end = back.BaseAddress + back.RegionSize;
back.RegionSize = std::min(range.end(), back_end) - back.BaseAddress;
// Discard all non-accessible.
overlapping.erase(std::remove_if(overlapping.begin(),
overlapping.end(),
[](const MEMORY_BASIC_INFORMATION64& mbi) {
return !RegionIsAccessible(mbi);
}),
overlapping.end());
if (overlapping.empty())
return std::vector<Range>();
// Convert to return type.
std::vector<Range> as_ranges;
for (const auto& mi : overlapping) {
as_ranges.push_back(Range(mi.BaseAddress, mi.RegionSize));
DCHECK(as_ranges.back().IsValid());
}
// Coalesce remaining regions.
std::vector<Range> result;
result.push_back(as_ranges[0]);
for (size_t i = 1; i < as_ranges.size(); ++i) {
if (result.back().end() == as_ranges[i].base()) {
result.back().SetRange(result.back().base(),
result.back().size() + as_ranges[i].size());
} else {
result.push_back(as_ranges[i]);
}
DCHECK(result.back().IsValid());
}
return result;
}
} // namespace crashpad