OLD | NEW |
---|---|
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // This file contains all the logic necessary to intercept allocations on | 5 // This file contains all the logic necessary to intercept allocations on |
6 // macOS. "malloc zones" are an abstraction that allows the process to intercept | 6 // macOS. "malloc zones" are an abstraction that allows the process to intercept |
7 // all malloc-related functions. There is no good mechanism [short of | 7 // all malloc-related functions. There is no good mechanism [short of |
8 // interposition] to determine new malloc zones are added, so there's no clean | 8 // interposition] to determine new malloc zones are added, so there's no clean |
9 // mechanism to intercept all malloc zones. This file contains logic to | 9 // mechanism to intercept all malloc zones. This file contains logic to |
10 // intercept the default and purgeable zones, which always exist. A cursory | 10 // intercept the default and purgeable zones, which always exist. A cursory |
(...skipping 246 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
257 typedef id (*allocWithZone_t)(id, SEL, NSZone*); | 257 typedef id (*allocWithZone_t)(id, SEL, NSZone*); |
258 allocWithZone_t g_old_allocWithZone; | 258 allocWithZone_t g_old_allocWithZone; |
259 | 259 |
260 id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) { | 260 id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) { |
261 id result = g_old_allocWithZone(self, _cmd, zone); | 261 id result = g_old_allocWithZone(self, _cmd, zone); |
262 if (!result) | 262 if (!result) |
263 TerminateBecauseOutOfMemory(0); | 263 TerminateBecauseOutOfMemory(0); |
264 return result; | 264 return result; |
265 } | 265 } |
266 | 266 |
267 } // namespace | |
268 | |
269 bool UncheckedMallocMac(size_t size, void** result) { | |
270 #if defined(ADDRESS_SANITIZER) | |
271 *result = malloc(size); | |
272 #else | |
273 if (g_old_zone.malloc) { | |
274 *result = g_old_zone.malloc(malloc_default_zone(), size); | |
275 } else { | |
276 *result = malloc(size); | |
277 } | |
278 #endif // defined(ADDRESS_SANITIZER) | |
279 | |
280 return *result != NULL; | |
281 } | |
282 | |
283 bool UncheckedCallocMac(size_t num_items, size_t size, void** result) { | |
284 #if defined(ADDRESS_SANITIZER) | |
285 *result = calloc(num_items, size); | |
286 #else | |
287 if (g_old_zone.calloc) { | |
288 *result = g_old_zone.calloc(malloc_default_zone(), num_items, size); | |
289 } else { | |
290 *result = calloc(num_items, size); | |
291 } | |
292 #endif // defined(ADDRESS_SANITIZER) | |
293 | |
294 return *result != NULL; | |
295 } | |
296 | |
297 void ReplaceZoneFunctions(ChromeMallocZone* zone, | 267 void ReplaceZoneFunctions(ChromeMallocZone* zone, |
298 const MallocZoneFunctions* functions) { | 268 const MallocZoneFunctions* functions) { |
299 // Remove protection. | 269 // Remove protection. |
300 mach_vm_address_t reprotection_start = 0; | 270 mach_vm_address_t reprotection_start = 0; |
301 mach_vm_size_t reprotection_length = 0; | 271 mach_vm_size_t reprotection_length = 0; |
302 vm_prot_t reprotection_value = VM_PROT_NONE; | 272 vm_prot_t reprotection_value = VM_PROT_NONE; |
303 DeprotectMallocZone(zone, &reprotection_start, &reprotection_length, | 273 DeprotectMallocZone(zone, &reprotection_start, &reprotection_length, |
304 &reprotection_value); | 274 &reprotection_value); |
305 | 275 |
306 CHECK(functions->malloc && functions->calloc && functions->valloc && | 276 CHECK(functions->malloc && functions->calloc && functions->valloc && |
(...skipping 18 matching lines...) Expand all Loading... | |
325 | 295 |
326 // Restore protection if it was active. | 296 // Restore protection if it was active. |
327 if (reprotection_start) { | 297 if (reprotection_start) { |
328 kern_return_t result = | 298 kern_return_t result = |
329 mach_vm_protect(mach_task_self(), reprotection_start, | 299 mach_vm_protect(mach_task_self(), reprotection_start, |
330 reprotection_length, false, reprotection_value); | 300 reprotection_length, false, reprotection_value); |
331 MACH_CHECK(result == KERN_SUCCESS, result) << "mach_vm_protect"; | 301 MACH_CHECK(result == KERN_SUCCESS, result) << "mach_vm_protect"; |
332 } | 302 } |
333 } | 303 } |
334 | 304 |
305 void UninterceptMallocZoneForTesting(struct _malloc_zone_t* zone) { | |
306 ChromeMallocZone* chrome_zone = reinterpret_cast<ChromeMallocZone*>(zone); | |
307 if (!IsMallocZoneAlreadyStored(chrome_zone)) | |
308 return; | |
309 MallocZoneFunctions& functions = GetFunctionsForZone(zone); | |
310 ReplaceZoneFunctions(chrome_zone, &functions); | |
311 } | |
312 | |
313 } // namespace | |
314 | |
315 bool UncheckedMallocMac(size_t size, void** result) { | |
316 #if defined(ADDRESS_SANITIZER) | |
317 *result = malloc(size); | |
318 #else | |
319 if (g_old_zone.malloc) { | |
320 *result = g_old_zone.malloc(malloc_default_zone(), size); | |
321 } else { | |
322 *result = malloc(size); | |
323 } | |
324 #endif // defined(ADDRESS_SANITIZER) | |
325 | |
326 return *result != NULL; | |
327 } | |
328 | |
329 bool UncheckedCallocMac(size_t num_items, size_t size, void** result) { | |
330 #if defined(ADDRESS_SANITIZER) | |
331 *result = calloc(num_items, size); | |
332 #else | |
333 if (g_old_zone.calloc) { | |
334 *result = g_old_zone.calloc(malloc_default_zone(), num_items, size); | |
335 } else { | |
336 *result = calloc(num_items, size); | |
337 } | |
338 #endif // defined(ADDRESS_SANITIZER) | |
339 | |
340 return *result != NULL; | |
341 } | |
342 | |
335 void StoreFunctionsForDefaultZone() { | 343 void StoreFunctionsForDefaultZone() { |
336 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( | 344 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( |
337 malloc_default_zone()); | 345 malloc_default_zone()); |
338 StoreMallocZone(default_zone); | 346 StoreMallocZone(default_zone); |
339 } | 347 } |
340 | 348 |
341 void StoreFunctionsForAllZones() { | 349 void StoreFunctionsForAllZones() { |
342 // This ensures that the default zone is always at the front of the array, | 350 // This ensures that the default zone is always at the front of the array, |
343 // which is important for performance. | 351 // which is important for performance. |
344 StoreFunctionsForDefaultZone(); | 352 StoreFunctionsForDefaultZone(); |
345 | 353 |
346 vm_address_t* zones; | 354 vm_address_t* zones; |
347 unsigned int count; | 355 unsigned int count; |
348 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); | 356 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); |
349 if (kr != KERN_SUCCESS) | 357 if (kr != KERN_SUCCESS) |
350 return; | 358 return; |
351 for (unsigned int i = 0; i < count; ++i) { | 359 for (unsigned int i = 0; i < count; ++i) { |
352 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); | 360 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); |
353 StoreMallocZone(zone); | 361 StoreMallocZone(zone); |
354 } | 362 } |
355 } | 363 } |
356 | 364 |
357 void ReplaceFunctionsForStoredZones(const MallocZoneFunctions* functions) { | 365 void ReplaceFunctionsForStoredZones(const MallocZoneFunctions* functions) { |
366 ChromeMallocZone* default_zone = | |
Primiano Tucci (use gerrit)
2017/03/21 19:39:23
can you add a comment explaining that the malloc z
erikchen
2017/03/24 21:59:03
Done.
| |
367 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); | |
368 if (IsMallocZoneAlreadyStored(default_zone) && | |
Primiano Tucci (use gerrit)
2017/03/21 19:39:23
minor comment: you are now repeating this:
IsMallo
erikchen
2017/03/24 21:59:03
Done.
| |
369 default_zone->malloc != functions->malloc) { | |
370 ReplaceZoneFunctions(default_zone, functions); | |
371 } | |
372 | |
358 vm_address_t* zones; | 373 vm_address_t* zones; |
359 unsigned int count; | 374 unsigned int count; |
360 kern_return_t kr = | 375 kern_return_t kr = |
361 malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count); | 376 malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count); |
362 if (kr != KERN_SUCCESS) | 377 if (kr != KERN_SUCCESS) |
363 return; | 378 return; |
364 for (unsigned int i = 0; i < count; ++i) { | 379 for (unsigned int i = 0; i < count; ++i) { |
365 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); | 380 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); |
366 if (IsMallocZoneAlreadyStored(zone) && zone->malloc != functions->malloc) { | 381 if (IsMallocZoneAlreadyStored(zone) && zone->malloc != functions->malloc) { |
367 ReplaceZoneFunctions(zone, functions); | 382 ReplaceZoneFunctions(zone, functions); |
(...skipping 18 matching lines...) Expand all Loading... | |
386 // allocations from non-default zones. | 401 // allocations from non-default zones. |
387 | 402 |
388 #if !defined(ADDRESS_SANITIZER) | 403 #if !defined(ADDRESS_SANITIZER) |
389 // Don't do anything special on OOM for the malloc zones replaced by | 404 // Don't do anything special on OOM for the malloc zones replaced by |
390 // AddressSanitizer, as modifying or protecting them may not work correctly. | 405 // AddressSanitizer, as modifying or protecting them may not work correctly. |
391 ChromeMallocZone* default_zone = | 406 ChromeMallocZone* default_zone = |
392 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); | 407 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); |
393 if (!IsMallocZoneAlreadyStored(default_zone)) { | 408 if (!IsMallocZoneAlreadyStored(default_zone)) { |
394 StoreZoneFunctions(default_zone, &g_old_zone); | 409 StoreZoneFunctions(default_zone, &g_old_zone); |
395 MallocZoneFunctions new_functions; | 410 MallocZoneFunctions new_functions; |
411 memset(&new_functions, 0, sizeof(MallocZoneFunctions)); | |
Primiano Tucci (use gerrit)
2017/03/21 19:39:23
Just MallocZoneFunctions new_functions = {} should
erikchen
2017/03/24 21:59:03
Done.
| |
396 new_functions.malloc = oom_killer_malloc; | 412 new_functions.malloc = oom_killer_malloc; |
397 new_functions.calloc = oom_killer_calloc; | 413 new_functions.calloc = oom_killer_calloc; |
398 new_functions.valloc = oom_killer_valloc; | 414 new_functions.valloc = oom_killer_valloc; |
399 new_functions.free = oom_killer_free; | 415 new_functions.free = oom_killer_free; |
400 new_functions.realloc = oom_killer_realloc; | 416 new_functions.realloc = oom_killer_realloc; |
401 new_functions.memalign = oom_killer_memalign; | 417 new_functions.memalign = oom_killer_memalign; |
402 | 418 |
403 ReplaceZoneFunctions(default_zone, &new_functions); | 419 ReplaceZoneFunctions(default_zone, &new_functions); |
404 g_replaced_default_zone = true; | 420 g_replaced_default_zone = true; |
405 } | 421 } |
406 | 422 |
407 ChromeMallocZone* purgeable_zone = | 423 ChromeMallocZone* purgeable_zone = |
408 reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); | 424 reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); |
409 if (purgeable_zone && !IsMallocZoneAlreadyStored(purgeable_zone)) { | 425 if (purgeable_zone && !IsMallocZoneAlreadyStored(purgeable_zone)) { |
410 StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone); | 426 StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone); |
411 MallocZoneFunctions new_functions; | 427 MallocZoneFunctions new_functions; |
428 memset(&new_functions, 0, sizeof(MallocZoneFunctions)); | |
412 new_functions.malloc = oom_killer_malloc_purgeable; | 429 new_functions.malloc = oom_killer_malloc_purgeable; |
413 new_functions.calloc = oom_killer_calloc_purgeable; | 430 new_functions.calloc = oom_killer_calloc_purgeable; |
414 new_functions.valloc = oom_killer_valloc_purgeable; | 431 new_functions.valloc = oom_killer_valloc_purgeable; |
415 new_functions.free = oom_killer_free_purgeable; | 432 new_functions.free = oom_killer_free_purgeable; |
416 new_functions.realloc = oom_killer_realloc_purgeable; | 433 new_functions.realloc = oom_killer_realloc_purgeable; |
417 new_functions.memalign = oom_killer_memalign_purgeable; | 434 new_functions.memalign = oom_killer_memalign_purgeable; |
418 ReplaceZoneFunctions(purgeable_zone, &new_functions); | 435 ReplaceZoneFunctions(purgeable_zone, &new_functions); |
419 } | 436 } |
420 #endif | 437 #endif |
421 | 438 |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
489 Method orig_method = | 506 Method orig_method = |
490 class_getClassMethod(nsobject_class, @selector(allocWithZone:)); | 507 class_getClassMethod(nsobject_class, @selector(allocWithZone:)); |
491 g_old_allocWithZone = | 508 g_old_allocWithZone = |
492 reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method)); | 509 reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method)); |
493 CHECK(g_old_allocWithZone) | 510 CHECK(g_old_allocWithZone) |
494 << "Failed to get allocWithZone allocation function."; | 511 << "Failed to get allocWithZone allocation function."; |
495 method_setImplementation(orig_method, | 512 method_setImplementation(orig_method, |
496 reinterpret_cast<IMP>(oom_killer_allocWithZone)); | 513 reinterpret_cast<IMP>(oom_killer_allocWithZone)); |
497 } | 514 } |
498 | 515 |
516 void UninterceptMallocZonesForTesting() { | |
517 UninterceptMallocZoneForTesting(malloc_default_zone()); | |
518 vm_address_t* zones; | |
519 unsigned int count; | |
520 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); | |
521 CHECK(kr == KERN_SUCCESS); | |
522 for (unsigned int i = 0; i < count; ++i) { | |
523 UninterceptMallocZoneForTesting( | |
524 reinterpret_cast<struct _malloc_zone_t*>(zones[i])); | |
525 } | |
526 | |
527 ClearAllMallocZonesForTesting(); | |
528 } | |
529 | |
499 } // namespace allocator | 530 } // namespace allocator |
500 } // namespace base | 531 } // namespace base |
OLD | NEW |