安卓如何收敛自身以及第三方sdk日志

安卓如何收敛自身以及第三方sdk日志 - Wesley’s Blog

安卓日志来源一般有自身 app 或者第三方 SDK,自己的比较容易归一化处理,优秀的 SDK 也会提供日志策略接口,但有一些喜欢直接用安卓的标准日志接口或者不能设置日志策略,这种就比较讨厌了,处理起来也比较麻烦。有两种办法:混淆时全部去掉或者 hook 系统日志接口。

编译期“阉割”:混淆处理

编译期“阉割”:配置R8/Proguard 日志优化规则,直接把日志方法当空气处理:

# 要全局去掉-dontoptimize
# 去掉 SDK 的 debug/info 日志
-assumenosideeffects class com.xxx.sdk.internal.SdkLog {
    public static int d(...);
    public static int i(...);
     # 不写 e(...),保留错误日志
}

# 去除全部log
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
    public static *** e(...);
    public static *** w(...);
}

# 去除System.out.println();
-assumenosideeffects class java.io.PrintStream { 
    public void println(...); 
} 

这样的打击范围太大,出现问题时无法收集日志。

hook系统日志打印接口

下面介绍使用字节跳动的bhook和腾讯的xlog来处理日志:前者用来hook日志接口,后者用来储存日志。

日志打印原理:

Android app层或framework层,通过调用Log/Slog/Rlog中的方法打印日志,接着通过JNI会调用到native层android_util_Log_println_native接口,最终通过liblog的__android_log_buf_write方法进行日志打印。

__android_log_buf_write

  -->__android_log_write_log_message

     -->get_logger_function()

        -->__android_log_logd_logger

           -->write_to_log

              -->LogdWrite

如果只需要拦截app层或framework层,只需要拦截android_util_Log_println_native 所在的libandroid_runtime.so 即可,android_util_Log_println_native 调用了__android_log_buf_write 方法。

实践

参考:bhook/doc/quickstart.zh-CN.md at main · bytedance/bhook

#include <jni.h>
#include <string>
//#include <android/log.h>
#include <bytehook.h>
#include "xlogger/android_xlog.h"
#define TAG "wesley-jni"
//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);

static bool g_consoleLogOpen = false;
static bool g_saveToFile = false;

int android_log_buf_write_proxy(int bufID, int prio, const char *tag, const char *text) {
    BYTEHOOK_STACK_SCOPE();
    //LOGE("hook  log")
    int ret = 0;
    char tag_buffer[50];
    if (g_consoleLogOpen) {
        snprintf(tag_buffer, sizeof(tag_buffer), "wesley-%s", tag);
        ret = BYTEHOOK_CALL_PREV(android_log_buf_write_proxy, bufID, prio, tag_buffer, text);
    }
    if (g_saveToFile) {
        if (prio >= ANDROID_LOG_ERROR) {
            XLOGE(tag, "%s", text);
        } else {
            XLOGI(tag, "%s", text);
        }
    }
    return ret;
}

// 过滤器函数。返回 true 表示需要 hook 这个 so;返回 false 表示不需要 hook 这个 so。
bool hook_log_allow_filter(const char *caller_path_name, void *arg)
{
    //if(nullptr != strstr(caller_path_name, "liblog.so")) return true;

    //android_util_Log_println_native 所在so
    if(nullptr != strstr(caller_path_name, "libandroid_runtime.so")) return true;

    return false;
}

bytehook_stub_t stub_of_android_log_buf_write = nullptr; // hook 的存根,用于后续 unhook
int hook_logcat() {
    //__android_log_write
    //bytehook_hook_all(nullptr, "android_log_print_proxy", (void *) (my_log_print), nullptr,
     //                 nullptr);
//    stub_of_android_log_buf_write = bytehook_hook_all(nullptr,
//                                                      "__android_log_buf_write",
//                                                      (void *) (android_log_buf_write_proxy),
//                                                      nullptr,
//                                                      nullptr);
    stub_of_android_log_buf_write = bytehook_hook_partial(
            hook_log_allow_filter,
            nullptr,
            nullptr,
            "__android_log_buf_write",
            reinterpret_cast<void *>(android_log_buf_write_proxy),
            nullptr,
            nullptr);
    if (stub_of_android_log_buf_write == nullptr) {
        return -1;
    }
    return 0;
}

void unhook_logcat() {
    if (stub_of_android_log_buf_write != nullptr) {
        bytehook_unhook(stub_of_android_log_buf_write);
        stub_of_android_log_buf_write = nullptr;
    }
}


extern "C"
JNIEXPORT void JNICALL
Java_com_wesley_android_c_toolkit_NativeHelper_00024Companion_unhookLogcat(JNIEnv *env,
                                                                         jobject thiz) {
    XLOGE(TAG, "unhook_logcat");
    unhook_logcat();
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_wesley_android_c_toolkit_NativeHelper_00024Companion_hookLogcat(JNIEnv *env, jobject thiz,
                                                                       jboolean console_log_open, jboolean save_to_file) {
    XLOGE(TAG,"hook_logcat");
    g_consoleLogOpen = (console_log_open == JNI_TRUE);
    g_saveToFile = (save_to_file == JNI_TRUE);
    if (hook_logcat() == 0) {
        return JNI_TRUE;
    }
    return JNI_FALSE;
}

app 层

package com.wesley.android.c.toolkit

import android.util.Log
import com.bytedance.android.bytehook.ByteHook

class NativeHelper {

    companion object {
        init {
            val ret = ByteHook.init()
            Log.e("ByteHook", "init ret: $ret")
            System.loadLibrary("toolkit")
        }
        external fun hookLogcat(consoleLogOpen: Boolean = true, saveToFile: Boolean = true): Boolean
        external fun unhookLogcat()

    }
}

完整代码参考:wesley666/AndroidToolkit: AndroidToolkit

运行 app 后可以看到日志 tag 加上了前缀 wesley,测试环境:小米 13,安卓 15。

参考

Android logd日志简介及典型案例分析

深入理解安卓日志系统(logcat / liblog / logd

Android帝国之日志系统–logd、logcat

Android logd日志原理 - Gityuan博客

Android日志是如何输出的:Log.d经历了什么

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值