NDK使用一

122 阅读2分钟

环境配置: app/src/main/cpp/CMakeLists.txt camke版本:

# 指定项目所需的最低 CMake 版本为 3.30.5。
cmake_minimum_required(VERSION 3.30.5)
# 定义项目名称为 app。
project(app)
# 启用 C 语言和汇编语言。
enable_language(ASM)
# 检查 DEPENDENCY_ON_LOCAL_LIBRARY 变量的值。如果为真,则包含本地依赖配置。
if(DEPENDENCY_ON_LOCAL_LIBRARY)
    # 包含本地依赖配置文件。
    include(${CMAKE_CURRENT_SOURCE_DIR}/../../../../shadowhook/local_dependency.cmake)
else()
    # 如果 DEPENDENCY_ON_LOCAL_LIBRARY 为假,则查找 shadowhook 包。查找 shadowhook 包,并将其标记为必需
    find_package(shadowhook REQUIRED CONFIG)
endif()

# 检查当前 Android ABI 是否为 arm64-v8a(64 位 ARM 架构)。
if(${ANDROID_ABI} STREQUAL "arm64-v8a")
    # 如果 ABI 是 arm64-v8a,则设置 ARCH 变量为 "arm64"
    set(ARCH "arm64")
    # 设置链接器标志 ARCH_LINK_FLAGS 为 "-Wl,-z,max-page-size=16384",用于指定最大页大小为 16KB。
    set(ARCH_LINK_FLAGS "-Wl,-z,max-page-size=16384")
elseif(${ANDROID_ABI} STREQUAL "armeabi-v7a")
    # 如果 ABI 是 armeabi-v7a,则设置 ARCH 变量为 "arm"。
    set(ARCH "arm")
    # 设置链接器标志 ARCH_LINK_FLAGS 为空字符串,因为 32 位 ARM 架构不需要特殊的链接器标志。
    set(ARCH_LINK_FLAGS "")
endif()

# libthread_hook.so
# 定义目标库的名称为 "thread_hook"。
set(TARGET "thread_hook")
# 查找当前目录下的所有 C 源文件,并将它们存储在变量 "SRC" 中。
file(GLOB SRC hookprint/*.c)
# 添加一个共享库目标,名为 "thread_hook",包含 "SRC" 中的源文件。
add_library(${TARGET} SHARED ${SRC})
# 查找 Android 的 log 库,并将其存储在 log-lib 变量中。
find_library(log-lib log)
# 为目标库 "thread_hook" 设置编译特性,包括 C 标准 17。
target_compile_features(${TARGET} PUBLIC c_std_17)
# 设置编译选项,包括使用 C17 标准、启用所有警告并将警告视为错误
target_compile_options(${TARGET} PUBLIC -std=c17 -Weverything -Werror)

image.png

myhook.c文件:

//
// Created by User on 2025/2/25.
//
#include <android/log.h>
#include <dlfcn.h>
#include <jni.h>

#define HACKER_JNI_VERSION    JNI_VERSION_1_6
#define HACKER_JNI_CLASS_NAME "com/example/signal/NativeTest"

static int unittest_jni_hook_sym_addr(JNIEnv *env, jobject thiz, jint api_level) {
  // 显式忽略未使用的参数,避免编译器警告
  (void)env;
  (void)thiz;

  return 2 * api_level;
}
/**
 * 这是JNI(Java Native Interface)的标准入口函数,在Java层加载native库时自动调用
 * 这个函数是Java层和native层之间的桥梁,确保Java层可以正确调用native层的函数实现。
 * 1.初始化JNI环境,获取 JNIEnv 指针
 * 2.查找Java类 com/example/signal/NativeTest
 * 3.定义并注册native方法,将Java层的native方法与C层的实现函数进行绑定
 * 4.返回JNI版本号 JNI_VERSION_1_6
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  // 显式忽略未使用的参数,避免编译器警告
  (void)reserved;

  // 检查JavaVM指针是否为空
  if (NULL == vm) return JNI_ERR;
  // 获取JNIEnv指针
  JNIEnv *env;
  if (JNI_OK != (*vm)->GetEnv(vm, (void **)&env, HACKER_JNI_VERSION)) return JNI_ERR;
  if (NULL == env || NULL == *env) return JNI_ERR;
  // 查找Java类
  jclass cls;
  if (NULL == (cls = (*env)->FindClass(env, HACKER_JNI_CLASS_NAME))) return JNI_ERR;
  // 定义要注册的native方法
  JNINativeMethod m[] = {{"nativeHookSymAddr2", "(I)I", (void *)unittest_jni_hook_sym_addr}};
  // 注册native方法
  if (0 != (*env)->RegisterNatives(env, cls, m, sizeof(m) / sizeof(m[0]))) return JNI_ERR;
  // 返回JNI版本号
  return HACKER_JNI_VERSION;
}

NativeTest.java代码

public class NativeTest {
    public static native int nativeHookSymAddr2(int apiLevel);
}

简单实现了java侧调用native代码的功能