summaryrefslogtreecommitdiffstats
path: root/chrome/app/image_pre_reader_win.cc
blob: 146d1c8b00722e82396eb312c58b50737500e155 (plain)
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 uint8 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 ScopedPtrVirtualFree {
  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,
                                    uint8 percentage) {
  DCHECK(section != NULL);
  DCHECK_GT(percentage, 0);
  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,
                        uint8 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,
                                               uint8 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_malloc<uint8, ScopedPtrVirtualFree> buffer(
      reinterpret_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,
                                                 uint8 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,
                                         uint8 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);
}