什么会导致Java本机函数(在C中)在进入时出现隔离?

项目

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

问题

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

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

我目前的工作

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

所有这些都让我感到困惑。为什么如果我注释掉整个函数,除了 return 语句,它仍然 segfault?如果问题出在另一个函数上,为什么它没有在那里失败?如果这是一个问题,即第一个函数弄乱了内存,第二个函数非法访问了损坏的内存,为什么不在非法访问的行上失败,而不是在进入函数时失败呢?

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

我的问题

是什么原因导致Java原生函数(在C中)在像这样进入时出现segfault?我可以寻找哪些具体的东西来帮助我压扁这个错误?将来如何编写代码来帮助我避免此问题?

实用信息

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

错误信息

#
# 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

---------------  T H R E A D  ---------------

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



---------------  P R O C E S S  ---------------

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


---------------  S Y S T E M  ---------------

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 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运行了它。我确保C库是用标志编译的。-g

$ 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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>...
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)]

...我在下一个命令上键入似乎导致segfault的命令;新线程是预期的...

[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)]

...我键入实际触发 segfault 的命令。新线程是预期的,因为该函数在其自己的线程中运行。如果它没有 segfault,它将创建与上一个命令相同数量的线程...

[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 ?? ()

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

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

答案 1

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

发生了什么事

分段错误的原因是我曾经为尚未分配值的指针分配一个值。这是错误的代码: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_stringsprintf()

解决此问题的一种方法是分配内存,然后将指针指向该内存,这可以通过 来完成。该解决方案将类似于以下内容: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()

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中)在像这样进入时出现segfault?

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

我可以寻找哪些具体的东西来帮助我压扁这个错误?

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

将来如何编写代码来帮助我避免此问题?

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

type *variable;

...然后寻找一条看起来像...

variable = ...;

...并确保这行在写入指向的内存之前出现。


答案 2

你有没有试过将GDB连接到JVM上?

为此,请执行以下操作:

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

您应该能够运行应用程序并使其处于中断状态。一旦它在那里,GDB应该在它出现错误的地方中断,你也许能够得到一个堆栈跟踪。确保使用调试符号编译库,以便您可以看到方法调用和行号。


推荐