??關(guān)于文件的監(jiān)控,主要有那么幾個(gè)維度:主線程讀寫文件,讀寫文件時(shí)長,讀寫文件時(shí)buffer大小,文件使用后未關(guān)閉。主線程讀寫文件可能會(huì)堵塞主線程造成卡頓甚至 ANR;讀寫文件時(shí)buffer過小會(huì)造成應(yīng)用層代碼重復(fù)運(yùn)行,底層有可能有大量寫入問題(也有可能是累積到一頁大小寫入);文件使用后未關(guān)閉會(huì)造成資源的浪費(fèi),嚴(yán)重時(shí)會(huì)出現(xiàn) OOM 的問題。
監(jiān)聽文件使用后未關(guān)閉
- 原理
File file = new File(getApplication().getFilesDir(), "zz.txt");
FileInputStream fileInputStream = new FileInputStream(file);
int read = fileInputStream.read();
fileInputStream.close();
??通常使用文件如上,打開文件流讀取文件后將流關(guān)閉。如果文件流不關(guān)閉的話,在未來的GC回收時(shí)(有可能)或者進(jìn)程快結(jié)束時(shí),會(huì)調(diào)用文件流的 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();
}
}
??如果文件流寫代碼時(shí)忘記關(guān)閉的話,從finalize方法的調(diào)用中也會(huì)將其關(guān)閉,而且還會(huì)調(diào)用 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 方法調(diào)用后為 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);
}
}
}
??可以看到如果文件未關(guān)閉的話,會(huì)調(diào)用接口類的一個(gè)report的方法,可以想到利用反射將REPORTER替換,就可以在文件流使用后自己未關(guān)閉而被自動(dòng)關(guān)閉時(shí)被調(diào)用report方法來進(jìn)行檢測。
代碼如下:
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", "文件打開沒有關(guān)閉");
}
return method.invoke(originalReporter, args);
}
});
Method setReporter = closeGuardClass.getDeclaredMethod("setReporter", reportInterfaceClass);
setReporter.setAccessible(true);
setReporter.invoke(null, newReporter);
}
- 注意點(diǎn)
??在 android10 之后dalvik.system.CloseGuard不但被標(biāo)記為 hide,還被注解為@SystemApi。在反射獲取方法時(shí)會(huì)報(bào)錯(cuò),需注意版本兼容。
主線程讀寫文件,讀寫文件時(shí)長,讀寫文件時(shí)buffer大小
??這里主要是運(yùn)用了 xhook 的能力來監(jiān)控系統(tǒng)打開、讀寫、關(guān)閉文件的操作。在 matrix 代碼中監(jiān)控的維度有文件的路徑,java堆棧,線程名,讀寫的次數(shù),大小和,時(shí)間和,單次讀寫的最大耗時(shí),最小buffer等。并根據(jù)相應(yīng)的檢測邏輯判斷,如主線程檢測需判斷單次讀寫的最大耗時(shí)是否大于閾值,二次操作的間隙等。
- hook 的代碼在 : io_canary_jni.cc
主要處理在文件打開時(shí)記錄其文件的路徑,java堆棧,單次讀寫的時(shí)間,關(guān)閉
- 處理的代碼在 : io_canary.cc io_info_collector.cc
主要處理在聚合統(tǒng)計(jì)文件讀寫的維度,讀寫的次數(shù),大小和,時(shí)間和,單次讀寫的最大耗時(shí),最小buffer等
- 檢測的代碼在 : 父類:detector.h 子類:reppeat_read_detector.cc main_thread_detector.cc small_buffer_detector.cc
子類各種處理自己關(guān)心的事情
- 主線程檢測需判斷單次讀寫的最大耗時(shí)是否大于閾值,二次操作的間隙。
- 小buffer檢測文件操作的次數(shù)大于閾值 20,平均每次操作buffer的大小小于4096 最長的一次操作時(shí)間大于 13*1000 μs。
matrix 到此分析結(jié)束。
給出參考 matrix 的代碼實(shí)現(xiàn):
//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", "監(jiān)聽到文件打開:%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", "監(jiān)聽到文件打開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", "監(jiān)聽到文件讀:%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", "監(jiān)聽到文件讀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", "監(jiān)聽到文件寫%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", "監(jiān)聽到文件寫:%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", "監(jiān)聽到文件關(guān)閉:%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", "監(jiān)聽到文件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) {
//主線程讀寫超時(shí)
__android_log_print(ANDROID_LOG_ERROR, "TAG", "主線程操作文件太耗時(shí):%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", "最小緩沖區(qū):%ld 平均緩沖區(qū)域太?。?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;
}
}