summaryrefslogtreecommitdiffstats
path: root/tools/memory_watcher/memory_hook.cc
blob: 0c0a05d7003d11fdc2ecb7cf8235cf54405ba546 (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
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
// Copyright (c) 2006-2008 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.

// Static class for hooking Win32 API routines.

// Some notes about how to hook Memory Allocation Routines in Windows.
//
// For our purposes we do not hook the libc routines.  There are two
// reasons for this.  First, the libc routines all go through HeapAlloc
// anyway.  So, it's redundant to log both HeapAlloc and malloc.
// Second, it can be tricky to hook in both static and dynamic linkages
// of libc.

#include <windows.h>

#include "memory_hook.h"
#include "memory_watcher.h"
#include "preamble_patcher.h"

// Calls GetProcAddress, but casts to the correct type.
#define GET_PROC_ADDRESS(hmodule, name) \
  ( (Type_##name)(::GetProcAddress(hmodule, #name)) )

// Macro to declare Patch functions.
#define DECLARE_PATCH(name) Patch<Type_##name> patch_##name

// Macro to install Patch functions.
#define INSTALL_PATCH(name)  do {                                       \
  patch_##name.set_original(GET_PROC_ADDRESS(hkernel32, ##name));       \
  patch_##name.Install(&Perftools_##name);                              \
} while (0)

// Macro to install Patch functions.
#define INSTALL_NTDLLPATCH(name)  do {                                  \
  patch_##name.set_original(GET_PROC_ADDRESS(hntdll, ##name));          \
  patch_##name.Install(&Perftools_##name);                              \
} while (0)

// Macro to uninstall Patch functions.
#define UNINSTALL_PATCH(name) patch_##name.Uninstall();



// Windows APIs to be hooked

// HeapAlloc routines
typedef HANDLE (WINAPI *Type_HeapCreate)(DWORD flOptions,
                                         SIZE_T dwInitialSize,
                                         SIZE_T dwMaximumSize);
typedef BOOL (WINAPI *Type_HeapDestroy)(HANDLE hHeap);
typedef LPVOID (WINAPI *Type_HeapAlloc)(HANDLE hHeap, DWORD dwFlags,
                                        DWORD_PTR dwBytes);
typedef LPVOID (WINAPI *Type_HeapReAlloc)(HANDLE hHeap, DWORD dwFlags,
                                          LPVOID lpMem, SIZE_T dwBytes);
typedef BOOL (WINAPI *Type_HeapFree)(HANDLE hHeap, DWORD dwFlags,
                                     LPVOID lpMem);

// GlobalAlloc routines
typedef HGLOBAL (WINAPI *Type_GlobalAlloc)(UINT uFlags, SIZE_T dwBytes);
typedef HGLOBAL (WINAPI *Type_GlobalReAlloc)(HGLOBAL hMem, SIZE_T dwBytes,
                                             UINT uFlags);
typedef HGLOBAL (WINAPI *Type_GlobalFree)(HGLOBAL hMem);

// LocalAlloc routines
typedef HLOCAL (WINAPI *Type_LocalAlloc)(UINT uFlags, SIZE_T uBytes);
typedef HLOCAL (WINAPI *Type_LocalReAlloc)(HLOCAL hMem, SIZE_T uBytes,
                                           UINT uFlags);
typedef HLOCAL (WINAPI *Type_LocalFree)(HLOCAL hMem);

// A Windows-API equivalent of mmap and munmap, for "anonymous regions"
typedef LPVOID (WINAPI *Type_VirtualAllocEx)(HANDLE process, LPVOID address,
                                             SIZE_T size, DWORD type,
                                             DWORD protect);
typedef BOOL (WINAPI *Type_VirtualFreeEx)(HANDLE process, LPVOID address,
                                          SIZE_T size, DWORD type);

// A Windows-API equivalent of mmap and munmap, for actual files
typedef LPVOID (WINAPI *Type_MapViewOfFile)(HANDLE hFileMappingObject,
                                            DWORD dwDesiredAccess,
                                            DWORD dwFileOffsetHigh,
                                            DWORD dwFileOffsetLow,
                                            SIZE_T dwNumberOfBytesToMap);
typedef LPVOID (WINAPI *Type_MapViewOfFileEx)(HANDLE hFileMappingObject,
                                              DWORD dwDesiredAccess,
                                              DWORD dwFileOffsetHigh,
                                              DWORD dwFileOffsetLow,
                                              SIZE_T dwNumberOfBytesToMap,
                                              LPVOID lpBaseAddress);
typedef BOOL (WINAPI *Type_UnmapViewOfFile)(LPVOID lpBaseAddress);

typedef DWORD (WINAPI *Type_NtUnmapViewOfSection)(HANDLE process,
                                                  LPVOID lpBaseAddress);


// Patch is a template for keeping the pointer to the original
// hooked routine, the function to call when hooked, and the
// stub routine which is patched.
template<class T>
class Patch {
 public:
  // Constructor.  Does not hook the function yet.
  Patch<T>()
    : original_function_(NULL),
      patch_function_(NULL),
      stub_function_(NULL) {
  }

  // Destructor.  Unhooks the function if it has been hooked.
  ~Patch<T>() {
    Uninstall();
  }

  // Patches original function with func.
  // Must have called set_original to set the original function.
  void Install(T func) {
    patch_function_ = func;
    CHECK(patch_function_ != NULL);
    CHECK(original_function_ != NULL);
    CHECK(stub_function_ == NULL);
    CHECK(sidestep::SIDESTEP_SUCCESS ==
          sidestep::PreamblePatcher::Patch(original_function_,
                                           patch_function_, &stub_function_));
  }

  // Un-patches the function.
  void Uninstall() {
    if (stub_function_)
      sidestep::PreamblePatcher::Unpatch(original_function_,
                                         patch_function_, stub_function_);
    stub_function_ = NULL;
  }

  // Set the function to be patched.
  void set_original(T original) { original_function_ = original; }

  // Get the original function being patched.
  T original() { return original_function_; }

  // Get the patched function.  (e.g. the replacement function)
  T patched() { return patch_function_; }

  // Access to the stub for calling the original function
  // while it is patched.
  T operator()() {
    DCHECK(stub_function_);
    return stub_function_;
  }

 private:
  // The function that we plan to patch.
  T original_function_;
  // The function to replace the original with.
  T patch_function_;
  // To unpatch, we also need to keep around a "stub" that points to the
  // pre-patched Windows function.
  T stub_function_;
};


// All Windows memory-allocation routines call through to one of these.
DECLARE_PATCH(HeapCreate);
DECLARE_PATCH(HeapDestroy);
DECLARE_PATCH(HeapAlloc);
DECLARE_PATCH(HeapReAlloc);
DECLARE_PATCH(HeapFree);
DECLARE_PATCH(VirtualAllocEx);
DECLARE_PATCH(VirtualFreeEx);
DECLARE_PATCH(MapViewOfFile);
DECLARE_PATCH(MapViewOfFileEx);
DECLARE_PATCH(UnmapViewOfFile);
DECLARE_PATCH(GlobalAlloc);
DECLARE_PATCH(GlobalReAlloc);
DECLARE_PATCH(GlobalFree);
DECLARE_PATCH(LocalAlloc);
DECLARE_PATCH(LocalReAlloc);
DECLARE_PATCH(LocalFree);
DECLARE_PATCH(NtUnmapViewOfSection);

// Our replacement functions.

static HANDLE WINAPI Perftools_HeapCreate(DWORD flOptions,
                                          SIZE_T dwInitialSize,
                                          SIZE_T dwMaximumSize) {
  if (dwInitialSize > 4096)
    dwInitialSize = 4096;
  return patch_HeapCreate()(flOptions, dwInitialSize, dwMaximumSize);
}

static BOOL WINAPI Perftools_HeapDestroy(HANDLE hHeap) {
  return patch_HeapDestroy()(hHeap);
}

static LPVOID WINAPI Perftools_HeapAlloc(HANDLE hHeap, DWORD dwFlags,
                                         DWORD_PTR dwBytes) {
  LPVOID rv = patch_HeapAlloc()(hHeap, dwFlags, dwBytes);
  MemoryHook::hook()->OnTrack(hHeap, reinterpret_cast<int32>(rv), dwBytes);
  return rv;
}

static BOOL WINAPI Perftools_HeapFree(HANDLE hHeap, DWORD dwFlags,
                                      LPVOID lpMem) {
  size_t size = 0;
  if (lpMem != 0) {
    size = HeapSize(hHeap, 0, lpMem);  // Will crash if lpMem is 0.
    // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
  }
  MemoryHook::hook()->OnUntrack(hHeap, reinterpret_cast<int32>(lpMem), size);
  return patch_HeapFree()(hHeap, dwFlags, lpMem);
}

static LPVOID WINAPI Perftools_HeapReAlloc(HANDLE hHeap, DWORD dwFlags,
                                           LPVOID lpMem, SIZE_T dwBytes) {
  // Don't call realloc, but instead do a free/malloc.  The problem is that
  // the builtin realloc may either expand a buffer, or it may simply
  // just call free/malloc.  If so, we will already have tracked the new
  // block via Perftools_HeapAlloc.

  LPVOID rv = Perftools_HeapAlloc(hHeap, dwFlags, dwBytes);
  DCHECK_EQ((HEAP_REALLOC_IN_PLACE_ONLY & dwFlags), 0);

  // If there was an old buffer, now copy the data to the new buffer.
  if (lpMem != 0) {
    size_t size = HeapSize(hHeap, 0, lpMem);
    if (size > dwBytes)
      size = dwBytes;
    // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
    memcpy(rv, lpMem, size);
    Perftools_HeapFree(hHeap, dwFlags, lpMem);
  }
  return rv;
}

static LPVOID WINAPI Perftools_VirtualAllocEx(HANDLE process, LPVOID address,
                                              SIZE_T size, DWORD type,
                                              DWORD protect) {
  bool already_committed = false;
  if (address != NULL) {
    MEMORY_BASIC_INFORMATION info;
    CHECK(VirtualQuery(address, &info, sizeof(info)));
    if (info.State & MEM_COMMIT) {
      already_committed = true;
      CHECK(size >= info.RegionSize);
    }
  }
  bool reserving = (address == NULL) || (type & MEM_RESERVE);
  bool committing = !already_committed && (type & MEM_COMMIT);


  LPVOID result = patch_VirtualAllocEx()(process, address, size, type,
                                         protect);
  MEMORY_BASIC_INFORMATION info;
  CHECK(VirtualQuery(result, &info, sizeof(info)));
  size = info.RegionSize;

  if (committing)
    MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result), size);

  return result;
}

static BOOL WINAPI Perftools_VirtualFreeEx(HANDLE process, LPVOID address,
                                           SIZE_T size, DWORD type) {
  int chunk_size = size;
  MEMORY_BASIC_INFORMATION info;
  CHECK(VirtualQuery(address, &info, sizeof(info)));
  if (chunk_size == 0)
    chunk_size = info.RegionSize;
  bool decommit = (info.State & MEM_COMMIT);

  if (decommit)
      MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(address),
                                     chunk_size);

  return patch_VirtualFreeEx()(process, address, size, type);
}

static Lock known_maps_lock;
static std::map<void*, int> known_maps;

static LPVOID WINAPI Perftools_MapViewOfFileEx(HANDLE hFileMappingObject,
                                               DWORD dwDesiredAccess,
                                               DWORD dwFileOffsetHigh,
                                               DWORD dwFileOffsetLow,
                                               SIZE_T dwNumberOfBytesToMap,
                                               LPVOID lpBaseAddress) {
  // For this function pair, you always deallocate the full block of
  // data that you allocate, so NewHook/DeleteHook is the right API.
  LPVOID result = patch_MapViewOfFileEx()(hFileMappingObject, dwDesiredAccess,
                                           dwFileOffsetHigh, dwFileOffsetLow,
                                           dwNumberOfBytesToMap, lpBaseAddress);
  {
    base::AutoLock lock(known_maps_lock);
    MEMORY_BASIC_INFORMATION info;
    if (known_maps.find(result) == known_maps.end()) {
      CHECK(VirtualQuery(result, &info, sizeof(info)));
      // TODO(mbelshe):  THIS map uses the standard heap!!!!
      known_maps[result] = 1;
      MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result),
                                  info.RegionSize);
    } else {
      known_maps[result] = known_maps[result] + 1;
    }
  }
  return result;
}

static LPVOID WINAPI Perftools_MapViewOfFile(HANDLE hFileMappingObject,
                                               DWORD dwDesiredAccess,
                                               DWORD dwFileOffsetHigh,
                                               DWORD dwFileOffsetLow,
                                               SIZE_T dwNumberOfBytesToMap) {
  return Perftools_MapViewOfFileEx(hFileMappingObject, dwDesiredAccess,
                                   dwFileOffsetHigh, dwFileOffsetLow,
                                   dwNumberOfBytesToMap, 0);
}

static BOOL WINAPI Perftools_UnmapViewOfFile(LPVOID lpBaseAddress) {
  // This will call into NtUnmapViewOfSection().
  return patch_UnmapViewOfFile()(lpBaseAddress);
}

static DWORD WINAPI Perftools_NtUnmapViewOfSection(HANDLE process,
                                                   LPVOID lpBaseAddress) {
  // Some windows APIs call directly into this routine rather
  // than calling UnmapViewOfFile.  If we didn't trap this function,
  // then we appear to have bogus leaks.
  {
    base::AutoLock lock(known_maps_lock);
    MEMORY_BASIC_INFORMATION info;
    CHECK(VirtualQuery(lpBaseAddress, &info, sizeof(info)));
    if (known_maps.find(lpBaseAddress) != known_maps.end()) {
      if (known_maps[lpBaseAddress] == 1) {
        MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(lpBaseAddress),
                                       info.RegionSize);
        known_maps.erase(lpBaseAddress);
      } else {
        known_maps[lpBaseAddress] = known_maps[lpBaseAddress] - 1;
      }
    }
  }
  return patch_NtUnmapViewOfSection()(process, lpBaseAddress);
}

static HGLOBAL WINAPI Perftools_GlobalAlloc(UINT uFlags, SIZE_T dwBytes) {
  // GlobalAlloc is built atop HeapAlloc anyway.  So we don't track these.
  // GlobalAlloc will internally call into HeapAlloc and we track there.

  // Force all memory to be fixed.
  uFlags &= ~GMEM_MOVEABLE;
  HGLOBAL rv = patch_GlobalAlloc()(uFlags, dwBytes);
  return rv;
}

static HGLOBAL WINAPI Perftools_GlobalFree(HGLOBAL hMem) {
  return patch_GlobalFree()(hMem);
}

static HGLOBAL WINAPI Perftools_GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes,
                                              UINT uFlags) {
  // TODO(jar): [The following looks like a copy/paste typo from LocalRealloc.]
  // GlobalDiscard is a macro which calls LocalReAlloc with size 0.
  if (dwBytes == 0) {
    return patch_GlobalReAlloc()(hMem, dwBytes, uFlags);
  }

  HGLOBAL rv = Perftools_GlobalAlloc(uFlags, dwBytes);
  if (hMem != 0) {
    size_t size = GlobalSize(hMem);
    if (size > dwBytes)
      size = dwBytes;
    // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
    memcpy(rv, hMem, size);
    Perftools_GlobalFree(hMem);
  }

  return rv;
}

static HLOCAL WINAPI Perftools_LocalAlloc(UINT uFlags, SIZE_T dwBytes) {
  // LocalAlloc is built atop HeapAlloc anyway.  So we don't track these.
  // LocalAlloc will internally call into HeapAlloc and we track there.

  // Force all memory to be fixed.
  uFlags &= ~LMEM_MOVEABLE;
  HLOCAL rv = patch_LocalAlloc()(uFlags, dwBytes);
  return rv;
}

static HLOCAL WINAPI Perftools_LocalFree(HLOCAL hMem) {
  return patch_LocalFree()(hMem);
}

static HLOCAL WINAPI Perftools_LocalReAlloc(HLOCAL hMem, SIZE_T dwBytes,
                                            UINT uFlags) {
  // LocalDiscard is a macro which calls LocalReAlloc with size 0.
  if (dwBytes == 0) {
    return patch_LocalReAlloc()(hMem, dwBytes, uFlags);
  }

  HGLOBAL rv = Perftools_LocalAlloc(uFlags, dwBytes);
  if (hMem != 0) {
    size_t size = LocalSize(hMem);
    if (size > dwBytes)
      size = dwBytes;
    // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
    memcpy(rv, hMem, size);
    Perftools_LocalFree(hMem);
  }

  return rv;
}

bool MemoryHook::hooked_ = false;
MemoryHook* MemoryHook::global_hook_ = NULL;

MemoryHook::MemoryHook()
  : watcher_(NULL),
    heap_(NULL) {
  CreateHeap();
}

MemoryHook::~MemoryHook() {
  // It's a bit dangerous to ever close this heap; MemoryWatchers may have
  // used this heap for their tracking data.  Closing the heap while any
  // MemoryWatchers still exist is pretty dangerous.
  CloseHeap();
}

bool MemoryHook::Initialize() {
  if (global_hook_ == NULL)
    global_hook_ = new MemoryHook();
  return true;
}

bool MemoryHook::Hook() {
  DCHECK(!hooked_);
  if (!hooked_) {
    DCHECK(global_hook_);

    // Luckily, Patch() doesn't call malloc or windows alloc routines
    // itself -- though it does call new (we can use PatchWithStub to
    // get around that, and will need to if we need to patch new).

    HMODULE hkernel32 = ::GetModuleHandle(L"kernel32");
    CHECK(hkernel32 != NULL);

    HMODULE hntdll = ::GetModuleHandle(L"ntdll");
    CHECK(hntdll != NULL);

    // Now that we've found all the functions, patch them
    INSTALL_PATCH(HeapCreate);
    INSTALL_PATCH(HeapDestroy);
    INSTALL_PATCH(HeapAlloc);
    INSTALL_PATCH(HeapReAlloc);
    INSTALL_PATCH(HeapFree);
    INSTALL_PATCH(VirtualAllocEx);
    INSTALL_PATCH(VirtualFreeEx);
    INSTALL_PATCH(MapViewOfFileEx);
    INSTALL_PATCH(MapViewOfFile);
    INSTALL_PATCH(UnmapViewOfFile);
    INSTALL_NTDLLPATCH(NtUnmapViewOfSection);
    INSTALL_PATCH(GlobalAlloc);
    INSTALL_PATCH(GlobalReAlloc);
    INSTALL_PATCH(GlobalFree);
    INSTALL_PATCH(LocalAlloc);
    INSTALL_PATCH(LocalReAlloc);
    INSTALL_PATCH(LocalFree);

    // We are finally completely hooked.
    hooked_ = true;
  }
  return true;
}

bool MemoryHook::Unhook() {
  if (hooked_) {
    // We need to go back to the system malloc/etc at global destruct time,
    // so objects that were constructed before tcmalloc, using the system
    // malloc, can destroy themselves using the system free.  This depends
    // on DLLs unloading in the reverse order in which they load!
    //
    // We also go back to the default HeapAlloc/etc, just for consistency.
    // Who knows, it may help avoid weird bugs in some situations.
    UNINSTALL_PATCH(HeapCreate);
    UNINSTALL_PATCH(HeapDestroy);
    UNINSTALL_PATCH(HeapAlloc);
    UNINSTALL_PATCH(HeapReAlloc);
    UNINSTALL_PATCH(HeapFree);
    UNINSTALL_PATCH(VirtualAllocEx);
    UNINSTALL_PATCH(VirtualFreeEx);
    UNINSTALL_PATCH(MapViewOfFile);
    UNINSTALL_PATCH(MapViewOfFileEx);
    UNINSTALL_PATCH(UnmapViewOfFile);
    UNINSTALL_PATCH(NtUnmapViewOfSection);
    UNINSTALL_PATCH(GlobalAlloc);
    UNINSTALL_PATCH(GlobalReAlloc);
    UNINSTALL_PATCH(GlobalFree);
    UNINSTALL_PATCH(LocalAlloc);
    UNINSTALL_PATCH(LocalReAlloc);
    UNINSTALL_PATCH(LocalFree);

    hooked_ = false;
  }
  return true;
}

bool MemoryHook::RegisterWatcher(MemoryObserver* watcher) {
  DCHECK(global_hook_->watcher_ == NULL);

  if (!hooked_)
    Hook();

  DCHECK(global_hook_);
  global_hook_->watcher_ = watcher;
  return true;
}

bool MemoryHook::UnregisterWatcher(MemoryObserver* watcher) {
  DCHECK(hooked_);
  DCHECK(global_hook_->watcher_ == watcher);
  // TODO(jar): changing watcher_ here is very racy.  Other threads may (without
  // a lock) testing, and then calling through this value.  We probably can't
  // remove this until we are single threaded.
  global_hook_->watcher_ = NULL;

  // For now, since there are no more watchers, unhook memory.
  return Unhook();
}

bool MemoryHook::CreateHeap() {
  // Create a heap for our own memory.
  DCHECK(heap_ == NULL);
  heap_ = HeapCreate(0, 0, 0);
  DCHECK(heap_ != NULL);
  return heap_ != NULL;
}

bool MemoryHook::CloseHeap() {
  DCHECK(heap_ != NULL);
  HeapDestroy(heap_);
  heap_ = NULL;
  return true;
}

void MemoryHook::OnTrack(HANDLE heap, int32 id, int32 size) {
  // Don't notify about allocations to our internal heap.
  if (heap == heap_)
    return;

  if (watcher_)
    watcher_->OnTrack(heap, id, size);
}

void MemoryHook::OnUntrack(HANDLE heap, int32 id, int32 size) {
  // Don't notify about allocations to our internal heap.
  if (heap == heap_)
    return;

  if (watcher_)
    watcher_->OnUntrack(heap, id, size);
}