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

Commit e09f4000 authored by Josh Gao's avatar Josh Gao Committed by Automerger Merge Worker
Browse files

Merge "Attach protobuf tombstones to ApplicationExitInfo." am: 2e1418c4 am:...

Merge "Attach protobuf tombstones to ApplicationExitInfo." am: 2e1418c4 am: 3d9f3f00 am: a5ef566c am: 46e4b638

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1573681

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I7327258a22fcf16412297f9a3288d76a3ff4f8c5
parents bdb7eaa9 46e4b638
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
@@ -353,6 +353,7 @@ import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.firewall.IntentFirewall;
import com.android.server.graphics.fonts.FontManagerInternal;
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;
@@ -8273,6 +8274,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,
@@ -8280,11 +8284,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.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
@@ -762,6 +765,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);
@@ -769,10 +776,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;
                }
            }
        }
    }