美文网首页
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