Отладка 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, соответствующие этой трассировке стека:
- 1: http://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_cancel.c
- 2: http://git.musl-libc.org/cgit/musl/tree/src/thread/__timedwait.c
- 3, 4: http://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_join.c
Исходный код 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 билет.