|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "mozilla/AvailableMemoryTracker.h" |
|
8 |
|
9 #if defined(XP_WIN) |
|
10 #include "prinrval.h" |
|
11 #include "prenv.h" |
|
12 #include "nsIMemoryReporter.h" |
|
13 #include "nsMemoryPressure.h" |
|
14 #endif |
|
15 |
|
16 #include "nsIObserver.h" |
|
17 #include "nsIObserverService.h" |
|
18 #include "nsIRunnable.h" |
|
19 #include "nsISupports.h" |
|
20 #include "nsThreadUtils.h" |
|
21 |
|
22 #include "mozilla/Preferences.h" |
|
23 #include "mozilla/Services.h" |
|
24 |
|
25 #if defined(XP_WIN) |
|
26 # include "nsWindowsDllInterceptor.h" |
|
27 # include <windows.h> |
|
28 #endif |
|
29 |
|
30 #if defined(MOZ_MEMORY) |
|
31 # include "mozmemory.h" |
|
32 #endif // MOZ_MEMORY |
|
33 |
|
34 using namespace mozilla; |
|
35 |
|
36 namespace { |
|
37 |
|
38 #if defined(XP_WIN) |
|
39 |
|
40 // We don't want our diagnostic functions to call malloc, because that could |
|
41 // call VirtualAlloc, and we'd end up back in here! So here are a few simple |
|
42 // debugging macros (modeled on jemalloc's), which hopefully won't allocate. |
|
43 |
|
44 // #define LOGGING_ENABLED |
|
45 |
|
46 #ifdef LOGGING_ENABLED |
|
47 |
|
48 #define LOG(msg) \ |
|
49 do { \ |
|
50 safe_write(msg); \ |
|
51 safe_write("\n"); \ |
|
52 } while(0) |
|
53 |
|
54 #define LOG2(m1, m2) \ |
|
55 do { \ |
|
56 safe_write(m1); \ |
|
57 safe_write(m2); \ |
|
58 safe_write("\n"); \ |
|
59 } while(0) |
|
60 |
|
61 #define LOG3(m1, m2, m3) \ |
|
62 do { \ |
|
63 safe_write(m1); \ |
|
64 safe_write(m2); \ |
|
65 safe_write(m3); \ |
|
66 safe_write("\n"); \ |
|
67 } while(0) |
|
68 |
|
69 #define LOG4(m1, m2, m3, m4) \ |
|
70 do { \ |
|
71 safe_write(m1); \ |
|
72 safe_write(m2); \ |
|
73 safe_write(m3); \ |
|
74 safe_write(m4); \ |
|
75 safe_write("\n"); \ |
|
76 } while(0) |
|
77 |
|
78 #else |
|
79 |
|
80 #define LOG(msg) |
|
81 #define LOG2(m1, m2) |
|
82 #define LOG3(m1, m2, m3) |
|
83 #define LOG4(m1, m2, m3, m4) |
|
84 |
|
85 #endif |
|
86 |
|
87 void safe_write(const char *a) |
|
88 { |
|
89 // Well, puts isn't exactly "safe", but at least it doesn't call malloc... |
|
90 fputs(a, stdout); |
|
91 } |
|
92 |
|
93 void safe_write(uint64_t x) |
|
94 { |
|
95 // 2^64 is 20 decimal digits. |
|
96 const unsigned int max_len = 21; |
|
97 char buf[max_len]; |
|
98 buf[max_len - 1] = '\0'; |
|
99 |
|
100 uint32_t i; |
|
101 for (i = max_len - 2; i < max_len && x > 0; i--) |
|
102 { |
|
103 buf[i] = "0123456789"[x % 10]; |
|
104 x /= 10; |
|
105 } |
|
106 |
|
107 safe_write(&buf[i + 1]); |
|
108 } |
|
109 |
|
110 #ifdef DEBUG |
|
111 #define DEBUG_WARN_IF_FALSE(cond, msg) \ |
|
112 do { \ |
|
113 if (!(cond)) { \ |
|
114 safe_write(__FILE__); \ |
|
115 safe_write(":"); \ |
|
116 safe_write(__LINE__); \ |
|
117 safe_write(" "); \ |
|
118 safe_write(msg); \ |
|
119 safe_write("\n"); \ |
|
120 } \ |
|
121 } while(0) |
|
122 #else |
|
123 #define DEBUG_WARN_IF_FALSE(cond, msg) |
|
124 #endif |
|
125 |
|
126 uint32_t sLowVirtualMemoryThreshold = 0; |
|
127 uint32_t sLowCommitSpaceThreshold = 0; |
|
128 uint32_t sLowPhysicalMemoryThreshold = 0; |
|
129 uint32_t sLowMemoryNotificationIntervalMS = 0; |
|
130 |
|
131 Atomic<uint32_t> sNumLowVirtualMemEvents; |
|
132 Atomic<uint32_t> sNumLowCommitSpaceEvents; |
|
133 Atomic<uint32_t> sNumLowPhysicalMemEvents; |
|
134 |
|
135 WindowsDllInterceptor sKernel32Intercept; |
|
136 WindowsDllInterceptor sGdi32Intercept; |
|
137 |
|
138 // Has Init() been called? |
|
139 bool sInitialized = false; |
|
140 |
|
141 // Has Activate() been called? The hooks don't do anything until this happens. |
|
142 bool sHooksActive = false; |
|
143 |
|
144 // Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires |
|
145 // a lock! |
|
146 volatile bool sHasScheduledOneLowMemoryNotification = false; |
|
147 volatile PRIntervalTime sLastLowMemoryNotificationTime; |
|
148 |
|
149 // These are function pointers to the functions we wrap in Init(). |
|
150 |
|
151 void* (WINAPI *sVirtualAllocOrig) |
|
152 (LPVOID aAddress, SIZE_T aSize, DWORD aAllocationType, DWORD aProtect); |
|
153 |
|
154 void* (WINAPI *sMapViewOfFileOrig) |
|
155 (HANDLE aFileMappingObject, DWORD aDesiredAccess, |
|
156 DWORD aFileOffsetHigh, DWORD aFileOffsetLow, |
|
157 SIZE_T aNumBytesToMap); |
|
158 |
|
159 HBITMAP (WINAPI *sCreateDIBSectionOrig) |
|
160 (HDC aDC, const BITMAPINFO *aBitmapInfo, |
|
161 UINT aUsage, VOID **aBits, |
|
162 HANDLE aSection, DWORD aOffset); |
|
163 |
|
164 /** |
|
165 * Fire a memory pressure event if it's been long enough since the last one we |
|
166 * fired. |
|
167 */ |
|
168 bool MaybeScheduleMemoryPressureEvent() |
|
169 { |
|
170 // If this interval rolls over, we may fire an extra memory pressure |
|
171 // event, but that's not a big deal. |
|
172 PRIntervalTime interval = PR_IntervalNow() - sLastLowMemoryNotificationTime; |
|
173 if (sHasScheduledOneLowMemoryNotification && |
|
174 PR_IntervalToMilliseconds(interval) < sLowMemoryNotificationIntervalMS) { |
|
175 |
|
176 LOG("Not scheduling low physical memory notification, " |
|
177 "because not enough time has elapsed since last one."); |
|
178 return false; |
|
179 } |
|
180 |
|
181 // There's a bit of a race condition here, since an interval may be a |
|
182 // 64-bit number, and 64-bit writes aren't atomic on x86-32. But let's |
|
183 // not worry about it -- the races only happen when we're already |
|
184 // experiencing memory pressure and firing notifications, so the worst |
|
185 // thing that can happen is that we fire two notifications when we |
|
186 // should have fired only one. |
|
187 sHasScheduledOneLowMemoryNotification = true; |
|
188 sLastLowMemoryNotificationTime = PR_IntervalNow(); |
|
189 |
|
190 LOG("Scheduling memory pressure notification."); |
|
191 NS_DispatchEventualMemoryPressure(MemPressure_New); |
|
192 return true; |
|
193 } |
|
194 |
|
195 void CheckMemAvailable() |
|
196 { |
|
197 if (!sHooksActive) { |
|
198 return; |
|
199 } |
|
200 |
|
201 MEMORYSTATUSEX stat; |
|
202 stat.dwLength = sizeof(stat); |
|
203 bool success = GlobalMemoryStatusEx(&stat); |
|
204 |
|
205 DEBUG_WARN_IF_FALSE(success, "GlobalMemoryStatusEx failed."); |
|
206 |
|
207 if (success) |
|
208 { |
|
209 // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes. |
|
210 if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) { |
|
211 // If we're running low on virtual memory, unconditionally schedule the |
|
212 // notification. We'll probably crash if we run out of virtual memory, |
|
213 // so don't worry about firing this notification too often. |
|
214 LOG("Detected low virtual memory."); |
|
215 ++sNumLowVirtualMemEvents; |
|
216 NS_DispatchEventualMemoryPressure(MemPressure_New); |
|
217 } |
|
218 else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) { |
|
219 LOG("Detected low available page file space."); |
|
220 if (MaybeScheduleMemoryPressureEvent()) { |
|
221 ++sNumLowCommitSpaceEvents; |
|
222 } |
|
223 } |
|
224 else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) { |
|
225 LOG("Detected low physical memory."); |
|
226 if (MaybeScheduleMemoryPressureEvent()) { |
|
227 ++sNumLowPhysicalMemEvents; |
|
228 } |
|
229 } |
|
230 } |
|
231 } |
|
232 |
|
233 LPVOID WINAPI |
|
234 VirtualAllocHook(LPVOID aAddress, SIZE_T aSize, |
|
235 DWORD aAllocationType, |
|
236 DWORD aProtect) |
|
237 { |
|
238 // It's tempting to see whether we have enough free virtual address space for |
|
239 // this allocation and, if we don't, synchronously fire a low-memory |
|
240 // notification to free some before we allocate. |
|
241 // |
|
242 // Unfortunately that doesn't work, principally because code doesn't expect a |
|
243 // call to malloc could trigger a GC (or call into the other routines which |
|
244 // are triggered by a low-memory notification). |
|
245 // |
|
246 // I think the best we can do here is try to allocate the memory and check |
|
247 // afterwards how much free virtual address space we have. If we're running |
|
248 // low, we schedule a low-memory notification to run as soon as possible. |
|
249 |
|
250 LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect); |
|
251 |
|
252 // Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low |
|
253 // virtual memory. Similarly, don't call CheckMemAvailable for MEM_COMMIT if |
|
254 // we're not tracking low physical memory. |
|
255 if ((sLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) || |
|
256 (sLowPhysicalMemoryThreshold != 0 && aAllocationType & MEM_COMMIT)) { |
|
257 LOG3("VirtualAllocHook(size=", aSize, ")"); |
|
258 CheckMemAvailable(); |
|
259 } |
|
260 |
|
261 return result; |
|
262 } |
|
263 |
|
264 LPVOID WINAPI |
|
265 MapViewOfFileHook(HANDLE aFileMappingObject, |
|
266 DWORD aDesiredAccess, |
|
267 DWORD aFileOffsetHigh, |
|
268 DWORD aFileOffsetLow, |
|
269 SIZE_T aNumBytesToMap) |
|
270 { |
|
271 LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess, |
|
272 aFileOffsetHigh, aFileOffsetLow, |
|
273 aNumBytesToMap); |
|
274 LOG("MapViewOfFileHook"); |
|
275 CheckMemAvailable(); |
|
276 return result; |
|
277 } |
|
278 |
|
279 HBITMAP WINAPI |
|
280 CreateDIBSectionHook(HDC aDC, |
|
281 const BITMAPINFO *aBitmapInfo, |
|
282 UINT aUsage, |
|
283 VOID **aBits, |
|
284 HANDLE aSection, |
|
285 DWORD aOffset) |
|
286 { |
|
287 // There are a lot of calls to CreateDIBSection, so we make some effort not |
|
288 // to CheckMemAvailable() for calls to CreateDIBSection which allocate only |
|
289 // a small amount of memory. |
|
290 |
|
291 // If aSection is non-null, CreateDIBSection won't allocate any new memory. |
|
292 bool doCheck = false; |
|
293 if (sHooksActive && !aSection && aBitmapInfo) { |
|
294 uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount; |
|
295 if (bitCount == 0) { |
|
296 // MSDN says bitCount == 0 means that it figures out how many bits each |
|
297 // pixel gets by examining the corresponding JPEG or PNG data. We'll just |
|
298 // assume the worst. |
|
299 bitCount = 32; |
|
300 } |
|
301 |
|
302 // |size| contains the expected allocation size in *bits*. Height may be |
|
303 // negative (indicating the direction the DIB is drawn in), so we take the |
|
304 // absolute value. |
|
305 int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth * |
|
306 aBitmapInfo->bmiHeader.biHeight; |
|
307 if (size < 0) |
|
308 size *= -1; |
|
309 |
|
310 // If we're allocating more than 1MB, check how much memory is left after |
|
311 // the allocation. |
|
312 if (size > 1024 * 1024 * 8) { |
|
313 LOG3("CreateDIBSectionHook: Large allocation (size=", size, ")"); |
|
314 doCheck = true; |
|
315 } |
|
316 } |
|
317 |
|
318 HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits, |
|
319 aSection, aOffset); |
|
320 |
|
321 if (doCheck) { |
|
322 CheckMemAvailable(); |
|
323 } |
|
324 |
|
325 return result; |
|
326 } |
|
327 |
|
328 static int64_t |
|
329 LowMemoryEventsVirtualDistinguishedAmount() |
|
330 { |
|
331 return sNumLowVirtualMemEvents; |
|
332 } |
|
333 |
|
334 static int64_t |
|
335 LowMemoryEventsPhysicalDistinguishedAmount() |
|
336 { |
|
337 return sNumLowPhysicalMemEvents; |
|
338 } |
|
339 |
|
340 class LowEventsReporter MOZ_FINAL : public nsIMemoryReporter |
|
341 { |
|
342 public: |
|
343 NS_DECL_ISUPPORTS |
|
344 |
|
345 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, |
|
346 nsISupports* aData) |
|
347 { |
|
348 nsresult rv; |
|
349 |
|
350 // We only do virtual-memory tracking on 32-bit builds. |
|
351 if (sizeof(void*) == 4) { |
|
352 rv = MOZ_COLLECT_REPORT( |
|
353 "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE, |
|
354 LowMemoryEventsVirtualDistinguishedAmount(), |
|
355 "Number of low-virtual-memory events fired since startup. We fire such an " |
|
356 "event if we notice there is less than memory.low_virtual_mem_threshold_mb of " |
|
357 "virtual address space available (if zero, this behavior is disabled). The " |
|
358 "process will probably crash if it runs out of virtual address space, so " |
|
359 "this event is dire."); |
|
360 NS_ENSURE_SUCCESS(rv, rv); |
|
361 } |
|
362 |
|
363 rv = MOZ_COLLECT_REPORT( |
|
364 "low-commit-space-events", KIND_OTHER, UNITS_COUNT_CUMULATIVE, |
|
365 sNumLowCommitSpaceEvents, |
|
366 "Number of low-commit-space events fired since startup. We fire such an " |
|
367 "event if we notice there is less than memory.low_commit_space_threshold_mb of " |
|
368 "commit space available (if zero, this behavior is disabled). Windows will " |
|
369 "likely kill the process if it runs out of commit space, so this event is " |
|
370 "dire."); |
|
371 NS_ENSURE_SUCCESS(rv, rv); |
|
372 |
|
373 rv = MOZ_COLLECT_REPORT( |
|
374 "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE, |
|
375 LowMemoryEventsPhysicalDistinguishedAmount(), |
|
376 "Number of low-physical-memory events fired since startup. We fire such an " |
|
377 "event if we notice there is less than memory.low_physical_memory_threshold_mb " |
|
378 "of physical memory available (if zero, this behavior is disabled). The " |
|
379 "machine will start to page if it runs out of physical memory. This may " |
|
380 "cause it to run slowly, but it shouldn't cause it to crash."); |
|
381 NS_ENSURE_SUCCESS(rv, rv); |
|
382 |
|
383 return NS_OK; |
|
384 } |
|
385 }; |
|
386 NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter) |
|
387 |
|
388 #endif // defined(XP_WIN) |
|
389 |
|
390 /** |
|
391 * This runnable is executed in response to a memory-pressure event; we spin |
|
392 * the event-loop when receiving the memory-pressure event in the hope that |
|
393 * other observers will synchronously free some memory that we'll be able to |
|
394 * purge here. |
|
395 */ |
|
396 class nsJemallocFreeDirtyPagesRunnable MOZ_FINAL : public nsIRunnable |
|
397 { |
|
398 public: |
|
399 NS_DECL_ISUPPORTS |
|
400 NS_DECL_NSIRUNNABLE |
|
401 }; |
|
402 |
|
403 NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable) |
|
404 |
|
405 NS_IMETHODIMP |
|
406 nsJemallocFreeDirtyPagesRunnable::Run() |
|
407 { |
|
408 MOZ_ASSERT(NS_IsMainThread()); |
|
409 |
|
410 #if defined(MOZ_MEMORY) |
|
411 jemalloc_free_dirty_pages(); |
|
412 #endif |
|
413 |
|
414 return NS_OK; |
|
415 } |
|
416 |
|
417 /** |
|
418 * The memory pressure watcher is used for listening to memory-pressure events |
|
419 * and reacting upon them. We use one instance per process currently only for |
|
420 * cleaning up dirty unused pages held by jemalloc. |
|
421 */ |
|
422 class nsMemoryPressureWatcher MOZ_FINAL : public nsIObserver |
|
423 { |
|
424 public: |
|
425 NS_DECL_ISUPPORTS |
|
426 NS_DECL_NSIOBSERVER |
|
427 |
|
428 void Init(); |
|
429 |
|
430 private: |
|
431 static bool sFreeDirtyPages; |
|
432 }; |
|
433 |
|
434 NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver) |
|
435 |
|
436 bool nsMemoryPressureWatcher::sFreeDirtyPages = false; |
|
437 |
|
438 /** |
|
439 * Initialize and subscribe to the memory-pressure events. We subscribe to the |
|
440 * observer service in this method and not in the constructor because we need |
|
441 * to hold a strong reference to 'this' before calling the observer service. |
|
442 */ |
|
443 void |
|
444 nsMemoryPressureWatcher::Init() |
|
445 { |
|
446 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); |
|
447 |
|
448 if (os) { |
|
449 os->AddObserver(this, "memory-pressure", /* ownsWeak */ false); |
|
450 } |
|
451 |
|
452 Preferences::AddBoolVarCache(&sFreeDirtyPages, "memory.free_dirty_pages", |
|
453 false); |
|
454 } |
|
455 |
|
456 /** |
|
457 * Reacts to all types of memory-pressure events, launches a runnable to |
|
458 * free dirty pages held by jemalloc. |
|
459 */ |
|
460 NS_IMETHODIMP |
|
461 nsMemoryPressureWatcher::Observe(nsISupports *subject, const char *topic, |
|
462 const char16_t *data) |
|
463 { |
|
464 MOZ_ASSERT(!strcmp(topic, "memory-pressure"), "Unknown topic"); |
|
465 |
|
466 if (sFreeDirtyPages) { |
|
467 nsRefPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable(); |
|
468 |
|
469 NS_DispatchToMainThread(runnable); |
|
470 } |
|
471 |
|
472 return NS_OK; |
|
473 } |
|
474 |
|
475 } // anonymous namespace |
|
476 |
|
477 namespace mozilla { |
|
478 namespace AvailableMemoryTracker { |
|
479 |
|
480 void Activate() |
|
481 { |
|
482 #if defined(_M_IX86) && defined(XP_WIN) |
|
483 MOZ_ASSERT(sInitialized); |
|
484 MOZ_ASSERT(!sHooksActive); |
|
485 |
|
486 // On 64-bit systems, hardcode sLowVirtualMemoryThreshold to 0 -- we assume |
|
487 // we're not going to run out of virtual memory! |
|
488 if (sizeof(void*) > 4) { |
|
489 sLowVirtualMemoryThreshold = 0; |
|
490 } |
|
491 else { |
|
492 Preferences::AddUintVarCache(&sLowVirtualMemoryThreshold, |
|
493 "memory.low_virtual_mem_threshold_mb", 128); |
|
494 } |
|
495 |
|
496 Preferences::AddUintVarCache(&sLowPhysicalMemoryThreshold, |
|
497 "memory.low_physical_memory_threshold_mb", 0); |
|
498 Preferences::AddUintVarCache(&sLowCommitSpaceThreshold, |
|
499 "memory.low_commit_space_threshold_mb", 128); |
|
500 Preferences::AddUintVarCache(&sLowMemoryNotificationIntervalMS, |
|
501 "memory.low_memory_notification_interval_ms", 10000); |
|
502 |
|
503 RegisterStrongMemoryReporter(new LowEventsReporter()); |
|
504 RegisterLowMemoryEventsVirtualDistinguishedAmount(LowMemoryEventsVirtualDistinguishedAmount); |
|
505 RegisterLowMemoryEventsPhysicalDistinguishedAmount(LowMemoryEventsPhysicalDistinguishedAmount); |
|
506 sHooksActive = true; |
|
507 #endif |
|
508 |
|
509 // This object is held alive by the observer service. |
|
510 nsRefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher(); |
|
511 watcher->Init(); |
|
512 } |
|
513 |
|
514 void Init() |
|
515 { |
|
516 // Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe |
|
517 // on 64-bit. (On 32-bit, it's probably thread-safe.) Even if we run Init() |
|
518 // before any other of our threads are running, another process may have |
|
519 // started a remote thread which could call VirtualAlloc! |
|
520 // |
|
521 // Moreover, the benefit of this code is less clear when we're a 64-bit |
|
522 // process, because we aren't going to run out of virtual memory, and the |
|
523 // system is likely to have a fair bit of physical memory. |
|
524 |
|
525 #if defined(_M_IX86) && defined(XP_WIN) |
|
526 // Don't register the hooks if we're a build instrumented for PGO: If we're |
|
527 // an instrumented build, the compiler adds function calls all over the place |
|
528 // which may call VirtualAlloc; this makes it hard to prevent |
|
529 // VirtualAllocHook from reentering itself. |
|
530 if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) { |
|
531 sKernel32Intercept.Init("Kernel32.dll"); |
|
532 sKernel32Intercept.AddHook("VirtualAlloc", |
|
533 reinterpret_cast<intptr_t>(VirtualAllocHook), |
|
534 (void**) &sVirtualAllocOrig); |
|
535 sKernel32Intercept.AddHook("MapViewOfFile", |
|
536 reinterpret_cast<intptr_t>(MapViewOfFileHook), |
|
537 (void**) &sMapViewOfFileOrig); |
|
538 |
|
539 sGdi32Intercept.Init("Gdi32.dll"); |
|
540 sGdi32Intercept.AddHook("CreateDIBSection", |
|
541 reinterpret_cast<intptr_t>(CreateDIBSectionHook), |
|
542 (void**) &sCreateDIBSectionOrig); |
|
543 } |
|
544 |
|
545 sInitialized = true; |
|
546 #endif |
|
547 } |
|
548 |
|
549 } // namespace AvailableMemoryTracker |
|
550 } // namespace mozilla |