Home | History | Annotate | Download | only in base
      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 <mach/mach.h>
     11 #include <mach/mach_init.h>
     12 #include <mach/task.h>
     13 #include <malloc/malloc.h>
     14 #include <spawn.h>
     15 #include <sys/mman.h>
     16 #include <sys/sysctl.h>
     17 #include <sys/types.h>
     18 #include <sys/wait.h>
     19 
     20 #include <string>
     21 
     22 #include "base/debug_util.h"
     23 #include "base/eintr_wrapper.h"
     24 #include "base/logging.h"
     25 #include "base/string_util.h"
     26 #include "base/sys_info.h"
     27 #include "base/sys_string_conversions.h"
     28 #include "base/time.h"
     29 
     30 namespace base {
     31 
     32 void RestoreDefaultExceptionHandler() {
     33   // This function is tailored to remove the Breakpad exception handler.
     34   // exception_mask matches s_exception_mask in
     35   // breakpad/src/client/mac/handler/exception_handler.cc
     36   const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS |
     37                                           EXC_MASK_BAD_INSTRUCTION |
     38                                           EXC_MASK_ARITHMETIC |
     39                                           EXC_MASK_BREAKPOINT;
     40 
     41   // Setting the exception port to MACH_PORT_NULL may not be entirely
     42   // kosher to restore the default exception handler, but in practice,
     43   // it results in the exception port being set to Apple Crash Reporter,
     44   // the desired behavior.
     45   task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL,
     46                            EXCEPTION_DEFAULT, THREAD_STATE_NONE);
     47 }
     48 
     49 NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name,
     50                                            const ProcessFilter* filter)
     51     : executable_name_(executable_name),
     52       index_of_kinfo_proc_(0),
     53       filter_(filter) {
     54   // Get a snapshot of all of my processes (yes, as we loop it can go stale, but
     55   // but trying to find where we were in a constantly changing list is basically
     56   // impossible.
     57 
     58   int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, geteuid() };
     59 
     60   // Since more processes could start between when we get the size and when
     61   // we get the list, we do a loop to keep trying until we get it.
     62   bool done = false;
     63   int try_num = 1;
     64   const int max_tries = 10;
     65   do {
     66     // Get the size of the buffer
     67     size_t len = 0;
     68     if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) {
     69       LOG(ERROR) << "failed to get the size needed for the process list";
     70       kinfo_procs_.resize(0);
     71       done = true;
     72     } else {
     73       size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
     74       // Leave some spare room for process table growth (more could show up
     75       // between when we check and now)
     76       num_of_kinfo_proc += 4;
     77       kinfo_procs_.resize(num_of_kinfo_proc);
     78       len = num_of_kinfo_proc * sizeof(struct kinfo_proc);
     79       // Load the list of processes
     80       if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) {
     81         // If we get a mem error, it just means we need a bigger buffer, so
     82         // loop around again.  Anything else is a real error and give up.
     83         if (errno != ENOMEM) {
     84           LOG(ERROR) << "failed to get the process list";
     85           kinfo_procs_.resize(0);
     86           done = true;
     87         }
     88       } else {
     89         // Got the list, just make sure we're sized exactly right
     90         size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
     91         kinfo_procs_.resize(num_of_kinfo_proc);
     92         done = true;
     93       }
     94     }
     95   } while (!done && (try_num++ < max_tries));
     96 
     97   if (!done) {
     98     LOG(ERROR) << "failed to collect the process list in a few tries";
     99     kinfo_procs_.resize(0);
    100   }
    101 }
    102 
    103 NamedProcessIterator::~NamedProcessIterator() {
    104 }
    105 
    106 const ProcessEntry* NamedProcessIterator::NextProcessEntry() {
    107   bool result = false;
    108   do {
    109     result = CheckForNextProcess();
    110   } while (result && !IncludeEntry());
    111 
    112   if (result) {
    113     return &entry_;
    114   }
    115 
    116   return NULL;
    117 }
    118 
    119 bool NamedProcessIterator::CheckForNextProcess() {
    120   std::string executable_name_utf8(base::SysWideToUTF8(executable_name_));
    121 
    122   std::string data;
    123   std::string exec_name;
    124 
    125   for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) {
    126     kinfo_proc* kinfo = &kinfo_procs_[index_of_kinfo_proc_];
    127 
    128     // Skip processes just awaiting collection
    129     if ((kinfo->kp_proc.p_pid > 0) && (kinfo->kp_proc.p_stat == SZOMB))
    130       continue;
    131 
    132     int mib[] = { CTL_KERN, KERN_PROCARGS, kinfo->kp_proc.p_pid };
    133 
    134     // Found out what size buffer we need
    135     size_t data_len = 0;
    136     if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) {
    137       LOG(ERROR) << "failed to figure out the buffer size for a commandline";
    138       continue;
    139     }
    140 
    141     data.resize(data_len);
    142     if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) {
    143       LOG(ERROR) << "failed to fetch a commandline";
    144       continue;
    145     }
    146 
    147     // Data starts w/ the full path null termed, so we have to extract just the
    148     // executable name from the path.
    149 
    150     size_t exec_name_end = data.find('\0');
    151     if (exec_name_end == std::string::npos) {
    152       LOG(ERROR) << "command line data didn't match expected format";
    153       continue;
    154     }
    155     size_t last_slash = data.rfind('/', exec_name_end);
    156     if (last_slash == std::string::npos)
    157       exec_name = data.substr(0, exec_name_end);
    158     else
    159       exec_name = data.substr(last_slash + 1, exec_name_end - last_slash - 1);
    160 
    161     // Check the name
    162     if (executable_name_utf8 == exec_name) {
    163       entry_.pid = kinfo->kp_proc.p_pid;
    164       entry_.ppid = kinfo->kp_eproc.e_ppid;
    165       base::strlcpy(entry_.szExeFile, exec_name.c_str(),
    166                     sizeof(entry_.szExeFile));
    167       // Start w/ the next entry next time through
    168       ++index_of_kinfo_proc_;
    169       // Done
    170       return true;
    171     }
    172   }
    173   return false;
    174 }
    175 
    176 bool NamedProcessIterator::IncludeEntry() {
    177   // Don't need to check the name, we did that w/in CheckForNextProcess.
    178   if (!filter_)
    179     return true;
    180   return filter_->Includes(entry_.pid, entry_.ppid);
    181 }
    182 
    183 
    184 // ------------------------------------------------------------------------
    185 // NOTE: about ProcessMetrics
    186 //
    187 // Getting a mach task from a pid for another process requires permissions in
    188 // general, so there doesn't really seem to be a way to do these (and spinning
    189 // up ps to fetch each stats seems dangerous to put in a base api for anyone to
    190 // call). Child processes ipc their port, so return something if available,
    191 // otherwise return 0.
    192 //
    193 bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
    194   return false;
    195 }
    196 
    197 static bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) {
    198   if (task == MACH_PORT_NULL)
    199     return false;
    200   mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
    201   kern_return_t kr = task_info(task,
    202                                TASK_BASIC_INFO_64,
    203                                reinterpret_cast<task_info_t>(task_info_data),
    204                                &count);
    205   // Most likely cause for failure: |task| is a zombie.
    206   return kr == KERN_SUCCESS;
    207 }
    208 
    209 size_t ProcessMetrics::GetPagefileUsage() const {
    210   task_basic_info_64 task_info_data;
    211   if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
    212     return 0;
    213   return task_info_data.virtual_size;
    214 }
    215 
    216 size_t ProcessMetrics::GetPeakPagefileUsage() const {
    217   return 0;
    218 }
    219 
    220 size_t ProcessMetrics::GetWorkingSetSize() const {
    221   task_basic_info_64 task_info_data;
    222   if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
    223     return 0;
    224   return task_info_data.resident_size;
    225 }
    226 
    227 size_t ProcessMetrics::GetPeakWorkingSetSize() const {
    228   return 0;
    229 }
    230 
    231 size_t ProcessMetrics::GetPrivateBytes() const {
    232   return 0;
    233 }
    234 
    235 void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const {
    236 }
    237 
    238 bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
    239   size_t priv = GetWorkingSetSize();
    240   if (!priv)
    241     return false;
    242   ws_usage->priv = priv / 1024;
    243   ws_usage->shareable = 0;
    244   ws_usage->shared = 0;
    245   return true;
    246 }
    247 
    248 #define TIME_VALUE_TO_TIMEVAL(a, r) do {  \
    249   (r)->tv_sec = (a)->seconds;             \
    250   (r)->tv_usec = (a)->microseconds;       \
    251 } while (0)
    252 
    253 double ProcessMetrics::GetCPUUsage() {
    254   mach_port_t task = TaskForPid(process_);
    255   if (task == MACH_PORT_NULL)
    256     return 0;
    257 
    258   kern_return_t kr;
    259 
    260   // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage()
    261   // in libtop.c), but this is more concise and gives the same results:
    262   task_thread_times_info thread_info_data;
    263   mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT;
    264   kr = task_info(task,
    265                  TASK_THREAD_TIMES_INFO,
    266                  reinterpret_cast<task_info_t>(&thread_info_data),
    267                  &thread_info_count);
    268   if (kr != KERN_SUCCESS) {
    269     // Most likely cause: |task| is a zombie.
    270     return 0;
    271   }
    272 
    273   task_basic_info_64 task_info_data;
    274   if (!GetTaskInfo(task, &task_info_data))
    275     return 0;
    276 
    277   /* Set total_time. */
    278   // thread info contains live time...
    279   struct timeval user_timeval, system_timeval, task_timeval;
    280   TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval);
    281   TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval);
    282   timeradd(&user_timeval, &system_timeval, &task_timeval);
    283 
    284   // ... task info contains terminated time.
    285   TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval);
    286   TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval);
    287   timeradd(&user_timeval, &task_timeval, &task_timeval);
    288   timeradd(&system_timeval, &task_timeval, &task_timeval);
    289 
    290   struct timeval now;
    291   int retval = gettimeofday(&now, NULL);
    292   if (retval)
    293     return 0;
    294 
    295   int64 time = TimeValToMicroseconds(now);
    296   int64 task_time = TimeValToMicroseconds(task_timeval);
    297 
    298   if ((last_system_time_ == 0) || (last_time_ == 0)) {
    299     // First call, just set the last values.
    300     last_system_time_ = task_time;
    301     last_time_ = time;
    302     return 0;
    303   }
    304 
    305   int64 system_time_delta = task_time - last_system_time_;
    306   int64 time_delta = time - last_time_;
    307   DCHECK(time_delta != 0);
    308   if (time_delta == 0)
    309     return 0;
    310 
    311   // We add time_delta / 2 so the result is rounded.
    312   double cpu = static_cast<double>((system_time_delta * 100.0) / time_delta);
    313 
    314   last_system_time_ = task_time;
    315   last_time_ = time;
    316 
    317   return cpu;
    318 }
    319 
    320 mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const {
    321   mach_port_t task = MACH_PORT_NULL;
    322   if (port_provider_)
    323     task = port_provider_->TaskForPid(process_);
    324   if (task == MACH_PORT_NULL && process_ == getpid())
    325     task = mach_task_self();
    326   return task;
    327 }
    328 
    329 // ------------------------------------------------------------------------
    330 
    331 // Bytes committed by the system.
    332 size_t GetSystemCommitCharge() {
    333   host_name_port_t host = mach_host_self();
    334   mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
    335   vm_statistics_data_t data;
    336   kern_return_t kr = host_statistics(host, HOST_VM_INFO,
    337                                      reinterpret_cast<host_info_t>(&data),
    338                                      &count);
    339   if (kr) {
    340     LOG(WARNING) << "Failed to fetch host statistics.";
    341     return 0;
    342   }
    343 
    344   vm_size_t page_size;
    345   kr = host_page_size(host, &page_size);
    346   if (kr) {
    347     LOG(ERROR) << "Failed to fetch host page size.";
    348     return 0;
    349   }
    350 
    351   return (data.active_count * page_size) / 1024;
    352 }
    353 
    354 // ------------------------------------------------------------------------
    355 
    356 namespace {
    357 
    358 typedef void* (*malloc_type)(struct _malloc_zone_t* zone,
    359                              size_t size);
    360 typedef void* (*calloc_type)(struct _malloc_zone_t* zone,
    361                              size_t num_items,
    362                              size_t size);
    363 typedef void* (*valloc_type)(struct _malloc_zone_t* zone,
    364                              size_t size);
    365 typedef void* (*realloc_type)(struct _malloc_zone_t* zone,
    366                               void* ptr,
    367                               size_t size);
    368 
    369 malloc_type g_old_malloc;
    370 calloc_type g_old_calloc;
    371 valloc_type g_old_valloc;
    372 realloc_type g_old_realloc;
    373 
    374 void* oom_killer_malloc(struct _malloc_zone_t* zone,
    375                         size_t size) {
    376   void* result = g_old_malloc(zone, size);
    377   if (size && !result)
    378     DebugUtil::BreakDebugger();
    379   return result;
    380 }
    381 
    382 void* oom_killer_calloc(struct _malloc_zone_t* zone,
    383                         size_t num_items,
    384                         size_t size) {
    385   void* result = g_old_calloc(zone, num_items, size);
    386   if (num_items && size && !result)
    387     DebugUtil::BreakDebugger();
    388   return result;
    389 }
    390 
    391 void* oom_killer_valloc(struct _malloc_zone_t* zone,
    392                         size_t size) {
    393   void* result = g_old_valloc(zone, size);
    394   if (size && !result)
    395     DebugUtil::BreakDebugger();
    396   return result;
    397 }
    398 
    399 void* oom_killer_realloc(struct _malloc_zone_t* zone,
    400                          void* ptr,
    401                          size_t size) {
    402   void* result = g_old_realloc(zone, ptr, size);
    403   if (size && !result)
    404     DebugUtil::BreakDebugger();
    405   return result;
    406 }
    407 
    408 }  // namespace
    409 
    410 void EnableTerminationOnOutOfMemory() {
    411   CHECK(!g_old_malloc && !g_old_calloc && !g_old_valloc && !g_old_realloc)
    412       << "EnableTerminationOnOutOfMemory() called twice!";
    413 
    414   // This approach is sub-optimal:
    415   // - Requests for amounts of memory larger than MALLOC_ABSOLUTE_MAX_SIZE
    416   //   (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will still fail with a NULL
    417   //   rather than dying (see
    418   //   http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for
    419   //   details).
    420   // - It is unclear whether allocations via the C++ operator new() are affected
    421   //   by this (although it is likely).
    422   // - This does not affect allocations from non-default zones.
    423   // - It is unclear whether allocations from CoreFoundation's
    424   //   kCFAllocatorDefault or +[NSObject alloc] are affected by this.
    425   // Nevertheless this is better than nothing for now.
    426   // TODO(avi):Do better. http://crbug.com/12673
    427 
    428   int32 major;
    429   int32 minor;
    430   int32 bugfix;
    431   SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
    432   bool zone_allocators_protected = ((major == 10 && minor > 6) || major > 10);
    433 
    434   malloc_zone_t* default_zone = malloc_default_zone();
    435 
    436   vm_address_t page_start = NULL;
    437   vm_size_t len = 0;
    438   if (zone_allocators_protected) {
    439     // See http://trac.webkit.org/changeset/53362/trunk/WebKitTools/DumpRenderTree/mac
    440     page_start = reinterpret_cast<vm_address_t>(default_zone) &
    441         static_cast<vm_size_t>(~(getpagesize() - 1));
    442     len = reinterpret_cast<vm_address_t>(default_zone) -
    443         page_start + sizeof(malloc_zone_t);
    444     mprotect(reinterpret_cast<void*>(page_start), len, PROT_READ | PROT_WRITE);
    445   }
    446 
    447   g_old_malloc = default_zone->malloc;
    448   g_old_calloc = default_zone->calloc;
    449   g_old_valloc = default_zone->valloc;
    450   g_old_realloc = default_zone->realloc;
    451   CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_realloc)
    452       << "Failed to get system allocation functions.";
    453 
    454   default_zone->malloc = oom_killer_malloc;
    455   default_zone->calloc = oom_killer_calloc;
    456   default_zone->valloc = oom_killer_valloc;
    457   default_zone->realloc = oom_killer_realloc;
    458 
    459   if (zone_allocators_protected) {
    460     mprotect(reinterpret_cast<void*>(page_start), len, PROT_READ);
    461   }
    462 }
    463 
    464 }  // namespace base
    465