Java层tombstone写入dropbox源码分析

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]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容