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

Commit 2e1418c4 authored by Josh Gao's avatar Josh Gao Committed by Gerrit Code Review
Browse files

Merge "Attach protobuf tombstones to ApplicationExitInfo."

parents a139d7cb c36f532d
Loading
Loading
Loading
Loading
+51 −7
Original line number Diff line number Diff line
@@ -428,6 +428,13 @@ public final class ApplicationExitInfo implements Parcelable {
     */
    private IAppTraceRetriever mAppTraceRetriever;

    /**
     * ParcelFileDescriptor pointing to a native tombstone.
     *
     * @see #getTraceInputStream
     */
    private IParcelFileDescriptorRetriever mNativeTombstoneRetriever;

    /** @hide */
    @IntDef(prefix = { "REASON_" }, value = {
        REASON_UNKNOWN,
@@ -603,22 +610,38 @@ public final class ApplicationExitInfo implements Parcelable {
     * prior to the death of the process; typically it'll be available when
     * the reason is {@link #REASON_ANR}, though if the process gets an ANR
     * but recovers, and dies for another reason later, this trace will be included
     * in the record of {@link ApplicationExitInfo} still.
     * in the record of {@link ApplicationExitInfo} still. Beginning with API 31,
     * tombstone traces will be returned for
     * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with
     * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>.
     * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be
     * overwritten by newer crashes (including from other applications), so this may still return
     * null.
     *
     * @return The input stream to the traces that was taken by the system
     *         prior to the death of the process.
     */
    public @Nullable InputStream getTraceInputStream() throws IOException {
        if (mAppTraceRetriever == null) {
        if (mAppTraceRetriever == null && mNativeTombstoneRetriever == null) {
            return null;
        }

        try {
            if (mNativeTombstoneRetriever != null) {
                final ParcelFileDescriptor pfd = mNativeTombstoneRetriever.getPfd();
                if (pfd == null) {
                    return null;
                }

                return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
            } else {
                final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
                        mPackageName, mPackageUid, mPid);
                if (fd == null) {
                    return null;
                }
                return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd));
            }
        } catch (RemoteException e) {
            return null;
        }
@@ -849,6 +872,15 @@ public final class ApplicationExitInfo implements Parcelable {
        mAppTraceRetriever = retriever;
    }

    /**
     * @see mNativeTombstoneRetriever
     *
     * @hide
     */
    public void setNativeTombstoneRetriever(final IParcelFileDescriptorRetriever retriever) {
        mNativeTombstoneRetriever = retriever;
    }

    @Override
    public int describeContents() {
        return 0;
@@ -878,6 +910,12 @@ public final class ApplicationExitInfo implements Parcelable {
        } else {
            dest.writeInt(0);
        }
        if (mNativeTombstoneRetriever != null) {
            dest.writeInt(1);
            dest.writeStrongBinder(mNativeTombstoneRetriever.asBinder());
        } else {
            dest.writeInt(0);
        }
    }

    /** @hide */
@@ -906,6 +944,7 @@ public final class ApplicationExitInfo implements Parcelable {
        mState = other.mState;
        mTraceFile = other.mTraceFile;
        mAppTraceRetriever = other.mAppTraceRetriever;
        mNativeTombstoneRetriever = other.mNativeTombstoneRetriever;
    }

    private ApplicationExitInfo(@NonNull Parcel in) {
@@ -928,6 +967,10 @@ public final class ApplicationExitInfo implements Parcelable {
        if (in.readInt() == 1) {
            mAppTraceRetriever = IAppTraceRetriever.Stub.asInterface(in.readStrongBinder());
        }
        if (in.readInt() == 1) {
            mNativeTombstoneRetriever = IParcelFileDescriptorRetriever.Stub.asInterface(
                    in.readStrongBinder());
        }
    }

    public @NonNull static final Creator<ApplicationExitInfo> CREATOR =
@@ -986,6 +1029,7 @@ public final class ApplicationExitInfo implements Parcelable {
        sb.append(" state=").append(ArrayUtils.isEmpty(mState)
                ? "empty" : Integer.toString(mState.length) + " bytes");
        sb.append(" trace=").append(mTraceFile);

        return sb.toString();
    }

+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.
 */

package android.app;

import android.os.ParcelFileDescriptor;

/**
 * An interface used to lazily provide a ParcelFileDescriptor to apps.
 *
 * @hide
 */
interface IParcelFileDescriptorRetriever {
    /**
     * Retrieve the ParcelFileDescriptor.
     */
    ParcelFileDescriptor getPfd();
}
+6 −0
Original line number Diff line number Diff line
@@ -364,6 +364,7 @@ import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.os.NativeTombstoneManager;
import com.android.server.pm.Installer;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.uri.GrantUri;
@@ -10410,6 +10411,9 @@ public class ActivityManagerService extends IActivityManager.Stub
        mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
                "getHistoricalProcessExitReasons", null);
        NativeTombstoneManager tombstoneService = LocalServices.getService(
                NativeTombstoneManager.class);
        final ArrayList<ApplicationExitInfo> results = new ArrayList<ApplicationExitInfo>();
        if (!TextUtils.isEmpty(packageName)) {
            final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
@@ -10417,11 +10421,13 @@ public class ActivityManagerService extends IActivityManager.Stub
            if (uid != Process.INVALID_UID) {
                mProcessList.mAppExitInfoTracker.getExitInfo(
                        packageName, uid, pid, maxNum, results);
                tombstoneService.collectTombstones(results, uid, pid, maxNum);
            }
        } else {
            // If no package name is given, use the caller's uid as the filter uid.
            mProcessList.mAppExitInfoTracker.getExitInfo(
                    packageName, callingUid, pid, maxNum, results);
            tombstoneService.collectTombstones(results, callingUid, pid, maxNum);
        }
        return new ParceledListSlice<ApplicationExitInfo>(results);
+10 −0
Original line number Diff line number Diff line
@@ -63,8 +63,10 @@ import com.android.internal.app.ProcessMap;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemServiceManager;
import com.android.server.os.NativeTombstoneManager;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -78,6 +80,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
@@ -770,6 +773,10 @@ public final class AppExitInfoTracker {
     * Helper function for shell command
     */
    void clearHistoryProcessExitInfo(String packageName, int userId) {
        NativeTombstoneManager tombstoneService = LocalServices.getService(
                NativeTombstoneManager.class);
        Optional<Integer> appId = Optional.empty();

        if (TextUtils.isEmpty(packageName)) {
            synchronized (mLock) {
                removeByUserIdLocked(userId);
@@ -777,10 +784,13 @@ public final class AppExitInfoTracker {
        } else {
            final int uid = mService.mPackageManagerInt.getPackageUid(packageName,
                    PackageManager.MATCH_ALL, userId);
            appId = Optional.of(UserHandle.getAppId(uid));
            synchronized (mLock) {
                removePackageLocked(packageName, uid, true, userId);
            }
        }

        tombstoneService.purge(Optional.of(userId), appId);
        schedulePersistProcessExitInfo(true);
    }

+226 −7
Original line number Diff line number Diff line
@@ -16,12 +16,18 @@

package com.android.server.os;

import static android.app.ApplicationExitInfo.REASON_CRASH_NATIVE;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;

import android.annotation.AppIdInt;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ApplicationExitInfo;
import android.app.IParcelFileDescriptorRetriever;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -32,6 +38,7 @@ import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoInputStream;
@@ -39,6 +46,7 @@ import android.util.proto.ProtoInputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.server.BootReceiver;
import com.android.server.ServiceThread;
import com.android.server.os.TombstoneProtos.Cause;
import com.android.server.os.TombstoneProtos.Tombstone;

import libcore.io.IoUtils;
@@ -47,7 +55,11 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * A class to manage native tombstones.
@@ -153,7 +165,13 @@ public final class NativeTombstoneManager {
        }
    }

    private void purge(Optional<Integer> userId, Optional<Integer> appId) {
    /**
     * Remove native tombstones matching a user and/or app.
     *
     * @param userId user id to filter by, selects all users if empty
     * @param appId app id to filter by, selects all users if empty
     */
    public void purge(Optional<Integer> userId, Optional<Integer> appId) {
        mHandler.post(() -> {
            synchronized (mLock) {
                for (int i = mTombstones.size() - 1; i >= 0; --i) {
@@ -214,18 +232,97 @@ public final class NativeTombstoneManager {
        }, filter, null, mHandler);
    }

    /**
     * Collect native tombstones.
     *
     * @param output list to append to
     * @param callingUid POSIX uid to filter by
     * @param pid pid to filter by, ignored if zero
     * @param maxNum maximum number of elements in output
     */
    public void collectTombstones(ArrayList<ApplicationExitInfo> output, int callingUid, int pid,
            int maxNum) {
        CompletableFuture<Object> future = new CompletableFuture<>();

        if (!UserHandle.isApp(callingUid)) {
            return;
        }

        final int userId = UserHandle.getUserId(callingUid);
        final int appId = UserHandle.getAppId(callingUid);

        mHandler.post(() -> {
            boolean appendedTombstones = false;

            synchronized (mLock) {
                final int tombstonesSize = mTombstones.size();

            tombstoneIter:
                for (int i = 0; i < tombstonesSize; ++i) {
                    TombstoneFile tombstone = mTombstones.valueAt(i);
                    if (tombstone.matches(Optional.of(userId), Optional.of(appId))) {
                        if (pid != 0 && tombstone.mPid != pid) {
                            continue;
                        }

                        // Try to attach to an existing REASON_CRASH_NATIVE.
                        final int outputSize = output.size();
                        for (int j = 0; j < outputSize; ++j) {
                            ApplicationExitInfo exitInfo = output.get(j);
                            if (tombstone.matches(exitInfo)) {
                                exitInfo.setNativeTombstoneRetriever(tombstone.getPfdRetriever());
                                continue tombstoneIter;
                            }
                        }

                        if (output.size() < maxNum) {
                            appendedTombstones = true;
                            output.add(tombstone.toAppExitInfo());
                        }
                    }
                }
            }

            if (appendedTombstones) {
                Collections.sort(output, (lhs, rhs) -> {
                    // Reports should be ordered with newest reports first.
                    long diff = rhs.getTimestamp() - lhs.getTimestamp();
                    if (diff < 0) {
                        return -1;
                    } else if (diff == 0) {
                        return 0;
                    } else {
                        return 1;
                    }
                });
            }
            future.complete(null);
        });

        try {
            future.get();
        } catch (ExecutionException | InterruptedException ex) {
            throw new RuntimeException(ex);
        }
    }

    static class TombstoneFile {
        final ParcelFileDescriptor mPfd;

        final @UserIdInt int mUserId;
        final @AppIdInt int mAppId;
        @UserIdInt int mUserId;
        @AppIdInt int mAppId;

        int mPid;
        int mUid;
        String mProcessName;
        @CurrentTimeMillisLong long mTimestampMs;
        String mCrashReason;

        boolean mPurged = false;
        final IParcelFileDescriptorRetriever mRetriever = new ParcelFileDescriptorRetriever();

        TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) {
        TombstoneFile(ParcelFileDescriptor pfd) {
            mPfd = pfd;
            mUserId = userId;
            mAppId = appId;
        }

        public boolean matches(Optional<Integer> userId, Optional<Integer> appId) {
@@ -244,6 +341,26 @@ public final class NativeTombstoneManager {
            return true;
        }

        public boolean matches(ApplicationExitInfo exitInfo) {
            if (exitInfo.getReason() != REASON_CRASH_NATIVE) {
                return false;
            }

            if (exitInfo.getPid() != mPid) {
                return false;
            }

            if (exitInfo.getRealUid() != mUid) {
                return false;
            }

            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) {
                return false;
            }

            return true;
        }

        public void dispose() {
            IoUtils.closeQuietly(mPfd);
        }
@@ -271,16 +388,43 @@ public final class NativeTombstoneManager {
            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
            final ProtoInputStream stream = new ProtoInputStream(is);

            int pid = 0;
            int uid = 0;
            String processName = "";
            String crashReason = "";
            String selinuxLabel = "";

            try {
                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                    switch (stream.getFieldNumber()) {
                        case (int) Tombstone.PID:
                            pid = stream.readInt(Tombstone.PID);
                            break;

                        case (int) Tombstone.UID:
                            uid = stream.readInt(Tombstone.UID);
                            break;

                        case (int) Tombstone.PROCESS_NAME:
                            processName = stream.readString(Tombstone.PROCESS_NAME);
                            break;

                        case (int) Tombstone.CAUSE:
                            long token = stream.start(Tombstone.CAUSE);
                        cause:
                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                                switch (stream.getFieldNumber()) {
                                    case (int) Cause.HUMAN_READABLE:
                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
                                        break cause;

                                    default:
                                        break;
                                }
                            }
                            stream.end(token);


                        case (int) Tombstone.SELINUX_LABEL:
                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
                            break;
@@ -299,6 +443,14 @@ public final class NativeTombstoneManager {
                return Optional.empty();
            }

            long timestampMs = 0;
            try {
                StructStat stat = Os.fstat(pfd.getFileDescriptor());
                timestampMs = stat.st_atim.tv_sec * 1000 + stat.st_atim.tv_nsec / 1000000;
            } catch (ErrnoException ex) {
                Slog.e(TAG, "Failed to get timestamp of tombstone", ex);
            }

            final int userId = UserHandle.getUserId(uid);
            final int appId = UserHandle.getAppId(uid);

@@ -307,7 +459,74 @@ public final class NativeTombstoneManager {
                return Optional.empty();
            }

            return Optional.of(new TombstoneFile(pfd, userId, appId));
            TombstoneFile result = new TombstoneFile(pfd);

            result.mUserId = userId;
            result.mAppId = appId;
            result.mPid = pid;
            result.mUid = uid;
            result.mProcessName = processName;
            result.mTimestampMs = timestampMs;
            result.mCrashReason = crashReason;

            return Optional.of(result);
        }

        public IParcelFileDescriptorRetriever getPfdRetriever() {
            return mRetriever;
        }

        public ApplicationExitInfo toAppExitInfo() {
            ApplicationExitInfo info = new ApplicationExitInfo();
            info.setPid(mPid);
            info.setRealUid(mUid);
            info.setPackageUid(mUid);
            info.setDefiningUid(mUid);
            info.setProcessName(mProcessName);
            info.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);

            // Signal numbers are architecture-specific!
            // We choose to provide nothing here, to avoid leading users astray.
            info.setStatus(0);

            // No way for us to find out.
            info.setImportance(RunningAppProcessInfo.IMPORTANCE_GONE);
            info.setPackageName("");
            info.setProcessStateSummary(null);

            // We could find out, but they didn't get OOM-killed...
            info.setPss(0);
            info.setRss(0);

            info.setTimestamp(mTimestampMs);
            info.setDescription(mCrashReason);

            info.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
            info.setNativeTombstoneRetriever(mRetriever);

            return info;
        }


        class ParcelFileDescriptorRetriever extends IParcelFileDescriptorRetriever.Stub {
            ParcelFileDescriptorRetriever() {}

            public @Nullable ParcelFileDescriptor getPfd() {
                if (mPurged) {
                    return null;
                }

                // Reopen the file descriptor as read-only.
                try {
                    final String path = "/proc/self/fd/" + mPfd.getFd();
                    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(path),
                            MODE_READ_ONLY);
                    return pfd;
                } catch (FileNotFoundException ex) {
                    Slog.e(TAG, "failed to reopen file descriptor as read-only", ex);
                    return null;
                }
            }
        }
    }