什么可以导致Java本机函数(在C中)在进入时出现段错误?

该项目

我正在使用Java Native Interface将Java命令行界面编写到内部网络和网络测试工具的C库中。 C代码(我没有写)是复杂的低级别,通常在位级操作内存,并且只使用原始套接字。 应用程序是C端的multithreading(后台运行的pthread)以及Java端(ScheduledThreadPoolExecutors运行调用本机代码的线程)。 也就是说,C库应该基本稳定。 事实certificate,Java和JNI接口代码会导致问题。

问题

应用程序在进入本机C函数时崩溃并出现分段错误。 这仅在程序处于特定状态时才会发生(即,成功运行特定本机函数会导致下一次调用另一个特定本机函数进行段错误)。 此外,在发出quit命令时,应用程序会以类似的段错误崩溃,但同样,只有在成功运行相同的特定本机函数之后才会崩溃。

我是一个没有经验的C开发人员和经验丰富的Java开发人员 – 我习惯崩溃,给我一个特定的原因和一个特定的行号。 在这种情况下我需要做的就是hs_err_pid*.log输出和核心转储。 在这个问题的最后,我已经包含了我所能做的。

我的工作到目前为止

  1. 当然,我想找到发生崩溃的特定代码行。 我在Java端的本机调用之前放置了一个System.out.println()并且将printf()作为本机函数的第一行,程序崩溃后肯定会直接使用fflush(stdout)System.out调用运行, printf调用没有。 这告诉我,在进入函数时发生了段错误 – 这是我以前从未见过的。
  2. 我将参数三重检查到函数,以确保它们不会起作用。 但是,我只传递一个参数(类型为jint )。 另外两个( JNIEnv *env, jobject j_object )是JNI构造,不受我的控制。
  3. 我注释掉了函数中的每一行,只留下了return 0; 在末尾。 段错误仍然发生。 这让我相信问题不在这个function中。
  4. 我以不同的顺序运行命令(有效地运行本机函数不同的命令)。 只有在崩溃函数调用之前运行一个特定的本机函数时才会发生段错误。 此特定function在运行时似乎表现正常。
  5. 我在另一个函数的末尾附近打印了env指针的值和&j_object的值,以确保我没有以某种方式破坏它们。 我不知道我是否损坏了它们,但是在退出函数时它们都具有非零值。
  6. 编辑1:通常,相同的函数在许multithreading中运行(通常不是并发的,但它应该是线程安全的)。 我从主线程运行该函数,而没有任何其他线程处于活动状态,以确保Java端的multithreading不会导致问题。 它不是,我得到了同样的段错误。

所有这一切都困扰着我。 如果我注释掉整个函数,除了return语句之外,为什么它仍然是段错误? 如果问题出现在另一个function中,为什么不在那里失败? 如果第一个函数弄乱内存并且第二个函数非法访问损坏的内存是一个问题,为什么不在非法访问的行上失败,而不是进入函数?

如果您看到一篇互联网文章,其中某人解释了与我类似的问题,请对其进行评论。 有很多段错误的文章,似乎都没有包含这个特定的问题。 对于SO问题同样如此。 问题也可能是我没有足够的经验来应用这个问题的抽象解决方案。

我的问题

什么可以导致Java本机函数(在C中)在这样输入时出现段错误? 我可以找到哪些具体的东西来帮我压扁这个bug? 我怎样才能在将来编写代码来帮助我避免这个问题呢?

有用的信息

为了记录,我实际上无法发布代码。 如果您认为代码的描述会有所帮助,请注释,我将对其进行编辑。

错误信息

 # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00002aaaaaf6d9c3, pid=2185, tid=1086892352 # # JRE version: 6.0_21-b06 # Java VM: Java HotSpot(TM) 64-Bit Server VM (17.0-b16 mixed mode linux-amd64 ) # Problematic frame: # j path.to.my.Object.native_function_name(I)I+0 # # An error report file with more information is saved as: # /path/to/hs_err_pid2185.log # # If you would like to submit a bug report, please visit: # http://java.sun.com/webapps/bugreport/crash.jsp # The crash happened outside the Java Virtual Machine in native code. # See problematic frame for where to report the bug. # 

hs_err_pid*.log文件的重要位

 --------------- THREAD --------------- Current thread (0x000000004fd13800): JavaThread "pool-1-thread-1" [_thread_in_native, id=2198, stack(0x0000000040b8a000,0x0000000040c8b000)] siginfo:si_signo=SIGSEGV: si_errno=0, si_code=128 (), si_addr=0x0000000000000000 Registers: RAX=0x34372e302e3095e1, RBX=0x00002aaaae39dcd0, RCX=0x0000000000000000, RDX=0x0000000000000000 RSP=0x0000000040c89870, RBP=0x0000000040c898c0, RSI=0x0000000040c898e8, RDI=0x000000004fd139c8 R8 =0x000000004fb631f0, R9 =0x000000004faf5d30, R10=0x00002aaaaaf6d999, R11=0x00002b1243b39580 R12=0x00002aaaae3706d0, R13=0x00002aaaae39dcd0, R14=0x0000000040c898e8, R15=0x000000004fd13800 RIP=0x00002aaaaaf6d9c3, EFL=0x0000000000010202, CSGSFS=0x0000000000000033, ERR=0x0000000000000000 TRAPNO=0x000000000000000d Stack: [0x0000000040b8a000,0x0000000040c8b000], sp=0x0000000040c89870, free space=3fe0000000000000018k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) j path.to.my.Object.native_function_name(I)I+0 j path.to.my.Object$CustomThread.fire()V+18 j path.to.my.CustomThreadSuperClass.run()V+1 j java.util.concurrent.Executors$RunnableAdapter.call()Ljava/lang/Object;+4 j java.util.concurrent.FutureTask$Sync.innerRun()V+30 j java.util.concurrent.FutureTask.run()V+4 j java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;)V+1 j java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run()V+15 j java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Ljava/lang/Runnable;)V+59 j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+28 j java.lang.Thread.run()V+11 v ~StubRoutines::call_stub V [libjvm.so+0x3e756d] V [libjvm.so+0x5f6f59] V [libjvm.so+0x3e6e39] V [libjvm.so+0x3e6eeb] V [libjvm.so+0x476387] V [libjvm.so+0x6ee452] V [libjvm.so+0x5f80df] Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) j path.to.my.Object.native_function_name(I)I+0 j path.to.my.Object$CustomThread.fire()V+18 j path.to.my.CustomThreadSuperClass.run()V+1 j java.util.concurrent.Executors$RunnableAdapter.call()Ljava/lang/Object;+4 j java.util.concurrent.FutureTask$Sync.innerRun()V+30 j java.util.concurrent.FutureTask.run()V+4 j java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;)V+1 j java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run()V+15 j java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Ljava/lang/Runnable;)V+59 j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+28 j java.lang.Thread.run()V+11 v ~StubRoutines::call_stub --------------- PROCESS --------------- Java Threads: ( => current thread ) 0x000000004fabc800 JavaThread "pool-1-thread-6" [_thread_new, id=2203, stack(0x0000000000000000,0x0000000000000000)] 0x000000004fbcb000 JavaThread "pool-1-thread-5" [_thread_blocked, id=2202, stack(0x0000000042c13000,0x0000000042d14000)] 0x000000004fbc9800 JavaThread "pool-1-thread-4" [_thread_blocked, id=2201, stack(0x0000000042b12000,0x0000000042c13000)] 0x000000004fbc7800 JavaThread "pool-1-thread-3" [_thread_blocked, id=2200, stack(0x0000000042a11000,0x0000000042b12000)] 0x000000004fc54800 JavaThread "pool-1-thread-2" [_thread_blocked, id=2199, stack(0x0000000042910000,0x0000000042a11000)] =>0x000000004fd13800 JavaThread "pool-1-thread-1" [_thread_in_native, id=2198, stack(0x0000000040b8a000,0x0000000040c8b000)] 0x000000004fb04800 JavaThread "Low Memory Detector" daemon [_thread_blocked, id=2194, stack(0x0000000041d0d000,0x0000000041e0e000)] 0x000000004fb02000 JavaThread "CompilerThread1" daemon [_thread_blocked, id=2193, stack(0x0000000041c0c000,0x0000000041d0d000)] 0x000000004fafc800 JavaThread "CompilerThread0" daemon [_thread_blocked, id=2192, stack(0x0000000040572000,0x0000000040673000)] 0x000000004fafa800 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=2191, stack(0x0000000040471000,0x0000000040572000)] 0x000000004fad6000 JavaThread "Finalizer" daemon [_thread_blocked, id=2190, stack(0x0000000041119000,0x000000004121a000)] 0x000000004fad4000 JavaThread "Reference Handler" daemon [_thread_blocked, id=2189, stack(0x0000000041018000,0x0000000041119000)] 0x000000004fa51000 JavaThread "main" [_thread_in_vm, id=2186, stack(0x00000000418cc000,0x00000000419cd000)] Other Threads: 0x000000004facf800 VMThread [stack: 0x0000000040f17000,0x0000000041018000] [id=2188] 0x000000004fb0f000 WatcherThread [stack: 0x0000000041e0e000,0x0000000041f0f000] [id=2195] VM state:not at safepoint (normal execution) VM Mutex/Monitor currently owned by a thread: None Heap PSYoungGen total 305856K, used 31465K [0x00002aaadded0000, 0x00002aaaf3420000, 0x00002aaaf3420000) eden space 262208K, 12% used [0x00002aaadded0000,0x00002aaadfd8a6a8,0x00002aaaedee0000) from space 43648K, 0% used [0x00002aaaf0980000,0x00002aaaf0980000,0x00002aaaf3420000) to space 43648K, 0% used [0x00002aaaedee0000,0x00002aaaedee0000,0x00002aaaf0980000) PSOldGen total 699072K, used 0K [0x00002aaab3420000, 0x00002aaadded0000, 0x00002aaadded0000) object space 699072K, 0% used [0x00002aaab3420000,0x00002aaab3420000,0x00002aaadded0000) PSPermGen total 21248K, used 3741K [0x00002aaaae020000, 0x00002aaaaf4e0000, 0x00002aaab3420000) object space 21248K, 17% used [0x00002aaaae020000,0x00002aaaae3c77c0,0x00002aaaaf4e0000) VM Arguments: jvm_args: -Xms1024m -Xmx1024m -XX:+UseParallelGC --------------- SYSTEM --------------- OS:Red Hat Enterprise Linux Client release 5.5 (Tikanga) uname:Linux 2.6.18-194.8.1.el5 #1 SMP Wed Jun 23 10:52:51 EDT 2010 x86_64 libc:glibc 2.5 NPTL 2.5 rlimit: STACK 10240k, CORE 102400k, NPROC 10000, NOFILE 1024, AS infinity load average:0.21 0.08 0.05 CPU:total 1 (1 cores per cpu, 1 threads per core) family 6 model 26 stepping 4, cmov, cx8, fxsr, mmx, sse, sse2, sse3, ssse3, sse4.1, sse4.2, popcnt Memory: 4k page, physical 3913532k(1537020k free), swap 1494004k(1494004k free) vm_info: Java HotSpot(TM) 64-Bit Server VM (17.0-b16) for linux-amd64 JRE (1.6.0_21-b06), built on Jun 22 2010 01:10:00 by "java_re" with gcc 3.2.2 (SuSE Linux) time: Tue Oct 15 15:08:13 2013 elapsed time: 13 seconds 

Valgrind输出

我真的不知道如何正确使用Valgrind。 这是运行valgrind app arg1

 ==2184== ==2184== HEAP SUMMARY: ==2184== in use at exit: 16,914 bytes in 444 blocks ==2184== total heap usage: 673 allocs, 229 frees, 32,931 bytes allocated ==2184== ==2184== LEAK SUMMARY: ==2184== definitely lost: 0 bytes in 0 blocks ==2184== indirectly lost: 0 bytes in 0 blocks ==2184== possibly lost: 0 bytes in 0 blocks ==2184== still reachable: 16,914 bytes in 444 blocks ==2184== suppressed: 0 bytes in 0 blocks ==2184== Rerun with --leak-check=full to see details of leaked memory ==2184== ==2184== For counts of detected and suppressed errors, rerun with: -v ==2184== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 7 from 7) 

编辑2:

GDB输出和回溯

我用GDB来完成它。 我确保使用-g标志编译C库。

 $ gdb `which java` GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-23.el5) Copyright (C) 2009 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later  This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: ... Reading symbols from /usr/bin/java...(no debugging symbols found)...done. (gdb) run -jar /opt/scts/scts.jar test.config Starting program: /usr/bin/java -jar /opt/scts/scts.jar test.config [Thread debugging using libthread_db enabled] Executing new program: /usr/lib/jvm/java-1.6.0-sun-1.6.0.21.x86_64/jre/bin/java [Thread debugging using libthread_db enabled] [New Thread 0x4022c940 (LWP 3241)] [New Thread 0x4032d940 (LWP 3242)] [New Thread 0x4042e940 (LWP 3243)] [New Thread 0x4052f940 (LWP 3244)] [New Thread 0x40630940 (LWP 3245)] [New Thread 0x40731940 (LWP 3246)] [New Thread 0x40832940 (LWP 3247)] [New Thread 0x40933940 (LWP 3248)] [New Thread 0x40a34940 (LWP 3249)] 

…我的程序做了一些工作,并启动后台线程…

 [New Thread 0x41435940 (LWP 3250)] 

…我在下一个命令中输入似乎导致段错误的命令; 预期新线程……

 [New Thread 0x41536940 (LWP 3252)] [New Thread 0x41637940 (LWP 3253)] [New Thread 0x41738940 (LWP 3254)] [New Thread 0x41839940 (LWP 3255)] [New Thread 0x4193a940 (LWP 3256)] 

…我输入实际触发段错误的命令。 由于函数在自己的线程中运行,因此需要新线程。 如果它没有段错误,它将创建与前一个命令相同数量的线程…

 [New Thread 0x41a3b940 (LWP 3257)] Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x41839940 (LWP 3255)] 0x00002aaaabcaec45 in ?? () 

…我疯狂地阅读gdb帮助,然后运行回溯…

 (gdb) bt #0 0x00002aaaabcaec45 in ?? () #1 0x00002aaaf3ad7800 in ?? () #2 0x00002aaaf3ad81e8 in ?? () #3 0x0000000041838600 in ?? () #4 0x00002aaaeacddcd0 in ?? () #5 0x0000000041838668 in ?? () #6 0x00002aaaeace23f0 in ?? () #7 0x0000000000000000 in ?? () 

…如果我用-g编译,那不应该有符号吗? 根据make的输出行,我做了:

 gcc -g -Wall -fPIC -c -I ... gcc -g -shared -W1,soname, ... 

看起来我已经解决了这个问题,为了其他人的利益,我将在此概述。

发生了什么

分段错误的原因是我使用sprintf()char *指针赋值,该指针尚未赋值。 这是错误的代码:

 char* ip_to_string(uint32_t ip) { unsigned char bytes[4]; bytes[0] = ip & 0xFF; bytes[1] = (ip >> 8) & 0xFF; bytes[2] = (ip >> 16) & 0xFF; bytes[3] = (ip >> 24) & 0xFF; char *ip_string; sprintf(ip_string, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); return ip_string; } 

指针ip_string在这里没有值,这意味着它指向什么都没有。 除此之外,这并非完全正确。 它指向的是未定义的 。 它可以指向任何地方。 所以在使用sprintf()为它赋值时,我无意中覆盖了一段随机的内存。 我相信奇怪行为的原因(虽然我从未证实这一点)是未定义的指针指向堆栈上的某个地方。 这导致计算机在调用某些函数时感到困惑。

解决此问题的一种方法是分配内存,然后将指针指向该内存,这可以通过malloc()来完成。 该解决方案看起来与此类似:

 char* ip_to_string(uint32_t ip) { unsigned char bytes[4]; bytes[0] = ip & 0xFF; bytes[1] = (ip >> 8) & 0xFF; bytes[2] = (ip >> 16) & 0xFF; bytes[3] = (ip >> 24) & 0xFF; char *ip_string = malloc(16); sprintf(ip_string, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); return ip_string; } 

这个问题是每个malloc()需要通过调用free()进行匹配,否则就会出现内存泄漏。 如果我在这个函数中调用free(ip_string)则返回的指针将是无用的,如果我不这样做,那么我必须依赖调用此函数的代码来释放内存,这非常危险。

据我所知,对此的“正确”解决方案是将已经分配的指针传递给函数,这样函数就有责任填充指向内存的函数。 这样,可以在代码块中调用malloc()free() 。 更安全。 这是新function:

 char* ip_to_string(uint32_t ip, char *ip_string) { unsigned char bytes[4]; bytes[0] = ip & 0xFF; bytes[1] = (ip >> 8) & 0xFF; bytes[2] = (ip >> 16) & 0xFF; bytes[3] = (ip >> 24) & 0xFF; sprintf(ip_string, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); return ip_string; } 

问题的答案

什么可以导致Java本机函数(在C中)在这样输入时出现段错误?

如果为尚未分配内存的指针分配值,则可能会意外覆盖堆栈上的内存。 这可能不会导致立即失败,但在以后调用其他函数时可能会导致问题。

我可以找到哪些具体的东西来帮我压扁这个bug?

像其他任何一样寻找分段错误。 比如为未分配的内存分配值或取消引用空指针。 我不是这方面的专家,但我愿意打赌,有很多网络资源 。

我怎样才能在将来编写代码来帮助我避免这个问题呢?

小心指针,特别是当你负责创建指针时。 如果您看到一行代码如下所示:

 type *variable; 

…然后找一条看起来像……的线

 variable = ...; 

…并确保在写入指向内存之前出现此行。

您是否尝试将GDB挂接到JVM上?

去做这个:

  1. 针对JVM的二进制文件运行GDB。 (/ usr / bin / java,或者你正在使用的任何JVM)
  2. 将GDB中的args设置为您传递到JVM的args
  3. 重新创建段错误。 这听起来像是可重复的。

您应该能够运行应用程序并使其处于损坏状态。 一旦它出现在那里,GDB应该在它出现故障的地方突破,你可能会得到一个堆栈跟踪。 确保使用调试符号编译库,以便您可以查看方法调用和行号。

我怎样才能在将来编写代码来帮助我避免这个问题呢?

您可以初始化/validationNULL,这是C中的最佳实践。

顺便说一下,你的sprintf的结尾是:47.0.0? 这就是它包含hexRAX寄存器的一部分:

RAX = 0x34372e302e3095e1

虽然sprintf应该在最后一个数字后添加一个NULL字符。