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"