Недавно я написал обработчик сигнала, который использует обратную трассировку из execinfo.h, и он отлично работает на MacO, но когда он используется в Linux (Ubuntu Debian), он ожидает блокировки на неопределенный срок. Я не уверен, помогает ли это, но моя многопоточная программа (pthread) использует RocksDB для хранения данных, и я намеренно сохранил segfault в RocksDB, чтобы я мог протестировать свой обработчик сигнала, если возникнет какая-либо проблема в конце Rockdb, но я не возможность отладки, почему блокировка ожидает.
Это трассировка стека, которую я получил в GDB:
#0 futex_wait (private=0, expected=2, futex_word=0x77e088a1ac80 <main_arena>) at ../sysdeps/nptl/futex-internal.h:146
#1 __GI___lll_lock_wait_private (futex=futex@entry=0x77e088a1ac80 <main_arena>) at ./nptl/lowlevellock.c:34
#2 0x000077e0888a53c8 in __GI___libc_malloc (bytes=408) at ./malloc/malloc.c:3327
#3 0x000077e088c024a3 in malloc (size=408) at ../include/rtld-malloc.h:56
#4 _dl_scope_free (old=old@entry=0x5660325219f0) at ./elf/dl-scope.c:34
#5 0x000077e088bf3308 in _dl_map_object_deps (map=map@entry=0x566032520dc0, preloads=preloads@entry=0x0, npreloads=npreloads@entry=0,
trace_mode=trace_mode@entry=0, open_mode=open_mode@entry=-2147483648) at ./elf/dl-deps.c:635
#6 0x000077e088bfda0f in dl_open_worker_begin (a=a@entry=0x7fff4a7a5010) at ./elf/dl-open.c:592
#7 0x000077e088974a98 in __GI__dl_catch_exception (exception=exception@entry=0x7fff4a7a4e70, operate=operate@entry=0x77e088bfd900 <dl_open_worker_begin>,
args=args@entry=0x7fff4a7a5010) at ./elf/dl-error-skeleton.c:208
#8 0x000077e088bfcf9a in dl_open_worker (a=a@entry=0x7fff4a7a5010) at ./elf/dl-open.c:782
#9 0x000077e088974a98 in __GI__dl_catch_exception (exception=exception@entry=0x7fff4a7a4ff0, operate=operate@entry=0x77e088bfcf60 <dl_open_worker>,
args=args@entry=0x7fff4a7a5010) at ./elf/dl-error-skeleton.c:208
#10 0x000077e088bfd34e in _dl_open (file=<optimized out>, mode=-2147483646, caller_dlopen=0x77e088925611 <__GI___libc_unwind_link_get+81>, nsid=-2, argc=3,
argv=<optimized out>, env=0x5660324f9fe0) at ./elf/dl-open.c:883
#11 0x000077e088974e01 in do_dlopen (ptr=ptr@entry=0x7fff4a7a5240) at ./elf/dl-libc.c:95
#12 0x000077e088974a98 in __GI__dl_catch_exception (exception=exception@entry=0x7fff4a7a51e0, operate=<optimized out>, args=<optimized out>)
at ./elf/dl-error-skeleton.c:208
#13 0x000077e088974b63 in __GI__dl_catch_error (objname=0x7fff4a7a5230, errstring=0x7fff4a7a5238, mallocedp=0x7fff4a7a522f, operate=<optimized out>,
args=<optimized out>) at ./elf/dl-error-skeleton.c:227
#14 0x000077e088974f37 in dlerror_run (args=0x7fff4a7a5240, operate=0x77e088974dc0 <do_dlopen>) at ./elf/dl-libc.c:45
#15 __libc_dlopen_mode (name=name@entry=0x77e0889db527 "libgcc_s.so.1", mode=mode@entry=-2147483646) at ./elf/dl-libc.c:162
#16 0x000077e088925611 in __GI___libc_unwind_link_get () at ./misc/unwind-link.c:50
#17 __GI___libc_unwind_link_get () at ./misc/unwind-link.c:40
#18 0x000077e088933b77 in __GI___backtrace (array=array@entry=0x77e088af0000 <backtrace_frames>, size=size@entry=1) at ./debug/backtrace.c:69
#19 0x000077e088a65f92 in dumpBackTrace () at my_faultHandler.c:366
#20 0x000077e088a66027 in faultHandler (signo=6) at my_faultHandler.c:344
#21 <signal handler called>
#22 __pthread_kill_implementation (no_tid=0, signo=6, threadid=131806249563968) at ./nptl/pthread_kill.c:44
#23 __pthread_kill_internal (signo=6, threadid=131806249563968) at ./nptl/pthread_kill.c:78
#24 __GI___pthread_kill (threadid=131806249563968, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#25 0x000077e088842476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#26 0x000077e0888287f3 in __GI_abort () at ./stdlib/abort.c:79
#27 0x000077e088889676 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x77e0889dbb77 "%s\n") at ../sysdeps/posix/libc_fatal.c:155
#28 0x000077e0888a0cfc in malloc_printerr (str=str@entry=0x77e0889de5b8 "malloc_consolidate(): unaligned fastbin chunk detected") at ./malloc/malloc.c:5664
#29 0x000077e0888a198c in malloc_consolidate (av=av@entry=0x77e088a1ac80 <main_arena>) at ./malloc/malloc.c:4750
#30 0x000077e0888a3bdb in _int_malloc (av=av@entry=0x77e088a1ac80 <main_arena>, bytes=bytes@entry=32816) at ./malloc/malloc.c:3965
#31 0x000077e0888a5139 in __GI___libc_malloc (bytes=bytes@entry=32816) at ./malloc/malloc.c:3329
#32 0x000077e0888e630b in __alloc_dir (statp=0x7fff4a7a5ec0, flags=0, close_fd=true, fd=39) at ../sysdeps/unix/sysv/linux/opendir.c:115
#33 opendir_tail (fd=39) at ../sysdeps/unix/sysv/linux/opendir.c:63
#34 __opendir (name=<optimized out>) at ../sysdeps/unix/sysv/linux/opendir.c:86
#35 0x000077e087f93748 in rocksdb::(anonymous namespace)::PosixEnv::GetChildren (this=<optimized out>,
dir = "/home/dummy/rocks", result=0x7fff4a7a6080)
at /usr/include/c++/9/bits/basic_string.h:2309
#36 0x000077e087eeaae0 in rocksdb::DBImpl::FindObsoleteFiles (this=this@entry=0x56603283fc40, job_context=job_context@entry=0x7fff4a7a6180, force=force@entry=true,
--Type <RET> for more, q to quit, c to continue without paging--
no_full_scan=no_full_scan@entry=false) at db/db_impl_files.cc:200
#37 0x000077e087eccfd3 in rocksdb::DBImpl::~DBImpl (this=0x56603283fc40, __in_chrg=<optimized out>) at db/db_impl.cc:308
#38 0x000077e087ecd3f6 in rocksdb::DBImpl::~DBImpl (this=0x56603283fc40, __in_chrg=<optimized out>) at db/db_impl.cc:357
#39 0x000077e087e66e9d in rocksdb_close (db=0x5660328a2b20) at db/c.cc:627
Код обработчика сигнала:
void RegisterFaultHandler(void)
{
struct sigaction bt_action;
sigemptyset(&bt_action.sa_mask);
bt_action.sa_handler = &faultHandler;
bt_action.sa_flags = SA_RESTART | SA_ONSTACK;
if (sigaction(SIGSEGV, &bt_action, prev_action + SIGSEGV) || sigaction(SIGBUS, &bt_action, prev_action + SIGBUS) ||
sigaction(SIGILL, &bt_action, prev_action + SIGILL) || sigaction(SIGABRT, &bt_action, prev_action + SIGABRT) ||
sigaction(SIGFPE, &bt_action, prev_action + SIGFPE) || sigaction(SIGSYS, &bt_action, prev_action + SIGSYS))
{
int savedErrno = errno;
exit(1);
}
}
static void unRegisterFaultHandler()
{
/* Install 'previous' fault handler for all 'crash' (fatal) signals */
sigaction(SIGSEGV, prev_action + SIGSEGV, NULL);
sigaction(SIGBUS, prev_action + SIGBUS, NULL);
sigaction(SIGILL, prev_action + SIGILL, NULL);
sigaction(SIGABRT, prev_action + SIGABRT, NULL);
sigaction(SIGFPE, prev_action + SIGFPE, NULL);
sigaction(SIGSYS, prev_action + SIGSYS, NULL);
}
static void faultHandler(int signo)
{
/* Disable fault_handler to call previous fault handlers, if any */
unRegisterFaultHandler();
dumpBackTrace();
/* Propagate the signal back to, previous handler */
raise(signo);
}
static void dumpBackTrace()
{
int bt_fd = openBackTraceFile(); /* This will just open my file with open() system call */
if (bt_fd >= 0)
{
static void *backtrace_frames[10];
int size = backtrace(backtrace_frames, 10);
backtrace_symbols_fd(backtrace_frames, size, bt_fd);
close(bt_fd);
}
else
{
const char error[] = "Cannot open backtrace file\n";
(void)write(STDERR_FILENO, error, sizeof(error));
}
}
Я понимаю, что причиной может быть вызов malloc в кадре №3, поскольку это небезопасно, но я не знаю, как это исправить. Попробовал поискать в Интернете ответы, мне удалось добраться только до части malloc. Пожалуйста, дайте мне знать, если вам нужна дополнительная информация.
РЕДАКТИРОВАТЬ-1: Я получаю эту проблему только тогда, когда ошибка сегментации возникает в конце Rockdb, и я не получаю ее в своей программе. Я думаю, это может быть связано с ошибкой, возникающей из-за malloc_consolidate, а сама обратная трассировка снова вызывает malloc.
В обработчике сигналов разрешено делать очень мало вещей. Подробности, например. вот .
Ваш обработчик сигналов принципиально сломан - он не безопасен для асинхронных сигналов.
По С11 7.1.4 Использование библиотечных функций, пункт 4:
Функции в стандартной библиотеке не гарантируют реентерабельность и могут изменять объекты со статической или потоковой продолжительностью хранения.188
Обратите внимание ссылку на сноску 188:
- Таким образом, обработчик сигналов, как правило, не может вызывать функции стандартной библиотеки.
В соответствии с POSIX 7 «Концепции сигналов » и справочной странице Linux по безопасности сигналов существует ограниченный набор «асинхронно-безопасных» функций, которые можно вызывать из обработчика сигналов.
Функции, которых нет в этих списках, нельзя безопасно вызывать из обработчика сигналов.
Особый интерес в вашей обратной трассировке представляет эта строка:
#3 0x000077e088c024a3 in malloc (size=408) at ../include/rtld-malloc.h:56
malloc()
нет ни в одном списке функций, безопасных для асинхронных сигналов, а вызов malloc()
из обработчика сигнала — даже косвенно — небезопасен и может вызвать проблемы, такие как взаимоблокировки.
Согласно документации backtrace(), backtrace()
небезопасен для асинхронных сигналов по нескольким причинам:
Функция: int backtrace (void **buffer, размер int)
Предварительно: | МТ-Сейф | AS-Unsafe блокировка плагина init heap dlopen | AC-Unsafe init mem lock fd | См. концепции безопасности POSIX.
libunwind обеспечивает функциональность, безопасную для асинхронных сигналов, и доступен как часть большинства дистрибутивов Linux.
Понял проблему. Можете ли вы предложить какое-то решение этой проблемы?
Если вы просто хотите увидеть трассировку стека, самый простой способ — убедиться, что ваш ulimit позволяет выгружать основной файл, а затем просто посмотреть в нем трассировку стека. SEGV
и т. д. в любом случае практически не подлежат восстановлению.
Дамп ядра в порядке, но он будет потреблять много памяти, верно? поэтому лучше было бы знать простую трассировку стека.
Вся эта «память» уже была в памяти. Он просто записывается на диск...
@widesense libunwind имеет функцию трассировки стека, безопасную для асинхронных сигналов. Обычно он является частью большинства дистрибутивов Linux.
Хорошо, проверю. Спасибо
Также виден код обработчика сигнала RocksDB. Похоже, они частично позаботились об этом. github.com/facebook/rocksdb/blob/main/port/stack_trace.cc
@widesense Возможно, это худший обработчик сигналов, который я когда-либо видел. fprintf()
? popen()
? backtrace()
сама - опять? Даже fork()
больше не требуется быть безопасным для асинхронных сигналов, потому что glibc сломал его, и они потратили более десяти лет на изнурение комитета POSIX, потому что им слишком сложно исправить glibc, чтобы сделать свою реализацию fork()
безопасной для асинхронных сигналов. (что-то, что не «слишком сложно» для кого-либо еще...)
Похоже, мне есть чему поучиться. Приятно это знать.
Как уже было сказано, проблема в том, что вы можете вызывать библиотечные функции, безопасные для асинхронных сигналов, только изнутри обработчика сигналов.
Самый простой способ вызвать функции, небезопасные для асинхронного сигнала, в ответ на сигнал — это просто ответить вне обработчика сигнала.
Обычно вы можете заблокировать доставку интересных сигналов во всех потоках, используя sigprocmask(SIG_BLOCK, ...)
, за исключением выделенного потока, который может ждать в sigwaitinfo()
сигналов для обработки в неасинхронном контексте.
К сожалению, это в любом случае не работает для SIGSEGV
, поскольку оно доставляется синхронно с нарушившим поток.
Итак, у вас есть обычный обработчик сигналов, но ему нужен безопасный для асинхронных сигналов способ связи с каким-то другим (неасинхронным) контекстом, а затем, возможно, переход в режим сна до тех пор, пока этот другой контекст не будет выполнен.
Это вполне осуществимо, за исключением того, что вызов backtrace()
теперь будет происходить в другом потоке, стек вызовов которого вас не волнует.
Поскольку некоторые из ваших сигналов в любом случае не подлежат восстановлению и по умолчанию выгружают ядро, содержащее трассировку стека, просто оставить их в покое и убедиться, что ulimit разрешает основной файл, является самым простым решением.
Если вы можете использовать C++23 и компилятор, который его поддерживает, и предоставить для него безопасный для асинхронных сигналов распределитель, то это может сработать.
Если вы не можете использовать C++23 или ваш компилятор еще не поддерживает его, вы все равно можете использовать Boost.Stacktrace, который был раньше.
Тем не менее, обратите внимание на эту заметку по этому вопросу в библиотеке Boost - по сути, перечисляя некоторые причины, по которым безопасность асинхронного сигнала проблематична, и говоря, что вам нужно самостоятельно проверить источник, чтобы иметь какую-либо уверенность.
Также github.com/libunwind/libunwind
Я знал, что забыл еще кое-что, спасибо!
Даже github.com/ianlancetaylor/libbacktrace упоминается как безопасный для асинхронных сигналов. @Бесполезный
Rockdb использует C++, а моя программа и Backtrace используют c, поэтому помечены оба. При необходимости удалю. Спасибо за предупреждение @RichardCritten