Attach protobuf tombstones to ApplicationExitInfo.

Bug: http://b/159164105
Test: atest CtsAppExitTestCases
Change-Id: I377247e8fc85f917676cb57eaa3128b955634fa5
This commit is contained in:
Josh Gao
2021-02-02 18:29:12 -08:00
parent 5814cc70a2
commit c36f532d91
5 changed files with 324 additions and 14 deletions

View File

@@ -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 {
final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
mPackageName, mPackageUid, mPid);
if (fd == null) {
return null;
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));
}
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();
}

View File

@@ -0,0 +1,31 @@
/*
* 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();
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
}
}
}
}