ART: Support per PID stack trace files.

Introduce an -Xstacktracedir argument that supplies a directory
under which stack traces are written, with a unique file created
per trace. The location of the actual directory in a production
system is still not decided, and follow up changes might be
introduced to supply a per process override.

Bug: 32064548
Test: test-art-host, test-art-target

Change-Id: If377ce6a2abe8b325f6441d8de222b1ea3f40ec9
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 8ffd8bb..fc91efa 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -238,6 +238,9 @@
       .Define("-Xlockprofthreshold:_")
           .WithType<unsigned int>()
           .IntoKey(M::LockProfThreshold)
+      .Define("-Xstacktracedir:_")
+          .WithType<std::string>()
+          .IntoKey(M::StackTraceDir)
       .Define("-Xstacktracefile:_")
           .WithType<std::string>()
           .IntoKey(M::StackTraceFile)
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index a48a58d..bfe9f9c 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -825,7 +825,7 @@
 
 void Runtime::StartSignalCatcher() {
   if (!is_zygote_) {
-    signal_catcher_ = new SignalCatcher(stack_trace_file_);
+    signal_catcher_ = new SignalCatcher(stack_trace_dir_, stack_trace_file_);
   }
 }
 
@@ -1035,6 +1035,7 @@
   abort_ = runtime_options.GetOrDefault(Opt::HookAbort);
 
   default_stack_size_ = runtime_options.GetOrDefault(Opt::StackSize);
+  stack_trace_dir_ = runtime_options.ReleaseOrDefault(Opt::StackTraceDir);
   stack_trace_file_ = runtime_options.ReleaseOrDefault(Opt::StackTraceFile);
 
   compiler_executable_ = runtime_options.ReleaseOrDefault(Opt::Compiler);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 20db628..86d8e4f 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -767,6 +767,7 @@
   ClassLinker* class_linker_;
 
   SignalCatcher* signal_catcher_;
+  std::string stack_trace_dir_;
   std::string stack_trace_file_;
 
   std::unique_ptr<JavaVMExt> java_vm_;
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 16190cd..77132a8 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -100,6 +100,7 @@
 RUNTIME_OPTIONS_KEY (Unit,                ForceNativeBridge)
 RUNTIME_OPTIONS_KEY (LogVerbosity,        Verbose)
 RUNTIME_OPTIONS_KEY (unsigned int,        LockProfThreshold)
+RUNTIME_OPTIONS_KEY (std::string,         StackTraceDir)
 RUNTIME_OPTIONS_KEY (std::string,         StackTraceFile)
 RUNTIME_OPTIONS_KEY (Unit,                MethodTrace)
 RUNTIME_OPTIONS_KEY (std::string,         MethodTraceFile,                "/data/misc/trace/method-trace-file.bin")
diff --git a/runtime/signal_catcher.cc b/runtime/signal_catcher.cc
index 0b7ea2f..3826433 100644
--- a/runtime/signal_catcher.cc
+++ b/runtime/signal_catcher.cc
@@ -27,6 +27,7 @@
 
 #include <sstream>
 
+#include "android-base/stringprintf.h"
 #include "arch/instruction_set.h"
 #include "base/time_utils.h"
 #include "base/unix_file/fd_file.h"
@@ -65,8 +66,10 @@
 #endif
 }
 
-SignalCatcher::SignalCatcher(const std::string& stack_trace_file)
-    : stack_trace_file_(stack_trace_file),
+SignalCatcher::SignalCatcher(const std::string& stack_trace_dir,
+                             const std::string& stack_trace_file)
+    : stack_trace_dir_(stack_trace_dir),
+      stack_trace_file_(stack_trace_file),
       lock_("SignalCatcher lock"),
       cond_("SignalCatcher::cond_", lock_),
       thread_(nullptr) {
@@ -100,19 +103,51 @@
   return halt_;
 }
 
+std::string SignalCatcher::GetStackTraceFileName() {
+  if (!stack_trace_dir_.empty()) {
+    // We'll try a maximum of ten times (arbitrarily selected) to create a file
+    // with a unique name, seeding the pseudo random generator each time.
+    //
+    // If this doesn't work, give up and log to stdout. Note that we could try
+    // indefinitely, but that would make problems in this code harder to detect
+    // since we'd be spinning in the signal catcher thread.
+    static constexpr uint32_t kMaxRetries = 10;
+
+    for (uint32_t i = 0; i < kMaxRetries; ++i) {
+        std::srand(NanoTime());
+        // Sample output for PID 1234 : /data/anr-pid1234-cafeffee.txt
+        const std::string file_name = android::base::StringPrintf(
+            "%s/anr-pid%" PRId32 "-%08" PRIx32 ".txt",
+            stack_trace_dir_.c_str(),
+            static_cast<int32_t>(getpid()),
+            static_cast<uint32_t>(std::rand()));
+
+        if (!OS::FileExists(file_name.c_str())) {
+          return file_name;
+        }
+    }
+
+    LOG(ERROR) << "Unable to obtain stack trace filename at path : " << stack_trace_dir_;
+    return "";
+  }
+
+  return stack_trace_file_;
+}
+
 void SignalCatcher::Output(const std::string& s) {
-  if (stack_trace_file_.empty()) {
+  const std::string stack_trace_file = GetStackTraceFileName();
+  if (stack_trace_file.empty()) {
     LOG(INFO) << s;
     return;
   }
 
   ScopedThreadStateChange tsc(Thread::Current(), kWaitingForSignalCatcherOutput);
-  int fd = open(stack_trace_file_.c_str(), O_APPEND | O_CREAT | O_WRONLY, 0666);
+  int fd = open(stack_trace_file.c_str(), O_APPEND | O_CREAT | O_WRONLY, 0666);
   if (fd == -1) {
     PLOG(ERROR) << "Unable to open stack trace file '" << stack_trace_file_ << "'";
     return;
   }
-  std::unique_ptr<File> file(new File(fd, stack_trace_file_, true));
+  std::unique_ptr<File> file(new File(fd, stack_trace_file, true));
   bool success = file->WriteFully(s.data(), s.size());
   if (success) {
     success = file->FlushCloseOrErase() == 0;
@@ -120,9 +155,9 @@
     file->Erase();
   }
   if (success) {
-    LOG(INFO) << "Wrote stack traces to '" << stack_trace_file_ << "'";
+    LOG(INFO) << "Wrote stack traces to '" << stack_trace_file << "'";
   } else {
-    PLOG(ERROR) << "Failed to write stack traces to '" << stack_trace_file_ << "'";
+    PLOG(ERROR) << "Failed to write stack traces to '" << stack_trace_file << "'";
   }
 }
 
diff --git a/runtime/signal_catcher.h b/runtime/signal_catcher.h
index de6a212..4cd7a98 100644
--- a/runtime/signal_catcher.h
+++ b/runtime/signal_catcher.h
@@ -32,7 +32,15 @@
  */
 class SignalCatcher {
  public:
-  explicit SignalCatcher(const std::string& stack_trace_file);
+  // If |stack_trace_dir| is non empty, traces will be written to a
+  // unique file under that directory.
+  //
+  // If |stack_trace_dir| is empty, and |stack_frace_file| is non-empty,
+  // traces will be appended to |stack_trace_file|.
+  //
+  // If both are empty, all traces will be written to the log buffer.
+  explicit SignalCatcher(const std::string& stack_trace_dir,
+                         const std::string& stack_trace_file);
   ~SignalCatcher();
 
   void HandleSigQuit() REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_,
@@ -43,12 +51,14 @@
   // NO_THREAD_SAFETY_ANALYSIS for static function calling into member function with excludes lock.
   static void* Run(void* arg) NO_THREAD_SAFETY_ANALYSIS;
 
+  std::string GetStackTraceFileName();
   void HandleSigUsr1();
   void Output(const std::string& s);
   void SetHaltFlag(bool new_value) REQUIRES(!lock_);
   bool ShouldHalt() REQUIRES(!lock_);
   int WaitForSignal(Thread* self, SignalSet& signals) REQUIRES(!lock_);
 
+  std::string stack_trace_dir_;
   std::string stack_trace_file_;
 
   mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
diff --git a/test/987-stack-trace-dumping/expected.txt b/test/987-stack-trace-dumping/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/987-stack-trace-dumping/expected.txt
diff --git a/test/987-stack-trace-dumping/info.txt b/test/987-stack-trace-dumping/info.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/987-stack-trace-dumping/info.txt
diff --git a/test/987-stack-trace-dumping/run b/test/987-stack-trace-dumping/run
new file mode 100755
index 0000000..dee3e8b
--- /dev/null
+++ b/test/987-stack-trace-dumping/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --set-stack-trace-dump-dir
diff --git a/test/987-stack-trace-dumping/src/Main.java b/test/987-stack-trace-dumping/src/Main.java
new file mode 100644
index 0000000..d1e8a1b
--- /dev/null
+++ b/test/987-stack-trace-dumping/src/Main.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.File;
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        if (args.length != 3) {
+            throw new AssertionError("Unexpected number of args: " + args.length);
+        }
+
+        if (!"--stack-trace-dir".equals(args[1])) {
+            throw new AssertionError("Unexpected argument in position 1: " + args[1]);
+        }
+
+        // Send ourselves signal 3, which forces stack traces to be written to disk.
+        android.system.Os.kill(android.system.Os.getpid(), 3);
+
+        File[] files = null;
+        final String stackTraceDir = args[2];
+        for (int i = 0; i < 5; ++i) {
+            // Give the signal handler some time to run and dump traces - up to a maximum
+            // of 5 seconds. This is a kludge, but it's hard to do this without using things
+            // like inotify / WatchService and the like.
+            Thread.sleep(1000);
+
+            files = (new File(stackTraceDir)).listFiles();
+            if (files != null && files.length == 1) {
+                break;
+            }
+        }
+
+
+        if (files == null) {
+            throw new AssertionError("Gave up waiting for traces: " + java.util.Arrays.toString(files));
+        }
+
+        final String fileName = files[0].getName();
+        if (!fileName.startsWith("anr-pid")) {
+            throw new AssertionError("Unexpected prefix: " + fileName);
+        }
+
+        if (!fileName.contains(String.valueOf(android.system.Os.getpid()))) {
+            throw new AssertionError("File name does not contain process PID: " + fileName);
+        }
+    }
+}
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 56cfd24..c243af5 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -67,6 +67,11 @@
 PROFILE="n"
 RANDOM_PROFILE="n"
 
+# if "y", set -Xstacktracedir and inform the test of its location. When
+# this is set, stack trace dumps (from signal 3) will be written to a file
+# under this directory instead of stdout.
+SET_STACK_TRACE_DUMP_DIR="n"
+
 # if "y", run 'sync' before dalvikvm to make sure all files from
 # build step (e.g. dex2oat) were finished writing.
 SYNC_BEFORE_RUN="n"
@@ -283,6 +288,9 @@
     elif [ "x$1" = "x--random-profile" ]; then
         RANDOM_PROFILE="y"
         shift
+    elif [ "x$1" = "x--set-stack-trace-dump-dir" ]; then
+        SET_STACK_TRACE_DUMP_DIR="y"
+        shift
     elif expr "x$1" : "x--" >/dev/null 2>&1; then
         echo "unknown $0 option: $1" 1>&2
         exit 1
@@ -291,12 +299,22 @@
     fi
 done
 
+mkdir_locations=""
+
 if [ "$USE_JVM" = "n" ]; then
     FLAGS="${FLAGS} ${ANDROID_FLAGS}"
     for feature in ${EXPERIMENTAL}; do
         FLAGS="${FLAGS} -Xexperimental:${feature} -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:${feature}"
         COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xexperimental:${feature}"
     done
+
+    if [ "$SET_STACK_TRACE_DUMP_DIR" = "y" ]; then
+        # Note that DEX_LOCATION is used as a proxy for tmpdir throughout this
+        # file (it will be under the test specific folder).
+        mkdir_locations="${mkdir_locations} $DEX_LOCATION/stack_traces"
+        FLAGS="${FLAGS} -Xstacktracedir:$DEX_LOCATION/stack_traces"
+        ARGS="${ARGS} --stack-trace-dir $DEX_LOCATION/stack_traces"
+    fi
 fi
 
 if [ "x$1" = "x" ] ; then
@@ -534,7 +552,7 @@
 profman_cmdline="true"
 dex2oat_cmdline="true"
 vdex_cmdline="true"
-mkdir_locations="${DEX_LOCATION}/dalvik-cache/$ISA"
+mkdir_locations="${mkdir_locations} ${DEX_LOCATION}/dalvik-cache/$ISA"
 strip_cmdline="true"
 sync_cmdline="true"