Средство Futex вернуло неожиданный код ошибки в профилировщике блокировки на основе предварительного загрузчика

Я разрабатываю утилиту профилирования потоков на основе предварительного загрузчика, которая подключается к 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

person George Hodgkins    schedule 19.11.2020    source источник
comment
Если вы используете strace, вы сможете увидеть вызов фьютекса и возвращенную им ошибку.   -  person user253751    schedule 20.11.2020
comment
Я склонен предположить, что EINVAL выдает фатальную ошибку где-то дальше по цепочке вызовов. Скорее всего в реальном pthread_cond_wait(). Помните, что strace отслеживает системные вызовы. Обычные вызовы функций пользовательского пространства невидимы для него, и в этом случае можно ожидать, что вызов futex_wake() в любом случае будет встроенным.   -  person John Bollinger    schedule 20.11.2020
comment
@JohnBollinger Я не думаю, что это так, ничто выше по цепочке вызывающих вызовов не вызывает futex_fatal_error(), которая определенно является функцией, которая вызывает сигнал (все, что выше futex_wake(), находится в Pthreads, поэтому было бы странно, если бы он использовал функция фьютекса для сообщения об ошибках в любом случае). Я прикреплю соответствующие кадры обратной трассировки GDB выше для справки.   -  person George Hodgkins    schedule 20.11.2020