Подтвердить что ты не робот

Что может вызвать родную функцию Java (в C) для segfault при входе?

Проект

Я пишу интерфейс командной строки Java в библиотеку C для внутренних сетевых и сетевых средств тестирования с использованием Java Native Interface. Код C (который я не писал) является сложным и низким уровнем, часто манипулирует памятью на уровне бит и использует исключительно сырые сокеты. Приложение многопоточное со стороны C (pthreads работает в фоновом режиме), а также на стороне Java (ScheduledThreadPoolExecutors работает с потоками, которые вызывают собственный код). Тем не менее, библиотека C должна быть в основном стабильной. Код интерфейса Java и JNI, как выясняется, вызывает проблемы.

Проблема (ы)

При сбое приложения с ошибкой сегментации при входе в нативную функцию C. Это происходит только тогда, когда программа находится в определенном состоянии (то есть успешно работает определенная нативная функция, вызывает следующий вызов другой конкретной нативной функции для segfault). Кроме того, приложение выходит из строя с аналогичным видом segfault, когда выдается команда quit, но опять же, только после успешного запуска той же самой конкретной встроенной функции.

Я - неопытный разработчик C и опытный разработчик Java. Я привык к сбоям, дающим мне определенную причину и конкретный номер строки. Все, от чего мне нужно работать, в этом случае - вывод hs_err_pid*.log и дамп ядра. Я включил все, что мог, в конце этого вопроса.

Моя работа до сих пор

  • Естественно, я хотел найти конкретную строку кода, где произошел сбой. Я поместил a System.out.println() прямо перед нативным вызовами на стороне Java и printf() в качестве первой строки встроенной функции, в которой программа аварийно отключилась, используя fflush(stdout) непосредственно после. Вызвал вызов System.out, а вызов printf - нет. Это говорит мне, что segfault произошел при входе в функцию - то, что я никогда раньше не видел.
  • Я triple проверил параметры функции, чтобы они не действовали. Однако, я передаю только один параметр (типа jint). Остальные два (JNIEnv *env, jobject j_object) являются конструкциями JNI и вне моего контроля.
  • Я прокомментировал каждую строчку в функции, оставив только return 0; в конце. Сегфафт все же произошел. Это заставляет меня думать, что проблема не в этой функции.
  • Я запускал команду в разных порядках (эффективно выполнял собственные функции разных заказов). Сегменты происходят только тогда, когда перед вызовом функции сбоя запускается одна конкретная нативная функция. Эта конкретная функция, как представляется, ведет себя правильно, когда она запускается.
  • Я напечатал значение указателя env и значение &j_object ближе к концу этой другой функции, чтобы убедиться, что я их каким-то образом не повредил. Я не знаю, испортил ли я их, но оба они имеют ненулевые значения при выходе из функции.
  • Изменить 1: Как правило, одна и та же функция запускается во многих потоках (обычно не параллельно, но должна быть потокобезопасной). Я запускал функцию из основного потока без каких-либо других потоков, чтобы гарантировать, что многопоточность на стороне Java не вызовет проблемы. Это было не так, и у меня был тот же самый segfault.

Все это меня озадачивает. Почему это все еще segfault, если я прокомментирую всю функцию, за исключением оператора return? Если проблема в этой другой функции, почему она не работает? Если это проблема, когда первая функция помешает памяти, а вторая функция незаконно обращается к поврежденной памяти, почему бы не сделать ошибку в строке с незаконным доступом, а не при входе в функцию?

Если вы видите интернет-статью, где кто-то объясняет проблему, похожую на мою, прокомментируйте ее. Существует так много статей 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. Это то, что появилось при запуске 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, а затем запустил backtrace...

(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, ...
4b9b3361

Ответ 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_string здесь не имеет значения, что означает, что он ничего не указывает. Кроме того, это не совсем так. Он указывает на undefined. Он мог указать куда угодно. Поэтому, назначив ему значение sprintf(), я непреднамеренно перезаписал случайный бит памяти. Я считаю, что причина нечетного поведения (хотя я и не подтвердила это) заключалась в том, что указатель undefined указывал куда-то в стеке. Это вызвало смущение компьютера, когда вызывались определенные функции.

Один из способов исправить это - выделить память, а затем указать указатель на эту память, которая может быть выполнена с помощью 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?

Для этого:

  • Запустите GDB против двоичного файла JVM. (/usr/bin/java или любой другой JVM, который вы используете)
  • Установите аргументы в GDB как аргументы, которые вы передаете в свою JVM
  • Восстановите segfault. Похоже, это повторяемо.

Вы должны иметь возможность запускать приложение и получать его в разбитом состоянии. Как только он там, GDB должен сломаться там, где он неисправен, и вы, возможно, сможете получить трассировку стека. Убедитесь, что библиотека скомпилирована с помощью отладочных символов, чтобы вы могли видеть вызовы методов и номера строк.

Ответ 3

Как я могу написать код в будущем, который поможет мне избежать этой проблемы?

Вы можете Инициализировать/проверить против NULL, что является лучшей практикой в ​​C.

Кстати, у вас заканчивается спринтер ip: 47.0.0? Это то, что содержит часть регистра RAX в шестнадцатеричном формате:

RAX = 0x34372e302e3095e1

Хотя sprintf должен был добавить символ NULL после последнего числа.