Loading core/java/android/app/ApplicationExitInfo.java +51 −7 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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; } Loading Loading @@ -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; Loading Loading @@ -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 */ Loading Loading @@ -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) { Loading @@ -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 = Loading Loading @@ -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(); } Loading core/java/android/app/IParcelFileDescriptorRetriever.aidl 0 → 100644 +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(); } services/core/java/com/android/server/am/ActivityManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading @@ -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); Loading services/core/java/com/android/server/am/AppExitInfoTracker.java +10 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); } Loading services/core/java/com/android/server/os/NativeTombstoneManager.java +226 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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); } Loading Loading @@ -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; Loading @@ -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); Loading @@ -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; } } } } Loading Loading
core/java/android/app/ApplicationExitInfo.java +51 −7 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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; } Loading Loading @@ -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; Loading Loading @@ -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 */ Loading Loading @@ -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) { Loading @@ -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 = Loading Loading @@ -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(); } Loading
core/java/android/app/IParcelFileDescriptorRetriever.aidl 0 → 100644 +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(); }
services/core/java/com/android/server/am/ActivityManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading @@ -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); Loading
services/core/java/com/android/server/am/AppExitInfoTracker.java +10 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); } Loading
services/core/java/com/android/server/os/NativeTombstoneManager.java +226 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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); } Loading Loading @@ -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; Loading @@ -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); Loading @@ -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; } } } } Loading