一、写在前面

作为一个Android UI Api调用工程师,在日常中咱们触摸的更多是java层,除非特别需求,不然很少直接触摸Native层,昨天逛社区的时分看到一篇大佬的文章,就点开看了下,发现逻辑是经过hook技能完成的,并且不是hook Java层,是hook了native层,然后就pull了源码,登时发现没触摸过C++的人看起来真费力啊,于是想写篇文章记载下。

声明一下本文仅仅针对怎样了解JNI的调用流程,做下梳理,适合初学者,大佬不要喷我。下面就开端吧!!!

二、餐前小吃

实战前先来个简略的比方,了解一下根本流程,下面是运用Jni的一个简略比方:

1、首要创立一个MainActivity:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
    public native String stringFromJNI();
}

MainActivity内部先试用静态代码块调用System.loadLibrary("native-lib"), 目的是先加载资源库,其间的“native-lib”是在CMakeLists文件中命名的。然后便是界说了一个native的stringFromJNI() 办法。

2、创立native-lib.cpp:

#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jniexample_MainActivity_stringFromJNI(JNIEnv* env, jclass clazz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

此文件便是上述stringFromJNI() 的native完成,能够看到办法如下:

Java_com_example_jniexample_MainActivity_stringFromJNI(Java_包名_java类名_办法名), 入参有(JNIEnv* env, jclass clazz) ,首要java层没有界说入参,native这个是必有得,一个是Jni环境的指针,一个是java层的类名,假如java层有其他的参数,native层也加在后边即可。

关于extern “C” JNIEXPORT jstring JNICALL:

  • extern “C” JNIEXPORT jstring JNICALL 是JNI接口函数的声明,它告知编译器如何将C/C++代码编译成能够被Java调用的本地库。

  • 在C++中,函数称号是由函数名和参数列表组成的,编译器会将函数称号转换为一种叫做mangled name的符号表明办法。可是,在JNI中,Java需求能够经过函数称号来调用本地库中的函数,Java不支撑mangled name,因而需求经过extern “C”来指定函数依照C语言办法进行编译,防止C++编译器将函数称号转换为mangled name。

  • JNIEXPORT指定了函数的链接特点,告知编译器应该导出函数,以便在本地库中能够被其他模块或程序运用。

  • jstring指定了函数的返回类型,表明返回一个Java的String类型。

  • JNICALL指定了函数调用约好,在Windows渠道上,JNI函数的调用约好为__stdcall,而在其他渠道上一般是__cdecl,JNICALL会依据渠道主动挑选正确的调用约好。

因而,extern “C” JNIEXPORT jstring JNICALL是JNI接口函数的标准声明办法,它告知编译器如何将C/C++代码编译成Java可调用的本地库。

3、创立CMakeLists

# 指定cmake的最小版别
cmake_minimum_required(VERSION 3.4.1)
# 创立一个库
add_library(
    native-lib
    SHARED
    native-lib.cpp
)
# 查找log库
find_library(
    log-lib
    log
)
# 链接库到方针库
target_link_libraries(
    native-lib
    ${log-lib}
)

关于CMakeLists:

CMakeLists是CMake构建体系时必需的文件,用于指定信息和依靠联系,文件由一系列指令组成,每个指令都由一个调用和一组参数组成。以下是一些常用的指令:

  • cmake_minimum_required(VERSION x) :指定CMake的最低版别,x为版别号。
  • project(name) :指定项目称号,能够包括项目的版别、描述等信息。
  • add_library(name type source1 source2 ...) :创立一个库,其间name为库的称号,type为库的类型(STATIC、SHARED或MODULE),source为库的源文件。
  • target_link_libraries(target library1 library2 ...) :将方针库与指定的库链接起来。target是方针库的称号,library是需求链接的库称号。
  • find_library(name path) :查找指定称号的库,并将其途径存储在变量中。name是库的称号,path是查找的途径。
  • include_directories(directory) :增加一个目录到包括途径中。
  • add_definitions(definition) :增加一个编译界说。

在Android JNI项目中,咱们能够运用add_library指令创立一个动态库,运用find_library指令查找需求链接的库,运用target_link_libraries指令将它们链接到方针库中。咱们还能够运用include_directories指令增加包括途径和add_definitions指令增加编译界说。

除了上述指令,还有其他指令可用于指定编译选项、设置环境变量、生成可履行文件等。CMakeLists文件的语法和指令十分灵敏,开发者能够依据自己的需求和项目特点进行自界说。

咱们创立的库是add_library(native-lib SHARED native-lib.cpp) 称号是“native-lib”,这个就跟MainActivity中 System.loadLibrary("native-lib") 对应。然后咱们创立的是SHARED library,也称为动态库或同享库,是一种在程序运转时被动态加载的库,它与静态库(Static library)相对。静态库在编译时被链接到可履行文件中,因而可履行文件比较大,可是静态库的加载速度较快。动态库则能够在程序运转时动态地加载和卸载,因而可履行文件较小,可是加载速度较慢。

关于库类型:

  1. SHARED library(动态库) 在多个程序之间同享代码和数据,因而能够减少内存的运用,进步程序的功率。例如,在Linux体系中,许多体系库都是以同享库的形式供给的,如libc.so、libm.so等。
  2. STATIC library(静态库) :静态库是在编译时被链接到可履行文件中的库,它的内容被仿制到可履行文件中,因而可履行文件比较大。静态库适用于需求在多个程序中同享的代码和数据,由于它们不需求在每个程序中从头加载。
  3. MODULE library(模块库) :模块库与同享库相似,可是它们的链接办法不同。模块库在运转时会被动态地加载,可是它们不会被链接到可履行文件中。模块库适用于需求动态加载和卸载的插件。
  4. OBJECT library(方针库) :方针库是一组编译好的方针文件,它们能够被多个方针文件或库同享。方针库能够看作是静态库和同享库的折中计划,它能够在编译时链接到可履行文件中,也能够在运转时动态加载。

SHARED library以外的创立办法根本相同,只需将add_library()指令的第二个参数改为相应的类型即可。例如:

add_library(my_static_lib STATIC my_static_lib.cpp)
add_library(my_module_lib MODULE my_module_lib.cpp)
add_library(my_object_lib OBJECT my_object_lib.cpp)

4、在gradle中装备cmake

需求依据详细的需求挑选合适的库类型,以达到最佳的性能和灵敏性。

android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions"
                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

在build.gradle中装备NDK。在Android Studio中,咱们需求在build.gradle文件中装备NDK以运用JNI。在这个示例项目中,咱们需求将以下代码增加到build.gradle文件中。

在这个代码中,咱们指定了编译选项(RTTI和异常),并挑选了支撑的ABI(armeabi-v7a、arm64-v8a、x86和x86_64)。咱们还指定了运用CMake来构建JNI代码,并将CMakeLists.txt文件的途径指定为外部构建途径。

这样一个简略的比方就说完了。

三、开饭

关于Hook

Native hook的开源库有字节的ShadowHook,和ByteHook,这俩一个是Android inline hook 一个是 Android PLT hook,简略说下差异:

1. 完成办法不同

Inline Hook是经过修正方针函数的前几条汇编指令,将其跳转到Hook函数的代码中,从而完成Hook的目的。这种办法需求对方针函数的汇编代码十分了解,并且需求处理一些细节问题,比较复杂。

PLT Hook(也称为“Got Hook”)是经过修正PLT(Procedure Linkage Table)中的地址,将其指向Hook函数的代码地址,从而完成Hook的目的。这种办法相对简略,不需求对方针函数的汇编代码进行处理,只需求对PLT表进行修正即可。

2. Hook办法不同

Inline Hook是在方针函数被调用时,将其跳转到Hook函数中履行,并在Hook函数中处理完毕后再跳回到原函数持续履行。这种办法需求仿制一份原函数的代码并进行修正,会占用一定的内存空间。

PLT Hook是在方针函数被调用时,将其直接跳转到Hook函数中履行,并不会再跳回到原函数持续履行。这种办法不需求仿制原函数的代码,因而不会占用额定的内存空间。

3. 兼容性不同

Inline Hook的兼容性相对较差,由于它需求修正方针函数的汇编代码,而方针函数的汇编代码跟着体系版别和架构的改变而不同,因而需求针对不同的体系版别和架构进行不同的处理。

PLT Hook的兼容性相对较好,由于它只需求修正PLT表中的地址,而PLT表的结构相对稳定,不会跟着体系版别和架构的改变而改变。

总的来说,Inline Hook和PLT Hook各有优缺点,详细运用哪种办法取决于详细的应用场景和需求。假如对Hook的性能和兼容性要求较高,能够考虑运用PLT Hook。假如需求Hook的函数比较特别,无法运用PLT Hook完成,能够考虑运用Inline Hook。

然后关于ShadowHook,它供给以下函数:

  • shadowhook_hook_func_addr: 经过肯定地址 hook 一个在 ELF 中没有符号信息的函数。
  • shadowhook_hook_sym_addr:经过肯定地址 hook 一个在 ELF 中有符号信息的函数。
  • shadowhook_hook_sym_name:经过符号名和 ELF 的文件名或途径名 hook 一个函数。
  • shadowhook_hook_sym_name_callback:和shadowhook_hook_sym_name相似,可是会在 hook 完成后调用指定的回调函数。
  • shadowhook_unhook:unhook。

.h 与.cpp

咱们知道在C++中,一般将程序的完成和声明分隔,完成一般放在.cpp文件中,而声明一般放在.h文件中。

.cpp文件(即源文件)包括了程序的详细完成,一般包括各种函数完成、类界说、全局变量等内容。每个.cpp文件一般都要包括对应的.h文件,以便在编译时将各个模块的代码整组成一个可履行文件。

.h文件(即头文件)包括了程序的各种声明和界说,一般包括函数声明、类界说、常量界说、宏界说等内容。.h文件一般会在程序的各个模块之间同享,以便在编译时进行链接。

将程序的完成和声明分隔有以下优点:

  1. 增加代码的可读性和可维护性。将程序的完成和声明分隔能够使代码更加明晰,易于了解和维护。
  2. 进步代码的复用性。将程序的声明和界说分隔能够使代码更易于重用,由于不同的模块能够同享同一个头文件。
  3. 进步编译功率。将程序的完成和声明分隔能够使编译器更容易地生成方针代码,由于编译器能够在编译时直接运用现已声明好的函数和变量。

总之,将程序的完成和声明分隔是一种良好的编程习气,能够使代码更加明晰、易于维护和重用,一起也能够进步编译功率。

上菜

首要贴上大佬的源码github.com/shixinzhang… 。 由于我不是分析大佬开源库的完成逻辑,仅仅以这个比方来说JNI的运用,所以不会贴出一切的代码,只挑关键的说了。

关于JNIEnv *env

在Android Native Development Kit(NDK)中,JNIEnv是一个指向JNI环境的指针,用于在Native代码和Java代码之间进行通信。JNIEnv是由JNI供给的一组API函数的集合,能够在Native代码中运用这些函数来调用Java代码、操作Java方针等。

JNIEnv是Java Native Interface(JNI)规范中界说的一部分,它是JNI的一种完成。在运用JNIEnv之前,需求将当时线程附加到Java虚拟机中,以便JNIEnv能够正确地操作Java方针,然后才能运用JNIEnv来调用Java办法、获取Java方针的字段、创立Java方针等。

JNIEnv供给了一系列的函数,例如CallVoidMethodCallObjectMethodGetFieldIDGetObjectField等,用于在Native代码中调用Java代码、获取Java方针的字段、办法等信息。这些函数都是经过JNIEnv指针来调用的。

需求注意的是,JNIEnv是与线程相关的,因而在运用JNIEnv之前,必须先运用AttachCurrentThread函数将当时线程附加到Java虚拟机中。在运用完JNIEnv之后,还需求运用DetachCurrentThread函数将当时线程与Java虚拟机别离,以防止资源走漏和内存走漏等问题,在此过程中需求用到JavaVM*,这个在JNI_OnLoad中会传入,咱们保存下来即可。

综上所述,JNIEnv是JNI环境的一个指针,供给了一系列的函数和操作,用于在Native代码和Java代码之间进行通信。

触及文件

与其他三方库相同,作者运用了ContentProvider,在里面registerActivityLifecycleCallbacks,可是Hook的初始化写在了Application中,由于有自界说的装备这个欠好主动初始化。关于Jni部分主要是两个文件BitmapMonitor.javabitmap_monitor.cpp 一个java层 一个native层

1、BitmapMonitor.java

@Keep
private static native int hookBitmapNative(long checkRecycleInterval, long getStackThreshold,
                                           long copyLocalThreshold, String copyDir, boolean clearFileWhenOutOfThreshold);
@Keep
private static native void stopHookBitmapNative();
/**
 * 仅仅获取数量
 *
 * @return
 */
@Keep
private static native BitmapMonitorData dumpBitmapCountNative();
/**
 * Get all bitmap info
 * @param ensureRestoreImage whether need check and restore again
 * @return
 */
@Keep
private static native BitmapMonitorData dumpBitmapInfoNative(boolean ensureRestoreImage);

2、bitmap_monitor.cpp

// 自界说数据类型 作者把临时变量类名以及JNI环境都收拢在这里,方便后续运用
static struct BitmapMonitorContext g_ctx;
// 这是启动时会调用的
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    g_ctx.java_vm = vm;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    ...
    return  JNI_VERSION_1_6;
}
// 以下是对应Java中的native函数
extern "C"
JNIEXPORT jint JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_hookBitmapNative(JNIEnv *env, jclass clazz,
                                                                  jlong check_recycle_interval,
                                                                  jlong get_stack_threshold,
                                                                  jlong restore_image_threshold,
                                                                  jstring restore_image_dir,
                                                                  jboolean notify_check_local_image_size) {
    const char* dir = env->GetStringUTFChars(restore_image_dir, 0);
    return do_hook_bitmap(check_recycle_interval, get_stack_threshold, restore_image_threshold, dir, notify_check_local_image_size);
}
extern "C"
JNIEXPORT void JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_stopHookBitmapNative(JNIEnv *env, jclass clazz) {
    g_ctx.open_hook = false;
    if (g_ctx.shadowhook_stub != nullptr) {
        shadowhook_unhook(g_ctx.shadowhook_stub);
    }
}
extern "C"
JNIEXPORT jobject JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapCountNative(JNIEnv *env, jclass clazz) {
    if (!g_ctx.open_hook) {
        return nullptr;
    }
    return do_dump_info(env, true, false);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapInfoNative(JNIEnv *env, jclass clazz, jboolean ensureRestoreImage) {
    return do_dump_info(env, false, ensureRestoreImage);
}

以上是贴了java与native调用的办法界说,Java层的就不看了,直接看cpp的。

3、详细函数

1、do_hook_bitmap

hook函数Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_hookBitmapNative调用了do_hook_bitmap。

jint do_hook_bitmap(long bitmap_recycle_check_interval,
                        long get_stack_threshold,
                        long restore_image_threshold,
                        const char *restore_image_dir,
                        bool notify_check_local_image_size) {
    g_recycle_check_interval_second = bitmap_recycle_check_interval;
    g_get_stack_threshold = get_stack_threshold;
    g_restore_image_threshold = restore_image_threshold;
    g_restore_image_dir = restore_image_dir;
    g_notify_check_local_image_size = notify_check_local_image_size;
    int api_level = get_api_level();
    if (api_level > 33) {
        return -2;
    }
    LOGI("hookBitmapNative called,  printStackThreshold: %ld, restore_image_threshold: %ld, api_level: %d",
         get_stack_threshold, restore_image_threshold, api_level);
    auto so = api_level > API_LEVEL_10_0 ? BITMAP_CREATE_SYMBOL_SO_RUNTIME_AFTER_10 : BITMAP_CREATE_SYMBOL_SO_RUNTIME;
    auto symbol = api_level >= API_LEVEL_8_0 ?  BITMAP_CREATE_SYMBOL_RUNTIME : BITMAP_CREATE_SYMBOL_BEFORE_8;
    auto stub = shadowhook_hook_sym_name(so, symbol, (void *) create_bitmap_proxy,nullptr);
    if (stub != nullptr) {
        g_ctx.open_hook = true;
        g_ctx.shadowhook_stub = stub;
        JNIEnv *jni_env;
        if (g_ctx.java_vm->AttachCurrentThread(&jni_env, nullptr) == JNI_OK) {
            jclass bitmap_java_class = jni_env->FindClass("android/graphics/Bitmap");
            g_ctx.bitmap_recycled_method = jni_env->GetMethodID(bitmap_java_class, "isRecycled",
                                                               "()Z");
            jclass bitmap_info_jobject = jni_env->FindClass(
                    "top/shixinzhang/bitmapmonitor/BitmapMonitorData");
            g_ctx.bitmap_info_jclass = static_cast<jclass>(jni_env->NewGlobalRef(
                    bitmap_info_jobject));
            g_ctx.report_bitmap_data_method = jni_env->GetStaticMethodID(g_ctx.bitmap_monitor_jclass,
                                                                        "reportBitmapInfo",
                                                                        "(Ltop/shixinzhang/bitmapmonitor/BitmapMonitorData;)V");
            g_ctx.report_bitmap_file_method = jni_env->GetStaticMethodID(g_ctx.bitmap_monitor_jclass,
                                                                         "reportBitmapFile",
                                                                         "(Ljava/lang/String;)V");
        }
        //hook 成功后,开启一个线程,定时轮训当时保存的数据,假如发现有被 recycle 的,移出去,更新整体数据
        start_loop_check_recycle_thread();
        return 0;
    }
    g_ctx.open_hook = false;
    g_ctx.shadowhook_stub = nullptr;
    return -1;
}

上述代码中是用于hook native的逻辑,运用的是Shadowhook的shadowhook_hook_sym_name(so, symbol, (void* ) 函数,关于so和symbol参数,做了判别

auto so = api_level > API_LEVEL_10_0 ? BITMAP_CREATE_SYMBOL_SO_RUNTIME_AFTER_10 : BITMAP_CREATE_SYMBOL_SO_RUNTIME;
auto symbol = api_level >= API_LEVEL_8_0 ?  BITMAP_CREATE_SYMBOL_RUNTIME : BITMAP_CREATE_SYMBOL_BEFORE_8;
#define BITMAP_CREATE_SYMBOL_SO_RUNTIME_AFTER_10 "libhwui.so"
#define BITMAP_CREATE_SYMBOL_SO_RUNTIME "libandroid_runtime.so"
#define BITMAP_CREATE_SYMBOL_RUNTIME "_ZN7android6bitmap12createBitmapEP7_JNIEnvPNS_6BitmapEiP11_jbyteArrayP8_jobjecti"
#define BITMAP_CREATE_SYMBOL_BEFORE_8 "_ZN11GraphicsJNI12createBitmapEP7_JNIEnvPN7android6BitmapEiP11_jbyteArrayP8_jobjecti"
关于shadowhook_hook_sym_name(so, symbol)的入参
1、so

这个so文件是体系的,那怎样拿到这个so文件呢,换句话说,我要检查下面的函数符号名,需求拿到这个so文件。咱们知道这些so文件是体系的,要获取它,先要把咱们的手机进行root处理,否则是拿不到的,下面简略说下步骤:

  1. 确认 Android 设备现已连接到本地 PC,能够经过以下指令来检查设备是否现已连接:

    adb devices

  2. 在本地 PC 上运用 adb shell 指令进入 Android 设备的 shell 环境:

    adb shell

  3. 运用 cd 指令进入到 .so 文件地点的目录,例如 /system/lib/system/lib64 目录:

    cd /system/lib

  4. 运用 ls 指令检查当时目录下的一切文件,找到需求仿制的 .so 文件:

    ls

  5. 运用 chmod 指令修正方针 .so 文件的权限为 777,如 chmod 777 libhello.so

    chmod 777 libhello.so

  6. 运用 exit 指令退出 Android 设备的 shell 环境,回到本地 PC 的指令行界面:

    exit

  7. 在本地 PC 的指令行界面中,运用 adb pull 指令将方针 .so 文件仿制到本地 PC,例如:

    adb pull /system/lib/libhello.so /path/to/local/folder
    

    其间 /system/lib/libhello.so 是要仿制的 .so 文件的途径,/path/to/local/folder 是要仿制到本地的目录途径。

2、symbol

symbol是函数符号名,关于检查这个称号能够经过readelf(本地需装置,brew update && brew install binutils,装置之后需求装备环境变量,装置之后,终端窗口会提示你的直接仿制粘贴即可),比方:

// 我仅仅举个比方 libmyLib不存在
readelf -Ws --wide libmyLib.so | grep "art::ArtMethod::Invoke"

这里我发现个问题,本人太菜,理论想经过上述命名直接过滤出来,可是readelf -Ws --wide libmyLib.so的打印结果如下:

Android Hook native初探
只有经过 C++ Name Mangler 处理后的函数符号名,所以加grep "art::ArtMethod::Invoke" 是过滤不出来的。所以菜鸟用菜办法:

i、先打印过滤出一切的原函数

readelf -WsC libart-64-Q.so | grep 'art::ArtMethod::Invoke'

Android Hook native初探

ii、打印一切的函数符号名,经过上面的id,找到方针

指令为readelf -Ws --wide libart-64-Q.so,以上面的id 5264为例,搜索后:

可知art::ArtMethod::Invoke对应的函数符号名为_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc

接着便是填充变量了,比方report_bitmap_data_method后边会用到。最终是调用 start_loop_check_recycle_thread启动一个线程,启动线程运用pthread_create,更多的函数能够检查pthread.h文件

Android Hook native初探

void start_loop_check_recycle_thread() {
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    /*
     * pthread_create (thread, attr, start_routine, arg)
     *
     *  thread:    指向线程标识符指针。
     *  attr:  一个不透明的特点方针,能够被用来设置线程特点。您能够指定线程特点方针,也能够运用默认值 NULL。
     *  start_routine: 线程运转函数起始地址,一旦线程被创立就会履行。
     *  arg:   运转函数的参数。它必须经过把引证作为指针强制转换为 void 类型进行传递。假如没有传递参数,则运用 NULL。
     */
    // 开启线程
    pthread_create(&thread, &attr, thread_routine, nullptr);
}

2、shadowhook_unhook

这个便是直接运用Shadowhook的shadowhook_unhook函数, 参数中的g_ctx.shadowhook_stub是在上面hook的时分填充的,代码如下:

auto stub = shadowhook_hook_sym_name(so, symbol, (void *) create_bitmap_proxy,nullptr);
if (stub != nullptr) {
    g_ctx.open_hook = true;
    g_ctx.shadowhook_stub = stub;
    ...
}

除了shadowhook_hook_sym_name以外,Shadowhook还供给其他几个函数

#include "shadowhook.h"
void *shadowhook_hook_func_addr(
    void *func_addr,
    void *new_addr,
    void **orig_addr);
void *shadowhook_hook_sym_addr(
    void *sym_addr,
    void *new_addr,
    void **orig_addr);
void *shadowhook_hook_sym_name(
    const char *lib_name,
    const char *sym_name,
    void *new_addr,
    void **orig_addr);
typedef void (*shadowhook_hooked_t)(
    int error_number,
    const char *lib_name,
    const char *sym_name,
    void *sym_addr,
    void *new_addr,
    void *orig_addr,
    void *arg);
void *shadowhook_hook_sym_name_callback(
    const char *lib_name,
    const char *sym_name,
    void *new_addr,
    void **orig_addr,
    shadowhook_hooked_t hooked,
    void *hooked_arg);
int shadowhook_unhook(void *stub);

详细怎样运用能够去gitHub检查。

3、do_dump_info

这个便是详细的拼装数据回传了,事务逻辑不说了,就说说数据是怎样拼装且返回的, 跟java相同咱们也需求拼装咱们需求的结构,这时分会用到env->XXXenv是前面说的JNI环境指针,他供给了许多函数创立数据结构NewObjectArrayNewCharArrayNewObject等等,要了解更多,能够自行检查。 咱们看下源码中新建一个记载的代码:

jobject java_record = env->NewObject(
        g_ctx.bitmap_record_class,
        g_ctx.bitmap_record_constructor_method,
        (jlong) record.native_ptr,
        (jint) record.width,
        (jint) record.height,
        (jint) (record.stride / record.width),
        (jint) record.format,
        record.time,
        save_path,
        stacks,
        current_scene
);

g_ctx.bitmap_record_class和g_ctx.bitmap_record_constructor_method的来源如下:

jclass bitmap_record_clz = env->FindClass("top/shixinzhang/bitmapmonitor/BitmapRecord");
g_ctx.bitmap_record_class = (jclass)env->NewGlobalRef(bitmap_record_clz);
g_ctx.bitmap_record_constructor_method = env->GetMethodID(g_ctx.bitmap_record_class,
                                                          "<init>",
                                                          "(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");

一个是java中的类,一个是类对应的结构函数名,解释下”(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" (JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V,能够依照以下办法解读:

  • (JIIIIJ:表明办法参数列表,依次为long类型、int类型、int类型、int类型、int类型、long类型。
  • Ljava/lang/String;:表明一个String方针。
  • Ljava/lang/String;:表明一个String方针。
  • Ljava/lang/String;:表明一个String方针。
  • )V:表明办法返回值类型为void

也便是说这个是规则结构函数入参的结构类型以及参数顺序的。

对比下Java代码

public BitmapRecord(long nativePtr, int width, int height, int bitsPerPixel,
                    int format, long time, String pictureExplorePath,
                    String createStack, String currentScene) {
    this.nativePtr = nativePtr;
    this.width = width;
    this.height = height;
    this.bitsPerPixel = bitsPerPixel;
    this.format = format;
    this.time = time;
    this.pictureExplorePath = pictureExplorePath;
    this.createStack = createStack;
    this.currentScene = currentScene;
}

是不是就对上了,哈哈哈,好了到这根本便是记载完了,回过头说,本文仍是对运用JNI的根本介绍,趁便拿大佬的代码作为比方。

四、最终

最终便是没有啥了,这篇文章仅仅用来了解JNI的,由于许多功能咱们很难再java层完成,所以学习一下native对咱们来说仍是很有协助的。