diff --git a/Android.bp b/Android.bp index f5c0b6d416b8bef10a046934f5fd49b3824e41bd..67822a376ed333fb9e49a3b734b20c3401c97a13 100644 --- a/Android.bp +++ b/Android.bp @@ -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", diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index e7adf203334e509c615dfbe626d3986cc02eaa21..d03bbd249b0075a7209ead0d6545564793a02354 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -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", ], diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..a7ecb4dfdd44095fe9635896e830fa58ed2f8590 --- /dev/null +++ b/STABILITY_OWNERS @@ -0,0 +1,2 @@ +gaillard@google.com + diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index 926d7a4d3ea607cc020dd2c94dc0b9958394dffc..5cdfca7392e3c9e3938e0a1e223fd79dcc0652a4 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -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,31 +347,33 @@ 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) { - 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); - } finally { - tmpFileLock.unlock(); - } + // 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)) { + tmpFileLock.lock(); + try { + 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 { diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index ab0d0d2626db1b5c8d97dfaed6d36b2af095d578..b7e737448d2d082d3f07d89d151435baba5c3bdf 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -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() { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 8cd55c7dd50670bd5f2fceca84c8a5cae76f80db..591a55972b81fafd9e3c56eb91bc00dbe20d5278 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -37,6 +37,7 @@ cc_library_static { "com_android_server_adb_AdbDebuggingManager.cpp", "com_android_server_am_BatteryStatsService.cpp", "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp", + "com_android_server_BootReceiver.cpp", "com_android_server_ConsumerIrService.cpp", "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", @@ -91,6 +92,16 @@ cc_library_static { header_libs: [ "bionic_libc_platform_headers", ], + + static_libs: [ + "libunwindstack", + ], + + whole_static_libs: [ + "libdebuggerd_tombstone_proto_to_text", + ], + + runtime_libs: ["libdexfile"], } cc_defaults { diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index fd9d4a44b65d91f39979ce1bcc80ba8a3c22af26..e6649f6fd1a6d06184b61d972121fabbc3a2dcb9 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -32,3 +32,4 @@ per-file com_android_server_companion_virtual_InputController.cpp = file:/servic # Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS +per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3892d284dafb5ef3aee05ce0ccdc4459c0565f6b --- /dev/null +++ b/services/core/jni/com_android_server_BootReceiver.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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. + */ + +#include <libdebuggerd/tombstone.h> +#include <nativehelper/JNIHelp.h> + +#include <sstream> + +#include "jni.h" +#include "tombstone.pb.h" + +namespace android { + +static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) { + ss << line << std::endl; +} + +static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject, + jbyteArray tombstoneBytes) { + Tombstone tombstone; + tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0), + env->GetArrayLength(tombstoneBytes)); + + std::stringstream tombstoneString; + + tombstone_proto_to_text(tombstone, + std::bind(&writeToString, std::ref(tombstoneString), + std::placeholders::_1, std::placeholders::_2)); + + return env->NewStringUTF(tombstoneString.str().c_str()); +} + +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"getTombstoneText", "([B)Ljava/lang/String;", + (jstring*)com_android_server_BootReceiver_getTombstoneText}, +}; + +int register_com_android_server_BootReceiver(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods, + NELEM(sMethods)); +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index a87902fe03c56121f365be90501fb899224954f6..e7de081bd5f806e6c117b03e7efd34f911ffc1ff 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -63,6 +63,7 @@ int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); int register_android_server_app_GameManagerService(JNIEnv* env); +int register_com_android_server_BootReceiver(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); @@ -122,6 +123,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_sensor_SensorService(vm, env); register_android_server_companion_virtual_InputController(env); register_android_server_app_GameManagerService(env); + register_com_android_server_BootReceiver(env); register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); register_com_android_server_SystemClockTime(env); diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..523c5c060cf526f95c08cadc9a690433e260d165 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 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 com.android.server; + +import static com.google.common.truth.Truth.assertThat; + +import android.test.AndroidTestCase; + +import com.android.server.os.TombstoneProtos; +import com.android.server.os.TombstoneProtos.Tombstone; + +public class BootReceiverTest extends AndroidTestCase { + private static final String TAG = "BootReceiverTest"; + + public void testRemoveMemoryFromTombstone() { + Tombstone tombstoneBase = Tombstone.newBuilder() + .setBuildFingerprint("build_fingerprint") + .setRevision("revision") + .setPid(123) + .setTid(23) + .setUid(34) + .setSelinuxLabel("selinux_label") + .addCommandLine("cmd1") + .addCommandLine("cmd2") + .addCommandLine("cmd3") + .setProcessUptime(300) + .setAbortMessage("abort") + .addCauses(TombstoneProtos.Cause.newBuilder() + .setHumanReadable("cause1") + .setMemoryError(TombstoneProtos.MemoryError.newBuilder() + .setTool(TombstoneProtos.MemoryError.Tool.SCUDO) + .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE))) + .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs( + TombstoneProtos.LogMessage.newBuilder() + .setTimestamp("123") + .setMessage("message"))) + .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path")) + .build(); + + Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder() + .putThreads(1, TombstoneProtos.Thread.newBuilder() + .setId(1) + .setName("thread1") + .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1)) + .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2)) + .addBacktraceNote("backtracenote1") + .addUnreadableElfFiles("files1") + .setTaggedAddrCtrl(1) + .setPacEnabledKeys(10) + .build()) + .build(); + + Tombstone tombstoneWithMemory = tombstoneBase.toBuilder() + .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder() + .setBeginAddress(1) + .setEndAddress(100) + .setOffset(10) + .setRead(true) + .setWrite(true) + .setExecute(false) + .setMappingName("mapping") + .setBuildId("build") + .setLoadBias(70)) + .putThreads(1, TombstoneProtos.Thread.newBuilder() + .setId(1) + .setName("thread1") + .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1)) + .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2)) + .addBacktraceNote("backtracenote1") + .addUnreadableElfFiles("files1") + .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder() + .setRegisterName("register1") + .setMappingName("mapping") + .setBeginAddress(10)) + .setTaggedAddrCtrl(1) + .setPacEnabledKeys(10) + .build()) + .build(); + + assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory)) + .isEqualTo(tombstoneWithoutMemory); + } +}