暴力突破 JNI

一、前言


JNI 简单来说就是 java 调用本地 C/C++ 的技术,JVM 就是通过大量的 JNI 技术使得 Java 能在不同平台上运行。它允许 Java 类的某些方法用原生方法实现,这些原生方法也可以使用 Java 对象,使用方法与 Java 代码调用 Java 对象的方法相同。原生方法可以创建新的 Java 对象或者使用 Java 应用程序创建的对象。

二、简单示例


现在我们来看下官方示例,在 Android Studio 上创建一个 Native C++ 工程:

 创建好后的项目结构如下:

可以看到比普通的 Android 项目多了一个 cpp 目录,并且在 MainActivity 中多了个一个 native 方法 stringFromJNI(),这个方法 在 Java 层是个空实现,它的具体实现在 native-lib.cpp 中:

该方法返回一个字符串,现在我们运行这个项目:

三、JNIEnv 和 jobject 解释


我们继续来看 stringFromJNI 方法:

extern "C" JNIEXPORT jstring JNICALL
Java_com_tencent_learnndk_MainActivity_stringFromJNI(JNIEnv *env, jobject instance) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

其中 JNIEXPORT 与 JNICALL 都是 JNI 的关键字,表示此函数是要被 JNI 调用的。接下来来看看方法参数。

3.1 JNIEnv *env

JNIEnv 表示 Java 调用 native 语言的环境,是一个封装了几乎全部 JNI 方法的指针。JNI 中有哪些方法可以参考 JNI 方法大全及使用示例

JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。native 环境中创建的线程,如果需要访问 JNI,必须要调用 AttachCurrentThread 关联,并使用 DetachCurrentThread 解除链接。

除此之外,JNIEnv 在 C 和 C++ 环境下的调用也是有区别的:

//C风格:
(*env)->NewStringUTF(env, “Hellow World!”);
//C++风格:
env->NewStringUTF(“Hellow World!”);

3.2 jobject instance

如果 native 方法不是 static 的话,这个 instance 就代表这个 native 方法的对象实例。如果 native 方法是 static 的话就代表这个 native 方法的类的 class 类实例。示例如下:

java 代码:

public native String test();

public static native String testStatic();

jni 代码:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_tencent_learnndk_MainActivity_test(JNIEnv *env, jobject instance) {
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_tencent_learnndk_MainActivity_testStatic(JNIEnv *env, jclass type) {
}

四、Java 和 C/C++ 中类型的映射关系


现在我们在 MainActivity 中新增一个 native 方法,参数为 name:

并根据代码提示自动生成 JNI 方法:

可以看到 Java 中 String 类型的参数,在 native 方法中变成了 jstring 类型。那是因为 JNI 是接口语言,所以会有一个中间的转型过程,数据类型的转变是这个过程中重要的类型对接方式。下面我们来看看各个类型的映射。

基本数据类型:

Java 类型JNI 类型C/C++ 类型
booleanjbooleanunsigned char
bytejbytechar
charjcharunsingned short 
shortjshortshort
intjintint
longjlonglong
floatjfloatfloat
doublejdoubledouble

引用类型:

Java 类型原生类型Java 类型原生类型
Java.lang.Classjclasschar[]jcharArray
Java.lang.Throwablejthrowableshort[]jshortArray
Java.lang.Stringjstringint[]jintArray
Other objectjobjectlong[]jlongArray
Java.lang.Object[]jobjectArrayfloat[]jfloatArray
boolean[]jbooleanArraydouble[]jdoubleArray
byte[]jbyteArrayOther arraysjarray

五、JNI 调用 Java 代码


上面描述的都是 java 调用 C/C++ 端代码,然而在 JNI 中还有一个非常重要的内容,即在 C/C++ 本地代码中访问 Java 端代码。我们先来看下例子。

package com.tencent.learnndk;

public class Person {

    private String name;

    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}

修改 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);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(getPerson().toString());
    }

    public native Person getPerson();
}

修改 native-lib.cpp 的代码如下:

#include <jni.h>
#include <string>
#include<android/log.h>

#define  LOG_TAG "learnNdk"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)

extern "C" JNIEXPORT jobject JNICALL
Java_com_tencent_learnndk_MainActivity_getPerson(JNIEnv *env, jobject instance) {

    //1. 拿到 Java 类的全路径
    const char *person_java = "com/tencent/learnndk/Person";
    const char *method = "<init>"; // Java构造方法的标识

    //2. 找到需要处理的 Java 对象 class
    jclass j_person_class = env->FindClass(person_java);

    //3. 拿到空参构造方法
    jmethodID person_constructor = env->GetMethodID(j_person_class, method, "()V");

    //4. 创建对象
    jobject person_obj = env->NewObject(j_person_class, person_constructor);

    //5. 拿到 setName 方法的签名,并拿到对应的 setName 方法
    const char *nameSig = "(Ljava/lang/String;)V";
    jmethodID nameMethodId = env->GetMethodID(j_person_class, "setName", nameSig);

    //6. 拿到 setAge 方法的签名,并拿到 setAge 方法
    const char *ageSig = "(I)V";
    jmethodID ageMethodId = env->GetMethodID(j_person_class, "setAge", ageSig);

    //7. 正在调用 Java 对象函数
    const char *name = "lerendan";
    jstring newStringName = env->NewStringUTF(name);
    env->CallVoidMethod(person_obj, nameMethodId, newStringName);
    env->CallVoidMethod(person_obj, ageMethodId, 28);

    const char *sig = "()Ljava/lang/String;";
    jmethodID jtoString = env->GetMethodID(j_person_class, "toString", sig);
    jobject obj_string = env->CallObjectMethod(person_obj, jtoString);
    jstring perStr = static_cast<jstring >(obj_string);
    const char *itemStr2 = env->GetStringUTFChars(perStr, NULL);
    LOGD("Person: %s", itemStr2);
    return person_obj;
}

 运行如下:

我们来具体看下 native-lib.cpp 中的 GetMethodId() 方法:

说明:获取类中某个非静态方法的ID

参数:
clazz:指定对象的类
name:这个方法在 Java 类中定义的名称,构造方法为 ““
sig:这个方法的类型描述符,例如 “()V”,其中括号内是方法的参数,括号后是返回值类型

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)

在上面代码中有一个新的问题,那便是类型描述符 sig。我们也可以通过使用 javap 命令来查看类型描述符,具体可参考我之前的文章 暴力突破 Android 编译插桩(八)- class 字节码

除了根据命令行来查看,其实也可以根据一定的规律自己来写,类型描述符的格式可以参考下表:

Java 类型签名 (描述符)Java 类型签名 (描述符)
booleanZbyteB
charCshortS
intIlongJ
floatFdoubleD
voidV其他引用类型L+全类名+;
type[][method type(参数)返回值

下面来看常用的对象方法回调示例:

// jni

JavaVM *g_VM;
jobject g_obj;
std::unique_ptr<DubManger> dub;

extern "C"
JNIEXPORT jint JNICALL
Java_XXX_native_1init(JNIEnv *env, jobject thiz, jstring file_path) {
    //JavaVM是虚拟机在JNI中的表示,等下再其他线程回调java层需要用到
    env->GetJavaVM(&g_VM);
    g_obj = env->NewGlobalRef(thiz);

...
...
    if (dub == nullptr) {
        // 传递 g_vm, g_obj
        dub = std::make_unique<DubManger>(*g_VM, g_obj);
    }

    char *filePath = const_cast<char *>(env->GetStringUTFChars(file_path, 0));
    dub->init(filePath);
    env->ReleaseStringUTFChars(file_path, filePath);
    return static_cast<jint>(0);
}

在需要回调的地方获取方法id,然后根据方法id调用方法:

// 回调方法示例
void DubManger::callBack(const char *methodName, const char *sig) {
    JNIEnv *env;
    // 获取当前native线程是否有没有被附加到jvm环境中
    int getEnvStat = mJavaVM.GetEnv((void **) &env, JNI_VERSION_1_6);
    if (getEnvStat == JNI_EDETACHED) {
        //如果没有, 主动附加到jvm环境中,获取到env
        if (mJavaVM.AttachCurrentThread(&env, nullptr) != JNI_OK) {
            return;
        }
        mNeedDetach = JNI_TRUE;
    }
    jclass javaClass = env->GetObjectClass(mJavaObj);
    if (javaClass == nullptr) {
        LOGE("Unable to find class");
        mJavaVM.DetachCurrentThread();
        return;
    }
    //获取要回调的方法ID
    jmethodID javaMethodId = env->GetMethodID(javaClass, methodName, sig);
    if (javaMethodId == nullptr) {
        LOGD("Unable to find method:%s", methodName);
        return;
    }

    env->CallVoidMethod(mJavaObj, javaMethodId, 1);

    //释放当前线程
    if (mNeedDetach) {
        mJavaVM.DetachCurrentThread();
    }
}

六、动态注册


在此之前我们一直在 JNI 中使用 Java_packagename_classname_methodname 来进行与 Java 方法的匹配,这种方式我们称之为静态注册。动态注册则意味着方法名可以不用这么长了,在 android aosp 源码中就大量使用了动态注册的方式。现在我们来看下动态注册使用的例子:

在 MainActivity 中增加:

public native void dynamicNativeTest1();

public native String dynamicNativeTest2();

native-lib.cpp:

void dynamicNative1(JNIEnv *env, jobject jobj) {
    LOGD("dynamicNative1 动态注册");
}

jstring dynamicNative2(JNIEnv *env, jobject jobj, jint i) {
    return env->NewStringUTF("我是动态注册的dynamicNative2方法");
}

//需要动态注册的方法数组
static const JNINativeMethod jniNativeMethod[] = {
        {"dynamicNativeTest1", "()V",                   (void *) dynamicNative1},
        {"dynamicNativeTest2", "(I)Ljava/lang/String;", (jstring *) dynamicNative2}
};

//需要动态注册native方法的类名
static const char *mClassName = "com/tencent/learnndk/MainActivity";

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
    //通过虚拟机 创建爱你全新的 evn
    JNIEnv *jniEnv = nullptr;
    jint result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);
    if (result != JNI_OK) {
        return JNI_ERR; // 主动报错
    }
    jclass mainActivityClass = jniEnv->FindClass(mClassName);
    jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(JNINativeMethod));//动态注册的数量

    return JNI_VERSION_1_6;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值