关于文件的监控,主要有那么几个维度:主线程读写文件,读写文件时长,读写文件时buffer大小,文件使用后未关闭。主线程读写文件可能会堵塞主线程造成卡顿甚至 ANR;读写文件时buffer过小会造成应用层代码重复运行,底层有可能有大量写入问题(也有可能是累积到一页大小写入);文件使用后未关闭会造成资源的浪费,严重时会出现 OOM 的问题。
监听文件使用后未关闭
- 原理
File file = new File(getApplication().getFilesDir(), "zz.txt");
FileInputStream fileInputStream = new FileInputStream(file);
int read = fileInputStream.read();
fileInputStream.close();
通常使用文件如上,打开文件流读取文件后将流关闭。如果文件流不关闭的话,在未来的GC回收时(有可能)或者进程快结束时,会调用文件流的 finalize 方法。
//FileInputStream.java
protected void finalize() throws IOException {
// Android-added: CloseGuard support.
if (guard != null) {
guard.warnIfOpen();
}
if ((fd != null) && (fd != FileDescriptor.in)) {
// Android-removed: Obsoleted comment about shared FileDescriptor handling.
close();
}
}
如果文件流写代码时忘记关闭的话,从finalize方法的调用中也会将其关闭,而且还会调用 guard.warnIfOpen();方法,来看看该方法
/*
* @hide
*/
public final class CloseGuard {
private static volatile boolean ENABLED = true;
private static volatile Reporter REPORTER = new DefaultReporter();
public static void setEnabled(boolean enabled) {
ENABLED = enabled;
}
public static boolean isEnabled() {
return ENABLED;
}
public static void setReporter(Reporter reporter) {
if (reporter == null) {
throw new NullPointerException("reporter == null");
}
REPORTER = reporter;
}
public static Reporter getReporter() {
return REPORTER;
}
public void close() {
currentTracker.close(allocationSite);
allocationSite = null;
}
//allocationSite 在 close 方法调用后为 null。
public void warnIfOpen() {
if (allocationSite == null || !ENABLED) {
return;
}
String message =
("A resource was acquired at attached stack trace but never released. "
+ "See java.io.Closeable for information on avoiding resource leaks.");
REPORTER.report(message, allocationSite);
}
public interface Reporter {
void report (String message, Throwable allocationSite);
}
private static final class DefaultReporter implements Reporter {
@Override public void report (String message, Throwable allocationSite) {
System.logW(message, allocationSite);
}
}
}
可以看到如果文件未关闭的话,会调用接口类的一个report的方法,可以想到利用反射将REPORTER替换,就可以在文件流使用后自己未关闭而被自动关闭时被调用report方法来进行检测。
代码如下:
private void startFileNotCloseProxy() throws Exception {
Class<?> closeGuardClass = Class.forName("dalvik.system.CloseGuard");
Method getReporter = closeGuardClass.getDeclaredMethod("getReporter", null);
getReporter.setAccessible(true);
originalReporter = getReporter.invoke(null);
Method setEnableMethod = closeGuardClass.getDeclaredMethod("setEnabled", boolean.class);
setEnableMethod.setAccessible(true);
orginalEnabled = setEnableMethod.invoke(null);
setEnableMethod.invoke(null, true);
Class<?> reportInterfaceClass = Class.forName("dalvik.system.CloseGuard$Reporter");
Object newReporter = Proxy.newProxyInstance(closeGuardClass.getClassLoader(), new Class[]{reportInterfaceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("report".equals(method.getName())) {
report();
Log.e("TAG", "文件打开没有关闭");
}
return method.invoke(originalReporter, args);
}
});
Method setReporter = closeGuardClass.getDeclaredMethod("setReporter", reportInterfaceClass);
setReporter.setAccessible(true);
setReporter.invoke(null, newReporter);
}
- 注意点
在 android10 之后dalvik.system.CloseGuard不但被标记为 hide,还被注解为@SystemApi。在反射获取方法时会报错,需注意版本兼容。
主线程读写文件,读写文件时长,读写文件时buffer大小
这里主要是运用了 xhook 的能力来监控系统打开、读写、关闭文件的操作。在 matrix 代码中监控的维度有文件的路径,java堆栈,线程名,读写的次数,大小和,时间和,单次读写的最大耗时,最小buffer等。并根据相应的检测逻辑判断,如主线程检测需判断单次读写的最大耗时是否大于阈值,二次操作的间隙等。
- hook 的代码在 : io_canary_jni.cc
主要处理在文件打开时记录其文件的路径,java堆栈,单次读写的时间,关闭
- 处理的代码在 : io_canary.cc io_info_collector.cc
主要处理在聚合统计文件读写的维度,读写的次数,大小和,时间和,单次读写的最大耗时,最小buffer等
- 检测的代码在 : 父类:detector.h 子类:reppeat_read_detector.cc main_thread_detector.cc small_buffer_detector.cc
子类各种处理自己关心的事情
- 主线程检测需判断单次读写的最大耗时是否大于阈值,二次操作的间隙。
- 小buffer检测文件操作的次數大于阈值 20,平均每次操作buffer的大小小于4096 最長的一次操作時間大于 13*1000 μs。
matrix 到此分析结束。
给出参考 matrix 的代码实现:
//jni
#include <jni.h>
#include <string>
#include <android/log.h>
#include "xhook.h"
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include "IOCanary.h"
namespace iocanary {
static int (*original_open)(const char *pathname, int flags, mode_t mode);
static int (*original_open64)(const char *pathname, int flags, mode_t mode);
static ssize_t (*original_read)(int fd, void *buf, size_t size);
static ssize_t (*original_read_chk)(int fd, void *buf, size_t count, size_t buf_size);
static ssize_t (*original_write)(int fd, const void *buf, size_t size);
static ssize_t (*original_write_chk)(int fd, const void *buf, size_t count, size_t buf_size);
static int (*original_close)(int fd);
static int (*original_android_fdsan_close_with_tag)(int fd, uint64_t ownerId);
int64_t getTickCount() {
timeval tv;
gettimeofday(&tv, 0);
return (int64_t) tv.tv_sec * 1000000 + (int64_t) tv.tv_usec;
}
bool isMain() {
return getpid() == gettid();
}
int proxy_open(const char *path, int flags, mode_t mode) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件打开:%s", path);
int fd = original_open(path, flags, mode);
iocanary::IOCanary::Get().OnOpen(fd, path, "java_stack");
return fd;
}
int proxy_open64(const char *path, int flags, mode_t mode) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件打开64:%s", path);
int fd = original_open64(path, flags, mode);
iocanary::IOCanary::Get().OnOpen(fd, path, "");
return fd;
}
ssize_t proxy_read(int fd, void *buf, size_t size) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件读:%d %d", fd, size);
int64_t start = getTickCount();
int readSize = original_read(fd, buf, size);
ino64_t cost = getTickCount() - start;
iocanary::IOCanary::Get().OnRead(fd, size, cost);
return readSize;
}
ssize_t proxy_read_chk(int fd, void *buf, size_t count, size_t buf_size) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件读chk:%d %d", fd, buf_size);
int64_t start = getTickCount();
int readSize = original_read_chk(fd, buf, count, buf_size);
ino64_t cost = getTickCount() - start;
iocanary::IOCanary::Get().OnRead(fd, buf_size, cost);
return readSize;
}
ssize_t proxy_write(int fd, const void *buf, size_t size) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件写%d %d", fd, size);
int64_t start = getTickCount();
int writeSize = original_write(fd, buf, size);
ino64_t cost = getTickCount() - start;
iocanary::IOCanary::Get().OnWrite(fd, size, cost);
return writeSize;
}
ssize_t proxy_write_chk(int fd, const void *buf, size_t count, size_t buf_size) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件写:%d %d", fd, buf_size);
int64_t start = getTickCount();
int writeSize = original_write_chk(fd, buf, count, buf_size);
ino64_t cost = getTickCount() - start;
iocanary::IOCanary::Get().OnWrite(fd, buf_size, cost);
return writeSize;
}
int proxy_close(int fd) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件关闭:%d", fd);
int result = original_close(fd);
iocanary::IOCanary::Get().OnClose(fd);
return result;
}
int proxy_android_fdsan_close_with_tag(int fd, uint64_t ownerId) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "监听到文件android_fdsan_close_with_tag:%d", fd);
int result = original_android_fdsan_close_with_tag(fd, ownerId);
iocanary::IOCanary::Get().OnClose(fd);
return result;
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_dabaicai_miniter_1hook_NativeBridge_fileIOHook(JNIEnv *env, jclass clazz, jboolean is_moniter_main) {
iocanary::IOCanary::Get().setMoniterMain(is_moniter_main);
//libopenjdkjvm.so
xhook_register("libopenjdkjvm.so", "open", (void *) iocanary::proxy_open, (void **) &iocanary::original_open);
xhook_register("libopenjdkjvm.so", "open64", (void *) iocanary::proxy_open64, (void **) &iocanary::original_open64);
xhook_register("libopenjdkjvm.so", "close", (void *) iocanary::proxy_close, (void **) &iocanary::original_close);
xhook_register("libopenjdkjvm.so", "android_fdsan_close_with_tag", (void *) iocanary::proxy_android_fdsan_close_with_tag,
(void **) &iocanary::original_android_fdsan_close_with_tag);
//libjavacore.so
xhook_register("libjavacore.so", "open", (void *) iocanary::proxy_open, (void **) &iocanary::original_open);
xhook_register("libjavacore.so", "open64", (void *) iocanary::proxy_open64, (void **) &iocanary::original_open64);
xhook_register("libjavacore.so", "close", (void *) iocanary::proxy_close, (void **) &iocanary::original_close);
xhook_register("libjavacore.so", "android_fdsan_close_with_tag", (void *) iocanary::proxy_android_fdsan_close_with_tag,
(void **) &iocanary::original_android_fdsan_close_with_tag);
if (xhook_register("libjavacore.so", "read", (void *) iocanary::proxy_read, (void **) &iocanary::original_read) != 0) {
if (xhook_register("libjavacore.so", "__read_chk", (void *) iocanary::proxy_read_chk, (void **) &iocanary::original_read_chk) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "libjavacore hook read file");
}
}
if (xhook_register("libjavacore.so", "write", (void *) iocanary::proxy_write, (void **) &iocanary::original_write) != 0) {
if (xhook_register("libjavacore.so", "__write_chk", (void *) iocanary::proxy_write_chk, (void **) &iocanary::original_write_chk) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "TAG", "libjavacore hook read file");
}
}
//libopenjdk.so
xhook_register("libopenjdk.so", "open", (void *) iocanary::proxy_open, (void **) &iocanary::original_open);
xhook_register("libopenjdk.so", "open64", (void *) iocanary::proxy_open64, (void **) &iocanary::original_open64);
xhook_register("libopenjdk.so", "close", (void *) iocanary::proxy_close, (void **) &iocanary::original_close);
xhook_register("libopenjdk.so", "android_fdsan_close_with_tag", (void *) iocanary::proxy_android_fdsan_close_with_tag,
(void **) &iocanary::original_android_fdsan_close_with_tag);
xhook_refresh(1);
__android_log_print(ANDROID_LOG_ERROR, "TAG", "fileIOHook");
}
//header
#ifndef MONITER_IOCANARY_H
#define MONITER_IOCANARY_H
#include <string>
#include <map>
#include <jni.h>
#include <android/log.h>
namespace iocanary {
class FileInfo {
public:
FileInfo(std::string path, std::string stack) : file_path(path), java_stack(stack) {}
long cost_time = 0;
long average_buffer_size = 0;
long min_buffer_size = 0;
long max_buffer_size = 0;
int action_count = 0;
std::string file_path;
std::string java_stack;
};
class IOCanary {
public:
static IOCanary &Get();
void setMoniterMain(bool isMoniterMain);
void OnOpen(int fd, std::string file_path, std::string java_stack);
void OnRead(int fd, int count, long cost_time);
void OnWrite(int fd, int count, long cost_time);
void OnClose(int fd);
private:
IOCanary();
std::map<int, FileInfo *> file_map;
bool isMoniterMain;
};
}
#endif //MONITER_IOCANARY_H
//cpp
#include "IOCanary.h"
namespace iocanary {
IOCanary::IOCanary() {
}
IOCanary &iocanary::IOCanary::Get() {
static IOCanary kInstance;
return kInstance;
}
void iocanary::IOCanary::setMoniterMain(bool isMoniterMain) {
this->isMoniterMain = isMoniterMain;
}
void IOCanary::OnOpen(int fd, std::string file_path, std::string java_stack) {
FileInfo *fileInfo = new FileInfo(file_path, java_stack);
file_map.insert(std::make_pair(fd, fileInfo));
}
void IOCanary::OnRead(int fd, int count, long cost_time) {
if (file_map.find(fd) == file_map.end()) {
//没有记录
return;
}
FileInfo *fileInfo = file_map.at(fd);
fileInfo->cost_time += cost_time;
fileInfo->action_count++;
fileInfo->average_buffer_size += count;
if (fileInfo->min_buffer_size > count) {
fileInfo->min_buffer_size = count;
}
if (fileInfo->max_buffer_size < count) {
fileInfo->max_buffer_size = count;
}
}
void IOCanary::OnWrite(int fd, int count, long cost_time) {
if (file_map.find(fd) == file_map.end()) {
//没有记录
return;
}
FileInfo *fileInfo = file_map.at(fd);
fileInfo->cost_time += cost_time;
fileInfo->action_count++;
fileInfo->average_buffer_size += count;
if (fileInfo->min_buffer_size > count) {
fileInfo->min_buffer_size = count;
}
if (fileInfo->max_buffer_size < count) {
fileInfo->max_buffer_size = count;
}
}
void IOCanary::OnClose(int fd) {
if (file_map.find(fd) == file_map.end()) {
//没有记录
return;
}
FileInfo *fileInfo = file_map.at(fd);
fileInfo->average_buffer_size = fileInfo->average_buffer_size / fileInfo->action_count;
if (isMoniterMain && fileInfo->cost_time > 100) {
//主线程读写超时
__android_log_print(ANDROID_LOG_ERROR, "TAG", "主线程操作文件太耗时:%d", fileInfo->cost_time);
__android_log_print(ANDROID_LOG_ERROR, "TAG", "%s", fileInfo->java_stack.c_str());
}
if (fileInfo->min_buffer_size < 4096 || fileInfo->average_buffer_size < 4096) {
//读写小buffer 平均读写小buffer
__android_log_print(ANDROID_LOG_ERROR, "TAG", "最小缓冲区:%ld 平均缓冲区域太小:%ld", fileInfo->min_buffer_size, fileInfo->average_buffer_size);
__android_log_print(ANDROID_LOG_ERROR, "TAG", "%s", fileInfo->java_stack.c_str());
}
file_map.erase(fd);
delete fileInfo;
}
}