Bug 1159473 - Add Mac-specific debug logging code. r=spohl
authorSteven Michaud <smichaud@pobox.com>
Thu, 30 Apr 2015 14:41:21 -0500
changeset 273284 76684a16659782d4bf263830e074866e4a40d887
parent 273283 68335ef1d4032da135884119909707fa0173e81c
child 273285 7ca29507520332b21e014f994ae74f7285bcf411
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1159473
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1159473 - Add Mac-specific debug logging code. r=spohl
toolkit/library/moz.build
widget/cocoa/moz.build
widget/cocoa/nsCocoaDebugUtils.h
widget/cocoa/nsCocoaDebugUtils.mm
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -149,16 +149,17 @@ if CONFIG['MOZ_WEBRTC']:
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     OS_LIBS += [
         '-framework OpenGL',
         '-framework SystemConfiguration',
         '-framework QTKit',
         '-framework IOKit',
         '-F%s' % CONFIG['MACOS_PRIVATE_FRAMEWORKS_DIR'],
         '-framework CoreUI',
+        '-framework CoreSymbolication',
         'cups',
     ]
 
 if CONFIG['MOZ_WMF']:
     OS_LIBS += [
         'mfuuid',
         'wmcodecdspuuid',
         'strmiids',
--- a/widget/cocoa/moz.build
+++ b/widget/cocoa/moz.build
@@ -9,16 +9,17 @@ XPIDL_SOURCES += [
 ]
 
 XPIDL_MODULE = 'widget_cocoa'
 
 EXPORTS += [
     'mozView.h',
     'nsBidiKeyboard.h',
     'nsChangeObserver.h',
+    'nsCocoaDebugUtils.h',
     'nsCocoaFeatures.h',
     'nsCocoaUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'ComplexTextInputPanel.mm',
     'GfxInfo.mm',
     'NativeKeyBindings.mm',
@@ -60,16 +61,17 @@ UNIFIED_SOURCES += [
     'VibrancyManager.mm',
     'WidgetTraceEvent.mm',
 ]
 
 # These files cannot be built in unified mode because they cause symbol conflicts
 SOURCES += [
     'nsChildView.mm',
     'nsClipboard.mm',
+    'nsCocoaDebugUtils.mm',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/layout/forms',
     '/layout/generic',
new file mode 100644
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaDebugUtils_h_
+#define nsCocoaDebugUtils_h_
+
+#include <CoreServices/CoreServices.h>
+
+// Definitions and declarations of stuff used by us from the CoreSymbolication
+// framework.  This is an undocumented, private framework available on OS X
+// 10.6 and up.  It's used by Apple utilities like dtrace, atos, ReportCrash
+// and crashreporterd.
+
+typedef struct _CSTypeRef {
+  unsigned long type;
+  void* contents;
+} CSTypeRef;
+
+typedef CSTypeRef CSSymbolicatorRef;
+typedef CSTypeRef CSSymbolOwnerRef;
+typedef CSTypeRef CSSymbolRef;
+typedef CSTypeRef CSSourceInfoRef;
+
+typedef struct _CSRange {
+  unsigned long long location;
+  unsigned long long length;
+} CSRange;
+
+typedef unsigned long long CSArchitecture;
+
+#define kCSNow LONG_MAX
+
+extern "C" {
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPid(pid_t pid);
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPidFlagsAndNotification(pid_t pid,
+                                                uint32_t flags,
+                                                uint32_t notification);
+
+CSArchitecture
+CSSymbolicatorGetArchitecture(CSSymbolicatorRef symbolicator);
+
+CSSymbolOwnerRef
+CSSymbolicatorGetSymbolOwnerWithAddressAtTime(CSSymbolicatorRef symbolicator,
+                                              unsigned long long address,
+                                              long time);
+
+const char*
+CSSymbolOwnerGetName(CSSymbolOwnerRef owner);
+
+unsigned long long
+CSSymbolOwnerGetBaseAddress(CSSymbolOwnerRef owner);
+
+CSSymbolRef
+CSSymbolOwnerGetSymbolWithAddress(CSSymbolOwnerRef owner,
+                                  unsigned long long address);
+
+CSSourceInfoRef
+CSSymbolOwnerGetSourceInfoWithAddress(CSSymbolOwnerRef owner,
+                                      unsigned long long address);
+
+const char*
+CSSymbolGetName(CSSymbolRef symbol);
+
+CSRange
+CSSymbolGetRange(CSSymbolRef symbol);
+
+const char*
+CSSourceInfoGetFilename(CSSourceInfoRef info);
+
+uint32_t
+CSSourceInfoGetLineNumber(CSSourceInfoRef info);
+
+CSTypeRef
+CSRetain(CSTypeRef);
+
+void
+CSRelease(CSTypeRef);
+
+bool
+CSIsNull(CSTypeRef);
+
+void
+CSShow(CSTypeRef);
+
+const char*
+CSArchitectureGetFamilyName(CSArchitecture);
+
+} // extern "C"
+
+class nsCocoaDebugUtils
+{
+public:
+  // Like NSLog() but records more information (for example the full path to
+  // the executable and the "thread name").  Like NSLog(), writes to both
+  // stdout and the system log.
+  static void DebugLog(const char* aFormat, ...);
+
+  // Logs a stack trace of the current point of execution, to both stdout and
+  // the system log.
+  static void PrintStackTrace();
+
+  // Returns the name of the module that "owns" aAddress.  This must be
+  // free()ed by the caller.
+  static char* GetOwnerName(void* aAddress);
+
+  // Returns a symbolicated representation of aAddress.  This must be
+  // free()ed by the caller.
+  static char* GetAddressString(void* aAddress);
+
+private:
+  static void DebugLogInt(bool aDecorate, const char* aFormat, ...);
+  static void DebugLogV(bool aDecorate, CFStringRef aFormat, va_list aArgs);
+
+  static void PrintAddress(void* aAddress);
+
+  // The values returned by GetOwnerNameInt() and GetAddressStringInt() must
+  // be free()ed by the caller.
+  static char* GetOwnerNameInt(void* aAddress,
+                               CSTypeRef aOwner = sInitializer);
+  static char* GetAddressStringInt(void* aAddress,
+                                   CSTypeRef aOwner = sInitializer);
+
+  static CSSymbolicatorRef GetSymbolicatorRef();
+  static void ReleaseSymbolicator();
+
+  static CSTypeRef sInitializer;
+  static CSSymbolicatorRef sSymbolicator;
+};
+
+#endif // nsCocoaDebugUtils_h_
new file mode 100644
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.mm
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCocoaDebugUtils.h"
+
+#include <pthread.h>
+#include <libproc.h>
+#include <stdarg.h>
+#include <time.h>
+#include <execinfo.h>
+#include <asl.h>
+
+static char gProcPath[PROC_PIDPATHINFO_MAXSIZE] = {0};
+static char gBundleID[MAXPATHLEN] = {0};
+
+static void MaybeGetPathAndID()
+{
+  if (!gProcPath[0]) {
+    proc_pidpath(getpid(), gProcPath, sizeof(gProcPath));
+  }
+  if (!gBundleID[0]) {
+    // Apple's CFLog() uses "com.apple.console" (in its call to asl_open()) if
+    // it can't find the bundle id.
+    CFStringRef bundleID = NULL;
+    CFBundleRef mainBundle = CFBundleGetMainBundle();
+    if (mainBundle) {
+      bundleID = CFBundleGetIdentifier(mainBundle);
+    }
+    if (!bundleID) {
+      strcpy(gBundleID, "com.apple.console");
+    } else {
+      CFStringGetCString(bundleID, gBundleID, sizeof(gBundleID),
+                         kCFStringEncodingUTF8);
+    }
+  }
+}
+
+static void GetThreadName(char* aName, size_t aSize)
+{
+  pthread_getname_np(pthread_self(), aName, aSize);
+}
+
+void
+nsCocoaDebugUtils::DebugLog(const char* aFormat, ...)
+{
+  va_list args;
+  va_start(args, aFormat);
+  CFStringRef formatCFSTR =
+    CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+                              kCFStringEncodingUTF8);
+  DebugLogV(true, formatCFSTR, args);
+  CFRelease(formatCFSTR);
+  va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogInt(bool aDecorate, const char* aFormat, ...)
+{
+  va_list args;
+  va_start(args, aFormat);
+  CFStringRef formatCFSTR =
+    CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+                              kCFStringEncodingUTF8);
+  DebugLogV(aDecorate, formatCFSTR, args);
+  CFRelease(formatCFSTR);
+  va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogV(bool aDecorate, CFStringRef aFormat,
+                             va_list aArgs)
+{
+  MaybeGetPathAndID();
+
+  CFStringRef message =
+    CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL,
+                                         aFormat, aArgs);
+
+  int msgLength =
+    CFStringGetMaximumSizeForEncoding(CFStringGetLength(message),
+                                      kCFStringEncodingUTF8);
+  char* msgUTF8 = (char*) calloc(msgLength, 1);
+  CFStringGetCString(message, msgUTF8, msgLength, kCFStringEncodingUTF8);
+  CFRelease(message);
+
+  int finishedLength = msgLength + PROC_PIDPATHINFO_MAXSIZE;
+  char* finished = (char*) calloc(finishedLength, 1);
+  const time_t currentTime = time(NULL);
+  char timestamp[30] = {0};
+  ctime_r(&currentTime, timestamp);
+  if (aDecorate) {
+    char threadName[MAXPATHLEN] = {0};
+    GetThreadName(threadName, sizeof(threadName));
+    snprintf(finished, finishedLength, "(%s) %s[%u] %s[%p] %s\n",
+            timestamp, gProcPath, getpid(), threadName, pthread_self(), msgUTF8);
+  } else {
+    snprintf(finished, finishedLength, "%s\n", msgUTF8);
+  }
+  free(msgUTF8);
+
+  fputs(finished, stdout);
+
+  // Use the Apple System Log facility, as NSLog and CFLog do.
+  aslclient asl = asl_open(NULL, gBundleID, ASL_OPT_NO_DELAY);
+  aslmsg msg = asl_new(ASL_TYPE_MSG);
+  asl_set(msg, ASL_KEY_LEVEL, "4"); // kCFLogLevelWarning, used by NSLog()
+  asl_set(msg, ASL_KEY_MSG, finished);
+  asl_send(asl, msg);
+  asl_free(msg);
+  asl_close(asl);
+
+  free(finished);
+}
+
+CSTypeRef
+nsCocoaDebugUtils::sInitializer = {0};
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::sSymbolicator = {0};
+
+#define STACK_MAX 256
+
+void
+nsCocoaDebugUtils::PrintStackTrace()
+{
+  void** addresses = (void**) calloc(STACK_MAX, sizeof(void*));
+  if (!addresses) {
+    return;
+  }
+
+  CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+  if (CSIsNull(symbolicator)) {
+    free(addresses);
+    return;
+  }
+
+  uint32_t count = backtrace(addresses, STACK_MAX);
+  for (uint32_t i = 0; i < count; ++i) {
+    PrintAddress(addresses[i]);
+  }
+
+  ReleaseSymbolicator();
+  free(addresses);
+}
+
+void
+nsCocoaDebugUtils::PrintAddress(void* aAddress)
+{
+  char* ownerName = "unknown";
+  char* addressString = "unknown + 0";
+  bool stringsNeedRelease = false;
+
+  CSSymbolOwnerRef owner = {0};
+  CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+
+  if (!CSIsNull(symbolicator)) {
+    owner =
+      CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+                                                    (unsigned long long) aAddress,
+                                                    kCSNow);
+  }
+  if (!CSIsNull(owner)) {
+    ownerName = GetOwnerNameInt(aAddress, owner);
+    addressString = GetAddressStringInt(aAddress, owner);
+    stringsNeedRelease = true;
+  }
+  DebugLogInt(false, "    (%s) %s", ownerName, addressString);
+
+  if (stringsNeedRelease) {
+    free(ownerName);
+    free(addressString);
+  }
+  ReleaseSymbolicator();
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerName(void* aAddress)
+{
+  return GetOwnerNameInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerNameInt(void* aAddress, CSTypeRef aOwner)
+{
+  char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+  const char* ownerName = "unknown";
+
+  CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+  CSTypeRef owner = aOwner;
+
+  if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+    owner =
+      CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+                                                    (unsigned long long) aAddress,
+                                                    kCSNow);
+  }
+
+  if (!CSIsNull(owner)) {
+    ownerName = CSSymbolOwnerGetName(owner);
+  }
+
+  snprintf(retval, MAXPATHLEN, "%s", ownerName);
+  ReleaseSymbolicator();
+
+  return retval;
+}
+
+char*
+nsCocoaDebugUtils::GetAddressString(void* aAddress)
+{
+  return GetAddressStringInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetAddressStringInt(void* aAddress, CSTypeRef aOwner)
+{
+  char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+  const char* addressName = "unknown";
+  unsigned long long addressOffset = 0;
+
+  CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+  CSTypeRef owner = aOwner;
+
+  if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+    owner =
+      CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+                                                    (unsigned long long) aAddress,
+                                                    kCSNow);
+  }
+
+  if (!CSIsNull(owner)) {
+    CSSymbolRef symbol =
+      CSSymbolOwnerGetSymbolWithAddress(owner,
+                                        (unsigned long long) aAddress);
+    if (!CSIsNull(symbol)) {
+      addressName = CSSymbolGetName(symbol);
+      CSRange range = CSSymbolGetRange(symbol);
+      addressOffset = (unsigned long long) aAddress - range.location;
+    }
+  }
+
+  snprintf(retval, MAXPATHLEN, "%s + 0x%llx",
+           addressName, addressOffset);
+  ReleaseSymbolicator();
+
+  return retval;
+}
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::GetSymbolicatorRef()
+{
+  if (CSIsNull(sSymbolicator)) {
+    // 0x40e0000 is the value returned by
+    // uint32_t CSSymbolicatorGetFlagsForNListOnlyData(void).  We don't use
+    // this method directly because it doesn't exist on OS X 10.6.  Unless
+    // we limit ourselves to NList data, it will take too long to get a
+    // stack trace where Dwarf debugging info is available (about 15 seconds
+    // with Firefox).  This means we won't be able to get a CSSourceInfoRef,
+    // or line number information.  Oh well.
+    sSymbolicator =
+      CSSymbolicatorCreateWithPidFlagsAndNotification(getpid(),
+                                                      0x40e0000, 0);
+  }
+  // Retaining just after creation prevents crashes when calling symbolicator
+  // code (for example from PrintStackTrace()) as Firefox is quitting.  Not
+  // sure why.  Doing this may mean that we leak sSymbolicator on quitting
+  // (if we ever created it).  No particular harm in that, though.
+  return CSRetain(sSymbolicator);
+}
+
+void
+nsCocoaDebugUtils::ReleaseSymbolicator()
+{
+  if (!CSIsNull(sSymbolicator)) {
+    CSRelease(sSymbolicator);
+  }
+}
+