win: Add implementation of ProcessInfo
This is as a precursor to ProcessReader. Some basic functionality is included for now, with more to be added later as necessary. The PEB code is pretty icky -- walking the doubly-linked list in the target's address space is cumbersome. The alternative is to use EnumProcessModules. That would work but: 1) needs different APIs for XP and Vista 64+ 2) retrieves modules in memory-location order, rather than initialization order. I felt retrieving them in initialization order might be useful when detecting third party DLL injections. In the end, we may want to make both orders available. R=mark@chromium.org BUG=crashpad:1 Review URL: https://codereview.chromium.org/977003003
This commit is contained in:
Родитель
ae1ccf621b
Коммит
bed7a543c0
|
@ -129,6 +129,8 @@
|
||||||
'synchronization/semaphore_posix.cc',
|
'synchronization/semaphore_posix.cc',
|
||||||
'synchronization/semaphore_win.cc',
|
'synchronization/semaphore_win.cc',
|
||||||
'synchronization/semaphore.h',
|
'synchronization/semaphore.h',
|
||||||
|
'win/process_info.cc',
|
||||||
|
'win/process_info.h',
|
||||||
'win/scoped_handle.cc',
|
'win/scoped_handle.cc',
|
||||||
'win/scoped_handle.h',
|
'win/scoped_handle.h',
|
||||||
'win/time.cc',
|
'win/time.cc',
|
||||||
|
@ -308,6 +310,7 @@
|
||||||
'test/multiprocess_exec_test.cc',
|
'test/multiprocess_exec_test.cc',
|
||||||
'test/multiprocess_posix_test.cc',
|
'test/multiprocess_posix_test.cc',
|
||||||
'test/scoped_temp_dir_test.cc',
|
'test/scoped_temp_dir_test.cc',
|
||||||
|
'win/process_info_test.cc',
|
||||||
'win/time_test.cc',
|
'win/time_test.cc',
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
|
@ -318,6 +321,13 @@
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
['OS=="win"', {
|
||||||
|
'link_settings': {
|
||||||
|
'libraries': [
|
||||||
|
'-lrpcrt4.lib',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -328,4 +338,28 @@
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'conditions': [
|
||||||
|
['OS=="win"', {
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'util_test_process_info_test_child',
|
||||||
|
'type': 'executable',
|
||||||
|
'sources': [
|
||||||
|
'win/process_info_test_child.cc',
|
||||||
|
],
|
||||||
|
# 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',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
// 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 "base/logging.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
NTSTATUS NtQueryInformationProcess(HANDLE process_handle,
|
||||||
|
PROCESSINFOCLASS process_information_class,
|
||||||
|
PVOID process_information,
|
||||||
|
ULONG process_information_length,
|
||||||
|
PULONG return_length) {
|
||||||
|
static decltype(::NtQueryInformationProcess)* nt_query_information_process =
|
||||||
|
reinterpret_cast<decltype(::NtQueryInformationProcess)*>(GetProcAddress(
|
||||||
|
LoadLibrary(L"ntdll.dll"), "NtQueryInformationProcess"));
|
||||||
|
DCHECK(nt_query_information_process);
|
||||||
|
return nt_query_information_process(process_handle,
|
||||||
|
process_information_class,
|
||||||
|
process_information,
|
||||||
|
process_information_length,
|
||||||
|
return_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsProcessWow64(HANDLE process_handle) {
|
||||||
|
static decltype(IsWow64Process)* is_wow64_process =
|
||||||
|
reinterpret_cast<decltype(IsWow64Process)*>(
|
||||||
|
GetProcAddress(LoadLibrary(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadUnicodeString(HANDLE process,
|
||||||
|
const UNICODE_STRING& 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, 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, uintptr_t 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PEB_LDR_DATA in winternl.h doesn't document the trailing
|
||||||
|
// InInitializationOrderModuleList field. See `dt ntdll!PEB_LDR_DATA`.
|
||||||
|
struct FULL_PEB_LDR_DATA : public PEB_LDR_DATA {
|
||||||
|
LIST_ENTRY InInitializationOrderModuleList;
|
||||||
|
};
|
||||||
|
|
||||||
|
// LDR_DATA_TABLE_ENTRY doesn't include InInitializationOrderLinks, define a
|
||||||
|
// complete version here. See `dt ntdll!_LDR_DATA_TABLE_ENTRY`.
|
||||||
|
struct FULL_LDR_DATA_TABLE_ENTRY {
|
||||||
|
LIST_ENTRY InLoadOrderLinks;
|
||||||
|
LIST_ENTRY InMemoryOrderLinks;
|
||||||
|
LIST_ENTRY InInitializationOrderLinks;
|
||||||
|
PVOID DllBase;
|
||||||
|
PVOID EntryPoint;
|
||||||
|
ULONG SizeOfImage;
|
||||||
|
UNICODE_STRING FullDllName;
|
||||||
|
UNICODE_STRING BaseDllName;
|
||||||
|
ULONG Flags;
|
||||||
|
WORD LoadCount;
|
||||||
|
WORD TlsIndex;
|
||||||
|
LIST_ENTRY HashLinks;
|
||||||
|
ULONG TimeDateStamp;
|
||||||
|
_ACTIVATION_CONTEXT* EntryPointActivationContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ProcessInfo::ProcessInfo()
|
||||||
|
: process_basic_information_(),
|
||||||
|
command_line_(),
|
||||||
|
modules_(),
|
||||||
|
is_64_bit_(false),
|
||||||
|
is_wow64_(false),
|
||||||
|
initialized_() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessInfo::~ProcessInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessInfo::Initialize(HANDLE process) {
|
||||||
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||||
|
|
||||||
|
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 ARCH_CPU_64_BITS
|
||||||
|
if (!is_64_bit_) {
|
||||||
|
LOG(ERROR) << "Reading different bitness not yet supported";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (is_64_bit_) {
|
||||||
|
LOG(ERROR) << "Reading x64 process from x86 process not supported";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ULONG bytes_returned;
|
||||||
|
NTSTATUS status =
|
||||||
|
crashpad::NtQueryInformationProcess(process,
|
||||||
|
ProcessBasicInformation,
|
||||||
|
&process_basic_information_,
|
||||||
|
sizeof(process_basic_information_),
|
||||||
|
&bytes_returned);
|
||||||
|
if (status < 0) {
|
||||||
|
LOG(ERROR) << "NtQueryInformationProcess: status=" << status;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bytes_returned != sizeof(process_basic_information_)) {
|
||||||
|
LOG(ERROR) << "NtQueryInformationProcess incorrect size";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read the process environment block.
|
||||||
|
PEB peb;
|
||||||
|
if (!ReadStruct(process,
|
||||||
|
reinterpret_cast<uintptr_t>(
|
||||||
|
process_basic_information_.PebBaseAddress),
|
||||||
|
&peb)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTL_USER_PROCESS_PARAMETERS process_parameters;
|
||||||
|
if (!ReadStruct(process,
|
||||||
|
reinterpret_cast<uintptr_t>(peb.ProcessParameters),
|
||||||
|
&process_parameters)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ReadUnicodeString(
|
||||||
|
process, process_parameters.CommandLine, &command_line_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FULL_PEB_LDR_DATA peb_ldr_data;
|
||||||
|
if (!ReadStruct(process, reinterpret_cast<uintptr_t>(peb.Ldr), &peb_ldr_data))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Include the first module in the memory order list to get our own name as
|
||||||
|
// it's not included in initialization order below.
|
||||||
|
std::wstring self_module;
|
||||||
|
FULL_LDR_DATA_TABLE_ENTRY self_ldr_data_table_entry;
|
||||||
|
if (!ReadStruct(process,
|
||||||
|
reinterpret_cast<uintptr_t>(
|
||||||
|
reinterpret_cast<const char*>(
|
||||||
|
peb_ldr_data.InMemoryOrderModuleList.Flink) -
|
||||||
|
offsetof(FULL_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)),
|
||||||
|
&self_ldr_data_table_entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ReadUnicodeString(
|
||||||
|
process, self_ldr_data_table_entry.FullDllName, &self_module)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
modules_.push_back(self_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.
|
||||||
|
const LIST_ENTRY* last = peb_ldr_data.InInitializationOrderModuleList.Blink;
|
||||||
|
FULL_LDR_DATA_TABLE_ENTRY ldr_data_table_entry;
|
||||||
|
for (const LIST_ENTRY* cur =
|
||||||
|
peb_ldr_data.InInitializationOrderModuleList.Flink;
|
||||||
|
;
|
||||||
|
cur = ldr_data_table_entry.InInitializationOrderLinks.Flink) {
|
||||||
|
// |cur| is the pointer to the LIST_ENTRY embedded in the
|
||||||
|
// FULL_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,
|
||||||
|
reinterpret_cast<uintptr_t>(reinterpret_cast<const char*>(cur) -
|
||||||
|
offsetof(FULL_LDR_DATA_TABLE_ENTRY,
|
||||||
|
InInitializationOrderLinks)),
|
||||||
|
&ldr_data_table_entry)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO(scottmg): Capture TimeDateStamp, Checksum, etc. too?
|
||||||
|
std::wstring module;
|
||||||
|
if (!ReadUnicodeString(process, ldr_data_table_entry.FullDllName, &module))
|
||||||
|
break;
|
||||||
|
modules_.push_back(module);
|
||||||
|
if (cur == last)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_basic_information_.UniqueProcessId;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t ProcessInfo::ParentProcessID() const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
return process_basic_information_.InheritedFromUniqueProcessId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessInfo::CommandLine(std::wstring* command_line) const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
*command_line = command_line_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessInfo::Modules(std::vector<std::wstring>* modules) const {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
*modules = modules_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright 2015 The Crashpad Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_UTIL_WIN_PROCESS_INFO_H_
|
||||||
|
#define CRASHPAD_UTIL_WIN_PROCESS_INFO_H_
|
||||||
|
|
||||||
|
#include <basetsd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "base/basictypes.h"
|
||||||
|
#include "util/misc/initialization_state_dcheck.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
//! \brief This structure matches PROCESS_BASIC_INFORMATION in winternl.h but
|
||||||
|
//! gives names to the Reserved fields (matching the WDK's ntddk.h).
|
||||||
|
struct FULL_PROCESS_BASIC_INFORMATION {
|
||||||
|
NTSTATUS ExitStatus;
|
||||||
|
PPEB PebBaseAddress;
|
||||||
|
KAFFINITY AffinityMask;
|
||||||
|
PVOID BasePriority;
|
||||||
|
ULONG UniqueProcessId;
|
||||||
|
ULONG InheritedFromUniqueProcessId;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
//! \brief Gathers information about a process given its `HANDLE`. This consists
|
||||||
|
//! primarily of information stored in the Process Environment Block.
|
||||||
|
class ProcessInfo {
|
||||||
|
public:
|
||||||
|
ProcessInfo();
|
||||||
|
~ProcessInfo();
|
||||||
|
|
||||||
|
//! \brief Initializes this object with information about the given
|
||||||
|
//! \a process.
|
||||||
|
//!
|
||||||
|
//! This method must be called successfully prior to calling any other
|
||||||
|
//! method in this class. This method may only be called once.
|
||||||
|
//!
|
||||||
|
//! \return `true` on success, `false` on failure with a message logged.
|
||||||
|
bool Initialize(HANDLE process);
|
||||||
|
|
||||||
|
//! \return `true` if the target process is a 64-bit process.
|
||||||
|
bool Is64Bit() const;
|
||||||
|
|
||||||
|
//! \return `true` if the target process is running on the Win32-on-Win64
|
||||||
|
//! subsystem.
|
||||||
|
bool IsWow64() const;
|
||||||
|
|
||||||
|
//! \return The target process's process ID.
|
||||||
|
pid_t ProcessID() const;
|
||||||
|
|
||||||
|
//! \return The target process's parent process ID.
|
||||||
|
pid_t ParentProcessID() const;
|
||||||
|
|
||||||
|
//! \return The command line from the target process's Process Environment
|
||||||
|
//! Block.
|
||||||
|
bool CommandLine(std::wstring* command_line) const;
|
||||||
|
|
||||||
|
//! \brief Retrieves the modules loaded into the target process.
|
||||||
|
//!
|
||||||
|
//! The modules are enumerated in initialization order as detailed in the
|
||||||
|
//! Process Environment Block. The main executable will always be the
|
||||||
|
//! first element.
|
||||||
|
bool Modules(std::vector<std::wstring>* modules) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
internal::FULL_PROCESS_BASIC_INFORMATION process_basic_information_;
|
||||||
|
std::wstring command_line_;
|
||||||
|
std::vector<std::wstring> modules_;
|
||||||
|
bool is_64_bit_;
|
||||||
|
bool is_wow64_;
|
||||||
|
InitializationStateDcheck initialized_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ProcessInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_UTIL_WIN_PROCESS_INFO_H_
|
|
@ -0,0 +1,134 @@
|
||||||
|
// 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 <rpc.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
#include "base/files/file_path.h"
|
||||||
|
#include "build/build_config.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "util/misc/uuid.h"
|
||||||
|
#include "util/test/executable_path.h"
|
||||||
|
#include "util/win/scoped_handle.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const wchar_t kNtdllName[] = L"\\ntdll.dll";
|
||||||
|
|
||||||
|
TEST(ProcessInfo, Self) {
|
||||||
|
ProcessInfo process_info;
|
||||||
|
ASSERT_TRUE(process_info.Initialize(GetCurrentProcess()));
|
||||||
|
EXPECT_EQ(GetCurrentProcessId(), process_info.ProcessID());
|
||||||
|
EXPECT_GT(process_info.ParentProcessID(), 0u);
|
||||||
|
|
||||||
|
#if defined(ARCH_CPU_64_BITS)
|
||||||
|
EXPECT_TRUE(process_info.Is64Bit());
|
||||||
|
EXPECT_FALSE(process_info.IsWow64());
|
||||||
|
#else
|
||||||
|
EXPECT_FALSE(process_info.Is64Bit());
|
||||||
|
// Assume we won't be running these tests on a 32 bit host OS.
|
||||||
|
EXPECT_TRUE(process_info.IsWow64());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::wstring command_line;
|
||||||
|
EXPECT_TRUE(process_info.CommandLine(&command_line));
|
||||||
|
EXPECT_EQ(std::wstring(GetCommandLine()), command_line);
|
||||||
|
|
||||||
|
std::vector<std::wstring> modules;
|
||||||
|
EXPECT_TRUE(process_info.Modules(&modules));
|
||||||
|
ASSERT_GE(modules.size(), 2u);
|
||||||
|
const wchar_t kSelfName[] = L"\\util_test.exe";
|
||||||
|
ASSERT_GE(modules[0].size(), wcslen(kSelfName));
|
||||||
|
EXPECT_EQ(kSelfName,
|
||||||
|
modules[0].substr(modules[0].size() - wcslen(kSelfName)));
|
||||||
|
ASSERT_GE(modules[1].size(), wcslen(kNtdllName));
|
||||||
|
EXPECT_EQ(kNtdllName,
|
||||||
|
modules[1].substr(modules[1].size() - wcslen(kNtdllName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProcessInfo, SomeOtherProcess) {
|
||||||
|
ProcessInfo process_info;
|
||||||
|
|
||||||
|
::UUID system_uuid;
|
||||||
|
ASSERT_EQ(RPC_S_OK, UuidCreate(&system_uuid));
|
||||||
|
UUID started_uuid(reinterpret_cast<const uint8_t*>(&system_uuid.Data1));
|
||||||
|
ASSERT_EQ(RPC_S_OK, UuidCreate(&system_uuid));
|
||||||
|
UUID done_uuid(reinterpret_cast<const uint8_t*>(&system_uuid.Data1));
|
||||||
|
|
||||||
|
ScopedKernelHANDLE started(
|
||||||
|
CreateEvent(nullptr, true, false, started_uuid.ToString16().c_str()));
|
||||||
|
ASSERT_TRUE(started.get());
|
||||||
|
ScopedKernelHANDLE done(
|
||||||
|
CreateEvent(nullptr, true, false, done_uuid.ToString16().c_str()));
|
||||||
|
ASSERT_TRUE(done.get());
|
||||||
|
|
||||||
|
base::FilePath test_executable = ExecutablePath();
|
||||||
|
std::wstring child_test_executable =
|
||||||
|
test_executable.RemoveFinalExtension().value() +
|
||||||
|
L"_process_info_test_child.exe";
|
||||||
|
// TODO(scottmg): Command line escaping utility.
|
||||||
|
std::wstring command_line = child_test_executable + L" " +
|
||||||
|
started_uuid.ToString16() + L" " +
|
||||||
|
done_uuid.ToString16();
|
||||||
|
STARTUPINFO startup_info = {0};
|
||||||
|
startup_info.cb = sizeof(startup_info);
|
||||||
|
PROCESS_INFORMATION process_information;
|
||||||
|
ASSERT_TRUE(CreateProcess(child_test_executable.c_str(),
|
||||||
|
&command_line[0],
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
false,
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Wait until the test has completed initialization.
|
||||||
|
ASSERT_EQ(WaitForSingleObject(started.get(), INFINITE), WAIT_OBJECT_0);
|
||||||
|
|
||||||
|
ASSERT_TRUE(process_info.Initialize(process_information.hProcess));
|
||||||
|
|
||||||
|
// Tell the test it's OK to shut down now that we've read our data.
|
||||||
|
SetEvent(done.get());
|
||||||
|
|
||||||
|
std::vector<std::wstring> modules;
|
||||||
|
EXPECT_TRUE(process_info.Modules(&modules));
|
||||||
|
ASSERT_GE(modules.size(), 3u);
|
||||||
|
const wchar_t kChildName[] = L"\\util_test_process_info_test_child.exe";
|
||||||
|
ASSERT_GE(modules[0].size(), wcslen(kChildName));
|
||||||
|
EXPECT_EQ(kChildName,
|
||||||
|
modules[0].substr(modules[0].size() - wcslen(kChildName)));
|
||||||
|
ASSERT_GE(modules[1].size(), wcslen(kNtdllName));
|
||||||
|
EXPECT_EQ(kNtdllName,
|
||||||
|
modules[1].substr(modules[1].size() - wcslen(kNtdllName)));
|
||||||
|
// lz32.dll is an uncommonly-used-but-always-available module that the test
|
||||||
|
// binary manually loads.
|
||||||
|
const wchar_t kLz32dllName[] = L"\\lz32.dll";
|
||||||
|
ASSERT_GE(modules.back().size(), wcslen(kLz32dllName));
|
||||||
|
EXPECT_EQ(
|
||||||
|
kLz32dllName,
|
||||||
|
modules.back().substr(modules.back().size() - wcslen(kLz32dllName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
|
@ -0,0 +1,45 @@
|
||||||
|
// 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 <stdlib.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// A simple binary to be loaded and inspected by ProcessInfo.
|
||||||
|
int wmain(int argc, wchar_t** argv) {
|
||||||
|
if (argc != 3)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
// Get handles to the events we use to communicate with our parent.
|
||||||
|
HANDLE started_event = CreateEvent(nullptr, true, false, argv[1]);
|
||||||
|
HANDLE done_event = CreateEvent(nullptr, true, false, argv[2]);
|
||||||
|
if (!started_event || !done_event)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
// Load an unusual module (that we don't depend upon) so we can do an
|
||||||
|
// existence check.
|
||||||
|
if (!LoadLibrary(L"lz32.dll"))
|
||||||
|
abort();
|
||||||
|
|
||||||
|
if (!SetEvent(started_event))
|
||||||
|
abort();
|
||||||
|
|
||||||
|
if (WaitForSingleObject(done_event, INFINITE) != WAIT_OBJECT_0)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
CloseHandle(done_event);
|
||||||
|
CloseHandle(started_event);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче