From 56c8359b2765c27b2af9fcc528d841a1106c8edf Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Fri, 25 Sep 2015 21:11:04 -0700 Subject: [PATCH] win: Gather memory information Not yet written as MINIDUMP_MEMORY_INFO_LIST to minidump. R=mark@chromium.org BUG=crashpad:20, crashpad:46 Review URL: https://codereview.chromium.org/1369833002 . --- util/win/process_info.cc | 57 +++++++++++++++++++++++++++++ util/win/process_info.h | 40 ++++++++++++++++++++ util/win/process_info_test.cc | 42 ++++++++++++++++----- util/win/process_info_test_child.cc | 23 ++++++++---- 4 files changed, 146 insertions(+), 16 deletions(-) diff --git a/util/win/process_info.cc b/util/win/process_info.cc index ff8689ed..ed5262c7 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -253,12 +253,58 @@ bool ReadProcessData(HANDLE process, return true; } +bool ReadMemoryInfo(HANDLE process, ProcessInfo* process_info) { + DCHECK(process_info->memory_info_.empty()); + + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + const WinVMAddress min_address = + reinterpret_cast(system_info.lpMinimumApplicationAddress); + const WinVMAddress max_address = + reinterpret_cast(system_info.lpMaximumApplicationAddress); + 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(address), + &memory_basic_information, + sizeof(memory_basic_information)); + if (result == 0) { + PLOG(ERROR) << "VirtualQueryEx"; + return false; + } + + process_info->memory_info_.push_back( + ProcessInfo::MemoryInfo(memory_basic_information)); + + if (memory_basic_information.RegionSize == 0) { + LOG(ERROR) << "RegionSize == 0"; + return false; + } + } + + return true; +} + ProcessInfo::Module::Module() : name(), dll_base(0), size(0), timestamp() { } ProcessInfo::Module::~Module() { } +ProcessInfo::MemoryInfo::MemoryInfo(const MEMORY_BASIC_INFORMATION& mbi) + : base_address(reinterpret_cast(mbi.BaseAddress)), + region_size(mbi.RegionSize), + allocation_base(reinterpret_cast(mbi.AllocationBase)), + state(mbi.State), + allocation_protect(mbi.AllocationProtect), + protect(mbi.Protect), + type(mbi.Type) { +} + +ProcessInfo::MemoryInfo::~MemoryInfo() { +} + ProcessInfo::ProcessInfo() : process_id_(), inherited_from_process_id_(), @@ -266,6 +312,7 @@ ProcessInfo::ProcessInfo() peb_address_(0), peb_size_(0), modules_(), + memory_info_(), is_64_bit_(false), is_wow64_(false), initialized_() { @@ -320,6 +367,11 @@ bool ProcessInfo::Initialize(HANDLE process) { return false; } + if (!ReadMemoryInfo(process, this)) { + LOG(ERROR) << "ReadMemoryInfo failed"; + return false; + } + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -361,4 +413,9 @@ bool ProcessInfo::Modules(std::vector* modules) const { return true; } +const std::vector& ProcessInfo::MemoryInformation() + const { + return memory_info_; +} + } // namespace crashpad diff --git a/util/win/process_info.h b/util/win/process_info.h index ed07ab09..5af28bd7 100644 --- a/util/win/process_info.h +++ b/util/win/process_info.h @@ -49,6 +49,40 @@ class ProcessInfo { time_t timestamp; }; + // \brief Contains information about a range of pages in the virtual address + // space of a process. + struct MemoryInfo { + explicit MemoryInfo(const MEMORY_BASIC_INFORMATION& mbi); + ~MemoryInfo(); + + //! \brief The base address of the region of pages. + WinVMAddress base_address; + + //! \brief The size of the region beginning at base_address in bytes. + WinVMSize region_size; + + //! \brief The base address of a range of pages that was allocated by + //! `VirtualAlloc()`. The page pointed to base_address is within this + //! range of pages. + WinVMAddress allocation_base; + + //! \brief The state of the pages, one of `MEM_COMMIT`, `MEM_FREE`, or + //! `MEM_RESERVE`. + uint32_t state; + + //! \brief The memory protection option when this page was originally + //! allocated. This will be `PAGE_EXECUTE`, `PAGE_EXECUTE_READ`, etc. + uint32_t allocation_protect; + + //! \brief The current memoryprotection state. This will be `PAGE_EXECUTE`, + //! `PAGE_EXECUTE_READ`, etc. + uint32_t protect; + + //! \brief The type of the pages. This will be one of `MEM_IMAGE`, + //! `MEM_MAPPED`, or `MEM_PRIVATE`. + uint32_t type; + }; + ProcessInfo(); ~ProcessInfo(); @@ -92,6 +126,9 @@ class ProcessInfo { //! first element. bool Modules(std::vector* modules) const; + //! \brief Retrieves information about all pages mapped into the process. + const std::vector& MemoryInformation() const; + private: template friend bool GetProcessBasicInformation(HANDLE process, @@ -104,12 +141,15 @@ class ProcessInfo { WinVMAddress peb_address_vmaddr, ProcessInfo* process_info); + friend bool ReadMemoryInfo(HANDLE process, ProcessInfo* process_info); + pid_t process_id_; pid_t inherited_from_process_id_; std::wstring command_line_; WinVMAddress peb_address_; WinVMSize peb_size_; std::vector modules_; + std::vector memory_info_; bool is_64_bit_; bool is_wow64_; InitializationStateDcheck initialized_; diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index 0fe49981..02b99f73 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -15,6 +15,7 @@ #include "util/win/process_info.h" #include +#include #include #include "base/files/file_path.h" @@ -55,6 +56,26 @@ bool IsProcessWow64(HANDLE process_handle) { return is_wow64; } +void VerifyAddressInInCodePage(const ProcessInfo& process_info, + WinVMAddress code_address) { + // Make sure the child code address is an code page address with the right + // information. + const std::vector& memory_info = + process_info.MemoryInformation(); + bool found_region = false; + for (const auto& mi : memory_info) { + if (mi.base_address <= code_address && + mi.base_address + mi.region_size > code_address) { + EXPECT_EQ(MEM_COMMIT, mi.state); + EXPECT_EQ(PAGE_EXECUTE_READ, mi.protect); + EXPECT_EQ(MEM_IMAGE, mi.type); + EXPECT_FALSE(found_region); + found_region = true; + } + } + EXPECT_TRUE(found_region); +} + TEST(ProcessInfo, Self) { ProcessInfo process_info; ASSERT_TRUE(process_info.Initialize(GetCurrentProcess())); @@ -101,17 +122,18 @@ TEST(ProcessInfo, Self) { // System modules are forced to particular stamps and the file header values // don't match the on-disk times. Just make sure we got some data here. EXPECT_GT(modules[1].timestamp, 0); + + // Find something we know is a code address and confirm expected memory + // information settings. + VerifyAddressInInCodePage(process_info, + reinterpret_cast(_ReturnAddress())); } void TestOtherProcess(const base::string16& directory_modification) { ProcessInfo process_info; - UUID started_uuid(UUID::InitializeWithNewTag{}); UUID done_uuid(UUID::InitializeWithNewTag{}); - 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()); @@ -126,20 +148,20 @@ void TestOtherProcess(const base::string16& directory_modification) { .value(); std::wstring args; - AppendCommandLineArgument(started_uuid.ToString16(), &args); - args += L" "; AppendCommandLineArgument(done_uuid.ToString16(), &args); ChildLauncher child(child_test_executable, args); child.Start(); - // Wait until the test has completed initialization. - ASSERT_EQ(WaitForSingleObject(started.get(), INFINITE), WAIT_OBJECT_0); + // The child sends us a code address we can look up in the memory map. + WinVMAddress code_address; + CheckedReadFile( + child.stdout_read_handle(), &code_address, sizeof(code_address)); ASSERT_TRUE(process_info.Initialize(child.process_handle())); // Tell the test it's OK to shut down now that we've read our data. - SetEvent(done.get()); + EXPECT_TRUE(SetEvent(done.get())); std::vector modules; EXPECT_TRUE(process_info.Modules(&modules)); @@ -159,6 +181,8 @@ void TestOtherProcess(const base::string16& directory_modification) { EXPECT_EQ(kLz32dllName, modules.back().name.substr(modules.back().name.size() - wcslen(kLz32dllName))); + + VerifyAddressInInCodePage(process_info, code_address); } TEST(ProcessInfo, OtherProcess) { diff --git a/util/win/process_info_test_child.cc b/util/win/process_info_test_child.cc index a0cf5c40..c587f5b8 100644 --- a/util/win/process_info_test_child.cc +++ b/util/win/process_info_test_child.cc @@ -12,19 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include #include #include // A simple binary to be loaded and inspected by ProcessInfo. int wmain(int argc, wchar_t** argv) { - if (argc != 3) + if (argc != 2) 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) + // Get a handle to the event we use to communicate with our parent. + HANDLE done_event = CreateEvent(nullptr, true, false, argv[1]); + if (!done_event) abort(); // Load an unusual module (that we don't depend upon) so we can do an @@ -32,14 +33,22 @@ int wmain(int argc, wchar_t** argv) { if (!LoadLibrary(L"lz32.dll")) abort(); - if (!SetEvent(started_event)) + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + if (out == INVALID_HANDLE_VALUE) abort(); + // We just want any valid address that's known to be code. + uint64_t code_address = reinterpret_cast(_ReturnAddress()); + DWORD bytes_written; + if (!WriteFile( + out, &code_address, sizeof(code_address), &bytes_written, nullptr) || + bytes_written != sizeof(code_address)) { + abort(); + } if (WaitForSingleObject(done_event, INFINITE) != WAIT_OBJECT_0) abort(); CloseHandle(done_event); - CloseHandle(started_event); return EXIT_SUCCESS; }