Skip to content

Latest commit

 

History

History
235 lines (186 loc) · 11 KB

README.md

File metadata and controls

235 lines (186 loc) · 11 KB

重构版本优点

原方案:

  • 添加原框架maven依赖,并在activity中调用init方法。
  • 在运行时动态生成View的hook dex,在加载到内存中。

新方案:

使用JVMTI监听Debug应用的方法回调。

优点:

  • 不需要在apk里面添加依赖,即硬编码,通过adb命令即插即用。
  • 不生成代理View,减少内存占用

缺点:

  • 仅支持Android8.0以上的Debug应用

使用方式

1、将so文件放入待测试应用的私有目录

1)选择当前应用对应arm版本的 SO文件位置:SO文件地址

2)通过AS左侧的Device File Explorer工具,将SO文件放入到当前待测试应用的私有目录。

以当前demo工程举例,放入文件位置为:

/data/data/com.spearbothy.simpletouch/libsimple_touch.so

上步骤2),因为手机没有root,暂时没有找到合适的adb命令直接push...

2、通过adb命令启动应用并挂载代理

adb shell am start --attach-agent /data/data/com.spearbothy.simpletouch/libsimple_touch.so -S com.spearbothy.simpletouch/.MainActivity

3、控制台过滤TAGSimple_Touch,并触摸应用,可看到日志

// 线程#全路径类名+hashCode#方法名#ActionMode#方法走向(>>调用 << 返回) 返回值
I/Simple_Touch: main#Lcom/android/internal/policy/DecorView;185412568#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroidx/appcompat/app/AppCompatDelegateImpl$AppCompatWindowCallback;151497521#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Lcom/spearbothy/simpletouch/MainActivity;66354895#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Lcom/android/internal/policy/DecorView;185412568#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Lcom/android/internal/policy/DecorView;185412568#onInterceptTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Lcom/android/internal/policy/DecorView;185412568#onInterceptTouchEvent()#ACTION_DOWN << false
I/Simple_Touch: main#Landroid/widget/LinearLayout;122880534#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroid/widget/LinearLayout;122880534#onInterceptTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroid/widget/LinearLayout;122880534#onInterceptTouchEvent()#ACTION_DOWN << false
I/Simple_Touch: main#Landroid/widget/FrameLayout;201246615#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroid/widget/FrameLayout;201246615#onInterceptTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroid/widget/FrameLayout;201246615#onInterceptTouchEvent()#ACTION_DOWN << false
I/Simple_Touch: main#Landroidx/appcompat/widget/ActionBarOverlayLayout;34942340#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroidx/appcompat/widget/ActionBarOverlayLayout;34942340#onInterceptTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroidx/appcompat/widget/ActionBarOverlayLayout;34942340#onInterceptTouchEvent()#ACTION_DOWN << false
I/Simple_Touch: main#Landroidx/appcompat/widget/ContentFrameLayout;251316589#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroidx/appcompat/widget/ContentFrameLayout;251316589#onInterceptTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroidx/appcompat/widget/ContentFrameLayout;251316589#onInterceptTouchEvent()#ACTION_DOWN << false
I/Simple_Touch: main#Landroid/widget/LinearLayout;24455586#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroid/widget/LinearLayout;24455586#onInterceptTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Landroid/widget/LinearLayout;24455586#onInterceptTouchEvent()#ACTION_DOWN << false
I/Simple_Touch: main#Lcom/spearbothy/simpletouch/CustomView;115230259#dispatchTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Lcom/spearbothy/simpletouch/CustomView;115230259#onTouchEvent()#ACTION_DOWN >>
I/Simple_Touch: main#Lcom/spearbothy/simpletouch/CustomView;115230259#onTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Lcom/spearbothy/simpletouch/CustomView;115230259#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Landroid/widget/LinearLayout;24455586#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Landroidx/appcompat/widget/ContentFrameLayout;251316589#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Landroidx/appcompat/widget/ActionBarOverlayLayout;34942340#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Landroid/widget/FrameLayout;201246615#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Landroid/widget/LinearLayout;122880534#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Lcom/android/internal/policy/DecorView;185412568#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Lcom/spearbothy/simpletouch/MainActivity;66354895#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Landroidx/appcompat/app/AppCompatDelegateImpl$AppCompatWindowCallback;151497521#dispatchTouchEvent()#ACTION_DOWN << true
I/Simple_Touch: main#Lcom/android/internal/policy/DecorView;185412568#dispatchTouchEvent()#ACTION_DOWN << true

原理介绍

核心原理及文档

其余相关文档可搜索JVMTI等查询。

1、编写JVMTI Agent_OnAttach回调实现

具体源码:Agent_OnAttach

extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
    ALOGI("Agent_OnAttach options %s", options);
    // 创建jvmti指针
    localJvmtiEnv = CreateJvmtiEnv(vm);

    if (localJvmtiEnv == nullptr) {
        ALOGI("Agent_OnAttach  jvmti_env err");
        return JNI_ERR;
    }
    // 开启调试功能
    SetAllCapabilities(localJvmtiEnv);

    // 设置方法进入&方法退出的回调方法,回调至JvmtiCallbacks
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.MethodEntry = &JvmtiCallbacks::methodEntry;
    callbacks.MethodExit = &JvmtiCallbacks::methodExit;
    int error = localJvmtiEnv->SetEventCallbacks(&callbacks, sizeof(callbacks));

    ALOGI("Agent_OnAttach_callbacks  SetEventCallbacks result = %d", error);

    // 设置监听的事件:方法进入&方法退出
    SetEventNotification(localJvmtiEnv, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY);
    SetEventNotification(localJvmtiEnv, JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT);

    return JNI_OK;
}

2、监听到回调后,对事件进行过滤,并输出日志

具体源码:jvmti_callbacks.cpp

回调方法的签名参数如下:

   static void JNICALL methodEntry(jvmtiEnv *jvmti_env, // jvmti指针
                                    JNIEnv *jni_env, // jni指针
                                    jthread thread, // 所在线程
                                    jmethodID method // 方法标识
    );

    static void JNICALL methodExit(jvmtiEnv *jvmti_env, // jvmti 指针
                                   JNIEnv *jni_env, // jni指针
                                   jthread thread, // 所在线程
                                   jmethodID method, // 方法标识
                                   jboolean was_popped_by_exception, // 是否是因为异常退出
                                   jvalue return_value // 返回值
                                   );

在回调方法中,我们结合jvmti指针&jni指针暴露的方法,获取更多信息,具体信息如下:

    // 获取方法签名
    char *methodName;
    char *methodSignature;
    jvmti_env->GetMethodName(method, &methodName, &methodSignature, NULL);
    // 根据方法签名判断是否是 onTouchEvent等
    if (!Utils::getInstance()->isTargetMethod(methodName)) {
        return;
    }

    // 获取线程信息
    char *threadName;
    Utils::getInstance()->getThreadName(jvmti_env, thread, &threadName);

    // 获取本地变量表,用于获取方法入参
    jint entry_count = 0;
    jvmtiLocalVariableEntry *table_ptr = NULL;
    jvmti_env->GetLocalVariableTable(method, &entry_count, &table_ptr);

    const char *motionEvent;
    for (int j = 0; j < entry_count; j++) {
        if (strstr(table_ptr[j].signature, "MotionEvent") != NULL) {
            jobject param_obj;
            // 根据本地变量表获取到的索引获取入参对象,即MotionEvent对象
            jvmti_env->GetLocalObject(thread, 0, table_ptr[j].slot, &param_obj);
            // 调用MotionEvent对象的actionToString方法,获取ACTION_DOWN等事件类型。
            motionEvent = jni_env->GetStringUTFChars(
                    Utils::getInstance()->motionEvent_actionToString(jni_env, param_obj), 0);
            // 释放资源
            jni_env->DeleteLocalRef(param_obj);
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(table_ptr[j].signature));
            jvmti_env->Deallocate(reinterpret_cast<unsigned char *>(table_ptr[j].name));
            jvmti_env->Deallocate(
                    reinterpret_cast<unsigned char *>(table_ptr[j].generic_signature));
        }
    }
    
    // 获取调用当前方法的对象
    jobject instance;
    jvmti_env->GetLocalInstance(thread, 0, &instance);
    // 获取当前对象的全路径类名
    jclass klass = jni_env->GetObjectClass(instance);
    char *className;
    jvmti_env->GetClassSignature(klass, &className, NULL);
    // 获取对象的hashCode
    jint hashCode;
    jvmti_env->GetObjectHashCode(instance, &hashCode);
    
    // 根据信息组装,输出日志
    // ...

3、补充注意

methodEntry & methodExit的回调,是以单一方法粒度回调,例如子类实现了onTouchEvent(),并在方法内部调用了super.onTouchEvent(),则进入和进出对子类和父类都会回调,即共四次。

但对于当前工具,期望是以对象为粒度,即只关注某一个对象最终对事件分发的处理结果,因此加了一些代码兼容,具体如下:

 if (is_entry) {
        // 如果当前进入对象和方法和前一个相同,则只输出第一个
        string lastMethod = string(className) + to_string(hashCode) + string(methodName);
        if (!methodList.empty()) {
            string preMethod = methodList.back();
            if (lastMethod != preMethod) {
                ALOGI("%s#%s%d#%s()#%s >>", threadName, className, hashCode, methodName,
                      motionEvent);
            }
        } else {
            ALOGI("%s#%s%d#%s()#%s >>", threadName, className, hashCode, methodName, motionEvent);
        }

        methodList.push_back(lastMethod);
    } else {
        // 如果退出的对象和方法和前一个退出相同,则只输出最后一个
        methodList.pop_back();

        string lastMethod = methodList.back();
        string curMethod = string(className) + to_string(hashCode) + string(methodName);
        if (curMethod != lastMethod) {
            ALOGI("%s#%s%d#%s()#%s << %s", threadName, className, hashCode, methodName, motionEvent,
                  return_value ? "true" : "false");
        }
    }