遇到问题
C/C++ 调Java方法时直接把long类型的变量,传给CallStaticVoidMethod导致对应的Java静态方法接收到了错误的long值
PS: 实际的业务场比较复杂,浪费了好多时间去分析才定位到是这个基础的、但容易忽略的问题!!!
问题原因
armv7架构下long是32位,jlong是64位,所以JVM去读时多读了函数栈中其它的内存空间的值!得到了一个异常的值,并赋给了java代码中的long变量。目前大家的手机都是armv8的,所以开发调试的时候一般都是构建了armv8的包,从而没有第一时间发现异常
//如下是 CallStaticVoidMethod的最后一个参数是,可变参数,即当可能参数传的值类型跟读取时的类型不一致时就有异常,这里的case就是你传了个32位的值,但于64位长度去读取
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
{
va_list args;
va_start(args, methodID);
//关键看 args参数是怎么读取的
functions->CallStaticVoidMethodV(this, clazz, methodID, args);
va_end(args);
}
PS: 这里需要对C的可变参数有些了解,建议看这篇文章 C语言可变参数函数
//JVM 读取 args的是根据Java方法的参数签名去读取的,即methodID的值的信息去解析与读取,关键代码如下
/*
* Issue a method call with a variable number of arguments. We process
* the contents of "args" by scanning the method signature.
*
* Pass in NULL for "obj" on calls to static methods.
*
* We don't need to take the class as an argument because, in Dalvik,
* we don't need to worry about static synchronized methods.
*/
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
const char* desc = &(method->shorty[1]); // [0] is the return type.
int verifyCount = 0;
ClassObject* clazz;
u4* ins;
clazz = callPrep(self, method, obj, false);
if (clazz == NULL)
return;
/* "ins" for new frame start at frame pointer plus locals */
ins = ((u4*)self->interpSave.curFrame) +
(method->registersSize - method->insSize);
//ALOGD(" FP is %p, INs live at >= %p", self->interpSave.curFrame, ins);
/* put "this" pointer into in0 if appropriate */
if (!dvmIsStaticMethod(method)) {
#ifdef WITH_EXTRA_OBJECT_VALIDATION
assert(obj != NULL && dvmIsHeapAddress(obj));
#endif
*ins++ = (u4) obj;
verifyCount++;
}
while (*desc != '\0') {
switch (*(desc++)) {
// J即,Jlong/ Java long ,对应这里的case的方法签名就是J
case 'D': case 'J': {
u8 val = va_arg(args, u8);
memcpy(ins, &val, 8); // EABI prevents direct store
ins += 2;
verifyCount += 2;
break;
}
case 'F': {
/* floats were normalized to doubles; convert back */
float f = (float) va_arg(args, double);
*ins++ = dvmFloatToU4(f);
verifyCount++;
break;
}
case 'L': { /* 'shorty' descr uses L for all refs, incl array */
void* arg = va_arg(args, void*);
assert(obj == NULL || dvmIsHeapAddress(obj));
jobject argObj = reinterpret_cast<jobject>(arg);
if (fromJni)
*ins++ = (u4) dvmDecodeIndirectRef(self, argObj);
else
*ins++ = (u4) argObj;
verifyCount++;
break;
}
default: {
/* Z B C S I -- all passed as 32-bit integers */
*ins++ = va_arg(args, u4);
verifyCount++;
break;
}
}
}
#ifndef NDEBUG
if (verifyCount != method->insSize) {
ALOGE("Got vfycount=%d insSize=%d for %s.%s", verifyCount,
method->insSize, clazz->descriptor, method->name);
assert(false);
goto bail;
}
#endif
//dvmDumpThreadStack(dvmThreadSelf());
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
#ifndef NDEBUG
bail:
#endif
dvmPopFrame(self);
}
补充说明:
因为long参数这里已经导致读取异常了,后面的其它参数也会影响到!即这里 JNI中回调Java层的函数,long类型的参数传递出错的问题 就有同学遇到了,最本质关建是C的可变参数函数中可变参数的读取
配置说明
只构建armv7的so,可重现该异常!!
ndk {
abiFilters 'armeabi-v7a'
}
问题本质
可变参数的读取异常,如下示例代码
#include <stdio.h>
#include <stdarg.h>
int add( int n, ... )
{
int i = 0;
int sum = 0;
va_list argptr;
va_start( argptr, n );
for ( i = 0; i < n; ++i ) {
int temp = va_arg(argptr, int);
printf("arg[%d]=%d \n", i, temp);
sum += temp;
}
va_end( argptr );
return sum;
}
int main() {
int a = 1;
int b = 2;
int c = 3;
int e = 4;
int f = 5;
//传了三个参数,但内部读取了6个,多余的3次读取,读到了函数调用栈,栈上的其它内存的值
add(6, a, b, c);
int sum = a + b + c + e + f;
printf("\nsum=%d\n", sum);
return 0;
}
上述代码运行结果
相关资料
- JNI中回调Java层的函数,long类型的参数传递出错的问题
- C语言可变参数函数
- C语言中可变参数函数实现原理
- Dalvik虚拟机的运行过程分析
- dvmCallMethodV
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)