27

プロジェクト

Java Native Interface を使用して、内部ネットワークおよびネットワーク テスト ツールの C ライブラリへの Java コマンド ライン インターフェイスを作成しています。C コード (私が書いたわけではありません) は複雑で低レベルであり、しばしばビット レベルでメモリを操作し、生のソケットのみを使用します。このアプリケーションは、C 側 (バックグラウンドで実行される pthreads) と Java 側 (ネイティブ コードを呼び出すスレッドを実行する ScheduledThreadPoolExecutors) からマルチスレッド化されています。そうは言っても、C ライブラリはほぼ安定しているはずです。結局のところ、Java および JNI インターフェース コードが問題を引き起こしています。

問題点)

ネイティブ C 関数に入ると、アプリケーションがセグメンテーション違反でクラッシュします。これは、プログラムが特定の状態にある場合にのみ発生します (つまり、特定のネイティブ関数を正常に実行すると、別の特定のネイティブ関数への次の呼び出しで segfault が発生します)。quitさらに、コマンドが発行されたときにアプリケーションが同様の segfault でクラッシュしますが、これも同じ特定のネイティブ関数が正常に実行された後でのみ発生します。

私は経験の浅い C 開発者であり、経験豊富な Java 開発者です。特定の理由と特定の行番号が表示されるクラッシュに慣れています。この場合、私が作業しなければならないのは、hs_err_pid*.log出力とコア ダンプだけです。この質問の最後にできることを含めました。

これまでの私の仕事

  1. 当然、クラッシュが発生した特定のコード行を見つけたいと思いました。System.out.println()Java 側のネイティブ呼び出しの直前にa を配置しprintf()、プログラムがクラッシュするネイティブ関数の最初の行として a を配置し、その直後に使用fflush(stdout)するようにしました。System.outコールが実行され、コールは実行されprintfませんでした。これは、セグメンテーション違反が関数に入ったときに発生したことを示しています。これまでに見たことのないものです。
  2. 関数のパラメーターをトリプルチェックして、それらが機能しないことを確認しました。ただし、(タイプのjint) 1 つのパラメーターのみを渡します。他の 2 つ ( JNIEnv *env, jobject j_object) は JNI コンストラクトであり、私には制御できません。
  3. 関数内のすべての行をコメントアウトしreturn 0;、最後に a だけを残しました。セグメンテーション違反はまだ発生していました。これにより、問題はこの機能にあるとは思えません。
  4. コマンドをさまざまな順序で実行しました (ネイティブ関数をさまざまな順序で効果的に実行しています)。segfaults は、クラッシュする関数呼び出しの前に 1 つの特定のネイティブ関数が実行された場合にのみ発生します。この特定の関数は、実行時に適切に動作するように見えます。
  5. envポインターの値とこの&j_object他の関数の末尾近くの値を出力して、何らかの形でそれらを破損しないようにしました。それらを破損したかどうかはわかりませんが、関数を終了すると両方ともゼロ以外の値になります。
  6. 編集 1:通常、同じ関数が多くのスレッドで実行されます (通常は同時にではありませんが、スレッドセーフである必要があります)。Java 側のマルチスレッドが問題を引き起こしていないことを確認するために、他のスレッドをアクティブにせずにメイン スレッドから関数を実行しました。そうではなく、同じ segfault が発生しました。

このすべてが私を困惑させます。return ステートメントを除いて、関数全体をコメントアウトしても、なぜ segfault になるのですか? 問題がこの他の関数にある場合、なぜそこで失敗しないのでしょうか? 最初の関数がメモリを台無しにし、2 番目の関数が破損したメモリに不正にアクセスする問題である場合、関数のエントリではなく、不正なアクセスのある行で if が失敗しないのはなぜですか?

誰かが私のような問題を説明しているインターネット記事を見つけたら、コメントしてください。非常に多くの segfault 記事があり、この特定の問題を含むものはないようです。SOの質問についても同様です。問題は、この問題に抽象的な解決策を適用するのに十分な経験がないことかもしれません。

私の質問

このようなエントリで Java ネイティブ関数 (C) がセグメンテーション違反になる原因は何ですか? このバグをつぶすのに役立つ特定のものを探すことができますか? 今後、この問題を回避するのに役立つコードを作成するにはどうすればよいですか?

お役立ち情報

記録のために、実際にコードを投稿することはできません。コードの説明が役立つと思われる場合は、コメントしてください。編集します。

エラーメッセージ

#
# 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 の適切な使用方法がよくわかりません。実行中に出てきたのはこれ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 ?? ()

...でコンパイルした場合、それはシンボルを持つべきではありません-gか? の出力からの行によると、私はしましたmake

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

3 に答える 3

13

私は問題を解決したようです。他の人の利益のためにここで概説します。

どうした

セグメンテーション違反の原因は、値が割り当てられていないポインターに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に値はありません。つまり、ポインターは何も指していません。ただし、それは完全に真実ではありません。それが指すものはundefinedです。それはどこでも指し示すことができます。そのため、 で値を割り当てる際にsprintf()、メモリのランダムなビットをうっかり上書きしてしまいました。奇妙な動作の理由は (これを確認したことはありませんが)、未定義のポインターがスタックのどこかを指していたためだと思います。これにより、特定の関数が呼び出されたときにコンピューターが混乱しました。

これを修正する 1 つの方法は、メモリを割り当ててから、ポインタをそのメモリにポイントすることです。これは、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;
}

これの問題は、 everymalloc()を の呼び出しで一致させる必要があるfree()か、メモリ リークが発生することです。この関数内で呼び出すとfree(ip_string)、返されたポインターは役に立たなくなります。そうしないと、この関数を呼び出しているコードに依存してメモリを解放する必要があり、非常に危険です。

私が知る限り、これに対する「正しい」解決策は、すでに割り当てられているポインターを関数に渡すことです。これにより、ポインターが指すメモリを埋めるのは関数の責任になります。malloc()このようにして、 andの呼び出しを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) がセグメンテーション違反になる原因は何ですか?

メモリが割り当てられていないポインターに値を割り当てると、スタック上のメモリを誤って上書きする可能性があります。これにより、すぐにエラーが発生することはありませんが、後で他の関数を呼び出すときに問題が発生する可能性があります。

このバグをつぶすのに役立つ特定のものを探すことができますか?

他の場合と同様に、セグメンテーション違反を探します。割り当てられていないメモリに値を割り当てたり、null ポインターを逆参照したりするようなこと。私はこれの専門家ではありませんが、これに関するWeb リソースがたくさんあることは間違いありません。

今後、この問題を回避するのに役立つコードを作成するにはどうすればよいですか?

ポインターを作成する責任がある場合は特に、ポインターに注意してください。次のようなコード行が表示された場合:

type *variable;

...次に、次のような行を探します...

variable = ...;

...そして、この行が指定されたメモリに書き込む前に来ることを確認してください。

于 2013-10-17T01:02:21.163 に答える
1

GDB を JVM に接続しようとしましたか?

これをする:

  1. JVM のバイナリに対して GDB を実行します。(/usr/bin/java、または使用している任意の JVM)
  2. GDB の引数を、JVM に渡す引数に設定します。
  3. segfault を再作成します。これは再現性がありそうです。

アプリケーションを実行して、壊れた状態にすることができるはずです。そこに到達すると、GDB はセグ フォールトが発生した場所で中断し、スタック トレースを取得できる可能性があります。メソッド呼び出しと行番号を確認できるように、ライブラリがデバッグ シンボルでコンパイルされていることを確認します。

于 2013-10-15T20:59:48.040 に答える
1

今後、この問題を回避するのに役立つコードを作成するにはどうすればよいですか?

NULL に対して初期化/検証できます。これは C でのベスト プラクティスです。

ところで、sprintf の IP は 47.0.0 で終わりますか? これは、16 進数で RAX レジスタの一部が含まれているものです。

RAX=0x34372e302e3095e1

sprintf は最後の番号の後に NULL 文字を追加する必要がありましたが。

于 2016-05-14T14:36:27.257 に答える