1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "base/process_util.h"
8 #import <Cocoa/Cocoa.h>
9 #include <crt_externs.h>
11 #include <mach/mach.h>
12 #include <mach/mach_init.h>
13 #include <mach/mach_vm.h>
14 #include <mach/shared_region.h>
15 #include <mach/task.h>
16 #include <malloc/malloc.h>
17 #import <objc/runtime.h>
20 #include <sys/sysctl.h>
21 #include <sys/types.h>
22 #include <sys/utsname.h>
28 #include "base/debug/debugger.h"
29 #include "base/eintr_wrapper.h"
30 #include "base/hash_tables.h"
31 #include "base/logging.h"
32 #include "base/string_util.h"
33 #include "base/sys_info.h"
34 #include "base/sys_string_conversions.h"
35 #include "base/time.h"
36 #include "third_party/apple_apsl/CFBase.h"
37 #include "third_party/apple_apsl/malloc.h"
41 void RestoreDefaultExceptionHandler() {
42 // This function is tailored to remove the Breakpad exception handler.
43 // exception_mask matches s_exception_mask in
44 // breakpad/src/client/mac/handler/exception_handler.cc
45 const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS |
46 EXC_MASK_BAD_INSTRUCTION |
50 // Setting the exception port to MACH_PORT_NULL may not be entirely
51 // kosher to restore the default exception handler, but in practice,
52 // it results in the exception port being set to Apple Crash Reporter,
53 // the desired behavior.
54 task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL,
55 EXCEPTION_DEFAULT, THREAD_STATE_NONE);
58 ProcessIterator::ProcessIterator(const ProcessFilter* filter)
59 : index_of_kinfo_proc_(0),
61 // Get a snapshot of all of my processes (yes, as we loop it can go stale, but
62 // but trying to find where we were in a constantly changing list is basically
65 int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, geteuid() };
67 // Since more processes could start between when we get the size and when
68 // we get the list, we do a loop to keep trying until we get it.
71 const int max_tries = 10;
73 // Get the size of the buffer
75 if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) {
76 LOG(ERROR) << "failed to get the size needed for the process list";
77 kinfo_procs_.resize(0);
80 size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
81 // Leave some spare room for process table growth (more could show up
82 // between when we check and now)
83 num_of_kinfo_proc += 16;
84 kinfo_procs_.resize(num_of_kinfo_proc);
85 len = num_of_kinfo_proc * sizeof(struct kinfo_proc);
86 // Load the list of processes
87 if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) {
88 // If we get a mem error, it just means we need a bigger buffer, so
89 // loop around again. Anything else is a real error and give up.
90 if (errno != ENOMEM) {
91 LOG(ERROR) << "failed to get the process list";
92 kinfo_procs_.resize(0);
96 // Got the list, just make sure we're sized exactly right
97 size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
98 kinfo_procs_.resize(num_of_kinfo_proc);
102 } while (!done && (try_num++ < max_tries));
105 LOG(ERROR) << "failed to collect the process list in a few tries";
106 kinfo_procs_.resize(0);
110 ProcessIterator::~ProcessIterator() {
113 bool ProcessIterator::CheckForNextProcess() {
115 for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) {
116 kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_];
118 // Skip processes just awaiting collection
119 if ((kinfo.kp_proc.p_pid > 0) && (kinfo.kp_proc.p_stat == SZOMB))
122 int mib[] = { CTL_KERN, KERN_PROCARGS, kinfo.kp_proc.p_pid };
124 // Find out what size buffer we need.
126 if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) {
127 DVPLOG(1) << "failed to figure out the buffer size for a commandline";
131 data.resize(data_len);
132 if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) {
133 DVPLOG(1) << "failed to fetch a commandline";
137 // |data| contains all the command line parameters of the process, separated
138 // by blocks of one or more null characters. We tokenize |data| into a
139 // vector of strings using '\0' as a delimiter and populate
140 // |entry_.cmd_line_args_|.
141 std::string delimiters;
142 delimiters.push_back('\0');
143 Tokenize(data, delimiters, &entry_.cmd_line_args_);
145 // |data| starts with the full executable path followed by a null character.
146 // We search for the first instance of '\0' and extract everything before it
147 // to populate |entry_.exe_file_|.
148 size_t exec_name_end = data.find('\0');
149 if (exec_name_end == std::string::npos) {
150 LOG(ERROR) << "command line data didn't match expected format";
154 entry_.pid_ = kinfo.kp_proc.p_pid;
155 entry_.ppid_ = kinfo.kp_eproc.e_ppid;
156 entry_.gid_ = kinfo.kp_eproc.e_pgid;
157 size_t last_slash = data.rfind('/', exec_name_end);
158 if (last_slash == std::string::npos)
159 entry_.exe_file_.assign(data, 0, exec_name_end);
161 entry_.exe_file_.assign(data, last_slash + 1,
162 exec_name_end - last_slash - 1);
163 // Start w/ the next entry next time through
164 ++index_of_kinfo_proc_;
171 bool NamedProcessIterator::IncludeEntry() {
172 return (executable_name_ == entry().exe_file() &&
173 ProcessIterator::IncludeEntry());
177 // ------------------------------------------------------------------------
178 // NOTE: about ProcessMetrics
180 // Getting a mach task from a pid for another process requires permissions in
181 // general, so there doesn't really seem to be a way to do these (and spinning
182 // up ps to fetch each stats seems dangerous to put in a base api for anyone to
183 // call). Child processes ipc their port, so return something if available,
184 // otherwise return 0.
187 ProcessMetrics::ProcessMetrics(ProcessHandle process,
188 ProcessMetrics::PortProvider* port_provider)
191 last_system_time_(0),
192 port_provider_(port_provider) {
193 processor_count_ = SysInfo::NumberOfProcessors();
197 ProcessMetrics* ProcessMetrics::CreateProcessMetrics(
198 ProcessHandle process,
199 ProcessMetrics::PortProvider* port_provider) {
200 return new ProcessMetrics(process, port_provider);
203 bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
207 static bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) {
208 if (task == MACH_PORT_NULL)
210 mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
211 kern_return_t kr = task_info(task,
213 reinterpret_cast<task_info_t>(task_info_data),
215 // Most likely cause for failure: |task| is a zombie.
216 return kr == KERN_SUCCESS;
219 size_t ProcessMetrics::GetPagefileUsage() const {
220 task_basic_info_64 task_info_data;
221 if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
223 return task_info_data.virtual_size;
226 size_t ProcessMetrics::GetPeakPagefileUsage() const {
230 size_t ProcessMetrics::GetWorkingSetSize() const {
231 task_basic_info_64 task_info_data;
232 if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
234 return task_info_data.resident_size;
237 size_t ProcessMetrics::GetPeakWorkingSetSize() const {
241 static bool GetCPUTypeForProcess(pid_t pid, cpu_type_t* cpu_type) {
242 size_t len = sizeof(*cpu_type);
243 int result = sysctlbyname("sysctl.proc_cputype",
249 PLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")";
256 static bool IsAddressInSharedRegion(mach_vm_address_t addr, cpu_type_t type) {
257 if (type == CPU_TYPE_I386)
258 return addr >= SHARED_REGION_BASE_I386 &&
259 addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386);
260 else if (type == CPU_TYPE_X86_64)
261 return addr >= SHARED_REGION_BASE_X86_64 &&
262 addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64);
267 // This is a rough approximation of the algorithm that libtop uses.
268 // private_bytes is the size of private resident memory.
269 // shared_bytes is the size of shared resident memory.
270 bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
271 size_t* shared_bytes) {
273 size_t private_pages_count = 0;
274 size_t shared_pages_count = 0;
276 if (!private_bytes && !shared_bytes)
279 mach_port_t task = TaskForPid(process_);
280 if (task == MACH_PORT_NULL) {
281 LOG(ERROR) << "Invalid process";
286 if (!GetCPUTypeForProcess(process_, &cpu_type))
289 // The same region can be referenced multiple times. To avoid double counting
290 // we need to keep track of which regions we've already counted.
291 base::hash_set<int> seen_objects;
293 // We iterate through each VM region in the task's address map. For shared
294 // memory we add up all the pages that are marked as shared. Like libtop we
295 // try to avoid counting pages that are also referenced by other tasks. Since
296 // we don't have access to the VM regions of other tasks the only hint we have
297 // is if the address is in the shared region area.
299 // Private memory is much simpler. We simply count the pages that are marked
300 // as private or copy on write (COW).
302 // See libtop_update_vm_regions in
303 // http://www.opensource.apple.com/source/top/top-67/libtop.c
304 mach_vm_size_t size = 0;
305 for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) {
306 vm_region_top_info_data_t info;
307 mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT;
308 mach_port_t object_name;
309 kr = mach_vm_region(task,
313 (vm_region_info_t)&info,
316 if (kr == KERN_INVALID_ADDRESS) {
317 // We're at the end of the address space.
319 } else if (kr != KERN_SUCCESS) {
320 LOG(ERROR) << "Calling mach_vm_region failed with error: "
321 << mach_error_string(kr);
325 if (IsAddressInSharedRegion(address, cpu_type) &&
326 info.share_mode != SM_PRIVATE)
329 if (info.share_mode == SM_COW && info.ref_count == 1)
330 info.share_mode = SM_PRIVATE;
332 switch (info.share_mode) {
334 private_pages_count += info.private_pages_resident;
335 private_pages_count += info.shared_pages_resident;
338 private_pages_count += info.private_pages_resident;
341 if (seen_objects.count(info.obj_id) == 0) {
342 // Only count the first reference to this region.
343 seen_objects.insert(info.obj_id);
344 shared_pages_count += info.shared_pages_resident;
353 kr = host_page_size(task, &page_size);
354 if (kr != KERN_SUCCESS) {
355 LOG(ERROR) << "Failed to fetch host page size, error: "
356 << mach_error_string(kr);
361 *private_bytes = private_pages_count * page_size;
363 *shared_bytes = shared_pages_count * page_size;
368 void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const {
371 bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
372 size_t priv = GetWorkingSetSize();
375 ws_usage->priv = priv / 1024;
376 ws_usage->shareable = 0;
377 ws_usage->shared = 0;
381 #define TIME_VALUE_TO_TIMEVAL(a, r) do { \
382 (r)->tv_sec = (a)->seconds; \
383 (r)->tv_usec = (a)->microseconds; \
386 double ProcessMetrics::GetCPUUsage() {
387 mach_port_t task = TaskForPid(process_);
388 if (task == MACH_PORT_NULL)
393 // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage()
394 // in libtop.c), but this is more concise and gives the same results:
395 task_thread_times_info thread_info_data;
396 mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT;
398 TASK_THREAD_TIMES_INFO,
399 reinterpret_cast<task_info_t>(&thread_info_data),
401 if (kr != KERN_SUCCESS) {
402 // Most likely cause: |task| is a zombie.
406 task_basic_info_64 task_info_data;
407 if (!GetTaskInfo(task, &task_info_data))
410 /* Set total_time. */
411 // thread info contains live time...
412 struct timeval user_timeval, system_timeval, task_timeval;
413 TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval);
414 TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval);
415 timeradd(&user_timeval, &system_timeval, &task_timeval);
417 // ... task info contains terminated time.
418 TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval);
419 TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval);
420 timeradd(&user_timeval, &task_timeval, &task_timeval);
421 timeradd(&system_timeval, &task_timeval, &task_timeval);
424 int retval = gettimeofday(&now, NULL);
428 int64 time = TimeValToMicroseconds(now);
429 int64 task_time = TimeValToMicroseconds(task_timeval);
431 if ((last_system_time_ == 0) || (last_time_ == 0)) {
432 // First call, just set the last values.
433 last_system_time_ = task_time;
438 int64 system_time_delta = task_time - last_system_time_;
439 int64 time_delta = time - last_time_;
440 DCHECK_NE(0U, time_delta);
444 // We add time_delta / 2 so the result is rounded.
445 double cpu = static_cast<double>((system_time_delta * 100.0) / time_delta);
447 last_system_time_ = task_time;
453 mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const {
454 mach_port_t task = MACH_PORT_NULL;
456 task = port_provider_->TaskForPid(process_);
457 if (task == MACH_PORT_NULL && process_ == getpid())
458 task = mach_task_self();
462 // ------------------------------------------------------------------------
464 // Bytes committed by the system.
465 size_t GetSystemCommitCharge() {
466 host_name_port_t host = mach_host_self();
467 mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
468 vm_statistics_data_t data;
469 kern_return_t kr = host_statistics(host, HOST_VM_INFO,
470 reinterpret_cast<host_info_t>(&data),
473 LOG(WARNING) << "Failed to fetch host statistics.";
478 kr = host_page_size(host, &page_size);
480 LOG(ERROR) << "Failed to fetch host page size.";
484 return (data.active_count * page_size) / 1024;
487 // ------------------------------------------------------------------------
491 bool g_oom_killer_enabled;
493 // === C malloc/calloc/valloc/realloc/posix_memalign ===
495 typedef void* (*malloc_type)(struct _malloc_zone_t* zone,
497 typedef void* (*calloc_type)(struct _malloc_zone_t* zone,
500 typedef void* (*valloc_type)(struct _malloc_zone_t* zone,
502 typedef void* (*realloc_type)(struct _malloc_zone_t* zone,
505 typedef void* (*memalign_type)(struct _malloc_zone_t* zone,
509 malloc_type g_old_malloc;
510 calloc_type g_old_calloc;
511 valloc_type g_old_valloc;
512 realloc_type g_old_realloc;
513 memalign_type g_old_memalign;
515 malloc_type g_old_malloc_purgeable;
516 calloc_type g_old_calloc_purgeable;
517 valloc_type g_old_valloc_purgeable;
518 realloc_type g_old_realloc_purgeable;
519 memalign_type g_old_memalign_purgeable;
521 void* oom_killer_malloc(struct _malloc_zone_t* zone,
523 void* result = g_old_malloc(zone, size);
525 debug::BreakDebugger();
529 void* oom_killer_calloc(struct _malloc_zone_t* zone,
532 void* result = g_old_calloc(zone, num_items, size);
533 if (!result && num_items && size)
534 debug::BreakDebugger();
538 void* oom_killer_valloc(struct _malloc_zone_t* zone,
540 void* result = g_old_valloc(zone, size);
542 debug::BreakDebugger();
546 void* oom_killer_realloc(struct _malloc_zone_t* zone,
549 void* result = g_old_realloc(zone, ptr, size);
551 debug::BreakDebugger();
555 void* oom_killer_memalign(struct _malloc_zone_t* zone,
558 void* result = g_old_memalign(zone, alignment, size);
559 // Only die if posix_memalign would have returned ENOMEM, since there are
560 // other reasons why NULL might be returned (see
561 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ).
562 if (!result && size && alignment >= sizeof(void*)
563 && (alignment & (alignment - 1)) == 0) {
564 debug::BreakDebugger();
569 void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone,
571 void* result = g_old_malloc_purgeable(zone, size);
573 debug::BreakDebugger();
577 void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone,
580 void* result = g_old_calloc_purgeable(zone, num_items, size);
581 if (!result && num_items && size)
582 debug::BreakDebugger();
586 void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone,
588 void* result = g_old_valloc_purgeable(zone, size);
590 debug::BreakDebugger();
594 void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone,
597 void* result = g_old_realloc_purgeable(zone, ptr, size);
599 debug::BreakDebugger();
603 void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone,
606 void* result = g_old_memalign_purgeable(zone, alignment, size);
607 // Only die if posix_memalign would have returned ENOMEM, since there are
608 // other reasons why NULL might be returned (see
609 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ).
610 if (!result && size && alignment >= sizeof(void*)
611 && (alignment & (alignment - 1)) == 0) {
612 debug::BreakDebugger();
617 // === C++ operator new ===
619 void oom_killer_new() {
620 debug::BreakDebugger();
623 // === Core Foundation CFAllocators ===
625 bool CanGetContextForCFAllocator(long darwin_version) {
626 // TODO(avi): remove at final release; http://crbug.com/74589
627 if (darwin_version == 11) {
628 NSLog(@"Unsure about the internals of CFAllocator but going to patch them "
629 "anyway. Watch out for crashes inside of CFAllocatorAllocate.");
631 return darwin_version == 9 ||
632 darwin_version == 10 ||
633 darwin_version == 11;
636 CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator,
637 long darwin_version) {
638 if (darwin_version == 9 || darwin_version == 10) {
639 ChromeCFAllocator9and10* our_allocator =
640 const_cast<ChromeCFAllocator9and10*>(
641 reinterpret_cast<const ChromeCFAllocator9and10*>(allocator));
642 return &our_allocator->_context;
643 } else if (darwin_version == 11) {
644 ChromeCFAllocator11* our_allocator =
645 const_cast<ChromeCFAllocator11*>(
646 reinterpret_cast<const ChromeCFAllocator11*>(allocator));
647 return &our_allocator->_context;
653 CFAllocatorAllocateCallBack g_old_cfallocator_system_default;
654 CFAllocatorAllocateCallBack g_old_cfallocator_malloc;
655 CFAllocatorAllocateCallBack g_old_cfallocator_malloc_zone;
657 void* oom_killer_cfallocator_system_default(CFIndex alloc_size,
660 void* result = g_old_cfallocator_system_default(alloc_size, hint, info);
662 debug::BreakDebugger();
666 void* oom_killer_cfallocator_malloc(CFIndex alloc_size,
669 void* result = g_old_cfallocator_malloc(alloc_size, hint, info);
671 debug::BreakDebugger();
675 void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size,
678 void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info);
680 debug::BreakDebugger();
684 // === Cocoa NSObject allocation ===
686 typedef id (*allocWithZone_t)(id, SEL, NSZone*);
687 allocWithZone_t g_old_allocWithZone;
689 id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone)
691 id result = g_old_allocWithZone(self, _cmd, zone);
693 debug::BreakDebugger();
699 malloc_zone_t* GetPurgeableZone() {
700 // malloc_default_purgeable_zone only exists on >= 10.6. Use dlsym to grab it
701 // at runtime because it may not be present in the SDK used for compilation.
702 typedef malloc_zone_t* (*malloc_default_purgeable_zone_t)(void);
703 malloc_default_purgeable_zone_t malloc_purgeable_zone =
704 reinterpret_cast<malloc_default_purgeable_zone_t>(
705 dlsym(RTLD_DEFAULT, "malloc_default_purgeable_zone"));
706 if (malloc_purgeable_zone)
707 return malloc_purgeable_zone();
711 void EnableTerminationOnOutOfMemory() {
712 if (g_oom_killer_enabled)
715 g_oom_killer_enabled = true;
717 // Not SysInfo::OperatingSystemVersionNumbers as that calls through to Gestalt
718 // which ends up (on > 10.6) spawning threads.
719 struct utsname machine_info;
720 if (uname(&machine_info)) {
724 // The string machine_info.release is the xnu/Darwin version number, "9.xxx"
725 // on Mac OS X 10.5, and "10.xxx" on Mac OS X 10.6. See
726 // http://en.wikipedia.org/wiki/Darwin_(operating_system) .
727 long darwin_version = strtol(machine_info.release, NULL, 10);
729 // === C malloc/calloc/valloc/realloc/posix_memalign ===
731 // This approach is not perfect, as requests for amounts of memory larger than
732 // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will
733 // still fail with a NULL rather than dying (see
734 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details).
735 // Unfortunately, it's the best we can do. Also note that this does not affect
736 // allocations from non-default zones.
738 CHECK(!g_old_malloc && !g_old_calloc && !g_old_valloc && !g_old_realloc &&
739 !g_old_memalign) << "Old allocators unexpectedly non-null";
741 CHECK(!g_old_malloc_purgeable && !g_old_calloc_purgeable &&
742 !g_old_valloc_purgeable && !g_old_realloc_purgeable &&
743 !g_old_memalign_purgeable) << "Old allocators unexpectedly non-null";
745 // See http://trac.webkit.org/changeset/53362/trunk/Tools/DumpRenderTree/mac
746 bool zone_allocators_protected = darwin_version > 10;
748 ChromeMallocZone* default_zone =
749 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone());
750 ChromeMallocZone* purgeable_zone =
751 reinterpret_cast<ChromeMallocZone*>(GetPurgeableZone());
753 vm_address_t page_start_default = NULL;
754 vm_address_t page_start_purgeable = NULL;
755 vm_size_t len_default = 0;
756 vm_size_t len_purgeable = 0;
757 if (zone_allocators_protected) {
758 page_start_default = reinterpret_cast<vm_address_t>(default_zone) &
759 static_cast<vm_size_t>(~(getpagesize() - 1));
760 len_default = reinterpret_cast<vm_address_t>(default_zone) -
761 page_start_default + sizeof(ChromeMallocZone);
762 mprotect(reinterpret_cast<void*>(page_start_default), len_default,
763 PROT_READ | PROT_WRITE);
765 if (purgeable_zone) {
766 page_start_purgeable = reinterpret_cast<vm_address_t>(purgeable_zone) &
767 static_cast<vm_size_t>(~(getpagesize() - 1));
768 len_purgeable = reinterpret_cast<vm_address_t>(purgeable_zone) -
769 page_start_purgeable + sizeof(ChromeMallocZone);
770 mprotect(reinterpret_cast<void*>(page_start_purgeable), len_purgeable,
771 PROT_READ | PROT_WRITE);
777 g_old_malloc = default_zone->malloc;
778 g_old_calloc = default_zone->calloc;
779 g_old_valloc = default_zone->valloc;
780 g_old_realloc = default_zone->realloc;
781 CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_realloc)
782 << "Failed to get system allocation functions.";
784 default_zone->malloc = oom_killer_malloc;
785 default_zone->calloc = oom_killer_calloc;
786 default_zone->valloc = oom_killer_valloc;
787 default_zone->realloc = oom_killer_realloc;
789 if (default_zone->version >= 5) {
790 g_old_memalign = default_zone->memalign;
792 default_zone->memalign = oom_killer_memalign;
795 // Purgeable zone (if it exists)
797 if (purgeable_zone) {
798 g_old_malloc_purgeable = purgeable_zone->malloc;
799 g_old_calloc_purgeable = purgeable_zone->calloc;
800 g_old_valloc_purgeable = purgeable_zone->valloc;
801 g_old_realloc_purgeable = purgeable_zone->realloc;
802 CHECK(g_old_malloc_purgeable && g_old_calloc_purgeable &&
803 g_old_valloc_purgeable && g_old_realloc_purgeable)
804 << "Failed to get system allocation functions.";
806 purgeable_zone->malloc = oom_killer_malloc_purgeable;
807 purgeable_zone->calloc = oom_killer_calloc_purgeable;
808 purgeable_zone->valloc = oom_killer_valloc_purgeable;
809 purgeable_zone->realloc = oom_killer_realloc_purgeable;
811 if (purgeable_zone->version >= 5) {
812 g_old_memalign_purgeable = purgeable_zone->memalign;
813 if (g_old_memalign_purgeable)
814 purgeable_zone->memalign = oom_killer_memalign_purgeable;
818 if (zone_allocators_protected) {
819 mprotect(reinterpret_cast<void*>(page_start_default), len_default,
821 if (purgeable_zone) {
822 mprotect(reinterpret_cast<void*>(page_start_purgeable), len_purgeable,
827 // === C malloc_zone_batch_malloc ===
829 // batch_malloc is omitted because the default malloc zone's implementation
830 // only supports batch_malloc for "tiny" allocations from the free list. It
831 // will fail for allocations larger than "tiny", and will only allocate as
832 // many blocks as it's able to from the free list. These factors mean that it
833 // can return less than the requested memory even in a non-out-of-memory
834 // situation. There's no good way to detect whether a batch_malloc failure is
835 // due to these other factors, or due to genuine memory or address space
836 // exhaustion. The fact that it only allocates space from the "tiny" free list
837 // means that it's likely that a failure will not be due to memory exhaustion.
838 // Similarly, these constraints on batch_malloc mean that callers must always
839 // be expecting to receive less memory than was requested, even in situations
840 // where memory pressure is not a concern. Finally, the only public interface
841 // to batch_malloc is malloc_zone_batch_malloc, which is specific to the
842 // system's malloc implementation. It's unlikely that anyone's even heard of
845 // === C++ operator new ===
847 // Yes, operator new does call through to malloc, but this will catch failures
848 // that our imperfect handling of malloc cannot.
850 std::set_new_handler(oom_killer_new);
852 // === Core Foundation CFAllocators ===
854 // This will not catch allocation done by custom allocators, but will catch
855 // all allocation done by system-provided ones.
857 CHECK(!g_old_cfallocator_system_default && !g_old_cfallocator_malloc &&
858 !g_old_cfallocator_malloc_zone)
859 << "Old allocators unexpectedly non-null";
861 bool cf_allocator_internals_known =
862 CanGetContextForCFAllocator(darwin_version);
864 if (cf_allocator_internals_known) {
865 CFAllocatorContext* context =
866 ContextForCFAllocator(kCFAllocatorSystemDefault, darwin_version);
867 CHECK(context) << "Failed to get context for kCFAllocatorSystemDefault.";
868 g_old_cfallocator_system_default = context->allocate;
869 CHECK(g_old_cfallocator_system_default)
870 << "Failed to get kCFAllocatorSystemDefault allocation function.";
871 context->allocate = oom_killer_cfallocator_system_default;
873 context = ContextForCFAllocator(kCFAllocatorMalloc, darwin_version);
874 CHECK(context) << "Failed to get context for kCFAllocatorMalloc.";
875 g_old_cfallocator_malloc = context->allocate;
876 CHECK(g_old_cfallocator_malloc)
877 << "Failed to get kCFAllocatorMalloc allocation function.";
878 context->allocate = oom_killer_cfallocator_malloc;
880 context = ContextForCFAllocator(kCFAllocatorMallocZone, darwin_version);
881 CHECK(context) << "Failed to get context for kCFAllocatorMallocZone.";
882 g_old_cfallocator_malloc_zone = context->allocate;
883 CHECK(g_old_cfallocator_malloc_zone)
884 << "Failed to get kCFAllocatorMallocZone allocation function.";
885 context->allocate = oom_killer_cfallocator_malloc_zone;
887 NSLog(@"Internals of CFAllocator not known; out-of-memory failures via "
888 "CFAllocator will not result in termination. http://crbug.com/45650");
891 // === Cocoa NSObject allocation ===
893 // Note that both +[NSObject new] and +[NSObject alloc] call through to
894 // +[NSObject allocWithZone:].
896 CHECK(!g_old_allocWithZone)
897 << "Old allocator unexpectedly non-null";
899 Class nsobject_class = [NSObject class];
900 Method orig_method = class_getClassMethod(nsobject_class,
901 @selector(allocWithZone:));
902 g_old_allocWithZone = reinterpret_cast<allocWithZone_t>(
903 method_getImplementation(orig_method));
904 CHECK(g_old_allocWithZone)
905 << "Failed to get allocWithZone allocation function.";
906 method_setImplementation(orig_method,
907 reinterpret_cast<IMP>(oom_killer_allocWithZone));
910 ProcessId GetParentProcessId(ProcessHandle process) {
911 struct kinfo_proc info;
912 size_t length = sizeof(struct kinfo_proc);
913 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process };
914 if (sysctl(mib, 4, &info, &length, NULL, 0) < 0) {
915 PLOG(ERROR) << "sysctl";
920 return info.kp_eproc.e_ppid;