美文网首页
Java层tombstone写入dropbox源码分析

Java层tombstone写入dropbox源码分析

作者: 付凯强 | 来源:发表于2024-02-20 15:10 被阅读0次

NativeTombstoneManager

NativeTombstoneManager负责进行Java层tombstone写入dropbox。
NativeTombstoneManager在NativeTombstoneManagerService执行onStart方法的时候初始化,执行onBootPhase方法的时候执行onSystemReady方法。

base/services/java/com/android/server/SystemServer.java startCoreServices方法

// Tracks native tombstones.
t.traceBegin("StartNativeTombstoneManagerService");
mSystemServiceManager.startService(NativeTombstoneManagerService.class);
t.traceEnd();
    @Override
    public void onStart() {
        mManager = new NativeTombstoneManager(getContext());
        LocalServices.addService(NativeTombstoneManager.class, mManager);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
            mManager.onSystemReady();
        }
    }

在初始化的时候做了三件事情:

  1. 创建类型为SparseArray<TombstoneFile>的mTombstones数据结构
  2. 创建名为NativeTombstoneManager:tombstoneWatcher的线程,线程Handler命名为mHandler.
  3. 创建TombstoneWatcher,对/data/tombstones目录进行监听。当有tombstone发生时,会在该目录下生成tombstone文件和tombstone.pb文件,也就是说一次tombstone生成两个文件。当TombstoneWatcher监听到tombstone文件生成的时候会通过mHandler对每个文件执行handleTombstone方法。
    NativeTombstoneManager(Context context) {
        mTombstones = new SparseArray<TombstoneFile>();
        mContext = context;

        final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher",
                THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
        thread.start();
        mHandler = thread.getThreadHandler();

        mWatcher = new TombstoneWatcher();
        mWatcher.startWatching();
    }
    class TombstoneWatcher extends FileObserver {
        TombstoneWatcher() {
            // Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE),
            // or by moving a named temporary file in the same directory on kernels where O_TMPFILE
            // isn't supported (MOVED_TO).
            super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO);
        }

        @Override
        public void onEvent(int event, @Nullable String path) {
            mHandler.post(() -> {
                handleTombstone(new File(TOMBSTONE_DIR, path));
            });
        }
    }

在onSystemReady方法执行的时候,会通过mHandler异步遍历/data/tombstones下的文件,并对每个文件执行handleTombstone方法。

handleTombstone

    private void handleTombstone(File path) {
        //校验文件名称,如果不以tombstone_开头 ,就返回。
        final String filename = path.getName();
        if (!filename.startsWith("tombstone_")) {
            return;
        }
    //processName默认为UNKNOWN
        String processName = "UNKNOWN";
        //如果文件以.pb结尾,就是protoFile
        final boolean isProtoFile = filename.endsWith(".pb");
        //这里会获取protoFile的路径
        File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
    //执行handleProtoTombstone方法处理proto文件,方法参数为文件路径(File类型)和是否是proto文件(bool类型),方法返回TombstoneFile
        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
        //文件存在就获取processName
        if (parsedTombstone.isPresent()) {
            processName = parsedTombstone.get().getProcessName();
        }
        //把tombstone写入dropbox
        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName);
    }

主要做了两件事情:

  1. 利用handleProtoTombstone处理proto文件,把文件的信息存储到TombstoneFile,利用该数据结构获取processName
  2. 利用addTombstoneToDropBox把tombstone proto文件和普通tombstone文件写入dropbox

handleProtoTombstone

    //有两个参数,第一个参数为文件(File类型),第二个参数为是否添加该tombstone到mTombstones数据结构中
    private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
        final String filename = path.getName();
        if (!filename.endsWith(".pb")) {
            Slog.w(TAG, "unexpected tombstone name: " + path);
            return Optional.empty();
        }

        final String suffix = filename.substring("tombstone_".length());
        final String numberStr = suffix.substring(0, suffix.length() - 3);

        int number;
        try {
            number = Integer.parseInt(numberStr);
            if (number < 0 || number > 99) {
                Slog.w(TAG, "unexpected tombstone name: " + path);
                return Optional.empty();
            }
        } catch (NumberFormatException ex) {
            Slog.w(TAG, "unexpected tombstone name: " + path);
            return Optional.empty();
        }

        ParcelFileDescriptor pfd;
        try {
            //使用ParcelFileDescriptor.open打开该pb文件
            pfd = ParcelFileDescriptor.open(path, MODE_READ_WRITE);
        } catch (FileNotFoundException ex) {
            Slog.w(TAG, "failed to open " + path, ex);
            return Optional.empty();
        }
    //使用TombstoneFile.parse解析该proto文件的内容到TombstoneFile数据结构中
        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
        if (!parsedTombstone.isPresent()) {
            IoUtils.closeQuietly(pfd);
            return Optional.empty();
        }
        //如果需要添加该tombstone.pb到mTombstones中,那么就以tombstone发生的次序号为key,TombstoneFile信息为value存储到mTombstones中
        if (addToList) {
            synchronized (mLock) {
                TombstoneFile previous = mTombstones.get(number);
                if (previous != null) {
                    previous.dispose();
                }

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

        return parsedTombstone;
    }

主要做了两件事情:

  1. 打开tombstone proto文件,然后解析该文件,把内容解析到TombstoneFile数据结构中
  2. 添加该TombstoneFile信息到mTombstones中

addTombstoneToDropBox

    //负责把tombstone文件(tombstone文件和tombstone.pb文件)写入dropbox
    public static void addTombstoneToDropBox(
                Context ctx, File tombstone, boolean proto, String processName) {
        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
        if (db == null) {
            Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
            return;
        }
        // 对tombstone文件的写入进行速度限制,有专门的文档讲解,这么不再多说
        // 要强调的是:一次tombstone的发生,会写入两次dropbox,分别是tombstone文件和tombstone.pb文件的写入
        // 所以在速度限制上:tombstone的约束为10分钟只能写入6次,即10分钟内第四个tombstone发生的时候,就无法再写入tombstone
        // 详细的规则参考 DropboxRateLimiter源码分析文档
        // Check if we should rate limit and abort early if needed. Do this for both proto and
        // non-proto tombstones, even though proto tombstones do not support including the counter
        // of events dropped since rate limiting activated yet.
        DropboxRateLimiter.RateLimitResult rateLimitResult =
                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE, processName);

        if (rateLimitResult.shouldRateLimit()) return;
       
        //从data/system/log-files.xml中读取已记录的错误信息
        HashMap<String, Long> timestamps = readTimestamps();
        try {
            //如果是tombstone.pb文件
            if (proto) {
                //如果之前没有记录过(recordFileTimestamp返回true),即之前没有把该错误信息写到dropbox,那么就调用dropboxmanager进行写入
                if (recordFileTimestamp(tombstone, timestamps)) {
                    db.addFile(TAG_TOMBSTONE_PROTO, tombstone, 0);
                }
            } else {
                //如果是普通tombstone文件,需要添加头信息 + 其他信息到dropbox文件,即需要特殊处理,不能直接写入
                // Add the header indicating how many events have been dropped due to rate limiting.
                //添加头信息
                final String headers = getBootHeadersToLogAndUpdate()
                        + rateLimitResult.createHeader();
                //调用addFileToDropBox,将头信息+其他信息写到dropbox文件
                addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
                                 TAG_TOMBSTONE);
            }
        } catch (IOException e) {
            Slog.e(TAG, "Can't log tombstone", e);
        }
        //如果之前没有写入过dropbox,即没有记录过,即data/system/log-files.xml中没有该错误的信息,那么就会将错误的信息进行记录
       //写属于覆盖写,而不是追加写,保证文件不会有重复内容。
        writeTimestamps(timestamps);
    }
    private static final String LOG_FILES_FILE = "log-files.xml";
    private static final AtomicFile sFile = new AtomicFile(new File(
            Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files");

readTimestamps

    private static HashMap<String, Long> readTimestamps() {
        synchronized (sFile) {
            HashMap<String, Long> timestamps = new HashMap<String, Long>();
            boolean success = false;
            try (final FileInputStream stream = sFile.openRead()) {
                TypedXmlPullParser parser = Xml.resolvePullParser(stream);

                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG
                        && type != XmlPullParser.END_DOCUMENT) {
                    ;
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new IllegalStateException("no start tag found");
                }

                int outerDepth = parser.getDepth();  // Skip the outer <log-files> tag.
                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                        continue;
                    }

                    String tagName = parser.getName();
                    if (tagName.equals("log")) {
                        //从sFile中查询所有的filename + timestamp,存储到timestamps中并返回
                        final String filename = parser.getAttributeValue(null, "filename");
                        final long timestamp = parser.getAttributeLong(null, "timestamp");
                        timestamps.put(filename, timestamp);
                    } else {
                        Slog.w(TAG, "Unknown tag: " + parser.getName());
                        XmlUtils.skipCurrentTag(parser);
                    }
                }
                success = true;
            } catch (FileNotFoundException e) {
                Slog.i(TAG, "No existing last log timestamp file " + sFile.getBaseFile() +
                        "; starting empty");
            } catch (IOException e) {
                Slog.w(TAG, "Failed parsing " + e);
            } catch (IllegalStateException e) {
                Slog.w(TAG, "Failed parsing " + e);
            } catch (NullPointerException e) {
                Slog.w(TAG, "Failed parsing " + e);
            } catch (XmlPullParserException e) {
                Slog.w(TAG, "Failed parsing " + e);
            } finally {
                if (!success) {
                    timestamps.clear();
                }
            }
            return timestamps;
        }
    }

方法负责从sFile中查询所有的filename + timestamp,存储到timestamps中并返回

recordFileTimestamp

    private static boolean recordFileTimestamp(File file, HashMap<String, Long> timestamps) {
        final long fileTime = file.lastModified();
        if (fileTime <= 0) return false;  // File does not exist

        final String filename = file.getPath();
        if (timestamps.containsKey(filename) && timestamps.get(filename) == fileTime) {
            return false;  // Already logged this particular file
        }

        timestamps.put(filename, fileTime);
        return true;
    }

如果readTimestamps中已经记录过此错误的信息(即已写过dropbox),那么就返回false,否则添加此错误的信息到timestamps。

writeTimestamps

    private static void writeTimestamps(HashMap<String, Long> timestamps) {
        synchronized (sFile) {
            final FileOutputStream stream;
            try {
                stream = sFile.startWrite();
            } catch (IOException e) {
                Slog.w(TAG, "Failed to write timestamp file: " + e);
                return;
            }

            try {
                TypedXmlSerializer out = Xml.resolveSerializer(stream);
                out.startDocument(null, true);
                out.startTag(null, "log-files");

                Iterator<String> itor = timestamps.keySet().iterator();
                while (itor.hasNext()) {
                    // 负责把timestamps的数据写回文件
                    String filename = itor.next();
                    out.startTag(null, "log");
                    out.attribute(null, "filename", filename);
                    out.attributeLong(null, "timestamp", timestamps.get(filename));
                    out.endTag(null, "log");
                }

                out.endTag(null, "log-files");
                out.endDocument();

                sFile.finishWrite(stream);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to write timestamp file, using the backup: " + e);
                sFile.failWrite(stream);
            }
        }
    }

负责把timestamps的数据写回文件。
所以readTimestamps + recordFileTimestamp + writeTimestamps 保证了相同tombstone文件不会重复写dropbox。
注意:以下内容决定此次写属于覆盖写,而不是追加写,保证文件中不会有重复记录。

stream = sFile.startWrite();
// ...
out.startDocument(null, true);

getBootHeadersToLogAndUpdate

    private static String getBootHeadersToLogAndUpdate() throws IOException {
        final String oldHeaders = getPreviousBootHeaders();
        final String newHeaders = getCurrentBootHeaders();

        try {
            FileUtils.stringToFile(lastHeaderFile, newHeaders);
        } catch (IOException e) {
            Slog.e(TAG, "Error writing " + lastHeaderFile, e);
        }

        if (oldHeaders == null) {
            // If we failed to read the old headers, use the current headers
            // but note this in the headers so we know
            return "isPrevious: false\n" + newHeaders;
        }

        return "isPrevious: true\n" + oldHeaders;
    }
    private static String getPreviousBootHeaders() {
        try {
            return FileUtils.readTextFile(lastHeaderFile, 0, null);
        } catch (IOException e) {
            return null;
        }
    }
    private static String getCurrentBootHeaders() throws IOException {
        return new StringBuilder(512)
            .append("Build: ").append(Build.FINGERPRINT).append("\n")
            .append("Hardware: ").append(Build.BOARD).append("\n")
            .append("Revision: ")
            .append(SystemProperties.get("ro.revision", "")).append("\n")
            .append("Bootloader: ").append(Build.BOOTLOADER).append("\n")
            .append("Radio: ").append(Build.getRadioVersion()).append("\n")
            .append("Kernel: ")
            .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
            .append("\n").toString();
    }
    private static final String LAST_HEADER_FILE = "last-header.txt";
    private static final File lastHeaderFile = new File(
            Environment.getDataSystemDirectory(), LAST_HEADER_FILE);

从data/system/last-header.txt中读取boot header信息,我们称之为旧的boot header信息。

  1. 用新的boot header信息替换data/system/last-header.txt中的boot header信息
  2. 如果旧的不为空,返回"isPrevious: true\n" + oldHeaders,如果为空,返回"isPrevious: false\n" + newHeaders

rateLimitResult.createHeader

指的是被速度限制规则丢弃的错误次数。

addFileToDropBox

    private static final int LOG_SIZE =
        SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536;
    private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
    addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
                 TAG_TOMBSTONE);

addFileToDropBox方法又调用了addFileWithFootersToDropBox,addFileWithFootersToDropBox增加了footers参数,此时footers参数为空字符串。

private static void addFileToDropBox(
            DropBoxManager db, HashMap<String, Long> timestamps,
            String headers, String filename, int maxSize, String tag) throws IOException {
        addFileWithFootersToDropBox(db, timestamps, headers, "", filename, maxSize, tag);
}

addFileWithFootersToDropBox

    private static void addFileWithFootersToDropBox(
            DropBoxManager db, HashMap<String, Long> timestamps,
            String headers, String footers, String filename, int maxSize,
            String tag) throws IOException {
        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled

        File file = new File(filename);
        //已经记录过,即已经写过dropbox,就返回
        if (!recordFileTimestamp(file, timestamps)) {
            return;
        }
    //对tombstone文件的内容进行截取,只保留maxSize的大小。
        String fileContents = FileUtils.readTextFile(file, maxSize, TAG_TRUNCATED);
        String text = headers + fileContents + footers;
        //创建一个额外的dropbox文件,dropbox文件名为system_server_native_crash
        // Create an additional report for system server native crashes, with a special tag.
        if (tag.equals(TAG_TOMBSTONE) && fileContents.contains(">>> system_server <<<")) {
            addTextToDropBox(db, "system_server_native_crash", text, filename, maxSize);
        }
        if (tag.equals(TAG_TOMBSTONE)) {
            FrameworkStatsLog.write(FrameworkStatsLog.TOMB_STONE_OCCURRED);
        }
        //将tombstone写入dropbox
        addTextToDropBox(db, tag, text, filename, maxSize);
    }

主要做了以下4个事情:

  1. 不再写入已经写入过dropbox的错误。
  2. 对tombstone的内容进行截取,并把header与tombstone内容进行合并
  3. 使用addTextToDropBox生成额外的dropbox文件,名为system_server_native_crash
  4. 使用addTextToDropBox将tombstone写入dropbox

addTextToDropBox

    private static void addTextToDropBox(DropBoxManager db, String tag, String text,
            String filename, int maxSize) {
        Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
        db.addText(tag, text);
        EventLog.writeEvent(DropboxLogTags.DROPBOX_FILE_COPY, filename, maxSize, tag);
    }

主要做了两件事情:

  1. addTextToDropBox方法使用DropBoxManager的addText进行dropbox文件的生成。
  2. 写一个event log,此event log定义于 base/core/java/com/android/server/DropboxLogTags.logtags
option java_package com.android.server;

# -----------------------------
# BootReceiver.java
# -----------------------------
81002 dropbox_file_copy (FileName|3),(Size|1),(Tag|3)

TAG 为dropbox_file_copy,日志内容有3个,分别是FileName、Size、Tag。
可以在out下查看生成的DropboxLogTags.java,可参考:
./soong/.intermediates/frameworks/base/framework-minus-apex/android_common/gen/logtags/frameworks/base/core/java/com/android/server/DropboxLogTags.java

package com.android.server;;

/**
 * @hide
 */
public class DropboxLogTags {
  private DropboxLogTags() { }  // don't instantiate

  /** 81002 dropbox_file_copy (FileName|3),(Size|1),(Tag|3) */
  public static final int DROPBOX_FILE_COPY = 81002;

  public static void writeDropboxFileCopy(String filename, int size, String tag) {
    android.util.EventLog.writeEvent(DROPBOX_FILE_COPY, filename, size, tag);
  }
}

示例如下:

02-21 14:40:37.326  2395  2832 I dropbox_file_copy: [/data/tombstones/tombstone_08,98304,SYSTEM_TOMBSTONE]

相关文章

网友评论

      本文标题:Java层tombstone写入dropbox源码分析

      本文链接:https://www.haomeiwen.com/subject/fvmbadtx.html