Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f013daa3 authored by Narayan Kamath's avatar Narayan Kamath
Browse files

ActivityManagerService: Add support for new stack dumping scheme.

Tombstoned now fully supports java traces and intercepts, and the
debuggerd dump API has been extended to support dumps of java traces.

This change switches ANR dumping over to using this API when the
right system property is set. The new flow is as follows :

- The system_server creates a new file using File.createTempFile for
  each ANR detected by the activity manager. All dumps associated
  with that ANR go into that file.

- All dumps are initiated using debuggerd client API (debuggerd_trigger_dump)
  which handles all the timeout measurement for us. It can also
  guarantee that no writes are made to the file after the method
  returns, so we have no need of inotify watches and other fiddly
  mechanisms to monitor progress. Also, this would give us the ability
  to add meta-information about timeouts etc. to the dump file itself,
  thougt that hasn't been implemented just yet.

Test: Manual
Bug: 32064548

Change-Id: I37e72c467e6dc29da4347c2a2829eeeeb1ad3490
parent d47fa266
Loading
Loading
Loading
Loading
+28 −13
Original line number Diff line number Diff line
@@ -16,13 +16,20 @@

package android.os;

import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.TypedProperties;

import android.app.AppGlobals;
import android.content.Context;
import android.util.Log;

import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.TypedProperties;

import dalvik.bytecode.OpcodeInfo;
import dalvik.system.VMDebug;

import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -31,21 +38,16 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;

import dalvik.bytecode.OpcodeInfo;
import dalvik.system.VMDebug;


/**
@@ -2219,13 +2221,26 @@ public final class Debug
    }

    /**
     * Append the stack traces of a given native process to a specified file.
     * Append the Java stack traces of a given native process to a specified file.
     *
     * @param pid pid to dump.
     * @param file path of file to append dump to.
     * @param timeoutSecs time to wait in seconds, or 0 to wait forever.
     * @hide
     */
    public static native boolean dumpJavaBacktraceToFileTimeout(int pid, String file,
                                                                int timeoutSecs);

    /**
     * Append the native stack traces of a given process to a specified file.
     *
     * @param pid pid to dump.
     * @param file path of file to append dump to.
     * @param timeoutSecs time to wait in seconds, or 0 to wait forever.
     * @hide
     */
    public static native void dumpNativeBacktraceToFileTimeout(int pid, String file, int timeoutSecs);
    public static native boolean dumpNativeBacktraceToFileTimeout(int pid, String file,
                                                                  int timeoutSecs);

    /**
     * Get description of unreachable native memory.
+27 −19
Original line number Diff line number Diff line
@@ -33,12 +33,14 @@
#include <string>

#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <debuggerd/client.h>
#include <log/log.h>
#include <utils/misc.h>
#include <utils/String8.h>

#include "JNIHelp.h"
#include "ScopedUtfChars.h"
#include "jni.h"
#include <memtrack/memtrack.h>
#include <memunreachable/memunreachable.h>
@@ -1008,30 +1010,34 @@ static void android_os_Debug_dumpNativeHeap(JNIEnv* env, jobject clazz,
    ALOGD("Native heap dump complete.\n");
}

static void android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
    jint pid, jstring fileName, jint timeoutSecs)
{
    if (fileName == NULL) {
        jniThrowNullPointerException(env, "file == null");
        return;
    }
    const jchar* str = env->GetStringCritical(fileName, 0);
    String8 fileName8;
    if (str) {
        fileName8 = String8(reinterpret_cast<const char16_t*>(str),
                            env->GetStringLength(fileName));
        env->ReleaseStringCritical(fileName, str);
static bool dumpTraces(JNIEnv* env, jint pid, jstring fileName, jint timeoutSecs,
                       DebuggerdDumpType dumpType) {
    const ScopedUtfChars fileNameChars(env, fileName);
    if (fileNameChars.c_str() == nullptr) {
        return false;
    }

    int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND, 0666);
    android::base::unique_fd fd(open(fileNameChars.c_str(),
                                     O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND,
                                     0666));
    if (fd < 0) {
        fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno));
        return;
        fprintf(stderr, "Can't open %s: %s\n", fileNameChars.c_str(), strerror(errno));
        return false;
    }

    dump_backtrace_to_file_timeout(pid, kDebuggerdNativeBacktrace, timeoutSecs, fd);
    return (dump_backtrace_to_file_timeout(pid, dumpType, timeoutSecs, fd) == 0);
}

    close(fd);
static jboolean android_os_Debug_dumpJavaBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
        jint pid, jstring fileName, jint timeoutSecs) {
    const bool ret =  dumpTraces(env, pid, fileName, timeoutSecs, kDebuggerdJavaBacktrace);
    return ret ? JNI_TRUE : JNI_FALSE;
}

static jboolean android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
        jint pid, jstring fileName, jint timeoutSecs) {
    const bool ret = dumpTraces(env, pid, fileName, timeoutSecs, kDebuggerdNativeBacktrace);
    return ret ? JNI_TRUE : JNI_FALSE;
}

static jstring android_os_Debug_getUnreachableMemory(JNIEnv* env, jobject clazz,
@@ -1074,7 +1080,9 @@ static const JNINativeMethod gMethods[] = {
            (void*)android_os_Debug_getProxyObjectCount },
    { "getBinderDeathObjectCount", "()I",
            (void*)android_os_Debug_getDeathObjectCount },
    { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)V",
    { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
            (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout },
    { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
            (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
    { "getUnreachableMemory", "(IZ)Ljava/lang/String;",
            (void*)android_os_Debug_getUnreachableMemory },
+116 −32
Original line number Diff line number Diff line
@@ -599,6 +599,8 @@ public class ActivityManagerService extends IActivityManager.Stub
    // the notification will not be legible to the user.
    private static final int MAX_BUGREPORT_TITLE_SIZE = 50;
    private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
    /** All system services */
    SystemServiceManager mSystemServiceManager;
    AssistUtils mAssistUtils;
@@ -5411,29 +5413,14 @@ public class ActivityManagerService extends IActivityManager.Stub
     * @param firstPids of dalvik VM processes to dump stack traces for first
     * @param lastPids of dalvik VM processes to dump stack traces for last
     * @param nativePids optional list of native pids to dump stack crawls
     * @return file containing stack traces, or null if no dump file is configured
     */
    public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
            ArrayList<Integer> nativePids) {
        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
        if (tracesPath == null || tracesPath.length() == 0) {
            return null;
        }
        File tracesFile = new File(tracesPath);
        try {
            if (clearTraces && tracesFile.exists()) tracesFile.delete();
            tracesFile.createNewFile();
            FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
        } catch (IOException e) {
            Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
            return null;
        }
        ArrayList<Integer> extraPids = null;
        // Lastly, measure CPU usage.
        // Measure CPU usage as soon as we're called in order to get a realistic sampling
        // of the top users at the time of the request.
        if (processCpuTracker != null) {
            processCpuTracker.init();
            try {
@@ -5459,14 +5446,70 @@ public class ActivityManagerService extends IActivityManager.Stub
            }
        }
        dumpStackTraces(tracesPath, firstPids, nativePids, extraPids);
        boolean useTombstonedForJavaTraces = false;
        File tracesFile;
        final String tracesDir = SystemProperties.get("dalvik.vm.stack-trace-dir", "");
        if (tracesDir.isEmpty()) {
            // When dalvik.vm.stack-trace-dir is not set, we are using the "old" trace
            // dumping scheme. All traces are written to a global trace file (usually
            // "/data/anr/traces.txt") so the code below must take care to unlink and recreate
            // the file if requested.
            //
            // This mode of operation will be removed in the near future.
            String globalTracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
            if (globalTracesPath.isEmpty()) {
                Slog.w(TAG, "dumpStackTraces: no trace path configured");
                return null;
            }
            tracesFile = new File(globalTracesPath);
            try {
                if (clearTraces && tracesFile.exists()) {
                    tracesFile.delete();
                }
                tracesFile.createNewFile();
                FileUtils.setPermissions(globalTracesPath, 0666, -1, -1); // -rw-rw-rw-
            } catch (IOException e) {
                Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesFile, e);
                return null;
            }
        } else {
            // When dalvik.vm.stack-trace-dir is set, we use the "new" trace dumping scheme.
            // Each set of ANR traces is written to a separate file and dumpstate will process
            // all such files and add them to a captured bug report if they're recent enough.
            //
            // NOTE: We should consider creating the file in native code atomically once we've
            // gotten rid of the old scheme of dumping and lot of the code that deals with paths
            // can be removed.
            try {
                tracesFile = File.createTempFile("anr_", "", new File(tracesDir));
                FileUtils.setPermissions(tracesFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
            } catch (IOException ioe) {
                Slog.w(TAG, "Unable to create ANR traces file: ", ioe);
                return null;
            }
            useTombstonedForJavaTraces = true;
        }
        dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids,
                useTombstonedForJavaTraces);
        return tracesFile;
    }
    /**
     * Legacy code, do not use. Existing users will be deleted.
     *
     * @deprecated
     */
    @Deprecated
    public static class DumpStackFileObserver extends FileObserver {
        // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
        private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
        static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
        private final String mTracesPath;
        private boolean mClosed;
@@ -5520,16 +5563,45 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
    }
    private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
            ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) {
    /**
     * Dump java traces for process {@code pid} to the specified file. If java trace dumping
     * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
     * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
     * attempting to obtain native traces in the case of a failure. Returns the total time spent
     * capturing traces.
     */
    private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
        final long timeStart = SystemClock.elapsedRealtime();
        if (!Debug.dumpJavaBacktraceToFileTimeout(pid, fileName, (int) (timeoutMs / 1000))) {
            Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
                    (NATIVE_DUMP_TIMEOUT_MS / 1000));
        }
        return SystemClock.elapsedRealtime() - timeStart;
    }
    private static void dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
            ArrayList<Integer> nativePids, ArrayList<Integer> extraPids,
            boolean useTombstonedForJavaTraces) {
        // We don't need any sort of inotify based monitoring when we're dumping traces via
        // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
        // control of all writes to the file in question.
        final DumpStackFileObserver observer;
        if (useTombstonedForJavaTraces) {
            observer = null;
        } else {
            // Use a FileObserver to detect when traces finish writing.
            // The order of traces is considered important to maintain for legibility.
        DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
            observer = new DumpStackFileObserver(tracesFile);
        }
        // We must complete all stack dumps within 20 seconds.
        long remainingTime = 20 * 1000;
        try {
            if (observer != null) {
                observer.startWatching();
            }
            // First collect all of the stacks of the most important pids.
            if (firstPids != null) {
@@ -5537,7 +5609,12 @@ public class ActivityManagerService extends IActivityManager.Stub
                for (int i = 0; i < num; i++) {
                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
                            + firstPids.get(i));
                    final long timeTaken = observer.dumpWithTimeout(firstPids.get(i), remainingTime);
                    final long timeTaken;
                    if (useTombstonedForJavaTraces) {
                        timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile, remainingTime);
                    } else {
                        timeTaken = observer.dumpWithTimeout(firstPids.get(i), remainingTime);
                    }
                    remainingTime -= timeTaken;
                    if (remainingTime <= 0) {
@@ -5556,12 +5633,11 @@ public class ActivityManagerService extends IActivityManager.Stub
            if (nativePids != null) {
                for (int pid : nativePids) {
                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
                    final long nativeDumpTimeoutMs = Math.min(
                            DumpStackFileObserver.NATIVE_DUMP_TIMEOUT_MS, remainingTime);
                    final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);
                    final long start = SystemClock.elapsedRealtime();
                    Debug.dumpNativeBacktraceToFileTimeout(
                            pid, tracesPath, (int) (nativeDumpTimeoutMs / 1000));
                            pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
                    final long timeTaken = SystemClock.elapsedRealtime() - start;
                    remainingTime -= timeTaken;
@@ -5582,7 +5658,13 @@ public class ActivityManagerService extends IActivityManager.Stub
                for (int pid : extraPids) {
                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + pid);
                    final long timeTaken = observer.dumpWithTimeout(pid, remainingTime);
                    final long timeTaken;
                    if (useTombstonedForJavaTraces) {
                        timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
                    } else {
                        timeTaken = observer.dumpWithTimeout(pid, remainingTime);
                    }
                    remainingTime -= timeTaken;
                    if (remainingTime <= 0) {
                        Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
@@ -5596,9 +5678,11 @@ public class ActivityManagerService extends IActivityManager.Stub
                }
            }
        } finally {
            if (observer != null) {
                observer.stopWatching();
            }
        }
    }
    final void logAppTooSlow(ProcessRecord app, long startTime, String msg) {
        if (true || IS_USER_BUILD) {
@@ -5643,7 +5727,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            if (app != null) {
                ArrayList<Integer> firstPids = new ArrayList<Integer>();
                firstPids.add(app.pid);
                dumpStackTraces(tracesPath, firstPids, null, null);
                dumpStackTraces(tracesPath, firstPids, null, null, true /* useTombstoned */);
            }
            File lastTracesFile = null;
+6 −4
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
@@ -908,7 +909,8 @@ class AppErrors {

        // For background ANRs, don't pass the ProcessCpuTracker to
        // avoid spending 1/2 second collecting stats to rank lastPids.
        File tracesFile = mService.dumpStackTraces(true, firstPids,
        File tracesFile = ActivityManagerService.dumpStackTraces(
                true, firstPids,
                (isSilentANR) ? null : processCpuTracker,
                (isSilentANR) ? null : lastPids,
                nativePids);