Vuo  2.2.1
VuoLog.cc
Go to the documentation of this file.
1 
10 #include <asl.h>
11 #include <assert.h>
12 #include <cxxabi.h>
13 #include <dlfcn.h>
14 #include <execinfo.h>
15 #include <mach/mach_time.h>
16 #include <math.h>
17 #include <objc/objc-runtime.h>
18 #include <regex.h>
19 #include <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <sys/sysctl.h>
23 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26 
27 #ifndef __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES
28  #define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
30 #endif
31 #include <CoreFoundation/CoreFoundation.h>
32 
33 #include <AvailabilityMacros.h>
34 #if (MAC_OS_X_VERSION_MIN_REQUIRED == MAC_OS_X_VERSION_10_6) || (MAC_OS_X_VERSION_MIN_REQUIRED == MAC_OS_X_VERSION_10_7)
35  #include <ApplicationServices/ApplicationServices.h>
36 #else
37  #include <CoreGraphics/CoreGraphics.h>
38  #include <CoreText/CoreText.h>
39 #endif
40 
41 #include "VuoLog.h"
42 #include "VuoHeap.h"
43 #include "VuoStringUtilities.hh"
44 
46 static std::terminate_handler nextTerminateHandler = nullptr;
47 
52 extern "C" [[noreturn]] void VuoTerminateHandler()
53 {
54  if (auto ep = std::current_exception())
55  try
56  {
57  std::rethrow_exception(ep);
58  }
59  catch (std::exception const &e)
60  {
61  int status;
62  const char *typeName = typeid(e).name();
63  char *unmangled = abi::__cxa_demangle(typeName, 0, 0, &status);
64  if (status == 0)
65  typeName = unmangled;
66 
67  VUserLog("Terminating due to uncaught %s: \"%s\"", typeName, e.what());
68  }
69  catch (...)
70  {
71  VUserLog("Terminating due to uncaught exception of unknown type");
72  }
73  else
74  // Could still be due to an exception:
75  // https://b33p.net/kosada/node/16404
76  VUserLog("Terminating because std::terminate was called (no exception data available)");
77 
79  abort();
80 }
81 
85 double VuoLogGetTime(void)
86 {
87  static mach_timebase_info_data_t info = {0,0};
88  if (info.denom == 0)
89  {
90  kern_return_t ret = mach_timebase_info(&info);
91  if (ret != KERN_SUCCESS)
92  {
93  VUserLog("Failed to get mach timebase: %d", ret);
94  // Default to 1/1, which is correct on at least some machines.
95  info.numer = 1;
96  info.denom = 1;
97  }
98  }
99 
100  return (double)mach_absolute_time() * info.numer / (info.denom * NSEC_PER_SEC);
101 }
102 
104 
105 const int VuoLogHistoryItems = 20;
107 dispatch_queue_t VuoLogHistoryQueue;
108 
109 #ifdef VUO_PROFILE
110 #include <map>
111 #include <string>
113 typedef struct
114 {
115  double total;
116  double min;
117  double max;
118  uint64_t count;
119 } VuoLogProfileEntry;
120 typedef std::map<std::string, VuoLogProfileEntry> VuoLogProfileType;
121 static VuoLogProfileType *VuoLogProfile;
122 static dispatch_queue_t VuoLogProfileQueue;
123 #endif
124 
130 static void __attribute__((constructor)) VuoLog_init(void)
131 {
133 
134  nextTerminateHandler = std::set_terminate(&VuoTerminateHandler);
135 
136 #ifdef VUO_PROFILE
137  VuoLogProfileQueue = dispatch_queue_create("VuoLogProfile", NULL);
138  VuoLogProfile = new VuoLogProfileType;
139 #endif
140 }
141 
142 #if defined(VUO_PROFILE) || defined(DOXYGEN)
143 
146 void VuoLog_recordTime(const char *name, double time)
147 {
148  dispatch_sync(VuoLogProfileQueue, ^{
149  VuoLogProfileType::iterator i = VuoLogProfile->find(name);
150  if (i != VuoLogProfile->end())
151  {
152  i->second.total += time;
153  if (time < i->second.min)
154  i->second.min = time;
155  if (time > i->second.max)
156  i->second.max = time;
157  ++i->second.count;
158  }
159  else
160  (*VuoLogProfile)[name] = (VuoLogProfileEntry){time, time, time, 1};
161  });
162 }
163 
167 extern "C" void __attribute__((destructor)) VuoLog_dumpProfile(void)
168 {
169  dispatch_sync(VuoLogProfileQueue, ^{
170  double totalRuntime = VuoLogGetElapsedTime();
171  for (VuoLogProfileType::iterator i = VuoLogProfile->begin(); i != VuoLogProfile->end(); ++i)
172  fprintf(stderr, "%30s %12.9f s (%7.4f%%) (min %12.9f, max %12.9f, avg %12.9f, count %lld)\n",
173  i->first.c_str(),
174  i->second.total,
175  i->second.total * 100. / totalRuntime,
176  i->second.min,
177  i->second.max,
178  i->second.total / i->second.count,
179  i->second.count);
180  });
181 }
182 #endif
183 
188 {
189  return VuoLogGetTime() - VuoLogStartTime;
190 }
191 
193 #define VuoCrashReport_alignment __attribute__((aligned(8)))
194 
199 typedef struct {
200  unsigned int version VuoCrashReport_alignment;
201  char *message VuoCrashReport_alignment;
202  char *signature VuoCrashReport_alignment;
203  char *backtrace VuoCrashReport_alignment;
204  char *message2 VuoCrashReport_alignment;
205  void *reserved VuoCrashReport_alignment;
206  void *reserved2 VuoCrashReport_alignment;
208 
210 VuoCrashReport_infoType VuoCrashReport __attribute__((section("__DATA,__crash_info"))) = { 4, NULL, NULL, NULL, NULL, NULL, NULL };
211 
212 void VuoLog_statusF(const char *file, const unsigned int linenumber, const char *function, const char *format, ...)
213 {
214  static dispatch_once_t statusInitialized = 0;
215  static dispatch_queue_t statusQueue;
216  dispatch_once(&statusInitialized, ^{
217  statusQueue = dispatch_queue_create("VuoLogStatus", NULL);
218  });
219 
220  char *message = nullptr;
221  if (format)
222  {
223  va_list args;
224  va_start(args, format);
225  vasprintf(&message, format, args);
226  va_end(args);
227  }
228 
229  dispatch_sync(statusQueue, ^{
230  if (VuoCrashReport.message2)
231  free(VuoCrashReport.message2);
232 
233  if (format)
234  VuoCrashReport.message2 = message;
235  else
236  VuoCrashReport.message2 = nullptr;
237  });
238 }
239 
244 {
245  Class NSProcessInfoClass = objc_getClass("NSProcessInfo");
246  id processInfo = objc_msgSend((id)NSProcessInfoClass, sel_getUid("processInfo"));
247  struct NSOperatingSystemVersion
248  {
249  long majorVersion;
250  long minorVersion;
251  long patchVersion;
252  };
253  typedef NSOperatingSystemVersion (*operatingSystemVersionType)(id receiver, SEL selector);
254  operatingSystemVersionType operatingSystemVersionFunc = (operatingSystemVersionType)objc_msgSend_stret;
255  NSOperatingSystemVersion operatingSystemVersion = operatingSystemVersionFunc(processInfo, sel_getUid("operatingSystemVersion"));
256  return operatingSystemVersion.minorVersion;
257 }
258 
265 static bool VuoLog_isDebuggerAttached(void)
266 {
267  int mib[4];
268  mib[0] = CTL_KERN;
269  mib[1] = KERN_PROC;
270  mib[2] = KERN_PROC_PID;
271  mib[3] = getpid();
272 
273  struct kinfo_proc info;
274  info.kp_proc.p_flag = 0;
275  size_t size = sizeof(info);
276  if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0))
277  {
278  VUserLog("Warning: Couldn't check debugger status: %s", strerror(errno));
279  return false;
280  }
281 
282  return info.kp_proc.p_flag & P_TRACED;
283 }
284 
285 void VuoLog(const char *file, const unsigned int linenumber, const char *function, const char *format, ...)
286 {
287  va_list args;
288 
289  va_start(args, format);
290  int size = vsnprintf(NULL, 0, format, args);
291  va_end(args);
292 
293  char *formattedString = (char *)malloc(size+1);
294  va_start(args, format);
295  vsnprintf(formattedString, size+1, format, args);
296  va_end(args);
297 
298  char *formattedFunction = NULL;
299 
300  // This may be a mangled function name of the form `__6+[f g]_block_invoke`.
301  // Trim the prefix and suffix, since the line number is sufficient to locate the code within the function+block.
302  if (function && function[0] == '_' && function[1] == '_')
303  {
304  int actualFunctionLength = atoi(function + 2);
305  if (actualFunctionLength)
306  formattedFunction = strndup(function + 3 + (int)log10(actualFunctionLength), actualFunctionLength);
307  else
308  formattedFunction = strndup(function + 2, strrchr(function + 2, '_') - (function + 2));
309 
310  // We may now be left with a C++-mangled symbol followed by `_block`,
311  // such as `_ZN9VuoRunner18replaceCompositionESsSsSs_block`.
312  // Extract just the method name (the last length-specified section).
313  if (strncmp(formattedFunction, "_ZN", 3) == 0)
314  {
315  int pos = 3;
316  int len, priorLen = 0;
317  while ((len = atoi(formattedFunction + pos)))
318  {
319  pos += 1 + (int)log10(len) + len;
320  priorLen = len;
321  }
322  char *f2 = strndup(formattedFunction + pos - priorLen, priorLen);
323  free(formattedFunction);
324  formattedFunction = f2;
325  }
326  }
327 
328  // Add a trailing `()`, unless it's an Objective-C method.
329  if (function)
330  {
331  const char *f = formattedFunction ? formattedFunction : function;
332  size_t fLen = strlen(f);
333  if (f[fLen - 1] != ']')
334  {
335  size_t mallocSize = fLen + 2 + 1;
336  char *f2 = (char *)malloc(mallocSize);
337  strlcpy(f2, f, mallocSize);
338  strlcat(f2, "()", mallocSize);
339  if (formattedFunction)
340  free(formattedFunction);
341  formattedFunction = f2;
342  }
343  }
344 
345  const char *formattedFile = file;
346 
347  if (!VuoHeap_isPointerReadable(formattedFile))
348  formattedFile = "(unknown)";
349 
350  // Trim the path, if present.
351  if (const char *lastSlash = strrchr(formattedFile, '/'))
352  formattedFile = lastSlash + 1;
353 
354  double time = VuoLogGetElapsedTime();
355 
356  // If it's been a while since the last log, add a separator.
357  static double priorTime = 0;
358  const char *separator = "";
359  if (priorTime > 0 && time - priorTime > 0.5)
360  separator = "\n";
361  priorTime = time;
362 
363  // ANSI-256's 6x6x6 color cube begins at index 16 and spans 216 indices.
364  // Skip the first 72 indices, which are darker (illegible against a black terminal background).
365  fprintf(stderr, "%s\033[38;5;%dm# pid=%5d t=%8.4fs %27.27s:%-4u %41.41s %s\033[0m\n", separator, getpid()%144+88, getpid(), time, formattedFile, linenumber, formattedFunction ? formattedFunction : function, formattedString);
366 
367 
368  // Also send it to the macOS Console.
369  {
370  // Can't just call this directly because (1) it only exists on 10.12+,
371  // and (2) it relies on Apple-specific complier extensions that aren't available in Clang 3.2.
372  // os_log(OS_LOG_DEFAULT, "...", ...);
373 
374  extern struct mach_header __dso_handle;
375  typedef void *(*vuoMacOsLogCreateType)(const char *subsystem, const char *category);
376  typedef void (*vuoMacOsLogInternalType)(void *dso, void *log, uint8_t type, const char *message, ...);
377  typedef void (*vuoMacOsLogImplType)(void *dso, void *log, uint8_t type, const char *format, uint8_t *buf, uint32_t size);
378  static vuoMacOsLogCreateType vuoMacOsLogCreate = NULL;
379  static vuoMacOsLogInternalType vuoMacOsLogInternal = NULL;
380  static vuoMacOsLogImplType vuoMacOsLogImpl = NULL;
381  static dispatch_once_t once = 0;
382  static bool debuggerAttached = false;
383  dispatch_once(&once, ^{
384  debuggerAttached = VuoLog_isDebuggerAttached();
385  vuoMacOsLogCreate = (vuoMacOsLogCreateType)dlsym(RTLD_SELF, "os_log_create");
386  if (VuoLog_getOSVersionMinor() == 12) // _os_log_impl doesn't work on macOS 10.12.
387  vuoMacOsLogInternal = (vuoMacOsLogInternalType)dlsym(RTLD_SELF, "_os_log_internal");
388  else if (VuoLog_getOSVersionMinor() > 12) // _os_log_internal doesn't work on macOS 10.13+.
389  vuoMacOsLogImpl = (vuoMacOsLogImplType)dlsym(RTLD_SELF, "_os_log_impl");
390  });
391 
392  if (!debuggerAttached && vuoMacOsLogCreate)
393  {
394  void *log = vuoMacOsLogCreate("org.vuo", formattedFile);
395 
396  if (vuoMacOsLogInternal)
397  vuoMacOsLogInternal(&__dso_handle, log, 0 /*OS_LOG_TYPE_DEFAULT*/, "%{public}41s:%-4u %{public}s", formattedFunction ? formattedFunction : function, linenumber, formattedString);
398  else if (vuoMacOsLogImpl)
399  {
400  char *formattedForOsLog;
401  asprintf(&formattedForOsLog, "%41s:%-4u %s", formattedFunction ? formattedFunction : function, linenumber, formattedString);
402 
403  // https://reviews.llvm.org/rC284990#C2197511NL2613
404  uint8_t logFormatDescriptor[12] = {
405  2, // "summary" = HasNonScalarItems (a C string)
406  1, // "numArgs" = one C string
407  34, // "argDescriptor" = 2 (IsPublic) + 2 (StringKind) << 4
408  8, // "argSize" = one C string (a 64-bit pointer)
409  };
410  // …followed by the pointer itself.
411  memcpy(logFormatDescriptor + 4, &formattedForOsLog, 8);
412 
413  vuoMacOsLogImpl(&__dso_handle, log, 0 /*OS_LOG_TYPE_DEFAULT*/, "%{public}s", logFormatDescriptor, sizeof(logFormatDescriptor));
414 
415  free(formattedForOsLog);
416  }
417 
418  os_release(log);
419  }
420 
421  else if (!debuggerAttached) // For Mac OS X 10.11 and prior
422  {
423  aslmsg msg = asl_new(ASL_TYPE_MSG);
424  asl_set(msg, ASL_KEY_READ_UID, "-1");
425  asl_log(NULL, msg, ASL_LEVEL_WARNING, "%27s:%-4u %41s %s", formattedFile, linenumber, formattedFunction ? formattedFunction : function, formattedString);
426  asl_free(msg);
427  }
428  }
429 
430 
431  static dispatch_once_t historyInitialized = 0;
432  dispatch_once(&historyInitialized, ^{
433  for (int i = 0; i < VuoLogHistoryItems; ++i)
434  VuoLogHistory[i] = NULL;
435  VuoLogHistoryQueue = dispatch_queue_create("VuoLogHistory", NULL);
436  });
437 
438  dispatch_sync(VuoLogHistoryQueue, ^{
439  // Keep the most recent messages in VuoLogHistory.
440  {
441  char *formattedPrefixedString;
442  asprintf(&formattedPrefixedString, "t=%8.4fs %27.27s:%-4u %41.41s %s", time, formattedFile, linenumber, formattedFunction ? formattedFunction : function, formattedString);
443  free(formattedString);
444  if (formattedFunction)
445  free(formattedFunction);
446 
447  // Find the first open history slot.
448  int i;
449  for (i = 0; i < VuoLogHistoryItems; ++i)
450  if (!VuoLogHistory[i])
451  break;
452 
453  if (i >= VuoLogHistoryItems)
454  {
455  // No open slots; expire 1 and rotate the rest.
456  free(VuoLogHistory[0]);
457  for (int i = 0; i < VuoLogHistoryItems-1; ++i)
458  VuoLogHistory[i] = VuoLogHistory[i+1];
459  i = VuoLogHistoryItems - 1;
460  }
461 
462  VuoLogHistory[i] = formattedPrefixedString;
463  }
464 
465  // Combine VuoLogHistory into a single string for the crash report.
466  {
467  if (VuoCrashReport.message)
468  free(VuoCrashReport.message);
469 
470  long size = 0;
471  for (int i = 0; i < VuoLogHistoryItems; ++i)
472  if (VuoLogHistory[i])
473  size += strlen(VuoLogHistory[i]) + 1 /* \n */;
474  size += 1 /* null terminator */;
475 
476  char *message = (char *)malloc(size);
477  strlcpy(message, VuoLogHistory[0], size);
478  for (int i = 1; i < VuoLogHistoryItems; ++i)
479  if (VuoLogHistory[i])
480  {
481  strlcat(message, "\n", size);
482  strlcat(message, VuoLogHistory[i], size);
483  }
484 
485  VuoCrashReport.message = message;
486  }
487  });
488 }
489 
499 {
500  static dispatch_once_t checked = 0;
501  static bool debug = false;
502  dispatch_once(&checked, ^{
503  debug = CFPreferencesGetAppBooleanValue(CFSTR("debug"), CFSTR("org.vuo.Editor"), NULL);
504  });
505  return debug;
506 }
507 
512 {
513  vector<string> backtrace = VuoLog_getBacktrace();
514 
515  // Skip the VuoLog_backtrace() function.
516  backtrace.erase(backtrace.begin());
517 
518  int i = 1;
519  for (auto line : backtrace)
520  fprintf(stderr, "%3d %s\n", i++, line.c_str());
521 }
522 
528 void VuoLog_replaceString(char *wholeString, const char *substringToRemove, const char *replacement)
529 {
530  size_t replacementLen = strlen(replacement);
531  regex_t regex;
532  regcomp(&regex, substringToRemove, REG_EXTENDED);
533  size_t nmatch = 1;
534  regmatch_t pmatch[nmatch];
535  while (!regexec(&regex, wholeString, nmatch, pmatch, 0))
536  {
537  strncpy(wholeString + pmatch[0].rm_so, replacement, replacementLen);
538  memmove(wholeString + pmatch[0].rm_so + replacementLen, wholeString + pmatch[0].rm_eo, strlen(wholeString + pmatch[0].rm_eo) + 1);
539  }
540  regfree(&regex);
541 }
542 
546 vector<string> VuoLog_getBacktrace(void)
547 {
548  void *array[100];
549  size_t size = backtrace(array, 100);
550  char **strings = backtrace_symbols(array, size);
551  vector<string> outputStrings;
552 
553  // Start at 1 to skip the VuoLog_getBacktrace() function.
554  for (size_t i = 1; i < size; i++)
555  {
556  // Trim off the line number, since callees may want to skip additional stack frames.
557  const int lineNumberLen = 4;
558  string trimmedLine = strings[i] + lineNumberLen;
559 
560  const int libraryLen = 36;
561  const int addressLen = 19;
562  string symbol = strings[i] + lineNumberLen + libraryLen + addressLen;
563  string instructionOffset = symbol.substr(symbol.find(' '));
564  symbol = symbol.substr(0, symbol.find(' '));
565 
566  int status;
567  char *unmangled = abi::__cxa_demangle(symbol.c_str(), nullptr, nullptr, &status);
568  if (status == 0)
569  {
570  // Boil down the C++ standard library's bloviation.
571  VuoLog_replaceString(unmangled, "std::__1::", "");
572  VuoLog_replaceString(unmangled, "basic_string<char, char_traits<char>, allocator<char> >", "string");
573  VuoLog_replaceString(unmangled, ", less<[^>]*>, allocator<pair<[^>]*, [^>]*> > >", ">"); // map<…>
574  VuoLog_replaceString(unmangled, ", less<[^>]*>, allocator<[^>]*> >", ">"); // set<…>
575  VuoLog_replaceString(unmangled, ", allocator<[^>]*> >", ">"); // vector<…>
576 
577  outputStrings.push_back(trimmedLine.substr(0, libraryLen + addressLen) + unmangled + instructionOffset);
578  free(unmangled);
579  }
580  else
581  outputStrings.push_back(trimmedLine);
582  }
583 
584  // Hide uninformative stack frames.
585  {
586  // If the last stack frame is something like "??? 0x0000000000000010 0x0 + 16",
587  // the stack parser's gone off the rails.
588  auto last = outputStrings.back();
589  if (last.substr(0, 4) == "??? "
590  && last.find(" 0x0 + ") != string::npos)
591  outputStrings.pop_back();
592 
593  last = outputStrings.back();
594  if (last.substr(0, 13) == "libdyld.dylib"
595  && last.find(" start + ") != string::npos)
596  outputStrings.pop_back();
597 
598  outputStrings.erase(
599  remove_if(outputStrings.begin(), outputStrings.end(),
600  [](const string &s) {
601  return s.find("_dispatch_queue_override_invoke") != string::npos
602  || s.find("_dispatch_root_queue_drain") != string::npos
603  || s.find("_dispatch_worker_thread2") != string::npos
604  || s.find("_dispatch_lane_serial_drain") != string::npos
605  || s.find("_dispatch_lane_barrier_sync_invoke_and_complete") != string::npos
606  || s.find("_dispatch_lane_invoke") != string::npos
607  || s.find("_dispatch_workloop_worker_thread") != string::npos
608  || s.find("_dispatch_continuation_pop") != string::npos
609  || s.find("_dispatch_main_queue_callback_4CF") != string::npos
610  || s.find("__CFRunLoopDoObservers") != string::npos
611  || s.find("__CFRunLoopDoSource0") != string::npos
612  || s.find("__CFRunLoopDoSources0") != string::npos
613  || s.find("__CFRunLoopRun") != string::npos
614  || s.find("__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__") != string::npos
615  || s.find("__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__") != string::npos
616  || s.find("__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__") != string::npos
617  || s.find("_CFXRegistrationPost") != string::npos
618  || s.find("_CFXNotification") != string::npos
619  || s.find("_NSWindowSendWindowDidMove") != string::npos
620  || s.find("_NSSendEventToObservers") != string::npos
621  || s.find("NSCarbonMenuImpl") != string::npos
622  || s.find("NSSLMMenuEventHandler") != string::npos
623  || s.find("CopyCarbonUIElementAttributeValue") != string::npos
624  || s.find("NSApplication(NSEvent) _") != string::npos
625  || s.find("NSApplication(NSAppleEventHandling) _") != string::npos
626  || s.find("withWindowOrderingObserverHeuristic") != string::npos
627  || s.find("aeProcessAppleEvent") != string::npos
628  || s.find("dispatchEventAndSendReply") != string::npos
629  || s.find("aeDispatchAppleEvent") != string::npos
630  || s.find("_NSAppleEventManagerGenericHandler") != string::npos
631  || s.find("NSWindow _") != string::npos
632  || s.find("HIServices") != string::npos
633  || s.find("HIToolbox") != string::npos
634  || s.find("RunCurrentEventLoopInMode") != string::npos
635  || s.find("ReceiveNextEventCommon") != string::npos
636  || s.find("_BlockUntilNextEventMatchingListInModeWithFilter") != string::npos
637  || s.find("_DPSNextEvent") != string::npos
638  || s.find("_pthread_wqthread") != string::npos
639  || s.find("qt_plugin_instance") != string::npos
640  || s.find("QWindowSystemInterface::") != string::npos
641  || s.find("QWidgetPrivate::") != string::npos
642  || s.find("QApplicationPrivate::") != string::npos
643  || s.find("QGuiApplicationPrivate::") != string::npos
644  || s.find("QCoreApplication::notifyInternal2") != string::npos
645  || s.find("QCoreApplicationPrivate::") != string::npos;
646  }),
647  outputStrings.end());
648  }
649 
650  free(strings);
651  return outputStrings;
652 }