1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/app/image_pre_reader_win.h"
#include <windows.h>
#include <algorithm>
#include <limits>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/thread_restrictions.h"
#include "base/win/pe_image.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
namespace {
// The minimum buffer size to allocate when reading the PE file headers.
//
// The PE file headers usually fit into a single 1KB page, and a PE file must
// at least contain the initial page with the headers. That said, as long as
// we expect at least sizeof(IMAGE_DOS_HEADER) bytes, we're ok.
const size_t kMinHeaderBufferSize = 0x400;
// A handy symbolic constant.
const size_t kOneHundredPercent = 100;
void StaticAssertions() {
COMPILE_ASSERT(kMinHeaderBufferSize >= sizeof(IMAGE_DOS_HEADER),
min_header_buffer_size_at_least_as_big_as_the_dos_header);
}
// This struct provides a deallocation functor for use with scoped_ptr<T>
// allocated with ::VirtualAlloc().
struct VirtualFreeDeleter {
void operator() (void* ptr) {
::VirtualFree(ptr, 0, MEM_RELEASE);
}
};
// A wrapper for the Win32 ::SetFilePointer() function with some error checking.
bool SetFilePointer(HANDLE file_handle, size_t position) {
return position <= static_cast<size_t>(std::numeric_limits<LONG>::max()) &&
::SetFilePointer(file_handle,
static_cast<LONG>(position),
NULL,
FILE_BEGIN) != INVALID_SET_FILE_POINTER;
}
// A helper function to read the next |bytes_to_read| bytes from the file
// given by |file_handle| into |buffer|.
bool ReadNextBytes(HANDLE file_handle, void* buffer, size_t bytes_to_read) {
DCHECK(file_handle != INVALID_HANDLE_VALUE);
DCHECK(buffer != NULL);
DCHECK(bytes_to_read > 0);
DWORD bytes_read = 0;
return bytes_to_read <= std::numeric_limits<DWORD>::max() &&
::ReadFile(file_handle,
buffer,
static_cast<DWORD>(bytes_to_read),
&bytes_read,
NULL) &&
bytes_read == bytes_to_read;
}
// A helper function to extend the |current_buffer| of bytes such that it
// contains |desired_length| bytes read from the file given by |file_handle|.
//
// It is assumed that |file_handle| has been used to sequentially populate
// |current_buffer| thus far and is already positioned at the appropriate
// read location.
bool ReadMissingBytes(HANDLE file_handle,
std::vector<uint8>* current_buffer,
size_t desired_length) {
DCHECK(file_handle != INVALID_HANDLE_VALUE);
DCHECK(current_buffer != NULL);
size_t current_length = current_buffer->size();
if (current_length >= desired_length)
return true;
size_t bytes_to_read = desired_length - current_length;
current_buffer->resize(desired_length);
return ReadNextBytes(file_handle,
&(current_buffer->at(current_length)),
bytes_to_read);
}
// Return a |percentage| of the number of initialized bytes in the given
// |section|.
//
// This returns a percentage of the lesser of the size of the raw data in
// the section and the virtual size of the section.
//
// Note that sections can have their tails implicitly initialized to zero
// (i.e., their virtual size is larger than the raw size) and that raw data
// is padded to the PE page size if the entire section is initialized (i.e.,
// their raw data size will be larger than the virtual size).
//
// Any data after the initialized portion of the section will be soft-faulted
// in (very quickly) as needed, so we don't need to include it in the returned
// length.
size_t GetPercentageOfSectionLength(const IMAGE_SECTION_HEADER* section,
size_t percentage) {
DCHECK(section != NULL);
DCHECK_GT(percentage, 0u);
DCHECK_LE(percentage, kOneHundredPercent);
size_t initialized_length = std::min(section->SizeOfRawData,
section->Misc.VirtualSize);
if (initialized_length == 0)
return 0;
size_t length = (initialized_length * percentage) / kOneHundredPercent;
return std::max<size_t>(length, 1);
}
// Helper function to read through a |percentage| of the given |section|
// of the file denoted by |file_handle|. The |temp_buffer| is (re)used as
// a transient storage area as the section is read in chunks of
// |temp_buffer_size| bytes.
bool ReadThroughSection(HANDLE file_handle,
const IMAGE_SECTION_HEADER* section,
size_t percentage,
void* temp_buffer,
size_t temp_buffer_size) {
DCHECK(file_handle != INVALID_HANDLE_VALUE);
DCHECK(section != NULL);
DCHECK_LE(percentage, kOneHundredPercent);
DCHECK(temp_buffer != NULL);
DCHECK(temp_buffer_size > 0);
size_t bytes_to_read = GetPercentageOfSectionLength(section, percentage);
if (bytes_to_read == 0)
return true;
if (!SetFilePointer(file_handle, section->PointerToRawData))
return false;
// Read all chunks except the last one.
while (bytes_to_read > temp_buffer_size) {
if (!ReadNextBytes(file_handle, temp_buffer, temp_buffer_size))
return false;
bytes_to_read -= temp_buffer_size;
}
// Read the last (possibly partial) chunk and return.
DCHECK(bytes_to_read > 0);
DCHECK(bytes_to_read <= temp_buffer_size);
return ReadNextBytes(file_handle, temp_buffer, bytes_to_read);
}
// A helper function to touch all pages in the range
// [base_addr, base_addr + length).
void TouchPagesInRange(void* base_addr, size_t length) {
DCHECK(base_addr != NULL);
DCHECK(length > 0);
// Get the system info so we know the page size. Also, make sure we use a
// non-zero value for the page size; GetSystemInfo() is hookable/patchable,
// and you never know what shenanigans someone could get up to.
SYSTEM_INFO system_info = {};
GetSystemInfo(&system_info);
if (system_info.dwPageSize == 0)
system_info.dwPageSize = 4096;
// We don't want to read outside the byte range (which could trigger an
// access violation), so let's figure out the exact locations of the first
// and final bytes we want to read.
volatile uint8* touch_ptr = reinterpret_cast<uint8*>(base_addr);
volatile uint8* final_touch_ptr = touch_ptr + length - 1;
// Read the memory in the range [touch_ptr, final_touch_ptr] with a stride
// of the system page size, to ensure that it's been paged in.
uint8 dummy;
while (touch_ptr < final_touch_ptr) {
dummy = *touch_ptr;
touch_ptr += system_info.dwPageSize;
}
dummy = *final_touch_ptr;
}
} // namespace
bool ImagePreReader::PartialPreReadImageOnDisk(const wchar_t* file_path,
size_t percentage,
size_t max_chunk_size) {
// TODO(rogerm): change this to have the number of bytes pre-read per
// section be driven by a static table within the PE file (defaulting to
// full read if it's not there?) that's initialized by the optimization
// toolchain.
DCHECK(file_path != NULL);
if (percentage == 0)
return true;
if (percentage > kOneHundredPercent)
percentage = kOneHundredPercent;
// Validate/setup max_chunk_size, imposing a 1MB minimum on the chunk size.
const size_t kMinChunkSize = 1024 * 1024;
max_chunk_size = std::max(max_chunk_size, kMinChunkSize);
// Open the file.
base::win::ScopedHandle file(
CreateFile(file_path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL));
if (!file.IsValid())
return false;
// Allocate a resizable buffer for the headers. We initially reserve as much
// space as we typically see as the header size for chrome.dll and other
// PE images.
std::vector<uint8> headers;
headers.reserve(kMinHeaderBufferSize);
// Read, hopefully, all of the headers.
if (!ReadMissingBytes(file, &headers, kMinHeaderBufferSize))
return false;
// The DOS header starts at offset 0 and allows us to get the offset of the
// NT headers. Let's ensure we've read enough to capture the NT headers.
size_t nt_headers_start =
reinterpret_cast<IMAGE_DOS_HEADER*>(&headers[0])->e_lfanew;
size_t nt_headers_end = nt_headers_start + sizeof(IMAGE_NT_HEADERS);
if (!ReadMissingBytes(file, &headers, nt_headers_end))
return false;
// Now that we've got the NT headers we can get the total header size,
// including all of the section headers. Let's ensure we've read enough
// to capture all of the header data.
size_t size_of_headers = reinterpret_cast<IMAGE_NT_HEADERS*>(
&headers[nt_headers_start])->OptionalHeader.SizeOfHeaders;
if (!ReadMissingBytes(file, &headers, size_of_headers))
return false;
// Now we have all of the headers. This is enough to let us use the PEImage
// wrapper to query the structure of the image.
base::win::PEImage pe_image(reinterpret_cast<HMODULE>(&headers[0]));
CHECK(pe_image.VerifyMagic());
// Allocate a buffer to hold the pre-read bytes.
scoped_ptr<uint8, VirtualFreeDeleter> buffer(
static_cast<uint8*>(
::VirtualAlloc(NULL, max_chunk_size, MEM_COMMIT, PAGE_READWRITE)));
if (buffer.get() == NULL)
return false;
// Iterate over each section, reading in a percentage of each.
const IMAGE_SECTION_HEADER* section = NULL;
for (UINT i = 0; (section = pe_image.GetSectionHeader(i)) != NULL; ++i) {
CHECK_LE(reinterpret_cast<const uint8*>(section + 1),
&headers[0] + headers.size());
if (!ReadThroughSection(
file, section, percentage, buffer.get(), max_chunk_size))
return false;
}
// We're done.
return true;
}
bool ImagePreReader::PartialPreReadImageInMemory(const wchar_t* file_path,
size_t percentage) {
// TODO(rogerm): change this to have the number of bytes pre-read per
// section be driven by a static table within the PE file (defaulting to
// full read if it's not there?) that's initialized by the optimization
// toolchain.
DCHECK(file_path != NULL);
if (percentage == 0)
return true;
if (percentage > kOneHundredPercent)
percentage = kOneHundredPercent;
HMODULE dll_module = ::LoadLibraryExW(
file_path,
NULL,
LOAD_WITH_ALTERED_SEARCH_PATH | DONT_RESOLVE_DLL_REFERENCES);
if (!dll_module)
return false;
base::win::PEImage pe_image(dll_module);
CHECK(pe_image.VerifyMagic());
// Iterate over each section, stepping through a percentage of each to page
// it in off the disk.
const IMAGE_SECTION_HEADER* section = NULL;
for (UINT i = 0; (section = pe_image.GetSectionHeader(i)) != NULL; ++i) {
// Get the extent we want to touch.
size_t length = GetPercentageOfSectionLength(section, percentage);
if (length == 0)
continue;
uint8* start =
static_cast<uint8*>(pe_image.RVAToAddr(section->VirtualAddress));
// Verify that the extent we're going to touch falls inside the section
// we expect it to (and by implication, inside the pe_image).
CHECK_EQ(section,
pe_image.GetImageSectionFromAddr(start));
CHECK_EQ(section,
pe_image.GetImageSectionFromAddr(start + length - 1));
// Page in the section range.
TouchPagesInRange(start, length);
}
FreeLibrary(dll_module);
return true;
}
bool ImagePreReader::PreReadImage(const wchar_t* file_path,
size_t size_to_read,
size_t step_size) {
base::ThreadRestrictions::AssertIOAllowed();
if (base::win::GetVersion() > base::win::VERSION_XP) {
// Vista+ branch. On these OSes, the forced reads through the DLL actually
// slows warm starts. The solution is to sequentially read file contents
// with an optional cap on total amount to read.
base::win::ScopedHandle file(
CreateFile(file_path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL));
if (!file.IsValid())
return false;
// Default to 1MB sequential reads.
const DWORD actual_step_size = std::max(static_cast<DWORD>(step_size),
static_cast<DWORD>(1024*1024));
LPVOID buffer = ::VirtualAlloc(NULL,
actual_step_size,
MEM_COMMIT,
PAGE_READWRITE);
if (buffer == NULL)
return false;
DWORD len;
size_t total_read = 0;
while (::ReadFile(file, buffer, actual_step_size, &len, NULL) &&
len > 0 &&
(size_to_read ? total_read < size_to_read : true)) {
total_read += static_cast<size_t>(len);
}
::VirtualFree(buffer, 0, MEM_RELEASE);
} else {
// WinXP branch. Here, reading the DLL from disk doesn't do
// what we want so instead we pull the pages into memory by loading
// the DLL and touching pages at a stride. We use the system's page
// size as the stride, ignoring the passed in step_size, to make sure
// each page in the range is touched.
HMODULE dll_module = ::LoadLibraryExW(
file_path,
NULL,
LOAD_WITH_ALTERED_SEARCH_PATH | DONT_RESOLVE_DLL_REFERENCES);
if (!dll_module)
return false;
base::win::PEImage pe_image(dll_module);
CHECK(pe_image.VerifyMagic());
// We don't want to read past the end of the module (which could trigger
// an access violation), so make sure to check the image size.
PIMAGE_NT_HEADERS nt_headers = pe_image.GetNTHeaders();
size_t dll_module_length = std::min(
size_to_read ? size_to_read : ~0,
static_cast<size_t>(nt_headers->OptionalHeader.SizeOfImage));
// Page in then release the module.
TouchPagesInRange(dll_module, dll_module_length);
FreeLibrary(dll_module);
}
return true;
}
bool ImagePreReader::PartialPreReadImage(const wchar_t* file_path,
size_t percentage,
size_t max_chunk_size) {
base::ThreadRestrictions::AssertIOAllowed();
if (percentage >= kOneHundredPercent) {
// If we're reading the whole image, we don't need to parse headers and
// navigate sections, the basic PreReadImage() can be used to just step
// blindly through the entire file / address-space.
return PreReadImage(file_path, 0, max_chunk_size);
}
if (base::win::GetVersion() > base::win::VERSION_XP) {
// Vista+ branch. On these OSes, we warm up the Image by reading its
// file off the disk.
return PartialPreReadImageOnDisk(file_path, percentage, max_chunk_size);
}
// WinXP branch. For XP, reading the image from disk doesn't do what we want
// so instead we pull the pages into memory by loading the DLL and touching
// initialized pages at a stride.
return PartialPreReadImageInMemory(file_path, percentage);
}
|