JNI:调用发生 memset 的函数导致 jvm-fetch 在第二次调用中崩溃

JNI: call to a function where memset happens causes jvm-fetch to crash in the second call

这个函数首先执行一些 memset 操作,然后调用基本上创建/获取 JVM 的函数,然后与 JVM 通信。

int
acCreateHeader(acInputParms *acCreate, acReturnParms *acRet3){

   int     Debug;
    int     rc;
    acInputParms        acInp;
    kc0InputParms       kc0Inp;
    kc0ReturnParms      kc0Ret3;

    initACReturnParms(acRet3);

    rc = invokeKc000(kc0Inp, &kc0Ret3); // JNI Operations

    return rc;
}

initACReturnParms(acRet3);的扩展如下:

void initACReturnParms (acReturnParms *in) {

    memset(in->msgId,0,sizeof(in->msgId));
    memset(in->msgText,0,sizeof(in->msgText));
    memset(in->returnHdrTrl,0,sizeof(in->returnHdrTrl));
    memset(in->returnPaySrc,0,sizeof(in->returnPaySrc));
    memset(in->returnPayLoc,0,sizeof(in->returnPayLoc));
    return;
}

所以,当我执行整个程序时,第一个 JNI 操作发生得很好,但是程序的逻辑是以函数 acCreateHeader 被调用两次的方式构建的,因此程序冻结了在第二次调用中的 (**jvm)->GetEnv(*jvm, (void**)&env, JNI_VERSION_1_4); 函数(基本上是尝试获取已经创建的 JVM)。

抱歉,由于程序非常大,几乎不可能将我的所有代码都粘贴到这里。

但问题就在这里:

当我复制函数 initACReturnParms 的所有逻辑并通过注释 initACReturnParms 的调用(如下所示)将其粘贴到函数 acCreateHeader 中时,程序没有崩溃总而言之,即使在第二次 JNI 操作之后,整个程序也按我预期的那样运行。

int
acCreateHeader(acInputParms *acCreate, acReturnParms *acRet3){

   int     Debug;
    int     rc;
    acInputParms        acInp;
    kc0InputParms       kc0Inp;
    kc0ReturnParms      kc0Ret3;

    memset(acRet3->msgId,0,sizeof(acRet3->msgId));
    memset(acRet3->msgText,0,sizeof(acRet3->msgText));
    memset(acRet3->returnHdrTrl,0,sizeof(acRet3->returnHdrTrl));
    memset(acRet3->returnPaySrc,0,sizeof(acRet3->returnPaySrc));
    memset(acRet3->returnPayLoc,0,sizeof(acRet3->returnPayLoc));
    //initACReturnParms(acRet3);

    rc = invokeKc000(kc0Inp, &kc0Ret3); // JNI Operations

    return rc;
}

那么,为什么这个函数 initACReturnParms 在这里调用了罪魁祸首?请帮助我理解。

如果您希望我提供更多信息/代码,请告诉我。

更新:

acRet3 是函数 acCreateHeader 中的局部变量,并作为以下函数的参数传递:

void writeLSHeader() {

  int rc;

  fillHdrTrl();

  rc=acCreateHeader(&acCreate,&acRet);

  //rest of the codes
}

并且acRet在下面显示的函数中被初始化:

int fillHdrTrl() {
    //Codes here
    memset(&acRet,0,sizeof(acReturnParms));
    memset(&acRet.returnHdrTrl[0],' ',AC_HDR_TRL_SZ);
    //Codes here
}

acRet虽然是一个全局变量,内存分配在栈上:

acReturnParms acRet;

acReturnParms 看起来像这样:

struct acReturnParms {
    char msgId[9];
    char msgText[61];
    char returnHdrTrl[351];
    char returnPaySrc[9];
    char returnPayLoc[3];
}

而上面使用的宏出现在头文件中是这样的:

#define AC_HDR_TRL_SZ            350

更新 2

以下是 JNI 操作的代码,这可能是这种未定义行为的原因:

#include "jni_funct.h"
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int invokeKc000(kc0InputParms kc0inp, kc0ReturnParms *kc0ret)
{
    JavaVM *jvm;
    JNIEnv *env;
    kc0InputParms input;
    kc0ReturnParms *kc000out;
    char *kc910Path, *kc910Lib;
    int retcode;
    retcode = 8;

    kc910Path = getenv("KC910_BIN");
    kc910Lib = getenv("KC910_LIB");
    if(kc910Path == NULL || strlen(kc910Path) <= 0){
        return -1;
    }

    if(kc910Lib == NULL || strlen(kc910Lib) <= 0){
        return -1;
    }
    env = createKc000Vm(&jvm, kc910Path, kc910Lib);
    if(env == NULL){
        printf("\n JNIEnv is NULL");
        return -2;
    }
    input = kc0inp;
    kc000out = kc0ret;
    communicateKc000(env, input, &kc000out, &retcode);
    return retcode;
}

JNIEnv* createKc000Vm(JavaVM **jvm, char *kc910Path, char *kc910lib)
{
        JNIEnv* env;
        JavaVMInitArgs args;
        JavaVMOption options;
        args.version = JNI_VERSION_1_4;
        args.nOptions = 1;
        char *classPath = "shared/misc/kc000";
        char *javaClassPathOption = "-Djava.class.path";
        char javaOptionString[2000];
        char kc910Lib[1000], kc910Bin[1000];

        memset(kc910Lib, '[=18=]', sizeof(kc910Lib));
        memset(kc910Bin, '[=18=]', sizeof(kc910Bin));
        memset(javaOptionString, '[=18=]', sizeof(javaOptionString));
        strncpy(javaOptionString, javaClassPathOption, strlen(javaClassPathOption));
        strncpy(kc910Lib, kc910lib, strlen(kc910lib));
        strncpy(kc910Bin, kc910Path, strlen(kc910Path));
        strcat(javaOptionString, "=");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/com.ibm.ws.ejb.thinclient_8.5.0.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/com.ibm.ws.runtime.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/com.ibm.ws.orb_8.5.0.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/ojdbc6.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/providerutil.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/j2ee.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/src.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/junit.jar:");
        strcat(javaOptionString, kc910Lib);
        strcat(javaOptionString, "/ldap.jar:");
        strcat(javaOptionString, classPath);
        strcat(javaOptionString, ":");
        strcat(javaOptionString, kc910Path);
        strcat(javaOptionString, "/C2J2.jar");

        options.optionString = javaOptionString;
        args.options = &options;
        args.ignoreUnrecognized = 0;
        int rv;
        jsize buf, num;
        rv = JNI_CreateJavaVM(jvm, (void**)&env, &args);
        if (rv < 0 || !env){
                printf("\nUnable to launch KC000 JVM %d. Perhaps you are attempting to create JVM. Attempting to fetch existing JVM, if running",rv);
                JNI_GetCreatedJavaVMs(jvm, buf, &num);
                (**jvm)->GetEnv(*jvm, (void**)&env, JNI_VERSION_1_4);
        }
        else
                printf("\nLaunched kc000 JVM!");

        return env;
}

void communicateKc000(JNIEnv* env, kc0InputParms in, kc0ReturnParms **kc000outparms, int *retcode)
{
    jclass init_class;
    jmethodID init_method;
    jstring inString, outString;
    kc0ReturnParms **kc000out;
    char returnCodeString[9];
    int flag = 8, i = 0;
    kc000out = kc000outparms;
    inString = (*env)->NewStringUTF(env, in.inStream);

    init_class = (*env)->FindClass(env, "shared/misc/kc000/Kc000");
    init_method = (*env)->GetStaticMethodID(env, init_class, "init", "(Ljava/lang/String;)Ljava/lang/String;");
    outString = (*env)->CallStaticObjectMethod(env, init_class, init_method, inString);
    const char *rawString = (*env)->GetStringUTFChars(env, outString, 0);
    jclass newExcCls = (*env)->FindClass(env, "java/lang/Throwable");
    if(newExcCls == NULL)
    {
        /*printf("Exception not found\n");*/
    }
    else
    {
        (*env)->ExceptionDescribe(env);
    }   
    strcpy((*kc000out)->inStream, rawString);
    memset(returnCodeString, '[=18=]', sizeof(returnCodeString));
    strncpy(returnCodeString, rawString, 8);
    printf("KC000 return code: [%s]\n", returnCodeString);
    if(strlen(returnCodeString) > 0){
        for(i = 0; i < 8; i++){
            if(returnCodeString[i] == ' '){
                flag = 0;
            }
            else{
                flag = 8;
            }
        }
        *retcode = flag;
    }
    else{
        printf("Nothing returned in the return code of KC000. Setting this as an error.\n");
        *retcode = 8;
    }
}

我自己找到了问题的答案。这是我为摆脱这种未定义行为所做的工作:

#include "jni_funct.h"
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

JavaVM *jvm;
JNIEnv *env;

int invokeKc000(kc0InputParms kc0inp, kc0ReturnParms *kc0ret)
{
    //JavaVM *jvm;
    //JNIEnv *env;
    kc0InputParms input;
    kc0ReturnParms *kc000out;
    char *kc910Path, *kc910Lib;
    int retcode;
    retcode = 8;

    //Rest of the codes

我将 JNIEnvJavaVM 作为全局变量而不是局部变量。

我仍然不明白为什么堆栈分配会导致这种未定义的行为,而数据段分配不会导致此类问题。我将 post 关于此更改的另一个问题。

感谢@Martin Zabel 的提示。