Отладка GDB OpenJDK Java в Alpine Linux завершается с ошибкой "Поток получен сигнал?, Неизвестный сигнал"

Я с трудом пытаюсь отладить Java OpenJDK в Alpine Linux с помощью gdb - кому-нибудь удалось это сделать?

При попытке отладки Java в GDB, например, gdb java а также r -version, он мгновенно завершается с:

Thread 1 "java" recieved signal ?, Unknown signal.
__cp_end () at src/thread/x86_64/syscall_cp.s:29

Я искал и искал, но не смог найти ни ссылки, ни решения для отладки OpenJDK на Alpine.

Другие потоки, имеющие дело с той же ошибкой GDB, замеченной на других платформах (macOS Sierra, MinGW), предполагают, что recieved signal ?, Unknown signal Это может быть вызвано различными причинами, включая ошибку GDB, необработанное исключение, переполнение стека и другие ошибки приложения.

За пределами gdb java работает без проблем, а gdb отлично работает для отладки простой программы на C++. Я бегу Alpine V3.8.

Вещи, которые я пробовал:

  • Разные версии GDB (8.0.1-r6, 8.0.1-r3, 7.12.1-r1).
  • Разные версии OpenJDK (1.8.0_171, 1.7.0_181).
  • Бег из разных снарядов (/bin/ash, /bin/bash), с и без sudo,
  • Отключение остановки по сигналам в .gdbinit: handle SIGSEGV nostop noprint pass и то же самое для SIGPIPE, SIGHUP, SIGFPE, SIG34,
  • Добавление set startup-with-shell off в .gdbinit,

Спасибо за любую помощь!

Редактировать:

Вот полный стек, в который генерируется неизвестный сигнал, что приводит к сбою JVMInit:

(gdb) r -version
Starting program: /usr/lib/jvm/java-1.8-openjdk/bin/java -version
process 16214 is executing new program: /usr/lib/jvm/java-1.8-openjdk/bin/java
[New LWP 16219]

Thread 1 "java" received signal ?, Unknown signal.
__cp_end () at src/thread/x86_64/syscall_cp.s:29
29  src/thread/x86_64/syscall_cp.s: No such file or directory.
(gdb) info threads
  Id   Target Id         Frame 
* 1    LWP 16214 "java"  __cp_end () at src/thread/x86_64/syscall_cp.s:29
  2    LWP 16219 "java"  __synccall (func=func@entry=0x7ffff7da2662 <do_setrlimit>, ctx=ctx@entry=0x7ffff7ff4720)
    at src/thread/synccall.c:143
(gdb) where
#0  __cp_end () at src/thread/x86_64/syscall_cp.s:29
#1  0x00007ffff7dbed2d in __syscall_cp_c (nr=202, u=<optimized out>, v=<optimized out>, w=<optimized out>, x=<optimized out>, 
    y=<optimized out>, z=0) at src/thread/pthread_cancel.c:35
#2  0x00007ffff7dbe350 in __timedwait_cp (addr=addr@entry=0x7ffff7ff4b20, val=16219, clk=clk@entry=0, at=at@entry=0x0, priv=priv@entry=0)
    at src/thread/__timedwait.c:31
#3  0x00007ffff7dbfdc4 in __pthread_timedjoin_np (t=0x7ffff7ff4ae8, res=res@entry=0x7fffffffa348, at=at@entry=0x0)
    at src/thread/pthread_join.c:16
#4  0x00007ffff7dbfe02 in __pthread_join (t=<optimized out>, res=res@entry=0x7fffffffa348) at src/thread/pthread_join.c:27
#5  0x00007ffff7b6695e in ContinueInNewThread0 (continuation=continuation@entry=0x7ffff7b61a60 <JavaMain>, stack_size=1048576, 
    args=args@entry=0x7fffffffa3e0)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/jdk/src/solaris/bin/java_md_solinux.c:1046
#6  0x00007ffff7b634a4 in ContinueInNewThread (ifn=ifn@entry=0x7fffffffa4f0, threadStackSize=<optimized out>, argc=1, 
    argv=<optimized out>, mode=mode@entry=841574793, what=what@entry=0x0, ret=0)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/jdk/src/share/bin/java.c:2024
#7  0x00007ffff7b66a08 in JVMInit (ifn=ifn@entry=0x7fffffffa4f0, threadStackSize=<optimized out>, argc=<optimized out>, 
    argv=<optimized out>, mode=841574793, mode@entry=0, what=what@entry=0x0, ret=<optimized out>)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/jdk/src/solaris/bin/java_md_solinux.c:1093
#8  0x00007ffff7b63e30 in JLI_Launch (argc=<optimized out>, argv=<optimized out>, jargc=<optimized out>, jargv=<optimized out>, 
    appclassc=1, appclassv=0x0, fullversion=0x555555554843 "1.8.0_171-b11", dotversion=0x55555555483f "1.8", pname=0x55555555483a "java", 
    lname=0x555555554832 "openjdk", javaargs=0 '\000', cpwildcard=1 '\001', javaw=0 '\000', ergo=0)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/jdk/src/share/bin/java.c:304
#9  0x0000555555554691 in main (argc=<optimized out>, argv=<optimized out>)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/jdk/src/share/bin/main.c:125
(gdb) 

Исходные файлы musl, соответствующие этой трассировке стека:

Исходный код OpenJDK:

JVMInit пытается создать JavaMain родной поток, позвонив ContinueInNewThread, который вызывает ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args) и там он взрывается.

1 ответ

К счастью, мне удалось найти обходной путь!
Сбой gdb при отладке Java Alpine OpenJDK можно обойти следующим образом:

  • Начать GDB
  • break os::init_2
  • Запустите Java с нужными аргументами командной строки
  • Когда точка останова достигнута, set MaxFDLimit=0
  • Продолжайте и отлаживайте нормально.

Я тестировал этот обходной путь с ранним доступом OpenJDK 8 и 11, поэтому он, вероятно, будет работать и с OpenJDK 9 и 10.

К сожалению, этот обходной путь будет работать только в определенных случаях:

  • Это требует, чтобы отлаженный JDK имел символы отладки. В Alpine это означает openjdk8 вместе с openjdk8-dbg пакет или локально отлаженный отладочный OpenJDK.
  • Он подходит только для командной строки GDB и не может использоваться с внешними интерфейсами GDB, такими как CLion и Eclipse CDT.

TL; DR:

Авария происходит, когда setrlimit Функция вызывается внутри GDB. MUSL-х setrlimit реализация сигналов потоков с SIGSYNCCALL, который не поддерживается GDB, и результаты с Unknown signal ошибка. Чтобы избежать ошибки, соответствующий код инициализации JavaMain отключается отключением MaxFDLimit глобальная переменная.

Полное объяснение:

Во время инициализации JVM JavaMain Родной поток создан, и создает виртуальную машину. Во время создания виртуальной машины происходит специфическая инициализация ОС, в которой setrlimit называется. Вот соответствующая часть трассировки стека:

#0  __synccall (func=func@entry=0x7ffff7da2662 <do_setrlimit>, ctx=ctx@entry=0x7ffff7ff4720) at src/thread/synccall.c:48
#1  0x00007ffff7da26a1 in setrlimit (resource=resource@entry=7, rlim=rlim@entry=0x7ffff7ff4750) at src/misc/setrlimit.c:42
#2  0x00007ffff73bd1fe in os::init_2 ()
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/hotspot/src/os/linux/vm/os_linux.cpp:5096
#3  0x00007ffff746177d in Threads::create_vm (args=0x7ffff7ff4a20, canTryAgain=canTryAgain@entry=0x7ffff7ff4987)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/hotspot/src/share/vm/runtime/thread.cpp:3361
#4  0x00007ffff729cd48 in JNI_CreateJavaVM (vm=0x7ffff7ff4a10, penv=0x7ffff7ff4a18, args=<optimized out>)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/hotspot/src/share/vm/prims/jni.cpp:5221
#5  0x00007ffff7b61b0b in InitializeJVM (ifn=<synthetic pointer>, penv=0x7ffff7ff4a18, pvm=0x7ffff7ff4a10)
    at /home/buildozer/aports/community/openjdk8/src/icedtea-3.8.0/openjdk/jdk/src/share/bin/java.c:1231
#6  JavaMain (_args=<optimized out>)

Виновником является setrlimit вызов функции. MUSL-х setrlimit реализация AS-Safe. Ограничения обновляются во всех потоках синхронизированным способом, вызывая __synccall ( setrlimit.c):

int setrlimit(int resource, const struct rlimit *rlim)
{
    struct ctx c = { .res = resource, .rlim = rlim, .err = -1 };
    __synccall(do_setrlimit, &c);
    if (c.err) {
        if (c.err>0) errno = c.err;
        return -1;
    }
    return 0;
}

Как только темы готовы к обновлению, __synccall ( synccall.c) отправляет им SIGSYNCCALL сигнал:

r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);

Тем не менее SIGSYNCCALL сигнал является внутренним для musl и не обрабатывается GDB. GDB явно обрабатывает все типы сигналов, но SIGSYNCCALL не включается в обработанные сигналы (см. сигналы gdb.c). Поэтому, когда сигнал повышается, GDB заканчивается с Unknown signal ошибка.

Временное решение:

Обходной путь - отключение вызова setrlimit в коде OpenJDK. Соответствующий код находится в os::init_2 функция ( os_linux.cpp):

  if (MaxFDLimit) {
    // set the number of file descriptors to max. print out error
    // if getrlimit/setrlimit fails but continue regardless.
    struct rlimit nbr_files;
    int status = getrlimit(RLIMIT_NOFILE, &nbr_files);
    if (status != 0) {
      if (PrintMiscellaneous && (Verbose || WizardMode))
        perror("os::init_2 getrlimit failed");
    } else {
      nbr_files.rlim_cur = nbr_files.rlim_max;
      status = setrlimit(RLIMIT_NOFILE, &nbr_files);
      if (status != 0) {
        if (PrintMiscellaneous && (Verbose || WizardMode))
          perror("os::init_2 setrlimit failed");
      }
    }
  }

Установив MaxFDLimit 0, приведенный выше код не выполняется, и инициализация ВМ может продолжаться нормально. Есть опция командной строки для переключения этой переменной, -XX:-MaxFDLimit, но он доступен только в Solaris, поэтому у нас нет другого выбора, кроме как отключить эту переменную вручную внутри gdb.

Причина позади MaxFDLimit является историческим и предназначался для увеличения предела по умолчанию для файловых дескрипторов в старых системах, которые имели очень низкий предел FD по умолчанию (256), как описано в JDK-8010126. Alpine V3.8 имеет ограничение по умолчанию 1024, поэтому можно безопасно отключить этот код - и при необходимости ограничения могут быть увеличены с ulimit, а не самой JVM.

Изменить: GDB билет.

Другие вопросы по тегам