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

Commit a7e73e3c authored by Siim Sammul's avatar Siim Sammul
Browse files

Change the way tombstones are added to dropbox.

Use the proto tombstone as source of truth and ignore the text
tombstone. Instead create the text tombstone by omitting the memory data
from the proto tombstone to save space on data that would get removed
anyway. This allows us to save bandwidth and avoid truncating useful
data from the tombstones.

Test: atest BootReceiverTest
Bug: 296207744

Change-Id: I809122d3206ce326cbbbceadb45c3b98b9762a3e
parent 3f4e2a78
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -201,6 +201,7 @@ java_library {
        "apex_aidl_interface-java",
        "packagemanager_aidl-java",
        "framework-protos",
        "libtombstone_proto_java",
        "updatable-driver-protos",
        "ota_metadata_proto_java",
        "android.hidl.base-V1.0-java",
+0 −1
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ gensrcs {
        ":ipconnectivity-proto-src",
        ":libstats_atom_enum_protos",
        ":libstats_atom_message_protos",
        ":libtombstone_proto-src",
        "core/proto/**/*.proto",
        "libs/incident/**/*.proto",
    ],

STABILITY_OWNERS

0 → 100644
+2 −0
Original line number Diff line number Diff line
gaillard@google.com
+57 −26
Original line number Diff line number Diff line
@@ -48,6 +48,8 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
import com.android.server.os.TombstoneProtos;
import com.android.server.os.TombstoneProtos.Tombstone;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +62,14 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Performs a number of miscellaneous, non-system-critical actions
@@ -327,12 +332,12 @@ public class BootReceiver extends BroadcastReceiver {
     *
     * @param ctx Context
     * @param tombstone path to the tombstone
     * @param proto whether the tombstone is stored as proto
     * @param tombstoneProto the parsed proto tombstone
     * @param processName the name of the process corresponding to the tombstone
     * @param tmpFileLock the lock for reading/writing tmp files
     */
    public static void addTombstoneToDropBox(
                Context ctx, File tombstone, boolean proto, String processName,
                Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
                ReentrantLock tmpFileLock) {
        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
        if (db == null) {
@@ -342,32 +347,34 @@ public class BootReceiver extends BroadcastReceiver {

        // Check if we should rate limit and abort early if needed.
        DropboxRateLimiter.RateLimitResult rateLimitResult =
                sDropboxRateLimiter.shouldRateLimit(
                        proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
        if (rateLimitResult.shouldRateLimit()) return;

        HashMap<String, Long> timestamps = readTimestamps();
        try {
            if (proto) {
            // Remove the memory data from the proto.
            Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);

            final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();

            // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
            String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
                    .append(rateLimitResult.createHeader())
                    .append(getTombstoneText(tombstoneBytes))
                    .toString();

            // Add the tombstone without memory data to dropbox.
            db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);

            // Add the tombstone proto to dropbox.
            if (recordFileTimestamp(tombstone, timestamps)) {
                    // We need to attach the count indicating the number of dropped dropbox entries
                    // due to rate limiting. Do this by enclosing the proto tombsstone in a
                    // container proto that has the dropped entry count and the proto tombstone as
                    // bytes (to avoid the complexity of reading and writing nested protos).
                tmpFileLock.lock();
                try {
                        addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
                    addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
                } finally {
                    tmpFileLock.unlock();
                }
            }
            } else {
                // Add the header indicating how many events have been dropped due to rate limiting.
                final String headers = getBootHeadersToLogAndUpdate()
                        + rateLimitResult.createHeader();
                addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
                                 TAG_TOMBSTONE);
            }
        } catch (IOException e) {
            Slog.e(TAG, "Can't log tombstone", e);
        }
@@ -375,11 +382,8 @@ public class BootReceiver extends BroadcastReceiver {
    }

    private static void addAugmentedProtoToDropbox(
                File tombstone, DropBoxManager db,
                File tombstone, byte[] tombstoneBytes, DropBoxManager db,
                DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
        // Read the proto tombstone file as bytes.
        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());

        final File tombstoneProtoWithHeaders = File.createTempFile(
                tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
        Files.setPosixFilePermissions(
@@ -412,6 +416,8 @@ public class BootReceiver extends BroadcastReceiver {
        }
    }

    private static native String getTombstoneText(byte[] tombstoneBytes);

    private static void addLastkToDropBox(
            DropBoxManager db, HashMap<String, Long> timestamps,
            String headers, String footers, String filename, int maxSize,
@@ -429,6 +435,31 @@ public class BootReceiver extends BroadcastReceiver {
        addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
    }

    /** Removes memory information from the Tombstone proto. */
    @VisibleForTesting
    public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
        Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
                .clearMemoryMappings()
                .clearThreads()
                .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
                        .stream()
                        .map(BootReceiver::clearMemoryDump)
                        .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));

        if (tombstoneProto.hasSignalInfo()) {
            tombstoneBuilder.setSignalInfo(
                    tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
        }

        return tombstoneBuilder.build();
    }

    private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
            Map.Entry<Integer, TombstoneProtos.Thread> e) {
        return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
            e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
    }

    private static void addFileToDropBox(
            DropBoxManager db, HashMap<String, Long> timestamps,
            String headers, String filename, int maxSize, String tag) throws IOException {
+66 −69
Original line number Diff line number Diff line
@@ -41,14 +41,13 @@ import android.system.Os;
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoParseException;

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 com.android.server.os.protobuf.CodedInputStream;

import libcore.io.IoUtils;

@@ -128,18 +127,21 @@ public final class NativeTombstoneManager {
            return;
        }

        String processName = "UNKNOWN";
        final boolean isProtoFile = filename.endsWith(".pb");
        File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
        if (!isProtoFile) {
            return;
        }

        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
        Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
        if (parsedTombstone.isPresent()) {
            processName = parsedTombstone.get().getProcessName();
            BootReceiver.addTombstoneToDropBox(
                    mContext, path, parsedTombstone.get().getTombstone(),
                    parsedTombstone.get().getProcessName(), mTmpFileLock);
        }
        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
    }

    private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
    private Optional<ParsedTombstone> handleProtoTombstone(
            File path, boolean addToList) {
        final String filename = path.getName();
        if (!filename.endsWith(".pb")) {
            Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -169,7 +171,7 @@ public final class NativeTombstoneManager {
            return Optional.empty();
        }

        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
        final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
        if (!parsedTombstone.isPresent()) {
            IoUtils.closeQuietly(pfd);
            return Optional.empty();
@@ -182,7 +184,7 @@ public final class NativeTombstoneManager {
                    previous.dispose();
                }

                mTombstones.put(number, parsedTombstone.get());
                mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
            }
        }

@@ -330,6 +332,27 @@ public final class NativeTombstoneManager {
        }
    }

    static class ParsedTombstone {
        TombstoneFile mTombstoneFile;
        Tombstone mTombstone;
        ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
            mTombstoneFile = tombstoneFile;
            mTombstone = tombstone;
        }

        public String getProcessName() {
            return mTombstoneFile.getProcessName();
        }

        public TombstoneFile getTombstoneFile() {
            return mTombstoneFile;
        }

        public Tombstone getTombstone() {
            return mTombstone;
        }
    }

    static class TombstoneFile {
        final ParcelFileDescriptor mPfd;

@@ -412,67 +435,21 @@ public final class NativeTombstoneManager {
            }
        }

        static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
            final ProtoInputStream stream = new ProtoInputStream(is);

            int pid = 0;
            int uid = 0;
            String processName = null;
            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.COMMAND_LINE:
                            if (processName == null) {
                                processName = stream.readString(Tombstone.COMMAND_LINE);
                            }
                            break;
        static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
            Tombstone tombstoneProto;
            try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
                final byte[] tombstoneBytes = is.readAllBytes();

                        case (int) Tombstone.CAUSES:
                            if (!crashReason.equals("")) {
                                // Causes appear in decreasing order of likelihood. For now we only
                                // want the most likely crash reason here, so ignore all others.
                                break;
                            }
                            long token = stream.start(Tombstone.CAUSES);
                        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);
                            break;

                        case (int) Tombstone.SELINUX_LABEL:
                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
                            break;

                        default:
                            break;
                    }
                }
            } catch (IOException | ProtoParseException ex) {
                tombstoneProto = Tombstone.parseFrom(
                        CodedInputStream.newInstance(tombstoneBytes));
            } catch (IOException ex) {
                Slog.e(TAG, "Failed to parse tombstone", ex);
                return Optional.empty();
            }

            int pid = tombstoneProto.getPid();
            int uid = tombstoneProto.getUid();

            if (!UserHandle.isApp(uid)) {
                Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
                return Optional.empty();
@@ -489,6 +466,7 @@ public final class NativeTombstoneManager {
            final int userId = UserHandle.getUserId(uid);
            final int appId = UserHandle.getAppId(uid);

            String selinuxLabel = tombstoneProto.getSelinuxLabel();
            if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
                Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
                return Optional.empty();
@@ -500,11 +478,30 @@ public final class NativeTombstoneManager {
            result.mAppId = appId;
            result.mPid = pid;
            result.mUid = uid;
            result.mProcessName = processName == null ? "" : processName;
            result.mProcessName = getCmdLineProcessName(tombstoneProto);
            result.mTimestampMs = timestampMs;
            result.mCrashReason = crashReason;
            result.mCrashReason = getCrashReason(tombstoneProto);

            return Optional.of(result);
            return Optional.of(new ParsedTombstone(result, tombstoneProto));
        }

        private static String getCmdLineProcessName(Tombstone tombstoneProto) {
            for (String cmdline : tombstoneProto.getCommandLineList()) {
                if (cmdline != null) {
                    return cmdline;
                }
            }
            return "";
        }

        private static String getCrashReason(Tombstone tombstoneProto) {
            for (Cause cause : tombstoneProto.getCausesList()) {
                if (cause.getHumanReadable() != null
                        && !cause.getHumanReadable().equals("")) {
                    return cause.getHumanReadable();
                }
            }
            return "";
        }

        public IParcelFileDescriptorRetriever getPfdRetriever() {
Loading