当前位置: 首页 > 知识库问答 >
问题:

JNIJava对象实例化过程中的分段故障

阴高刚
2023-03-14

(我知道这个问题。这是一个单独的问题。

我从C JNI代码实例化Java对象时得到了一个segfault。

这是分段故障:

#  SIGSEGV (0xb) at pc=0x00007f2c7ba13548, pid=2809, tid=139829052933888
#
# JRE version: Java(TM) SE Runtime Environment (8.0_45-b14) (build 1.8.0_45-b14)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.45-b02 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V  [libjvm.so+0x559548]  Dictionary::add_protection_domain(int, unsigned int, instanceKlassHandle, ClassLoaderData*, Handle, Thread*)+0x128

以下是hs_err文件中堆栈的相关部分:

Stack: [0x00007f2c7cf78000,0x00007f2c7d079000],  sp=0x00007f2c7d0760c0,  free space=1016k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x559548]  Dictionary::add_protection_domain(int, unsigned int, instanceKlassHandle, ClassLoaderData*, Handle, Thread*)+0x128
V  [libjvm.so+0xa1f4e2]  SystemDictionary::validate_protection_domain(instanceKlassHandle, Handle, Handle, Thread*)+0x142
V  [libjvm.so+0xa230d4]  SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, Thread*)+0x3c4
V  [libjvm.so+0xa24a13]  SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, Thread*)+0x33
V  [libjvm.so+0x4ea4f8]  ConstantPool::klass_at_impl(constantPoolHandle, int, Thread*)+0x158
V  [libjvm.so+0x4efbd0]  ConstantPool::klass_ref_at(int, Thread*)+0xa0
V  [libjvm.so+0x7e0eb1]  LinkResolver::resolve_pool(KlassHandle&, Symbol*&, Symbol*&, KlassHandle&, constantPoolHandle, int, Thread*)+0x71
V  [libjvm.so+0x7e7206]  LinkResolver::resolve_invokeinterface(CallInfo&, Handle, constantPoolHandle, int, Thread*)+0xa6
V  [libjvm.so+0x7e9ad0]  LinkResolver::resolve_invoke(CallInfo&, Handle, constantPoolHandle, int, Bytecodes::Code, Thread*)+0xb0
V  [libjvm.so+0x67a972]  InterpreterRuntime::resolve_invoke(JavaThread*, Bytecodes::Code)+0x1b2
j  [my-package].NativeRef$X509_ATTRIBUTE.<init>(J)V+21
v  ~StubRoutines::call_stub
V  [libjvm.so+0x681a26]  JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)+0x1056
V  [libjvm.so+0x6c432b]  jni_invoke_nonstatic(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)+0x41b
V  [libjvm.so+0x6d943b]  jni_NewObjectV+0x2ab
C  [[my-so].so+0x2b855]  JNIEnv_::NewObject(_jclass*, _jmethodID*, ...)+0xb5
C  [[my-so].so+0x7d31e]  _jobject* newNativeRef<UniquePtr<x509_attributes_st, X509_ATTRIBUTE_Delete> >(JNIEnv_*, char const*, UniquePtr<x509_attributes_st, X509_ATTRIBUTE_Delete>*, bool)+0x352

快速概述我的库:我的代码围绕OpenSSL,通过JCAAPI为Java应用程序提供对OpenSSL的访问。(注意:并非所有代码都是从零开始编写的。一些代码源于执行类似功能的Android库Conscrypt。)

我有许多几乎相同的类(本机指针的简单Java包装器,它自动从finalize()方法中释放本机对象),它们都扩展了一个名为NativeRef的类。(部分代码如下。

因此,我创建了一个通用的本机函数(newNativeRef())来获取本机指针并实例化Java对象,将本机指针传递给构造函数。此功能已经使用了一年多,并经过了非常严格的测试。到目前为止,我没有遇到任何问题。(部分代码如下)

但是我现在正在编写一个新的JNI函数,它调用newNativeRef()并给出segfault。

下面是我正在编写的新JNI函数中的调用:

ret = newNativeRef<Unique_X509_ATTRIBUTE>(env, CLASS_NR_X509_ATTRIBUTE, &attr, false);

attr是一个简单的智能指针(Unique_X509_ATTRIBUTE),它环绕着指向OpenSSLX509_ATTRIBUTE对象的基本指针。

CLASS_NR_X509_ATTRIBUTE被定义为“[my-package]/native ref$X509 _ ATTRIBUTE”

以下是Unique_X509_ATTRIBUTE的定义:

struct X509_ATTRIBUTE_Delete {
    void operator()(X509_ATTRIBUTE* p) const {
        X509_ATTRIBUTE_free(p);
    }
};
typedef UniquePtr<X509_ATTRIBUTE, X509_ATTRIBUTE_Delete> Unique_X509_ATTRIBUTE;

下面是来自UniquePtr的相关(精简)代码

template <typename T, typename D = DefaultDelete<T> >
class UniquePtr {
public:
    // Construct a new UniquePtr, taking ownership of the given raw pointer.
    explicit UniquePtr(T* ptr = (T*) NULL) : mPtr(ptr) {
    }

    T* get() const { return mPtr; }

    // Returns the raw pointer and hands over ownership to the caller.
    // The pointer will not be deleted by UniquePtr.
    T* release() CJ_ATTRIBUTE(((warn_unused_result))){
        T* result = mPtr;
        mPtr = NULL;
        return result;
    }

    // Takes ownership of the given raw pointer.
    // If this smart pointer previously owned a different raw pointer, that
    // raw pointer will be freed.
    void reset(T* ptr = (T*) NULL) {
        if (ptr != mPtr) {
            D()(mPtr); // Basically, this calls X509_ATTRIBUTE_free(mPtr)
            mPtr = ptr;
        }
    }

private:
    // The raw pointer.
    T* mPtr;
};

下面是发生segfault的函数:

template<typename T> jobject newNativeRef(JNIEnv* env, const char* className,
        T* addr) {

    jclass clazz;
    jobject object = JNIHandle_NULL;

    try {

        clazz = env->FindClass(className);
        [snip - error-checking]

        jmethodID constructor = env->GetMethodID(clazz, "<init>",
                NATIVE_FUNC_SIG(SIG_VOID, SIG_LONG));

        jlong jaddr = JNIHandle_NULL;

        if (addr != NULL) {
            jaddr =
                    static_cast<jlong>(reinterpret_cast<uintptr_t>((*addr).get()));
        }

        // This line generates the segfault.
        object = env->NewObject(clazz, constructor, jaddr);

        if (addr != NULL) {
            // Ownership of the pointer has been passed to the Java object
            OWNERSHIP_TRANSFERRED((*addr));
        }

    [snip - error handling]

    return object;
}

NATIVE_FUNC_SIG(SIG_VOID,SIG_LONG)的计算结果为“(J)V”

下面是一个Java类,用于包装指向X509_

public static class X509_ATTRIBUTE extends NativeRef {
    public X509_ATTRIBUTE(long ctx) {
        super(ctx);
    }

    protected void opensslFree() {
        [Native-function-class].X509_ATTRIBUTE_free(this);
    }
}

以及NativeRef超类(缩减):

public abstract class NativeRef {

    transient volatile long context = 0L;

    public NativeRef(long ctx) {
        this.context = ctx;
    }

    @Override
    protected synchronized void finalize() throws Throwable {
        try {
            free();
        } finally {
            super.finalize();
        }
    }

    public synchronized void free() {
        try {
            if (isValid()) {
                opensslFree();
            }
        } finally {
            context = 0L;
        }
    }

    protected abstract void opensslFree();
}

提前感谢!

[编辑]

有趣的是,崩溃似乎不是这个newNativeRef()调用特有的。注释掉newNativeRef()调用后,下次JNI尝试实例化Java对象(在本例中为Java异常)时,会显示相同的segfault。

我甚至在不包含我的任何JNI代码的stacktraces中看到过相同的segfault。下面是这样一个崩溃的hs_err堆栈跟踪:

Stack: [0x00007f8535bf7000,0x00007f8535cf8000],  sp=0x00007f8535cf5bf0,  free space=1018k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x559548]  Dictionary::add_protection_domain(int, unsigned int, instanceKlassHandle, ClassLoaderData*, Handle, Thread*)+0x128
V  [libjvm.so+0xa1f4e2]  SystemDictionary::validate_protection_domain(instanceKlassHandle, Handle, Handle, Thread*)+0x142
V  [libjvm.so+0xa230d4]  SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, Thread*)+0x3c4
V  [libjvm.so+0xa24a13]  SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, Thread*)+0x33
V  [libjvm.so+0x4ea4f8]  ConstantPool::klass_at_impl(constantPoolHandle, int, Thread*)+0x158
V  [libjvm.so+0x89d141]  Method::fast_exception_handler_bci_for(methodHandle, KlassHandle, int, Thread*)+0x141
V  [libjvm.so+0x6774dd]  InterpreterRuntime::exception_handler_for_exception(JavaThread*, oopDesc*)+0x32d
j  CertRequest.setAttribute(Ljava/lang/String;Ljava/lang/String;)V+32
j  MyApp.run()V+176
j  MyApp.main([Ljava/lang/String;)V+7
v  ~StubRoutines::call_stub
V  [libjvm.so+0x681a26]  JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)+0x1056
V  [libjvm.so+0x6c3692]  jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)+0x362
V  [libjvm.so+0x6e009a]  jni_CallStaticVoidMethod+0x17a
C  [libjli.so+0x7bcc]  JavaMain+0x80c
C  [libpthread.so.0+0x7dc5]  start_thread+0xc5

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  CertRequest.setAttribute(Ljava/lang/String;Ljava/lang/String;)V+32
j  MyApp.run()V+176
j  MyApp.main([Ljava/lang/String;)V+7
v  ~StubRoutines::call_stub

共有1个答案

钮巴英
2023-03-14

事实证明,早期的函数正在损坏一些内存,这将在JNI的下一次Java调用期间导致半随机错误。

 类似资料:
  • 本文向大家介绍Java子类对象的实例化过程分析,包括了Java子类对象的实例化过程分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Java子类对象的实例化过程。分享给大家供大家参考,具体如下: 一 点睛 子类对象在实例化时,子类对象会默认先调用父类中的无参构造函数,然后再调用子类构造构造方法。 二 实战 1 代码 2 运行 ***** 父类构造:1. publicPerson() ##

  • 问题内容: 是否使用对象的新(或不同)实例来运行JUnit测试用例中的每个测试方法?还是一个实例可用于所有测试? 运行此测试时,将创建多少个类实例? 如果可能的话,提供一个指向文档或源代码的链接,我可以在其中验证行为。 问题答案: 我在JUnit文档中找不到关于您问题的明确答案,但正如anjanb所写,其目的是每个测试都独立于其他测试,因此可以为要运行的每个测试创建一个新的TestCase实例。

  • 本文向大家介绍java对象序列化操作实例分析,包括了java对象序列化操作实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了java对象序列化操作。分享给大家供大家参考,具体如下: 在java中可以将对象进行序列化操作 要使对象能够被序列化,那么被序列化的对象要实现接口Serializable,此接口位于java.io包中 序列化对象案例程序,网上的教程是将序列化的对象输出到文件,但

  • 问题内容: 我是编程的新手,我想知道实例化对象时哪里出错了。下面是代码: 问题答案: 您的代码中没有类。您声明的是私有方法。 使用当前代码段,您需要实例化该类并利用该方法。注意,在这种情况下,您的类定义前面有关键字 class。 但这并没有实际意义,您的方法总是会返回。 您是否正在尝试执行以下操作:

  • 本文向大家介绍java new一个对象的过程实例解析,包括了java new一个对象的过程实例解析的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了java new一个对象的过程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限

  • 我最近开始学习Spring。由于我是Spring的新手,我想到了几个问题。其中之一是: 如本文所述,“只要容器加载了spring配置,所有bean都会被实例化。org.springframework.context.ApplicationContext容器遵循预加载方法。”链接 1-这是否意味着使用Spring ApplicationContext创建的所有对象都是单例对象? 我创建了这个简单的测