process_util_mac.mm revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2008 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.
4
5
6#include "base/process_util.h"
7
8#import <Cocoa/Cocoa.h>
9#include <crt_externs.h>
10#include <dlfcn.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>
18#include <spawn.h>
19#include <sys/mman.h>
20#include <sys/sysctl.h>
21#include <sys/types.h>
22#include <sys/utsname.h>
23#include <sys/wait.h>
24
25#include <new>
26#include <string>
27
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"
38
39namespace base {
40
41void 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 |
47                                          EXC_MASK_ARITHMETIC |
48                                          EXC_MASK_BREAKPOINT;
49
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);
56}
57
58ProcessIterator::ProcessIterator(const ProcessFilter* filter)
59    : index_of_kinfo_proc_(0),
60      filter_(filter) {
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
63  // impossible.
64
65  int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, geteuid() };
66
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.
69  bool done = false;
70  int try_num = 1;
71  const int max_tries = 10;
72  do {
73    // Get the size of the buffer
74    size_t len = 0;
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);
78      done = true;
79    } else {
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);
93          done = true;
94        }
95      } else {
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);
99        done = true;
100      }
101    }
102  } while (!done && (try_num++ < max_tries));
103
104  if (!done) {
105    LOG(ERROR) << "failed to collect the process list in a few tries";
106    kinfo_procs_.resize(0);
107  }
108}
109
110ProcessIterator::~ProcessIterator() {
111}
112
113bool ProcessIterator::CheckForNextProcess() {
114  std::string data;
115  for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) {
116    kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_];
117
118    // Skip processes just awaiting collection
119    if ((kinfo.kp_proc.p_pid > 0) && (kinfo.kp_proc.p_stat == SZOMB))
120      continue;
121
122    int mib[] = { CTL_KERN, KERN_PROCARGS, kinfo.kp_proc.p_pid };
123
124    // Find out what size buffer we need.
125    size_t data_len = 0;
126    if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) {
127      LOG(ERROR) << "failed to figure out the buffer size for a commandline";
128      continue;
129    }
130
131    data.resize(data_len);
132    if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) {
133      LOG(ERROR) << "failed to fetch a commandline";
134      continue;
135    }
136
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_);
144
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";
151      continue;
152    }
153
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);
160    else
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_;
165    // Done
166    return true;
167  }
168  return false;
169}
170
171bool NamedProcessIterator::IncludeEntry() {
172  return (executable_name_ == entry().exe_file() &&
173          ProcessIterator::IncludeEntry());
174}
175
176
177// ------------------------------------------------------------------------
178// NOTE: about ProcessMetrics
179//
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.
185//
186
187ProcessMetrics::ProcessMetrics(ProcessHandle process,
188                               ProcessMetrics::PortProvider* port_provider)
189    : process_(process),
190      last_time_(0),
191      last_system_time_(0),
192      port_provider_(port_provider) {
193  processor_count_ = SysInfo::NumberOfProcessors();
194}
195
196// static
197ProcessMetrics* ProcessMetrics::CreateProcessMetrics(
198    ProcessHandle process,
199    ProcessMetrics::PortProvider* port_provider) {
200  return new ProcessMetrics(process, port_provider);
201}
202
203bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
204  return false;
205}
206
207static bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) {
208  if (task == MACH_PORT_NULL)
209    return false;
210  mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
211  kern_return_t kr = task_info(task,
212                               TASK_BASIC_INFO_64,
213                               reinterpret_cast<task_info_t>(task_info_data),
214                               &count);
215  // Most likely cause for failure: |task| is a zombie.
216  return kr == KERN_SUCCESS;
217}
218
219size_t ProcessMetrics::GetPagefileUsage() const {
220  task_basic_info_64 task_info_data;
221  if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
222    return 0;
223  return task_info_data.virtual_size;
224}
225
226size_t ProcessMetrics::GetPeakPagefileUsage() const {
227  return 0;
228}
229
230size_t ProcessMetrics::GetWorkingSetSize() const {
231  task_basic_info_64 task_info_data;
232  if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
233    return 0;
234  return task_info_data.resident_size;
235}
236
237size_t ProcessMetrics::GetPeakWorkingSetSize() const {
238  return 0;
239}
240
241static bool GetCPUTypeForProcess(pid_t pid, cpu_type_t* cpu_type) {
242  size_t len = sizeof(*cpu_type);
243  int result = sysctlbyname("sysctl.proc_cputype",
244                            cpu_type,
245                            &len,
246                            NULL,
247                            0);
248  if (result != 0) {
249    PLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")";
250    return false;
251  }
252
253  return true;
254}
255
256static 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);
263  else
264    return false;
265}
266
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.
270bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
271                                    size_t* shared_bytes) {
272  kern_return_t kr;
273  size_t private_pages_count = 0;
274  size_t shared_pages_count = 0;
275
276  if (!private_bytes && !shared_bytes)
277    return true;
278
279  mach_port_t task = TaskForPid(process_);
280  if (task == MACH_PORT_NULL) {
281    LOG(ERROR) << "Invalid process";
282    return false;
283  }
284
285  cpu_type_t cpu_type;
286  if (!GetCPUTypeForProcess(process_, &cpu_type))
287    return false;
288
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;
292
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.
298  //
299  // Private memory is much simpler. We simply count the pages that are marked
300  // as private or copy on write (COW).
301  //
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,
310                        &address,
311                        &size,
312                        VM_REGION_TOP_INFO,
313                        (vm_region_info_t)&info,
314                        &info_count,
315                        &object_name);
316    if (kr == KERN_INVALID_ADDRESS) {
317      // We're at the end of the address space.
318      break;
319    } else if (kr != KERN_SUCCESS) {
320      LOG(ERROR) << "Calling mach_vm_region failed with error: "
321                 << mach_error_string(kr);
322      return false;
323    }
324
325    if (IsAddressInSharedRegion(address, cpu_type) &&
326        info.share_mode != SM_PRIVATE)
327      continue;
328
329    if (info.share_mode == SM_COW && info.ref_count == 1)
330      info.share_mode = SM_PRIVATE;
331
332    switch (info.share_mode) {
333      case SM_PRIVATE:
334        private_pages_count += info.private_pages_resident;
335        private_pages_count += info.shared_pages_resident;
336        break;
337      case SM_COW:
338        private_pages_count += info.private_pages_resident;
339        // Fall through
340      case SM_SHARED:
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;
345        }
346        break;
347      default:
348        break;
349    }
350  }
351
352  vm_size_t page_size;
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);
357    return false;
358  }
359
360  if (private_bytes)
361    *private_bytes = private_pages_count * page_size;
362  if (shared_bytes)
363    *shared_bytes = shared_pages_count * page_size;
364
365  return true;
366}
367
368void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const {
369}
370
371bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
372  size_t priv = GetWorkingSetSize();
373  if (!priv)
374    return false;
375  ws_usage->priv = priv / 1024;
376  ws_usage->shareable = 0;
377  ws_usage->shared = 0;
378  return true;
379}
380
381#define TIME_VALUE_TO_TIMEVAL(a, r) do {  \
382  (r)->tv_sec = (a)->seconds;             \
383  (r)->tv_usec = (a)->microseconds;       \
384} while (0)
385
386double ProcessMetrics::GetCPUUsage() {
387  mach_port_t task = TaskForPid(process_);
388  if (task == MACH_PORT_NULL)
389    return 0;
390
391  kern_return_t kr;
392
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;
397  kr = task_info(task,
398                 TASK_THREAD_TIMES_INFO,
399                 reinterpret_cast<task_info_t>(&thread_info_data),
400                 &thread_info_count);
401  if (kr != KERN_SUCCESS) {
402    // Most likely cause: |task| is a zombie.
403    return 0;
404  }
405
406  task_basic_info_64 task_info_data;
407  if (!GetTaskInfo(task, &task_info_data))
408    return 0;
409
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);
416
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);
422
423  struct timeval now;
424  int retval = gettimeofday(&now, NULL);
425  if (retval)
426    return 0;
427
428  int64 time = TimeValToMicroseconds(now);
429  int64 task_time = TimeValToMicroseconds(task_timeval);
430
431  if ((last_system_time_ == 0) || (last_time_ == 0)) {
432    // First call, just set the last values.
433    last_system_time_ = task_time;
434    last_time_ = time;
435    return 0;
436  }
437
438  int64 system_time_delta = task_time - last_system_time_;
439  int64 time_delta = time - last_time_;
440  DCHECK(time_delta != 0);
441  if (time_delta == 0)
442    return 0;
443
444  // We add time_delta / 2 so the result is rounded.
445  double cpu = static_cast<double>((system_time_delta * 100.0) / time_delta);
446
447  last_system_time_ = task_time;
448  last_time_ = time;
449
450  return cpu;
451}
452
453mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const {
454  mach_port_t task = MACH_PORT_NULL;
455  if (port_provider_)
456    task = port_provider_->TaskForPid(process_);
457  if (task == MACH_PORT_NULL && process_ == getpid())
458    task = mach_task_self();
459  return task;
460}
461
462// ------------------------------------------------------------------------
463
464// Bytes committed by the system.
465size_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),
471                                     &count);
472  if (kr) {
473    LOG(WARNING) << "Failed to fetch host statistics.";
474    return 0;
475  }
476
477  vm_size_t page_size;
478  kr = host_page_size(host, &page_size);
479  if (kr) {
480    LOG(ERROR) << "Failed to fetch host page size.";
481    return 0;
482  }
483
484  return (data.active_count * page_size) / 1024;
485}
486
487// ------------------------------------------------------------------------
488
489namespace {
490
491bool g_oom_killer_enabled;
492
493// === C malloc/calloc/valloc/realloc/posix_memalign ===
494
495typedef void* (*malloc_type)(struct _malloc_zone_t* zone,
496                             size_t size);
497typedef void* (*calloc_type)(struct _malloc_zone_t* zone,
498                             size_t num_items,
499                             size_t size);
500typedef void* (*valloc_type)(struct _malloc_zone_t* zone,
501                             size_t size);
502typedef void* (*realloc_type)(struct _malloc_zone_t* zone,
503                              void* ptr,
504                              size_t size);
505typedef void* (*memalign_type)(struct _malloc_zone_t* zone,
506                               size_t alignment,
507                               size_t size);
508
509malloc_type g_old_malloc;
510calloc_type g_old_calloc;
511valloc_type g_old_valloc;
512realloc_type g_old_realloc;
513memalign_type g_old_memalign;
514
515malloc_type g_old_malloc_purgeable;
516calloc_type g_old_calloc_purgeable;
517valloc_type g_old_valloc_purgeable;
518realloc_type g_old_realloc_purgeable;
519memalign_type g_old_memalign_purgeable;
520
521void* oom_killer_malloc(struct _malloc_zone_t* zone,
522                        size_t size) {
523  void* result = g_old_malloc(zone, size);
524  if (!result && size)
525    debug::BreakDebugger();
526  return result;
527}
528
529void* oom_killer_calloc(struct _malloc_zone_t* zone,
530                        size_t num_items,
531                        size_t size) {
532  void* result = g_old_calloc(zone, num_items, size);
533  if (!result && num_items && size)
534    debug::BreakDebugger();
535  return result;
536}
537
538void* oom_killer_valloc(struct _malloc_zone_t* zone,
539                        size_t size) {
540  void* result = g_old_valloc(zone, size);
541  if (!result && size)
542    debug::BreakDebugger();
543  return result;
544}
545
546void* oom_killer_realloc(struct _malloc_zone_t* zone,
547                         void* ptr,
548                         size_t size) {
549  void* result = g_old_realloc(zone, ptr, size);
550  if (!result && size)
551    debug::BreakDebugger();
552  return result;
553}
554
555void* oom_killer_memalign(struct _malloc_zone_t* zone,
556                          size_t alignment,
557                          size_t size) {
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();
565  }
566  return result;
567}
568
569void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone,
570                                  size_t size) {
571  void* result = g_old_malloc_purgeable(zone, size);
572  if (!result && size)
573    debug::BreakDebugger();
574  return result;
575}
576
577void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone,
578                                  size_t num_items,
579                                  size_t size) {
580  void* result = g_old_calloc_purgeable(zone, num_items, size);
581  if (!result && num_items && size)
582    debug::BreakDebugger();
583  return result;
584}
585
586void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone,
587                                  size_t size) {
588  void* result = g_old_valloc_purgeable(zone, size);
589  if (!result && size)
590    debug::BreakDebugger();
591  return result;
592}
593
594void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone,
595                                   void* ptr,
596                                   size_t size) {
597  void* result = g_old_realloc_purgeable(zone, ptr, size);
598  if (!result && size)
599    debug::BreakDebugger();
600  return result;
601}
602
603void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone,
604                                    size_t alignment,
605                                    size_t size) {
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();
613  }
614  return result;
615}
616
617// === C++ operator new ===
618
619void oom_killer_new() {
620  debug::BreakDebugger();
621}
622
623// === Core Foundation CFAllocators ===
624
625typedef ChromeCFAllocator* ChromeCFAllocatorRef;
626
627CFAllocatorAllocateCallBack g_old_cfallocator_system_default;
628CFAllocatorAllocateCallBack g_old_cfallocator_malloc;
629CFAllocatorAllocateCallBack g_old_cfallocator_malloc_zone;
630
631void* oom_killer_cfallocator_system_default(CFIndex alloc_size,
632                                            CFOptionFlags hint,
633                                            void* info) {
634  void* result = g_old_cfallocator_system_default(alloc_size, hint, info);
635  if (!result)
636    debug::BreakDebugger();
637  return result;
638}
639
640void* oom_killer_cfallocator_malloc(CFIndex alloc_size,
641                                    CFOptionFlags hint,
642                                    void* info) {
643  void* result = g_old_cfallocator_malloc(alloc_size, hint, info);
644  if (!result)
645    debug::BreakDebugger();
646  return result;
647}
648
649void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size,
650                                         CFOptionFlags hint,
651                                         void* info) {
652  void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info);
653  if (!result)
654    debug::BreakDebugger();
655  return result;
656}
657
658// === Cocoa NSObject allocation ===
659
660typedef id (*allocWithZone_t)(id, SEL, NSZone*);
661allocWithZone_t g_old_allocWithZone;
662
663id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone)
664{
665  id result = g_old_allocWithZone(self, _cmd, zone);
666  if (!result)
667    debug::BreakDebugger();
668  return result;
669}
670
671}  // namespace
672
673malloc_zone_t* GetPurgeableZone() {
674  // malloc_default_purgeable_zone only exists on >= 10.6. Use dlsym to grab it
675  // at runtime because it may not be present in the SDK used for compilation.
676  typedef malloc_zone_t* (*malloc_default_purgeable_zone_t)(void);
677  malloc_default_purgeable_zone_t malloc_purgeable_zone =
678      reinterpret_cast<malloc_default_purgeable_zone_t>(
679          dlsym(RTLD_DEFAULT, "malloc_default_purgeable_zone"));
680  if (malloc_purgeable_zone)
681    return malloc_purgeable_zone();
682  return NULL;
683}
684
685void EnableTerminationOnOutOfMemory() {
686  if (g_oom_killer_enabled)
687    return;
688
689  g_oom_killer_enabled = true;
690
691  // Not SysInfo::OperatingSystemVersionNumbers as that calls through to Gestalt
692  // which ends up (on > 10.6) spawning threads.
693  struct utsname machine_info;
694  if (uname(&machine_info)) {
695    return;
696  }
697
698  // The string machine_info.release is the xnu/Darwin version number, "9.xxx"
699  // on Mac OS X 10.5, and "10.xxx" on Mac OS X 10.6. See
700  // http://en.wikipedia.org/wiki/Darwin_(operating_system) .
701  long darwin_version = strtol(machine_info.release, NULL, 10);
702
703  // === C malloc/calloc/valloc/realloc/posix_memalign ===
704
705  // This approach is not perfect, as requests for amounts of memory larger than
706  // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will
707  // still fail with a NULL rather than dying (see
708  // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details).
709  // Unfortunately, it's the best we can do. Also note that this does not affect
710  // allocations from non-default zones.
711
712  CHECK(!g_old_malloc && !g_old_calloc && !g_old_valloc && !g_old_realloc &&
713        !g_old_memalign) << "Old allocators unexpectedly non-null";
714
715  CHECK(!g_old_malloc_purgeable && !g_old_calloc_purgeable &&
716        !g_old_valloc_purgeable && !g_old_realloc_purgeable &&
717        !g_old_memalign_purgeable) << "Old allocators unexpectedly non-null";
718
719  // See http://trac.webkit.org/changeset/53362/trunk/Tools/DumpRenderTree/mac
720  bool zone_allocators_protected = darwin_version > 10;
721
722  ChromeMallocZone* default_zone =
723      reinterpret_cast<ChromeMallocZone*>(malloc_default_zone());
724  ChromeMallocZone* purgeable_zone =
725      reinterpret_cast<ChromeMallocZone*>(GetPurgeableZone());
726
727  vm_address_t page_start_default = NULL;
728  vm_address_t page_start_purgeable = NULL;
729  vm_size_t len_default = 0;
730  vm_size_t len_purgeable = 0;
731  if (zone_allocators_protected) {
732    page_start_default = reinterpret_cast<vm_address_t>(default_zone) &
733        static_cast<vm_size_t>(~(getpagesize() - 1));
734    len_default = reinterpret_cast<vm_address_t>(default_zone) -
735        page_start_default + sizeof(ChromeMallocZone);
736    mprotect(reinterpret_cast<void*>(page_start_default), len_default,
737             PROT_READ | PROT_WRITE);
738
739    if (purgeable_zone) {
740      page_start_purgeable = reinterpret_cast<vm_address_t>(purgeable_zone) &
741          static_cast<vm_size_t>(~(getpagesize() - 1));
742      len_purgeable = reinterpret_cast<vm_address_t>(purgeable_zone) -
743          page_start_purgeable + sizeof(ChromeMallocZone);
744      mprotect(reinterpret_cast<void*>(page_start_purgeable), len_purgeable,
745               PROT_READ | PROT_WRITE);
746    }
747  }
748
749  // Default zone
750
751  g_old_malloc = default_zone->malloc;
752  g_old_calloc = default_zone->calloc;
753  g_old_valloc = default_zone->valloc;
754  g_old_realloc = default_zone->realloc;
755  CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_realloc)
756      << "Failed to get system allocation functions.";
757
758  default_zone->malloc = oom_killer_malloc;
759  default_zone->calloc = oom_killer_calloc;
760  default_zone->valloc = oom_killer_valloc;
761  default_zone->realloc = oom_killer_realloc;
762
763  if (default_zone->version >= 5) {
764    g_old_memalign = default_zone->memalign;
765    if (g_old_memalign)
766      default_zone->memalign = oom_killer_memalign;
767  }
768
769  // Purgeable zone (if it exists)
770
771  if (purgeable_zone) {
772    g_old_malloc_purgeable = purgeable_zone->malloc;
773    g_old_calloc_purgeable = purgeable_zone->calloc;
774    g_old_valloc_purgeable = purgeable_zone->valloc;
775    g_old_realloc_purgeable = purgeable_zone->realloc;
776    CHECK(g_old_malloc_purgeable && g_old_calloc_purgeable &&
777          g_old_valloc_purgeable && g_old_realloc_purgeable)
778        << "Failed to get system allocation functions.";
779
780    purgeable_zone->malloc = oom_killer_malloc_purgeable;
781    purgeable_zone->calloc = oom_killer_calloc_purgeable;
782    purgeable_zone->valloc = oom_killer_valloc_purgeable;
783    purgeable_zone->realloc = oom_killer_realloc_purgeable;
784
785    if (purgeable_zone->version >= 5) {
786      g_old_memalign_purgeable = purgeable_zone->memalign;
787      if (g_old_memalign_purgeable)
788        purgeable_zone->memalign = oom_killer_memalign_purgeable;
789    }
790  }
791
792  if (zone_allocators_protected) {
793    mprotect(reinterpret_cast<void*>(page_start_default), len_default,
794             PROT_READ);
795    if (purgeable_zone) {
796      mprotect(reinterpret_cast<void*>(page_start_purgeable), len_purgeable,
797               PROT_READ);
798    }
799  }
800
801  // === C malloc_zone_batch_malloc ===
802
803  // batch_malloc is omitted because the default malloc zone's implementation
804  // only supports batch_malloc for "tiny" allocations from the free list. It
805  // will fail for allocations larger than "tiny", and will only allocate as
806  // many blocks as it's able to from the free list. These factors mean that it
807  // can return less than the requested memory even in a non-out-of-memory
808  // situation. There's no good way to detect whether a batch_malloc failure is
809  // due to these other factors, or due to genuine memory or address space
810  // exhaustion. The fact that it only allocates space from the "tiny" free list
811  // means that it's likely that a failure will not be due to memory exhaustion.
812  // Similarly, these constraints on batch_malloc mean that callers must always
813  // be expecting to receive less memory than was requested, even in situations
814  // where memory pressure is not a concern. Finally, the only public interface
815  // to batch_malloc is malloc_zone_batch_malloc, which is specific to the
816  // system's malloc implementation. It's unlikely that anyone's even heard of
817  // it.
818
819  // === C++ operator new ===
820
821  // Yes, operator new does call through to malloc, but this will catch failures
822  // that our imperfect handling of malloc cannot.
823
824  std::set_new_handler(oom_killer_new);
825
826  // === Core Foundation CFAllocators ===
827
828  // This will not catch allocation done by custom allocators, but will catch
829  // all allocation done by system-provided ones.
830
831  CHECK(!g_old_cfallocator_system_default && !g_old_cfallocator_malloc &&
832        !g_old_cfallocator_malloc_zone)
833      << "Old allocators unexpectedly non-null";
834
835  bool cf_allocator_internals_known =
836      darwin_version == 9 || darwin_version == 10;
837
838  if (cf_allocator_internals_known) {
839    ChromeCFAllocatorRef allocator = const_cast<ChromeCFAllocatorRef>(
840        reinterpret_cast<const ChromeCFAllocator*>(kCFAllocatorSystemDefault));
841    g_old_cfallocator_system_default = allocator->_context.allocate;
842    CHECK(g_old_cfallocator_system_default)
843        << "Failed to get kCFAllocatorSystemDefault allocation function.";
844    allocator->_context.allocate = oom_killer_cfallocator_system_default;
845
846    allocator = const_cast<ChromeCFAllocatorRef>(
847        reinterpret_cast<const ChromeCFAllocator*>(kCFAllocatorMalloc));
848    g_old_cfallocator_malloc = allocator->_context.allocate;
849    CHECK(g_old_cfallocator_malloc)
850        << "Failed to get kCFAllocatorMalloc allocation function.";
851    allocator->_context.allocate = oom_killer_cfallocator_malloc;
852
853    allocator = const_cast<ChromeCFAllocatorRef>(
854        reinterpret_cast<const ChromeCFAllocator*>(kCFAllocatorMallocZone));
855    g_old_cfallocator_malloc_zone = allocator->_context.allocate;
856    CHECK(g_old_cfallocator_malloc_zone)
857        << "Failed to get kCFAllocatorMallocZone allocation function.";
858    allocator->_context.allocate = oom_killer_cfallocator_malloc_zone;
859  } else {
860    NSLog(@"Internals of CFAllocator not known; out-of-memory failures via "
861        "CFAllocator will not result in termination. http://crbug.com/45650");
862  }
863
864  // === Cocoa NSObject allocation ===
865
866  // Note that both +[NSObject new] and +[NSObject alloc] call through to
867  // +[NSObject allocWithZone:].
868
869  CHECK(!g_old_allocWithZone)
870      << "Old allocator unexpectedly non-null";
871
872  Class nsobject_class = [NSObject class];
873  Method orig_method = class_getClassMethod(nsobject_class,
874                                            @selector(allocWithZone:));
875  g_old_allocWithZone = reinterpret_cast<allocWithZone_t>(
876      method_getImplementation(orig_method));
877  CHECK(g_old_allocWithZone)
878      << "Failed to get allocWithZone allocation function.";
879  method_setImplementation(orig_method,
880                           reinterpret_cast<IMP>(oom_killer_allocWithZone));
881}
882
883}  // namespace base
884