[LSan] Add new experimental option for checking long-running processes. 80/159380/9
authorMichail Kashkarov <m.kashkarov@partner.samsung.com>
Thu, 19 Oct 2017 19:02:41 +0000 (22:02 +0300)
committerDongkyun Son <dongkyun.s@samsung.com>
Fri, 21 Sep 2018 07:10:03 +0000 (07:10 +0000)
New LSan option: leak_check_interval_s:
Spawns a background thread at startup which periodically do recoverable
leak check with given interval.

Report files (if log_path option != stderr|stdout) are appended with
prefix "in-progress" and for the DoLeakCheck() call at exit -
are restores back.

System daemons can be leak sanitized right after system startup by adding
"Environment=LSAN_OPTIONS=...:leak_check_interval_s=60"
field in the systemd service file for leak checking every 60 seconds.

Change-Id: Ia78044ec45092f0de96818bd0114d4c7a49cb7e1

libsanitizer/lsan/lsan.cc
libsanitizer/lsan/lsan_common.cc
libsanitizer/lsan/lsan_common.h
libsanitizer/lsan/lsan_common_linux.cc
libsanitizer/lsan/lsan_interceptors.cc
libsanitizer/sanitizer_common/sanitizer_common.cc
libsanitizer/sanitizer_common/sanitizer_common.h
libsanitizer/sanitizer_common/sanitizer_flags.inc
libsanitizer/sanitizer_common/sanitizer_posix.cc

index da21ddf..6b68c61 100644 (file)
@@ -105,8 +105,10 @@ extern "C" void __lsan_init() {
   ThreadStart(tid, GetTid());
   SetCurrentThread(tid);
 
-  if (common_flags()->detect_leaks && common_flags()->leak_check_at_exit)
-    Atexit(DoLeakCheck);
+  if (common_flags()->detect_leaks) {
+    MaybeStartBackgroudLeakCheckingThread();
+    if (common_flags()->leak_check_at_exit) Atexit(DoLeakCheck);
+  }
 
   InitializeCoverage(common_flags()->coverage, common_flags()->coverage_dir);
 
index 3808f2b..0371b28 100644 (file)
@@ -466,6 +466,7 @@ static bool CheckForLeaks() {
   }
   param.leak_report.ApplySuppressions();
   uptr unsuppressed_count = param.leak_report.UnsuppressedLeakCount();
+  CommonSanitizerReportMutex.Lock();
   if (unsuppressed_count > 0) {
     Decorator d;
     Printf("\n"
@@ -482,9 +483,11 @@ static bool CheckForLeaks() {
     param.leak_report.PrintSummary();
     if (common_flags()->print_cmdline)
        PrintCmdline();
+    CommonSanitizerReportMutex.Unlock();
     lsan_check_in_progress = false;
     return true;
   }
+  CommonSanitizerReportMutex.Unlock();
   lsan_check_in_progress = false;
   return false;
 }
@@ -494,6 +497,7 @@ void DoLeakCheck() {
   static bool already_done;
   if (already_done) return;
   already_done = true;
+  MaybeRestoreLogPath();
   bool have_leaks = CheckForLeaks();
   if (!have_leaks) {
     return;
@@ -545,6 +549,26 @@ static Suppression *GetSuppressionForStack(u32 stack_trace_id) {
   return nullptr;
 }
 
+void MaybeRestoreLogPath() {
+  if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet)
+    return;
+  __sanitizer_set_report_path(common_flags()->log_path);
+}
+
+void MaybeAppendToLogPath(const char *suffix) {
+  if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet)
+    return;
+  const char *log_path = common_flags()->log_path;
+  if (internal_strcmp(log_path, "stderr") == 0 ||
+      internal_strcmp(log_path, "stdout") == 0 || !log_path)
+    return;
+  static char new_log_path[kMaxPathLength] = {'\0'};
+  if (UNLIKELY(new_log_path[0] == '\0')) {
+    internal_snprintf(new_log_path, kMaxPathLength, "%s.%s", log_path, suffix);
+    __sanitizer_set_report_path(new_log_path);
+  }
+}
+
 ///// LeakReport implementation. /////
 
 // A hard limit on the number of distinct leaks, to avoid quadratic complexity
index 85b76db..d13b4c9 100644 (file)
@@ -119,6 +119,11 @@ void InitCommonLsan();
 void DoLeakCheck();
 void DoRecoverableLeakCheckVoid();
 bool DisabledInThisThread();
+void MaybeRestoreLogPath();
+void MaybeAppendToLogPath(const char *suffix);
+
+static const uptr kLeaksIntervalNotSet = 0;
+void MaybeStartBackgroudLeakCheckingThread();
 
 // Used to implement __lsan::ScopedDisabler.
 void DisableInThisThread();
index abbb61f..d49c3c0 100644 (file)
@@ -179,6 +179,23 @@ void DoStopTheWorld(StopTheWorldCallback callback, void *argument) {
   dl_iterate_phdr(DoStopTheWorldCallback, &param);
 }
 
+void BackgroudLeakCheckingThread(void *arg) {
+  ScopedInterceptorDisabler disabler;
+  uptr interval_s = common_flags()->leak_check_interval_s;
+  MaybeAppendToLogPath("in-progress");
+  while (true) {
+    SleepForSeconds(interval_s);
+    report_file.Truncate();
+    DoRecoverableLeakCheckVoid();
+  }
+  MaybeRestoreLogPath();
+}
+
+void MaybeStartBackgroudLeakCheckingThread() {
+  if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return;
+  internal_start_thread(BackgroudLeakCheckingThread, nullptr);
+}
+
 } // namespace __lsan
 
 #endif // CAN_SANITIZE_LEAKS && SANITIZER_LINUX
index 6a01907..4bc178c 100644 (file)
 #include <stddef.h>
 #include <sys/mman.h>
 
+#if SANITIZER_POSIX
+#include "sanitizer_common/sanitizer_posix.h"
+#endif
+
 using namespace __lsan;
 
 extern "C" {
@@ -277,6 +281,8 @@ INTERCEPTOR(int, pthread_join, void *th, void **ret) {
   return res;
 }
 
+DEFINE_REAL_PTHREAD_FUNCTIONS
+
 static inline bool isSharedMmap(void *addr, int flags) {
   return (flags & MAP_SHARED) && (addr == NULL);
 }
index 51ba4a1..d84cd8a 100644 (file)
@@ -91,6 +91,15 @@ void ReportFile::SetReportPath(const char *path) {
   }
 }
 
+void ReportFile::Truncate() {
+  if (fd == kStdoutFd || fd == kStderrFd) return;
+  if (!full_path) return;
+  SpinMutexLock l(mu);
+  if (fd != kInvalidFd)
+    CloseFile(fd);
+  fd = OpenFile(full_path, WrTrunc);
+}
+
 // PID of the tracer task in StopTheWorld. It shares the address space with the
 // main process, but has a different PID and thus requires special handling.
 uptr stoptheworld_tracer_pid = 0;
index 140ff3e..3c1d1a5 100644 (file)
@@ -202,6 +202,7 @@ struct ReportFile {
   void Write(const char *buffer, uptr length);
   bool SupportsColors();
   void SetReportPath(const char *path);
+  void Truncate();
 
   // Don't use fields directly. They are only declared public to allow
   // aggregate initialization.
@@ -230,7 +231,8 @@ extern uptr stoptheworld_tracer_ppid;
 enum FileAccessMode {
   RdOnly,
   WrOnly,
-  RdWr
+  RdWr,
+  WrTrunc
 };
 
 // Returns kInvalidFd on error.
index cb49473..4b651ca 100644 (file)
@@ -67,6 +67,10 @@ COMMON_FLAG(
     "Invoke leak checking in an atexit handler. Has no effect if "
     "detect_leaks=false, or if __lsan_do_leak_check() is called before the "
     "handler has a chance to run.")
+COMMON_FLAG(uptr, leak_check_interval_s, 0,
+          "Experimental. If set, creates background thread that performs "
+          "recoverable leak checking every leak_check_interval_s seconds. "
+          "Disabled if zero or detect_leaks=false.\n")
 COMMON_FLAG(bool, allocator_may_return_null, false,
             "If false, the allocator will crash instead of returning 0 on "
             "out-of-memory.")
index ed43d24..f55ef30 100644 (file)
@@ -239,6 +239,7 @@ fd_t OpenFile(const char *filename, FileAccessMode mode, error_t *errno_p) {
     case RdOnly: flags = O_RDONLY; break;
     case WrOnly: flags = O_WRONLY | O_CREAT; break;
     case RdWr: flags = O_RDWR | O_CREAT; break;
+    case WrTrunc: flags = O_WRONLY | O_CREAT | O_TRUNC; break;
   }
   fd_t res = internal_open(filename, flags, 0660);
   if (internal_iserror(res, errno_p))