在C ++中与JNI一起使用的第三方jar中诊断SIGBUS

我将CLion与c ++项目(cmake)一起使用,该项目启动了jvm。 Java部分是用gradle构建的。该项目有效,但是调试时遇到了问题。

启动JVM时,我立即获得了SIGSEGV。我了解这是正常现象,除了忽略SIGSEGV之外,没有其他解决方法。有点烦人,但还算不错,因为它每次会话只发生一次。

但是,此后,我继续进行调试,并获得恒定的SIGBUS信号。

<unknown> 0x000000011f108385
<unknown> 0x000000011761dca7
<unknown> 0x000000011761dca7
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x0000000117614849
JavaCalls::call_helper(JavaValue*,methodHandle const&,JavaCallArguments*,Thread*) 0x000000010bf3a582
StackWalk::fetchFirstBatch(BaseFrameStream&,Handle,long,int,objArrayHandle,Thread*) 0x000000010c227cac
StackWalk::walk(Handle,Thread*) 0x000000010c2278fc
JVM_CallStackWalk 0x000000010bfb14a2
<unknown> 0x0000000117623950
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x0000000117614849
JavaCalls::call_helper(JavaValue*,Thread*) 0x000000010bf3a582
InstanceKlass::call_class_initializer(Thread*) 0x000000010bf22af7
InstanceKlass::initialize_impl(Thread*) 0x000000010bf2244f
Reflection::invoke_constructor(oopDesc*,Thread*) 0x000000010c1ebdbb
JVM_NewInstanceFromConstructor 0x000000010bfc14f6
<unknown> 0x0000000117623950
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761dae2
<unknown> 0x000000011761dcec
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761dae2
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x000000011761da00
<unknown> 0x0000000117614849
JavaCalls::call_helper(JavaValue*,Thread*) 0x000000010bf3a582
jni_invoke_static(JNIEnv_*,JavaValue*,_jobject*,JNICallType,_jmethodID*,JNI_ArgumentPusher*,Thread*) 0x000000010bf7e2af
jni_CallStaticVoidMethodV 0x000000010bf81c69
JNIEnv_::CallStaticVoidMethod(_jclass*,...) jni.h:1521
main main.cpp:80
start 0x00007fff6f6563d5
start 0x00007fff6f6563d5

它不会在我的代码中停止。除了忽略所有SIGBUS,我不明白为什么会这样,或者是否有可能避免它们。

我最小化了代码,并创建了最简单的示例来重现该问题。基本上,我创建了一个cpp项目,该项目以 org / junit / platform / console / ConsoleLauncher 作为主要对象(junit5)启动了jni,它进行了一个简单的测试。 SIGBUS发生了。它发生在我的测试甚至没有运行之前。

我怀疑JUnit中有什么,但不确定。有什么办法可以找到根本原因?

用于复制的样本项目在这里:https://github.com/tallavi/sigbus-reproduction

如果我运行它,您会看到代码在调用Java部分之后停止运行,没有“之后调用”,没有“ CppMainEnd”:

CppMainStart
current_path: /Users/tal/Development/v2x/qa-automation/sigbus-reproduction/out
Loading JAR: jars/junit-platform-console-standalone-1.5.2.jar
Loading JAR: jars/.DS_Store
Loading JAR: jars/junit-platform-console-standalone-1.6.0-M1.jar
Loading JAR: jars/sigbus-reproduction.jar
CreateVM:       JVM loaded successfully!
Before call
test START
test END

Thanks for using JUnit! Support its development at https://junit.org/sponsoring

.
+-- JUnit Jupiter [OK]
| '-- FirstTest [OK]
|   '-- myTest() [OK]
'-- JUnit Vintage [OK]

Test run finished after 154 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         0 tests failed          ]


Process finished with exit code 0

如果我只是将main从JUnit5更改为main并运行相同的代码,则一切正常:

CppMainStart
current_path: /Users/tal/Development/v2x/qa-automation/sigbus-reproduction/out
Loading JAR: jars/junit-platform-console-standalone-1.5.2.jar
Loading JAR: jars/.DS_Store
Loading JAR: jars/junit-platform-console-standalone-1.6.0-M1.jar
Loading JAR: jars/sigbus-reproduction.jar
CreateVM:       JVM loaded successfully!
Before call
main START
main END
After call
CppMainEnd

Process finished with exit code 0

我通过@ Oo.oO的建议来管理信号,但这当然不能解决问题。 Java代码已完成,但是如果我尝试访问该JVM(例如销毁它),它将挂起! :

在C ++中与JNI一起使用的第三方jar中诊断SIGBUS

但是,如果我让它运行(而不是尝试对其进行调试),则会崩溃并显示其他错误:

main(31549,0x1177515c0) malloc: *** error for object 0x7ffee6360628: pointer being freed was not allocated
main(31549,0x1177515c0) malloc: *** set a breakpoint in malloc_error_break to debug

使用此跟踪:

在C ++中与JNI一起使用的第三方jar中诊断SIGBUS

请注意,并非总是会发生SIGBUS,但是JVM调用之后的代码会100%地停止运行。

希望这对任何人都有意义。

更新:这就是它在lldb中的样子:

MyComputer:out tal$ lldb main
(lldb) target create "main"
Current executable set to 'main' (x86_64).
(lldb) r
Process 57274 launched: '/Users/tal/Development/v2x/qa-automation/sigbus-reproduction/out/main' (x86_64)
CppMainStart
Process 57274 stopped
* thread #1,queue = 'com.apple.main-thread',stop reason = signal SIGSEGV
    frame #0: 0x000000010b33f51b
->  0x10b33f51b: movl   (%rsi),%eax
    0x10b33f51d: leaq   0x30(%rbp),%rsi
    0x10b33f521: movl   $0x10000,%eax            ; imm = 0x10000
    0x10b33f526: andl   0x4(%rsi),%eax
Target 0: (main) stopped.
(lldb) c
Process 57274 resuming
CreateVM:       JVM loaded successfully!
Before call
Process 57274 stopped
* thread #1,stop reason = signal SIGBUS
    frame #0: 0x0000000112e263ff
->  0x112e263ff: testl  %eax,(%r10)
    0x112e26402: retq
    0x112e26403: nop
    0x112e26404: nop
Target 0: (main) stopped.
(lldb) c
Process 57274 resuming
test START
test END

Thanks for using JUnit! Support its development at https://junit.org/sponsoring

╷
├─ JUnit Jupiter ✔
│  └─ FirstTest ✔
│     └─ myTest() ✔
└─ JUnit Vintage ✔

Test run finished after 2740 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         0 tests failed          ]

After call
before destroying
after destroying
CppMainEnd
Process 57274 exited with status = 0 (0x00000000)
quanta1 回答:在C ++中与JNI一起使用的第三方jar中诊断SIGBUS

可能很难在不确切知道您所拥有的环境的情况下找到它。这里有多个因素:

  • 增强版本
  • Java版本
  • 编译器版本

如果我拿您的样品,将其剥离到最低限度(像这样)

# Linux

> g++ -o obj/main \
  -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux/ \
  -L${JAVA_HOME}/jre/lib/amd64/server -ljvm \
  -L${BOOST_LIB} -lboost_system -lboost_filesystem \
  -I$BOOST_INC src/main/cpp/main.cpp

> javac -cp jars/junit-platform-console-standalone.jar \
  -d target src/main/java/FirstTest.java

> jar cf jars/sigbus-reproduction.jar -C target .

> ./obj/main

或者在macOS上稍作修改

# macOS

> g++ -std=c++11 -o obj/main \
  -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin/ \
  -L${JAVA_HOME}/lib/server -rpath ${JAVA_HOME}/lib/server -ljvm \
  -L${BOOST_LIB} -rpath ${BOOST_LIB} -lboost_system -lboost_filesystem \
  -I$BOOST_INC src/main/cpp/main.cpp

它只是按预期工作。另外,SIGSEGVSIGBUS

中既没有gdb也没有lldb
> ./obj/main
CppMainStart
current_path: /Users/michalo/tmp/sigbus-reproduction
Loading JAR: jars/junit-platform-console-standalone.jar
Loading JAR: jars/sigbus-reproduction.jar
CreateVM:       JVM loaded successfully!
test START
test END

Thanks for using JUnit! Support its development at https://junit.org/sponsoring

╷
├─ JUnit Jupiter ✔
│  └─ FirstTest ✔
│     └─ myTest() ✔
└─ JUnit Vintage ✔

Test run finished after 5061 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         0 tests failed          ]

我想,可能需要一些时间和精力才能找到可以重现您问题的人。

调用JUnit作为方法

#include <iostream>
...
...
...

int main(int argc,char **argv) {

  // make sure to store oryginal stdout
  // JVM (JUnit) will mess with it
  int old_stdout = dup(1);

  std::cout << "CppMainStart" << std::endl;

...
...
...

  env->SetObjectArrayElement(argsArray,env->NewStringUTF("--class-path"));
  env->SetObjectArrayElement(argsArray,1,env->NewStringUTF(V2X_FILE_NAME.c_str()));
  env->SetObjectArrayElement(argsArray,2,env->NewStringUTF((std::string("--scan-classpath")).c_str()));

// instead of calling main,you can call execute

  jclass system_class     = env->FindClass( "java/lang/System");
  jfieldID field_id_out   = env->GetStaticFieldID(system_class,"out","Ljava/io/PrintStream;");
  jobject field_id_out_v  = env->GetStaticObjectField(system_class,field_id_out);

  jfieldID field_id_err   = env->GetStaticFieldID(system_class,"err","Ljava/io/PrintStream;");
  jobject field_id_err_v  = env->GetStaticObjectField(system_class,field_id_err);

  jmethodID execMethod = env->GetStaticMethodID(mainClass,"execute","(Ljava/io/PrintStream;Ljava/io/PrintStream;[Ljava/lang/String;)Lorg/junit/platform/console/ConsoleLauncherExecutionResult;");

  jobject result = env->CallStaticObjectMethod(mainClass,execMethod,field_id_out_v,field_id_err_v,argsArray);

  jvm->DestroyJavaVM();

  // restore oryginal stdout
  FILE *fp2 = fdopen(old_stdout,"w");
  *stdout = *fp2;

  std::cout  << "CppMainEnd" << std::endl << std::flush;

  return 0;
}

然后您就可以开始了。末尾有CppMainEnd

> ./obj/main
CppMainStart
current_path: /Users/michalo/tmp/sigbus-reproduction
Loading JAR: jars/junit-platform-console-standalone.jar
Loading JAR: jars/sigbus-reproduction.jar
CreateVM:       JVM loaded successfully!
test START
test END

Thanks for using JUnit! Support its development at https://junit.org/sponsoring

╷
├─ JUnit Jupiter ✔
│  └─ FirstTest ✔
│     └─ myTest() ✔
└─ JUnit Vintage ✔

Test run finished after 5060 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         0 tests failed          ]

CppMainEnd

我建议尽量减少代码的内容。制作是必不可少的。否则,您将很难找到问题的根源。

如果我运行这种代码(这确实接近JNI调用的本质)。

#include <iostream>
#include <jni.h>
#include <unistd.h>

int main(int argc,char **argv) {

  int old_stdout = dup(1);

  std::cout << "Cpp Start" << std::endl;

  JavaVM *jvm;
  JNIEnv *env;
  JavaVMInitArgs vm_args;
  JavaVMOption* options = new JavaVMOption[1];

  options[0].optionString = const_cast<char *>("-Djava.class.path=jars/junit-platform-console-standalone.jar:jars/sigbus-reproduction.jar");
  vm_args.version = JNI_VERSION_1_6;
  vm_args.nOptions = 1;
  vm_args.options = options;
  vm_args.ignoreUnrecognized = false;

  long status = JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args);

  jclass mainClass = env->FindClass("org/junit/platform/console/ConsoleLauncher");

  jclass stringClass = env->FindClass("java/lang/String");

  jobject emptyStringObject = env->NewStringUTF("");

  jobjectArray argsArray = env->NewObjectArray(3,stringClass,emptyStringObject);

  env->SetObjectArrayElement(argsArray,env->NewStringUTF("jars/sigbus-reproduction.jar"));
  env->SetObjectArrayElement(argsArray,env->NewStringUTF("--scan-classpath"));

  jclass system_class     = env->FindClass( "java/lang/System");
  jfieldID field_id_out   = env->GetStaticFieldID(system_class,"w");
  *stdout = *fp2;

  std::cout  << "CppMainEnd" << std::endl << std::flush;

  delete[] options;

  return 0;
}

lldb

中没有什么奇怪的
lldb obj/main
(lldb) target create "obj/main"
Current executable set to 'obj/main' (x86_64).
(lldb) run
Process 921 launched: '.../main' (x86_64)
Cpp Start
Process 921 stopped
* thread #1,queue = 'com.apple.main-thread',stop reason = signal SIGSEGV
    frame #0: 0x000000010b33f51b
->  0x10b33f51b: movl   (%rsi),%eax
    0x10b33f51d: leaq   0x30(%rbp),%rsi
    0x10b33f521: movl   $0x10000,%eax            ; imm = 0x10000
    0x10b33f526: andl   0x4(%rsi),%eax
Target 0: (main) stopped.
(lldb) cont
Process 921 resuming
test START
test END

Thanks for using JUnit! Support its development at https://junit.org/sponsoring

╷
├─ JUnit Jupiter ✔
│  └─ FirstTest ✔
│     └─ myTest() ✔
└─ JUnit Vintage ✔

Test run finished after 5060 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         0 tests failed          ]

CppMainEnd
Process 921 exited with status = 0 (0x00000000)

多次运行

无论我运行多少次代码,都没有SIGBUS:(

您可以像这样轻松地运行代码(数千次):

--- 8< --- CUT HERE --- lldb_run --- 8< --- CUT HERE ---

target create main
break set -n main -C "process handle --pass true --stop false SIGSEGV" -C "continue"
run
script import os; os._exit(0)

--- 8< --- CUT HERE --- lldb_run --- 8< --- CUT HERE ---

然后在循环中运行它:for i in {1..100}; do lldb --source ./lldb_run; done

,

您错误地假设诸如SIGSEGVSIGBUS之类的信号表示Java中存在问题。您还可能破坏诸如空指针检测之类的东西。

Why am I seeing SIGSEGV when I strace a Java application on Linux?!

  

主要文章

     

大多数使用Unix的人都有一段时间,他们偶尔会因编写不良的程序而看到“分段错误(内核已转储)”。如果这是您对Unix的全部了解,并且您查看了Java进程上strace的输出,您会认为某些事情是严重错误的(“哇,看看所有这些段错误。Sun/ Oracle的那些人一定是可怕的程序员,他们没有不知道他们在做什么!”)。

     

真实的情况完全不同-SIGSEGV在Java进程中几乎总是完全正常且完全安全的。

     

...

     

JVM是一个多线程进程,因此在幕后它使用信号进行OS级线程化。 ...

     

...

     

信号说明

     
      
  • SIGSEGVSIGBUSSIGFPESIGPIPESIGILL在实现中用于隐式null检查,等等。
  •   
  • SIGQUIT线程转储支持:以标准错误流转储Java堆栈跟踪。 (可选。)
  •   
     

...

     

表从http://download.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/signals.html被盗批发

that link

  

6.1 Solaris OS和Linux上的信号处理

     

HotSpot虚拟机安装信号处理程序以实现各种功能并处理致命的错误情况。例如,为了避免在很少抛出java.lang.NullPointerException,捕获并处理SIGSEGV信号以及抛出NullPointerException的情况下避免显式null检查的优化。

     

通常情况下,会出现两类信号/陷阱。

     
      
  • 期望和处理信号的情况。示例包括上面引用的隐式空处理。另一个示例是安全点轮询机制,该机制可在需要安全点时保护内存中的页面。任何访问该页面的线程都会导致SIGSEGV,这将导致存根的执行,该存根会将线程带到安全点。

  •   
  • 意外信号。在VM代码,JNI代码或本机代码中执行时,这包括SIGSEGV。在这些情况下,信号是意外的,因此将调用致命错误处理来创建错误日志并终止该过程。

  •   

如果您需要处理致命信号,请参阅Signal Handling on Linux when using Java/JNI

本文链接:https://www.f2er.com/2910549.html

大家都在问