zhkl0228 / unidbg

Allows you to emulate an Android native library, and an experimental iOS emulation
Apache License 2.0
3.64k stars 924 forks source link

com.github.unidbg.arm.backend.BackendException: dvmObject="two", dvmClass=class java/lang/String, jmethodID=unidbg@0xffffffffd6cb375b #628

Closed HappyTsing closed 2 weeks ago

HappyTsing commented 1 month ago

为 HashMap 补了一个环境后报错,说是 methodid问题?如何解决呢?

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;":
                Map map = (Map) dvmObject.getValue();
                Object key = vaList.getObjectArg(0).getValue();
                return ProxyDvmObject.createObject(vm, map.get(key));
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
    public void call_javaCallC(){
        String arg1_id = "2";
        HashMap<String,String> map = new HashMap<String,String>(){
            {
                put("1","one");
                put("2","two");
            }
        };
        DvmObject<?> mapObject = ProxyDvmObject.createObject(vm, map);
        DvmObject<?> ret = MainActivity.newObject(null).callJniMethodObject(emulator, "javaCallC(Ljava/lang/String;Ljava/util/HashMap;)Ljava/lang/String;", arg1_id, mapObject);
        System.out.println(ret);
    }
    public static void main(String[] args) {
        testSO testSO = new testSO();
        testSO.call_javaCallC();
    }
ddzgy commented 1 month ago

用ProxyDvmObject.createObject()创建 hashMap

HappyTsing commented 1 month ago

用ProxyDvmObject.createObject()创建 hashMap

我就是这样创建的呀:

HashMap<String,String> map = new HashMap<String,String>(){
{
        put("1","one");
        put("2","two");
}};
DvmObject<?> mapObject = ProxyDvmObject.createObject(vm, map);

如上述代码所示,是我理解的不对吗?

HappyTsing commented 1 month ago

最新进展,我通过将补环境修改为:

 @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;":
                return dvmObject; // get 方法为什么要返回 dvmObject,而不是 Map 中取出的 value 呢?
            case "java/lang/Object->toString()Ljava/lang/String;":
                Map map = (Map) dvmObject.getValue();
//                Object key = vaList.getObjectArg(0).getValue(); // 现在拿不到 key了,怎么从 map 中取出值啊
                return new StringObject(vm, "mapValue");
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

现在程序可以正常运行并输出 “mapValue”,但这并不是真实的值呀,理论上我的key=“2”,应该输出value=“two” ,如何做到?

现在有几个疑问,写在注释里了,有人能解答一下嘛?

我的 native 方法是:

public native String javaCallC(String key, HashMap<String, String> hashMap);

方法的实现就是通过传入的 id,从 Map 中拿出 value。如何通过 unidbg 调用该方法,并传入一个 hashmap,通过 key,获取 map 中的 value 呢?

另外:unidbg 有文档吗?

ddzgy commented 1 month ago

case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;": return dvmObject; // get 方法为什么要返回 dvmObject,而不是 Map 中取出的 value 呢? unidbg不知道你map里存的键值对是什么,所以用基类java/lang/Object代指 实际你返回的就是你取出的value, Map map = (Map) dvmObject.getValue(); String key =(String) vaList.getObjectArg(0).getValue(); reture ProxyDvmObject.createObject(vm, map.get(key));

ddzgy commented 1 month ago

没有文档,就是网上找点入门资料学一学,然后有问题 下断跟到源码里看, 我觉得unidbg真是大杀器,我都想去录门课普及普及

HappyTsing commented 1 month ago

case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;": return dvmObject; // get 方法为什么要返回 dvmObject,而不是 Map 中取出的 value 呢? unidbg不知道你map里存的键值对是什么,所以用基类java/lang/Object代指 实际你返回的就是你取出的value, Map map = (Map) dvmObject.getValue(); String key =(String) vaList.getObjectArg(0).getValue(); reture ProxyDvmObject.createObject(vm, map.get(key));

再次修改:这样就可以通过 key 获取 value 了。

String key = "";
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;": // case1 获取 key,但是返回 dvmObject
                System.out.println(vaList.getObjectArg(0));
                key = (String) vaList.getObjectArg(0).getValue();
                return dvmObject;
            case "java/lang/Object->toString()Ljava/lang/String;": // case2 中再取出值。
                Map map = (Map) dvmObject.getValue();
                return ProxyDvmObject.createObject(vm, map.get(key));
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

疑惑的点在于 case1 就应该直接返回值呀,因为我补的是 map.get() 方法。

extern "C" JNIEXPORT jstring JNICALL
Java_com_wang_sotest_MainActivity_javaCallC(
        JNIEnv* env,
        jobject /* this */,
        jstring id,
        jobject map) {

        // 获取Map类的Class引用
        jclass mapClass = env->FindClass("java/util/HashMap");
        // 获取Map.get方法的Method ID
        jmethodID mapGet = env->GetMethodID(mapClass, "get",
                                            "(Ljava/lang/Object;)Ljava/lang/Object;");
        // 调用Map.get方法
        jobject value = env->CallObjectMethod(map, mapGet, id);

        // 将value转换为字符串
        jclass objectClass = env->FindClass("java/lang/Object");
        jmethodID toStringMethod = env->GetMethodID(objectClass, "toString", "()Ljava/lang/String;");
        jstring valueString = (jstring)env->CallObjectMethod(value, toStringMethod);
        printf("Java_com_wang_sotest_MainActivity_javaCallC is called");
        return valueString;
}

这是我的 native 方法的实现。所以我怀疑我补的是不是获取 methodid 的这个方法,而并非 map.get 这个调用呢?不知道是否表达明确,期待您的解答!!

ddzgy commented 1 month ago
        case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;": // case1 获取 key,但是返回 dvmObject
            System.out.println(vaList.getObjectArg(0));
            key = (String) vaList.getObjectArg(0).getValue();
            return dvmObject;

你这样写不对,你这样return的是你的map,而这里要的是你的通过key拿到的value 你首先要明白他为什么写成"java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;" 这样: 1.java标准中 map.get()就是这么定义的,而且你c代码也是这样写的呀: jmethodID mapGet = env->GetMethodID(mapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); // 调用Map.get方法 jobject value = env->CallObjectMethod(map, mapGet, id); 2、unidbg不知道你的map里存的是什么,map可以存任何东西,所以他也是在用java/lang/Object来代指子类。举个例子,你如果把你的map改成value 是int类型,他也是这样的。为啥?因为他不知道你的value具体类型,所以只能用object代指所有,你实际分析的是啥就给她啥

你c里写的后面我有点不懂,你为啥要拿java/lang/Object tostring,hashmap本身已经重写了 tostring了,你要调用基类的 tostring吗?

ddzgy commented 1 month ago

你要不留个联系方式,我跟你说下,这好像也说不太清楚

HappyTsing commented 1 month ago
        case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;": // case1 获取 key,但是返回 dvmObject
            System.out.println(vaList.getObjectArg(0));
            key = (String) vaList.getObjectArg(0).getValue();
            return dvmObject;

你这样写不对,你这样return的是你的map,而这里要的是你的通过key拿到的value 你首先要明白他为什么写成"java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;" 这样: 1.java标准中 map.get()就是这么定义的,而且你c代码也是这样写的呀: jmethodID mapGet = env->GetMethodID(mapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); // 调用Map.get方法 jobject value = env->CallObjectMethod(map, mapGet, id); 2、unidbg不知道你的map里存的是什么,map可以存任何东西,所以他也是在用java/lang/Object来代指子类。举个例子,你如果把你的map改成value 是int类型,他也是这样的。为啥?因为他不知道你的value具体类型,所以只能用object代指所有,你实际分析的是啥就给她啥

你c里写的后面我有点不懂,你为啥要拿java/lang/Object tostring,hashmap本身已经重写了 tostring了,你要调用基类的 tostring吗?

我按照你说的修改了我的native方法:

extern "C" JNIEXPORT jstring JNICALL
Java_com_wang_sotest_MainActivity_javaCallC(
        JNIEnv* env,
        jobject /* this */,
        jstring id,
        jobject map) {

        // 获取Map类的Class引用
        jclass mapClass = env->FindClass("java/util/HashMap");
        // 获取Map.get方法的Method ID
        jmethodID mapGet = env->GetMethodID(mapClass, "get",
                                            "(Ljava/lang/Object;)Ljava/lang/Object;");
        // 调用Map.get方法
        jobject value = env->CallObjectMethod(map, mapGet, id);
        printf("Java_com_wang_sotest_MainActivity_javaCallC is called");
        return (jstring) value;
}

现在我的补环境如下:

@Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;":
                System.out.println("key is: " + vaList.getObjectArg(0));
                String key = (String) vaList.getObjectArg(0).getValue();
                Map map = (Map) dvmObject.getValue();
                return ProxyDvmObject.createObject(vm, map.get(key));
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

可以正常运行了。 但是 ============ 分割线 =============

在原来的版本的 native 方法的写法中,我必须要向之前那种方式补环境才能正常运行,也就是返回 dvmObject 这也是我之前一直疑惑的点,我也觉得应该直接返回 map.get 的结果。

但是当时如果直接返回 return ProxyDvmObject.createObject(vm, map.get(key)); 的话,就会报 methodid 报错。

现在修改 native 函数的写法后可以正常返回了,但觉得很神奇之前的必须要向我最初那么补环境才行。

ps:原来的 native 函数是chatgpt 生成的,也可以正常运行,就是补函数必须写成上述那种情况了。

HappyTsing commented 1 month ago

你要不留个联系方式,我跟你说下,这好像也说不太清楚

感谢!

ddzgy commented 1 month ago

chatgpt生成的这段代码,本身就有问题,自己要写写c代码,对用好unidbg有帮助,祝好