diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 009713f0f942be2e5832dd95a282272d7d79427d..90ec5aad235d38cae7069c51b47997f7051951b9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14908,6 +14908,17 @@ public final class Settings { @Readable public static final String DROPBOX_TAG_PREFIX = "dropbox:"; + /** + * Lines of kernel logs to include with system crash/ANR/etc. reports, as a + * prefix of the dropbox tag of the report type. For example, + * "kernel_logs_for_system_server_anr" controls the lines of kernel logs + * captured with system server ANR reports. 0 to disable. + * + * @hide + */ + @Readable + public static final String ERROR_KERNEL_LOG_PREFIX = "kernel_logs_for_"; + /** * Lines of logcat to include with system crash/ANR/etc. reports, as a * prefix of the dropbox tag of the report type. For example, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 92167ee4d423d39303e38d96f908276fc25c5f10..ab9a30b65e08d897d8af49b52818f63ad1ba52df 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -233,6 +233,7 @@ public class SettingsBackupTest { Settings.Global.ENHANCED_4G_MODE_ENABLED, Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES, + Settings.Global.ERROR_KERNEL_LOG_PREFIX, Settings.Global.ERROR_LOGCAT_PREFIX, Settings.Global.EUICC_PROVISIONED, Settings.Global.EUICC_SUPPORTED_COUNTRIES, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 00c2df67c8f0e1ba649b52348652b33562324062..e2452cc17c87776c38c5c23aa231203f4cb42b51 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -598,6 +598,9 @@ public class ActivityManagerService extends IActivityManager.Stub // as one line, but close enough for now. static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100; + // How many seconds should the system wait before terminating the spawned logcat process. + static final int LOGCAT_TIMEOUT_SEC = 10; + // Necessary ApplicationInfo flags to mark an app as persistent static final int PERSISTENT_MASK = ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT; @@ -9939,126 +9942,70 @@ public class ActivityManagerService extends IActivityManager.Stub // If process is null, we are being called from some internal code // and may be about to die -- run this synchronously. final boolean runSynchronously = process == null; - Thread worker = - new Thread("Error dump: " + dropboxTag) { - @Override - public void run() { - if (report != null) { - sb.append(report); - } - - String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; - String maxBytesSetting = - Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; - int lines = - Build.IS_USER - ? 0 - : Settings.Global.getInt( - mContext.getContentResolver(), logcatSetting, 0); - int dropboxMaxSize = - Settings.Global.getInt( - mContext.getContentResolver(), - maxBytesSetting, - DROPBOX_DEFAULT_MAX_SIZE); - - if (dataFile != null) { - // Attach the stack traces file to the report so collectors can load - // them - // by file if they have access. - sb.append(DATA_FILE_PATH_HEADER) - .append(dataFile.getAbsolutePath()) - .append('\n'); - - int maxDataFileSize = - dropboxMaxSize - - sb.length() - - lines * RESERVED_BYTES_PER_LOGCAT_LINE - - DATA_FILE_PATH_FOOTER.length(); - - if (maxDataFileSize > 0) { - // Inline dataFile contents if there is room. - try { - sb.append( - FileUtils.readTextFile( - dataFile, - maxDataFileSize, - "\n\n[[TRUNCATED]]\n")); - } catch (IOException e) { - Slog.e(TAG, "Error reading " + dataFile, e); - } - } - - // Always append the footer, even there wasn't enough space to inline - // the - // dataFile contents. - sb.append(DATA_FILE_PATH_FOOTER); - } - - if (crashInfo != null && crashInfo.stackTrace != null) { - sb.append(crashInfo.stackTrace); - } - - if (lines > 0 && !runSynchronously) { - sb.append("\n"); - - InputStreamReader input = null; - try { - java.lang.Process logcat = - new ProcessBuilder( - // Time out after 10s of inactivity, but - // kill logcat with SEGV - // so we can investigate why it didn't - // finish. - "/system/bin/timeout", - "-i", - "-s", - "SEGV", - "10s", - // Merge several logcat streams, and take - // the last N lines. - "/system/bin/logcat", - "-v", - "threadtime", - "-b", - "events", - "-b", - "system", - "-b", - "main", - "-b", - "crash", - "-t", - String.valueOf(lines)) - .redirectErrorStream(true) - .start(); - - try { - logcat.getOutputStream().close(); - } catch (IOException e) { - } - try { - logcat.getErrorStream().close(); - } catch (IOException e) { - } - input = new InputStreamReader(logcat.getInputStream()); - - int num; - char[] buf = new char[8192]; - while ((num = input.read(buf)) > 0) sb.append(buf, 0, num); - } catch (IOException e) { - Slog.e(TAG, "Error running logcat", e); - } finally { - if (input != null) - try { - input.close(); - } catch (IOException e) { - } - } + Thread worker = new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + if (report != null) { + sb.append(report); + } + + String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; + String kerLogSetting = Settings.Global.ERROR_KERNEL_LOG_PREFIX + dropboxTag; + String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; + int logcatLines = Build.IS_USER + ? 0 + : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0); + int kernelLogLines = Build.IS_USER + ? 0 + : Settings.Global.getInt(mContext.getContentResolver(), kerLogSetting, 0); + int dropboxMaxSize = Settings.Global.getInt( + mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE); + + if (dataFile != null) { + // Attach the stack traces file to the report so collectors can load them + // by file if they have access. + sb.append(DATA_FILE_PATH_HEADER) + .append(dataFile.getAbsolutePath()).append('\n'); + + int maxDataFileSize = dropboxMaxSize + - sb.length() + - logcatLines * RESERVED_BYTES_PER_LOGCAT_LINE + - kernelLogLines * RESERVED_BYTES_PER_LOGCAT_LINE + - DATA_FILE_PATH_FOOTER.length(); + + if (maxDataFileSize > 0) { + // Inline dataFile contents if there is room. + try { + sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize, + "\n\n[[TRUNCATED]]\n")); + } catch (IOException e) { + Slog.e(TAG, "Error reading " + dataFile, e); } + } + // Always append the footer, even there wasn't enough space to inline the + // dataFile contents. + sb.append(DATA_FILE_PATH_FOOTER); + } - dbox.addText(dropboxTag, sb.toString()); + if (crashInfo != null && crashInfo.stackTrace != null) { + sb.append(crashInfo.stackTrace); + } + boolean shouldAddLogs = logcatLines > 0 || kernelLogLines > 0; + if (!runSynchronously && shouldAddLogs) { + sb.append("\n"); + if (logcatLines > 0) { + fetchLogcatBuffers(sb, logcatLines, LOGCAT_TIMEOUT_SEC, + List.of("events", "system", "main", "crash")); } - }; + if (kernelLogLines > 0) { + fetchLogcatBuffers(sb, kernelLogLines, LOGCAT_TIMEOUT_SEC / 2, + List.of("kernel")); + } + } + + dbox.addText(dropboxTag, sb.toString()); + } + }; if (runSynchronously) { final int oldMask = StrictMode.allowThreadDiskWritesMask(); @@ -10320,6 +10267,67 @@ public class ActivityManagerService extends IActivityManager.Stub Binder.getCallingPid(), state); } + /** + * Retrieves logs from specified logcat buffers and appends them to a StringBuilder + * in the supplied order. The method executes a logcat command to fetch specific + * log entries from the supplied buffers. + * + * @param sb the StringBuilder to append the logcat output to. + * @param lines the number of lines to retrieve. + * @param timeout the maximum allowed time in seconds for logcat to run before being terminated. + * @param buffers the list of log buffers from which to retrieve logs. + */ + private static void fetchLogcatBuffers(StringBuilder sb, int lines, + int timeout, List<String> buffers) { + + if (buffers.size() == 0 || lines <= 0 || timeout <= 0) { + return; + } + + List<String> command = new ArrayList<>(10 + (2 * buffers.size())); + // Time out after 10s of inactivity, but kill logcat with SEGV + // so we can investigate why it didn't finish. + command.add("/system/bin/timeout"); + command.add("-i"); + command.add("-s"); + command.add("SEGV"); + command.add(timeout + "s"); + + // Merge several logcat streams, and take the last N lines. + command.add("/system/bin/logcat"); + command.add("-v"); + // This adds a timestamp and thread info to each log line. + command.add("threadtime"); + for (String buffer : buffers) { + command.add("-b"); + command.add(buffer); + } + // Limit the output to the last N lines. + command.add("-t"); + command.add(String.valueOf(lines)); + + try { + java.lang.Process proc = + new ProcessBuilder(command).redirectErrorStream(true).start(); + + // Close the output stream immediately as we do not send input to the process. + try { + proc.getOutputStream().close(); + } catch (IOException e) { + } + + try (InputStreamReader reader = new InputStreamReader(proc.getInputStream())) { + char[] buffer = new char[8192]; + int numRead; + while ((numRead = reader.read(buffer, 0, buffer.length)) > 0) { + sb.append(buffer, 0, numRead); + } + } + } catch (IOException e) { + Slog.e(TAG, "Error running logcat", e); + } + } + /** * Check if the calling process has the permission to dump given package, * throw SecurityException if it doesn't have the permission.