blob: af93769a81cbe0d224b1664a9e0117dd06652e89 [file] [log] [blame]
// Copyright 2011 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/wsdump/process_working_set.h"
#include <psapi.h>
#include <tlhelp32.h>
#include <algorithm>
#include <map>
#include "base/memory/scoped_ptr.h"
#include "base/win/scoped_handle.h"
#include "syzygy/common/com_utils.h"
#include "syzygy/core/address_space.h"
namespace wsdump {
namespace {
const size_t kPageSize = 4096;
// These are inferred from the MSDN page for QueryWorkingSet.
const int kPageReadOnly = 0x001;
const int kPageExecute = 0x002;
const int kPageExecuteRead = 0x003;
const int kPageReadWrite = 0x004;
const int kPageWriteCopy = 0x005;
const int kPageExecuteReadWrite = 0x006;
const int kPageExecuteWriteCopy = 0x007;
bool LessModuleName(const ProcessWorkingSet::ModuleStats& a,
const ProcessWorkingSet::ModuleStats& b) {
return a.module_name < b.module_name;
}
} // namespace
bool ProcessWorkingSet::Initialize(DWORD process_id) {
ModuleAddressSpace modules;
if (!CaptureModules(process_id, &modules))
return false;
const DWORD kProcessPermissions = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
base::win::ScopedHandle process(
::OpenProcess(kProcessPermissions, FALSE, process_id));
if (!process.IsValid()) {
DWORD err = ::GetLastError();
LOG(ERROR) << "OpenProcess failed: " << common::LogWe(err);
return false;
}
ScopedWsPtr working_set;
if (!CaptureWorkingSet(process.Get(), &working_set))
return false;
// The new stats we're building.
ModuleStatsVector new_stats;
// This maps from module name to index in the above vector.
typedef std::map<std::wstring, size_t> NameToIndexMap;
NameToIndexMap name_to_index;
for (size_t i = 0; i < working_set->NumberOfEntries; ++i) {
PSAPI_WORKING_SET_BLOCK entry = working_set->WorkingSetInfo[i];
size_t address = entry.VirtualPage * kPageSize;
ModuleAddressSpace::Range page_range(address, kPageSize);
ModuleAddressSpace::RangeMap::const_iterator it =
modules.FindContaining(page_range);
Stats* stats = NULL;
if (it == modules.end()) {
stats = &non_module_stats_;
} else {
// Find the module with this name, or add it if it's missing.
const std::wstring& module_name = it->second;
NameToIndexMap::const_iterator it = name_to_index.find(module_name);
if (it == name_to_index.end()) {
// We haven't seen this module, add it to the end of the vector.
name_to_index[module_name] = new_stats.size();
new_stats.push_back(ModuleStats());
ModuleStats* module_stats = &new_stats.back();
module_stats->module_name = module_name;
stats = module_stats;
} else {
stats = &new_stats[it->second];
}
}
DCHECK(stats != NULL);
total_stats_.pages++;
stats->pages++;
if (entry.Shared) {
total_stats_.shareable_pages++;
stats->shareable_pages++;
}
if (entry.ShareCount > 1) {
total_stats_.shared_pages++;
stats->shared_pages++;
}
if (entry.Protection & kPageReadWrite) {
total_stats_.writable_pages++;
stats->writable_pages++;
} else if (entry.Protection & kPageExecute) {
total_stats_.executable_pages++;
stats->executable_pages++;
} else if (entry.Protection & kPageReadOnly) {
total_stats_.read_only_pages++;
stats->read_only_pages++;
}
}
std::sort(new_stats.begin(), new_stats.end(), LessModuleName);
new_stats.swap(module_stats_);
return true;
}
bool ProcessWorkingSet::CaptureWorkingSet(HANDLE process,
ScopedWsPtr* working_set) {
DCHECK(working_set != NULL);
// Estimate the starting buffer size by the current WS size.
PROCESS_MEMORY_COUNTERS counters = {};
if (!::GetProcessMemoryInfo(process, &counters, sizeof(counters))) {
DWORD err = ::GetLastError();
LOG(ERROR) << "Unable to get process memory info: " << common::LogWe(err);
return false;
}
scoped_ptr<PSAPI_WORKING_SET_INFORMATION> buffer;
DWORD number_of_entries = counters.WorkingSetSize / kPageSize;
int retries = 5;
for (;;) {
DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) +
(number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK));
// If we can't expand the buffer, don't leak the previous
// contents or pass a NULL pointer to QueryWorkingSet.
buffer.reset(reinterpret_cast<PSAPI_WORKING_SET_INFORMATION*>(
new char[buffer_size]));
if (!buffer.get()) {
LOG(ERROR) << "Unable to allocate working set buffer.";
return false;
}
// Zero the buffer as Gary Nebbet warns that undefined bits may not be set
// in the Windows NT/2000 Native API Reference.
memset(buffer.get(), 0, buffer_size);
// Call the function once to get number of items.
if (::QueryWorkingSet(process, buffer.get(), buffer_size))
break;
if (::GetLastError() != ERROR_BAD_LENGTH) {
return false;
}
number_of_entries = static_cast<DWORD>(buffer->NumberOfEntries);
// Maybe some entries are being added right now. Increase the buffer to
// take that into account.
number_of_entries = static_cast<DWORD>(number_of_entries * 1.25);
if (--retries == 0) {
LOG(ERROR) << "Out of retries to query working set.";
return false;
}
}
working_set->swap(buffer);
return true;
}
bool ProcessWorkingSet::CaptureModules(DWORD process_id,
ModuleAddressSpace* modules) {
DCHECK(modules != NULL);
base::win::ScopedHandle snap(
::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, process_id));
if (!snap.IsValid()) {
DWORD err = ::GetLastError();
LOG(ERROR) << "CreateToolhelp32Snapshot failed: " << common::LogWe(err);
return false;
}
MODULEENTRY32 module = { sizeof(module) };
if (!::Module32First(snap.Get(), &module)) {
DWORD err = ::GetLastError();
LOG(ERROR) << "Module32First failed: " << common::LogWe(err);
return false;
}
do {
ModuleAddressSpace::Range range(
reinterpret_cast<size_t>(module.modBaseAddr), module.modBaseSize);
if (!modules->Insert(range, module.szExePath)) {
LOG(ERROR) << "Module insertion failed, overlapping modules?";
return false;
}
} while (::Module32Next(snap.Get(), &module));
DWORD err = ::GetLastError();
if (err != ERROR_NO_MORE_FILES) {
LOG(ERROR) << "Module32Next failed: " << common::LogWe(err);
return false;
}
return true;
}
} // namespace wsdump