Я разрабатываю утилиту профилирования потоков на основе предварительного загрузчика, которая подключается к Pthreads, и, похоже, она вызывает ошибку для профилируемых программ, использующих условные переменные. Вчера я задал вопрос о каком-то странном поведении, которое, по моему мнению, вызывало ошибка, но дальнейшее расследование показало, что это не является причиной (хотя и там тоже интересует ответ).
Итак, моя программа работает, предоставляя оболочки, которые заменяют соответствующие функции Pthreads во время выполнения; они ведут журнал, а затем передают аргументы реальной функции Pthreads для выполнения работы. Они не изменяют аргументы, передаваемые Pthreads. Когда мой инструмент подключен к тестовой программе (в частности, набору тестов Kyotocabinet), программа аварийно завершает работу в одном из pthread_cond_wait/signal/broadcast (тестовая программа недетерминирована), а средство фьютекса возвращает непредвиденную ошибку. Код этих функций:
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* lk) {
// log arrival at wait
the_tracer.add_event(lktrace::event::COND_WAIT, (size_t) cond);
// run pthreads function
GET_REAL_FN(pthread_cond_wait, int, pthread_cond_t*, pthread_mutex_t*);
int e = REAL_FN(cond, lk);
if (e == 0) the_tracer.add_event(lktrace::event::COND_LEAVE, (size_t) cond);
else {
the_tracer.add_event(lktrace::event::COND_ERR, (size_t) cond);
}
return e;
}
int pthread_cond_signal(pthread_cond_t* cond) {
// log cond signal
the_tracer.add_event(lktrace::event::COND_SIGNAL, (size_t) cond);
// run pthreads function
GET_REAL_FN(pthread_cond_signal, int, pthread_cond_t*);
return REAL_FN(cond);
}
int pthread_cond_broadcast(pthread_cond_t* cond) {
the_tracer.add_event(lktrace::event::COND_BRDCST, (size_t) cond);
GET_REAL_FN(pthread_cond_broadcast, int, pthread_cond_t*);
return REAL_FN(cond);
}
// GET_REAL_FN is defined as:
#define GET_REAL_FN(name, rtn, params...) \
typedef rtn (*real_fn_t)(params); \
static const real_fn_t REAL_FN = (real_fn_t) dlsym(RTLD_NEXT, #name); \
assert(REAL_FN != NULL) // semicolon absence intentional
Я вижу в GDB, что вызовы этих функций много раз завершаются успешно, прежде чем завершатся с ошибкой. Я также должен отметить, что мой инструмент имеет аналогичные оболочки для pthread_mutex_lock/unlock(), которые еще не выдавали подобных ошибок (и были протестированы гораздо больше, чем condvar).
Однако это становится более странным. Ошибка возникает из-за функции futex_wake (glibc 2.31, удалены некоторые комментарии для экономии места):
static __always_inline void
futex_wake (unsigned int* futex_word, int processes_to_wake, int private)
{
int res = lll_futex_wake (futex_word, processes_to_wake, private);
if (res >= 0)
return;
switch (res)
{
case -EFAULT:
case -EINVAL:
return;
case -ENOSYS: /* Must have been caused by a glibc bug. */
/* No other errors are documented at this time. */
default:
futex_fatal_error ();
}
}
lll_futex_wake() вызывает системный вызов futex wake, а futex_fatal_error() печатает сообщение об ошибке и вылетает. Как видите, единственной ошибкой, которая считается фатальной (при условии, что системный вызов не возвращает недокументированные ошибки), является ENOSYS [см. обновление ниже; согласно strace он возвращает EINVAL]. Однако это не имеет смысла. В комментарии говорится, что это может быть вызвано только ошибкой glibc, а руководство по glibc говорит следующее:
«Функция не реализована». Это говорит о том, что вызываемая функция вообще не реализована ни в самой библиотеке Си, ни в операционной системе. Когда вы получаете эту ошибку, вы можете быть уверены, что эта конкретная функция всегда будет давать сбой с ENOSYS, если вы не установите новую версию библиотеки C или операционной системы.
Но, как отмечалось выше, вызовы этих функций завершаются успешно много раз, прежде чем возникнет ошибка (и вообще я почти уверен, что фьютексы реализованы в стандартной Ubuntu 20.04). И это почти наверняка не ошибка в glibc, так как это происходит только тогда, когда мой инструмент подключен. Некоторые другие ответы, которые я видел, говорят, что средство futex вернуло неожиданный код ошибки, указывающий на недопустимый объект синхронизации, но я не думаю, что это то, что здесь происходит, потому что а) мои функции-оболочки не изменяют переданные им аргументы и б ) эти сообщения были о семафорах, а не о кондварах; насколько я понимаю, функции pthread_cond_* возвращают EINVAL, если condvar недействителен, они не приводят к сбою всей программы.
Так что, в общем, я довольно озадачен этой ошибкой. Мой инструмент как-то портит стек? Это проблема ABI (целевая программа и мой инструмент написаны на C++)? Системный вызов возвращает недокументированную ошибку? Я протестировал этот инструмент на другой, гораздо более простой целевой программе, использующей условные переменные, и там ошибка не возникает. Если кто-нибудь знает другую более сложную программу с открытым исходным кодом, использующую условные переменные Pthreads (или C++ std::condition_variable), которую я мог бы попытаться воспроизвести, это было бы полезно для поиска решения.
Обновление: Сделал страйк, как было предложено. Strace сообщает, что системный вызов возвращает EINVAL (источником ошибки является процесс 97114):
[pid 97114] futex(0x7fc3f0000d3a, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid 97108] <... futex resumed>) = 0
[pid 97114] <... futex resumed>) = -1 EINVAL (Invalid argument)
[pid 97108] futex(0x7fc3f0000d3e, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 97114] writev(2, [{iov_base="The futex facility returned an u"..., iov_len=54}], 1 <unfinished ...>
The futex facility returned an unexpected error code.
Однако я понятия не имею, почему EINVAL может вызвать фатальную ошибку. В источнике этого не видно. И даже если это так, я понятия не имею, почему мой трассировщик может вызвать это, учитывая, что он не изменяет аргументы, передаваемые в Pthreads.
ОБНОВЛЕНИЕ: вот соответствующая часть трассировки в дампе ядра из GDB:
#3 0x00007f0eb8296460 in __GI___libc_fatal (
message=message@entry=0x7f0eb809e000 "The futex facility returned an unexpected error code.\n") at ../sysdeps/posix/libc_fatal.c:164
#4 0x00007f0eb80963fa in futex_fatal_error () at ../sysdeps/nptl/futex-internal.h:380
#5 futex_wake (private=<optimized out>, processes_to_wake=<optimized out>,
futex_word=<optimized out>) at ../sysdeps/nptl/futex-internal.h:380
#6 __condvar_confirm_wakeup (private=<optimized out>, cond=<optimized out>)
at pthread_cond_wait.c:55
#7 __pthread_cond_wait_common (abstime=0x0, clockid=0, mutex=0x5566318e7ec0,
cond=0x7f0ea8000d12) at pthread_cond_wait.c:425
#8 __pthread_cond_wait (cond=0x7f0ea8000d12, mutex=0x5566318e7ec0) at pthread_cond_wait.c:638
#9 0x00007f0eb88a3620 in pthread_cond_wait (cond=0x5566318e8290, lk=0x5566318e7ec0)
at pthread_trace.cpp:56
#10 0x00007f0eb87b6106 in kyotocabinet::CondVar::wait (this=<optimized out>,
mutex=<optimized out>) at kcthread.cc:1839
strace
, вы сможете увидеть вызов фьютекса и возвращенную им ошибку. - person user253751   schedule 20.11.2020EINVAL
выдает фатальную ошибку где-то дальше по цепочке вызовов. Скорее всего в реальномpthread_cond_wait()
. Помните, чтоstrace
отслеживает системные вызовы. Обычные вызовы функций пользовательского пространства невидимы для него, и в этом случае можно ожидать, что вызовfutex_wake()
в любом случае будет встроенным. - person John Bollinger   schedule 20.11.2020