Home | History | Annotate | Download | only in app
      1 // Copyright 2013 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 #import "components/crash/app/breakpad_mac.h"
      6 
      7 #include <CoreFoundation/CoreFoundation.h>
      8 #import <Foundation/Foundation.h>
      9 
     10 #include "base/auto_reset.h"
     11 #include "base/base_switches.h"
     12 #import "base/basictypes.h"
     13 #include "base/command_line.h"
     14 #include "base/debug/crash_logging.h"
     15 #include "base/debug/dump_without_crashing.h"
     16 #include "base/files/file_path.h"
     17 #include "base/files/file_util.h"
     18 #import "base/logging.h"
     19 #include "base/mac/bundle_locations.h"
     20 #include "base/mac/mac_util.h"
     21 #include "base/mac/scoped_cftyperef.h"
     22 #import "base/mac/scoped_nsautorelease_pool.h"
     23 #include "base/strings/sys_string_conversions.h"
     24 #include "base/threading/platform_thread.h"
     25 #include "base/threading/thread_restrictions.h"
     26 #import "breakpad/src/client/mac/Framework/Breakpad.h"
     27 #include "components/crash/app/crash_reporter_client.h"
     28 
     29 using crash_reporter::GetCrashReporterClient;
     30 
     31 namespace breakpad {
     32 
     33 namespace {
     34 
     35 BreakpadRef gBreakpadRef = NULL;
     36 
     37 void SetCrashKeyValue(NSString* key, NSString* value) {
     38   // Comment repeated from header to prevent confusion:
     39   // IMPORTANT: On OS X, the key/value pairs are sent to the crash server
     40   // out of bounds and not recorded on disk in the minidump, this means
     41   // that if you look at the minidump file locally you won't see them!
     42   if (gBreakpadRef == NULL) {
     43     return;
     44   }
     45 
     46   BreakpadAddUploadParameter(gBreakpadRef, key, value);
     47 }
     48 
     49 void ClearCrashKeyValue(NSString* key) {
     50   if (gBreakpadRef == NULL) {
     51     return;
     52   }
     53 
     54   BreakpadRemoveUploadParameter(gBreakpadRef, key);
     55 }
     56 
     57 void SetCrashKeyValueImpl(const base::StringPiece& key,
     58                           const base::StringPiece& value) {
     59   SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()),
     60                    base::SysUTF8ToNSString(value.as_string()));
     61 }
     62 
     63 void ClearCrashKeyValueImpl(const base::StringPiece& key) {
     64   ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string()));
     65 }
     66 
     67 bool FatalMessageHandler(int severity, const char* file, int line,
     68                          size_t message_start, const std::string& str) {
     69   // Do not handle non-FATAL.
     70   if (severity != logging::LOG_FATAL)
     71     return false;
     72 
     73   // In case of OOM condition, this code could be reentered when
     74   // constructing and storing the key.  Using a static is not
     75   // thread-safe, but if multiple threads are in the process of a
     76   // fatal crash at the same time, this should work.
     77   static bool guarded = false;
     78   if (guarded)
     79     return false;
     80 
     81   base::AutoReset<bool> guard(&guarded, true);
     82 
     83   // Only log last path component.  This matches logging.cc.
     84   if (file) {
     85     const char* slash = strrchr(file, '/');
     86     if (slash)
     87       file = slash + 1;
     88   }
     89 
     90   NSString* fatal_key = @"LOG_FATAL";
     91   NSString* fatal_value =
     92       [NSString stringWithFormat:@"%s:%d: %s",
     93                                  file, line, str.c_str() + message_start];
     94   SetCrashKeyValue(fatal_key, fatal_value);
     95 
     96   // Rather than including the code to force the crash here, allow the
     97   // caller to do it.
     98   return false;
     99 }
    100 
    101 // BreakpadGenerateAndSendReport() does not report the current
    102 // thread.  This class can be used to spin up a thread to run it.
    103 class DumpHelper : public base::PlatformThread::Delegate {
    104  public:
    105   static void DumpWithoutCrashing() {
    106     DumpHelper dumper;
    107     base::PlatformThreadHandle handle;
    108     if (base::PlatformThread::Create(0, &dumper, &handle)) {
    109       // The entire point of this is to block so that the correct
    110       // stack is logged.
    111       base::ThreadRestrictions::ScopedAllowIO allow_io;
    112       base::PlatformThread::Join(handle);
    113     }
    114   }
    115 
    116  private:
    117   DumpHelper() {}
    118 
    119   virtual void ThreadMain() OVERRIDE {
    120     base::PlatformThread::SetName("CrDumpHelper");
    121     BreakpadGenerateAndSendReport(gBreakpadRef);
    122   }
    123 
    124   DISALLOW_COPY_AND_ASSIGN(DumpHelper);
    125 };
    126 
    127 void SIGABRTHandler(int signal) {
    128   // The OSX abort() (link below) masks all signals for the process,
    129   // and all except SIGABRT for the thread.  SIGABRT will be masked
    130   // when the SIGABRT is sent, which means at this point only SIGKILL
    131   // and SIGSTOP can be delivered.  Unmask others so that the code
    132   // below crashes as desired.
    133   //
    134   // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abort.c
    135   sigset_t mask;
    136   sigemptyset(&mask);
    137   sigaddset(&mask, signal);
    138   pthread_sigmask(SIG_SETMASK, &mask, NULL);
    139 
    140   // Most interesting operations are not safe in a signal handler, just crash.
    141   char* volatile death_ptr = NULL;
    142   *death_ptr = '!';
    143 }
    144 
    145 }  // namespace
    146 
    147 bool IsCrashReporterEnabled() {
    148   return gBreakpadRef != NULL;
    149 }
    150 
    151 // Only called for a branded build of Chrome.app.
    152 void InitCrashReporter(const std::string& process_type) {
    153   DCHECK(!gBreakpadRef);
    154   base::mac::ScopedNSAutoreleasePool autorelease_pool;
    155 
    156   // Check whether crash reporting should be enabled. If enterprise
    157   // configuration management controls crash reporting, it takes precedence.
    158   // Otherwise, check whether the user has consented to stats and crash
    159   // reporting. The browser process can make this determination directly.
    160   // Helper processes may not have access to the disk or to the same data as
    161   // the browser process, so the browser passes the decision to them on the
    162   // command line.
    163   NSBundle* main_bundle = base::mac::FrameworkBundle();
    164   bool is_browser = !base::mac::IsBackgroundOnlyProcess();
    165   bool enable_breakpad = false;
    166   CommandLine* command_line = CommandLine::ForCurrentProcess();
    167 
    168   if (is_browser) {
    169     // Since the configuration management infrastructure is possibly not
    170     // initialized when this code runs, read the policy preference directly.
    171     if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy(
    172             &enable_breakpad)) {
    173       // Controlled by the user. The crash reporter may be enabled by
    174       // preference or through an environment variable, but the kDisableBreakpad
    175       // switch overrides both.
    176       enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() ||
    177                         GetCrashReporterClient()->IsRunningUnattended();
    178       enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad);
    179     }
    180   } else {
    181     // This is a helper process, check the command line switch.
    182     enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter);
    183   }
    184 
    185   if (!enable_breakpad) {
    186     VLOG_IF(1, is_browser) << "Breakpad disabled";
    187     return;
    188   }
    189 
    190   // Tell Breakpad where crash_inspector and crash_report_sender are.
    191   NSString* resource_path = [main_bundle resourcePath];
    192   NSString *inspector_location =
    193       [resource_path stringByAppendingPathComponent:@"crash_inspector"];
    194   NSString *reporter_bundle_location =
    195       [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"];
    196   NSString *reporter_location =
    197       [[NSBundle bundleWithPath:reporter_bundle_location] executablePath];
    198 
    199   if (!inspector_location || !reporter_location) {
    200     VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled";
    201     return;
    202   }
    203 
    204   NSDictionary* info_dictionary = [main_bundle infoDictionary];
    205   NSMutableDictionary *breakpad_config =
    206       [[info_dictionary mutableCopy] autorelease];
    207   [breakpad_config setObject:inspector_location
    208                       forKey:@BREAKPAD_INSPECTOR_LOCATION];
    209   [breakpad_config setObject:reporter_location
    210                       forKey:@BREAKPAD_REPORTER_EXE_LOCATION];
    211 
    212   // In the main application (the browser process), crashes can be passed to
    213   // the system's Crash Reporter.  This allows the system to notify the user
    214   // when the application crashes, and provide the user with the option to
    215   // restart it.
    216   if (is_browser)
    217     [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT];
    218 
    219   base::FilePath dir_crash_dumps;
    220   GetCrashReporterClient()->GetCrashDumpLocation(&dir_crash_dumps);
    221   [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value())
    222                       forKey:@BREAKPAD_DUMP_DIRECTORY];
    223 
    224   // Temporarily run Breakpad in-process on 10.10 and later because APIs that
    225   // it depends on got broken (http://crbug.com/386208).
    226   // This can catch crashes in the browser process only.
    227   if (is_browser && base::mac::IsOSYosemiteOrLater()) {
    228     [breakpad_config setObject:[NSNumber numberWithBool:YES]
    229                         forKey:@BREAKPAD_IN_PROCESS];
    230   }
    231 
    232   // Initialize Breakpad.
    233   gBreakpadRef = BreakpadCreate(breakpad_config);
    234   if (!gBreakpadRef) {
    235     LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initializaiton failed";
    236     return;
    237   }
    238 
    239   // Initialize the scoped crash key system.
    240   base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl,
    241                                              &ClearCrashKeyValueImpl);
    242   GetCrashReporterClient()->RegisterCrashKeys();
    243 
    244   // Set Breakpad metadata values.  These values are added to Info.plist during
    245   // the branded Google Chrome.app build.
    246   SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]);
    247   SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]);
    248   SetCrashKeyValue(@"plat", @"OS X");
    249 
    250   if (!is_browser) {
    251     // Get the guid from the command line switch.
    252     std::string client_guid =
    253         command_line->GetSwitchValueASCII(switches::kEnableCrashReporter);
    254     GetCrashReporterClient()->SetCrashReporterClientIdFromGUID(client_guid);
    255   }
    256 
    257   logging::SetLogMessageHandler(&FatalMessageHandler);
    258   base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing);
    259 
    260   // abort() sends SIGABRT, which breakpad does not intercept.
    261   // Register a signal handler to crash in a way breakpad will
    262   // intercept.
    263   struct sigaction sigact;
    264   memset(&sigact, 0, sizeof(sigact));
    265   sigact.sa_handler = SIGABRTHandler;
    266   CHECK(0 == sigaction(SIGABRT, &sigact, NULL));
    267 }
    268 
    269 void InitCrashProcessInfo(const std::string& process_type_switch) {
    270   if (gBreakpadRef == NULL) {
    271     return;
    272   }
    273 
    274   // Determine the process type.
    275   NSString* process_type = @"browser";
    276   if (!process_type_switch.empty()) {
    277     process_type = base::SysUTF8ToNSString(process_type_switch);
    278   }
    279 
    280   GetCrashReporterClient()->InstallAdditionalFilters(gBreakpadRef);
    281 
    282   // Store process type in crash dump.
    283   SetCrashKeyValue(@"ptype", process_type);
    284 
    285   NSString* pid_value =
    286       [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())];
    287   SetCrashKeyValue(@"pid", pid_value);
    288 }
    289 
    290 }  // namespace breakpad
    291