valgrind: получить адрес неинициализированной памяти

Я исправляю проблему, которая возникает только в порте PPC64 моей программы.

У меня есть тестовый пример, в котором библиотеке C qsort предоставляется замыкание, сгенерированное libffi, в качестве обратного вызова сравнения строк. Строки правильно передаются обратному вызову, а возвращаемое значение сохраняется именно в буфере возвращаемого значения, переданном libffi функции закрытия.

Однако массив неправильно отсортирован по qsort. Более того, Valgrind сообщает, что код qsort библиотеки C обращается к неинициализированной памяти, а --track-orgins=yes показывает, что эта память была выделена Libffi в стеке. Я сильно подозреваю, что это возвращаемое значение, и поэтому сортировка неверна из-за сравнений мусора.

т.е. Libffi выделила буфер для возвращаемого значения и передает это значение вызывающей стороне обратного вызова; но моя функция отправки закрытия получает неправильный указатель, и поэтому возвращаемое значение не помещается в нужное место.

По какой-то странной причине Valgrind не сообщает адрес неинициализированной памяти, а только то, где в коде произошло использование и где она была выделена.

Я просто хочу сравнить адрес этого места с указателем, который передается функции закрытия: они хоть отдаленно близки?

Есть ли способ получить эту информацию от Valgrind?


ОБНОВЛЕНИЕ: я работаю на машине GCC Compile Farm, где у меня нет root; установленная libffi не имеет отладочной информации. Это версия 3.0.13.

Тем не менее, проблема воспроизводится с головой libffi git, которую я только что создал.

Я подтвердил, что это область возвращаемого значения, которая не инициализирована.

Я добавил инструкцию в ассемблерный код диспетчеризации замыкания ffi_closure_LINUX64 для инициализации области размером в двойное слово в нижней части RETVAL части кадра стека диспетчеризации замыкания. Это устраняет ошибку Valgrind; но, конечно, возвращаемое значение - мусор. Это также подтверждает базовую часть здравого смысла: код перед вызовом помощника отправки закрытия и код после ссылаются на одну и ту же область для возвращаемого значения. (Указатель стека не перемещался неожиданно, и ссылки на фреймы верны.) Любой адрес, который в конечном итоге получает пользовательский код, не указывает на это возвращаемое значение.

Затем я переместил инициализацию области возврата вниз в функцию C под названием ffi_closure_helper_LINUX64, рядом с входом в функцию. Это также по-прежнему устраняет неинициализированную ошибку, подтверждая, что помощник получает правильный адрес области возвращаемого значения через %r6 (аргумент 4).


person Kaz    schedule 26.05.2017    source источник
comment
Какую версию Valgrind вы используете? И на какой ОС?   -  person Paul Floyd    schedule 26.05.2017
comment
@PaulFloyd Хороший вопрос. Valgrind 3.9.0 в системе Fedora с Glibc 2.18.   -  person Kaz    schedule 26.05.2017
comment
Можно ли использовать 3.12? (3.13 вот-вот выйдет)   -  person Paul Floyd    schedule 26.05.2017
comment
@PaulFloyd Сейчас я отлаживаю его через libffi, используя git head.   -  person Kaz    schedule 27.05.2017


Ответы (3)


В valgrind нет возможности сообщить адрес памяти uninit, так как это (в большинстве случаев) не помогло бы пользователю: адрес стека или адрес кучи на самом деле не могут указывать на многое.

Возможно, вы получите дополнительную информацию, установив точку останова во фрейме, о котором сообщает Valgrind, и пометьте различные части стека как инициализированные с помощью команд монитора gdb+vgdb+memcheck. При установке ошибочного местоположения как инициализированного valgrind больше не должен сообщать об ошибке. Возможно, вам придется сделать несколько прогонов, каждый раз отмечая другие переменные/зоны стека.

См. http://www.valgrind.org/docs/manual/mc-manual.html#mc-manual.monitor-commands и руководство пользователя GDB, чтобы увидеть, как писать (сложные) команды, запускаемые при достижении точки останова.

person phd    schedule 26.05.2017

По какой-то странной причине Valgrind не сообщает адрес неинициализированной памяти, а только то, где в коде произошло использование и где она была выделена.

Это задокументированное поведение инструмента Valgrind Memcheck, см. эту часть руководства. о --track-orgins=yes:

Для неинициализированных значений, происходящих из выделения стека, Memcheck может сообщить вам, какая функция выделила значение, но не более того — обычно он показывает исходное расположение открывающей фигурной скобки функции. Поэтому вам следует тщательно проверить, правильно ли инициализированы все локальные переменные функции.

person ks1322    schedule 27.05.2017

Хорошо, я отладил проблему.

Проблема в том, что код PPC64 в LibFFI содержит варианты с прямым порядком байтов, которые не соответствуют моим ожиданиям.

Я применил этот тестовый патч:

--- a/src/powerpc/linux64_closure.S
+++ b/src/powerpc/linux64_closure.S
@@ -27,7 +27,8 @@
 #define LIBFFI_ASM
 #include <fficonfig.h>
 #include <ffi.h>
-
+#undef __LITTLE_ENDIAN__
+#define __LITTLE_ENDIAN__ 1
        .file   "linux64_closure.S"

 #ifdef POWERPC64

и все мои тесты проходят. Что __LITTLE_ENDIAN__ контролирует, так это условно включенные блоки кода, подобные этому:

# case FFI_TYPE_INT
# ifdef __LITTLE_ENDIAN__
        lwa %r3, RETVAL+0(%r1)
# else
        lwa %r3, RETVAL+4(%r1)
# endif
        mtlr %r0
        addi %r1, %r1, STACKFRAME
        .cfi_def_cfa_offset 0
        blr
        .cfi_def_cfa_offset STACKFRAME

Ожидается, что клиентский код с прямым порядком байтов вытеснит сохраняемое возвращаемое значение, чтобы оно было выровнено по началу 8-байтового слова.

Таким образом, чтобы сохранить int (четыре байта), ожидается, что код будет выполнять *(int *)(retptr+4) = val, а не просто *(int *)retptr = val, как это делает мой код.

Похоже, ожидается, что приложение должно хранить 8-байтовое слово в возвращаемом значении независимо от типа FFI: будь то char, short, int или (64 бита) long. То есть:

(int64_t)retptr = val; / val — это char, short, что угодно */

Таким образом, младший значащий байт значения находится в retptr + 7, и поэтому этот адрес используется, если фактический тип char; retptr + 6 используется, если это short и так далее. Таким образом, код FFI имеет смысл. Проблема в том, что это неудобно и непоследовательно; аргументы FFI не должны рассматриваться таким образом.

Например, аргумент int в следующем вызове не смещается на 4 байта; он просто записывается в базовый адрес буфера, предоставленного libffi

This is the TXR Lisp interactive listener of TXR 176.
Use the :quit command or type Ctrl-D on empty line to exit.
1> (with-dyn-lib nil (deffi printf "printf" int (str : int)))
#:lib-0185
2> (printf "foo %d\n" 1)
foo 1 
0

Но, о, смотрите; возвращаемое значение является фиктивным! Возвращаемые значения вызова внешней функции имеют аналогичную проблему.

Похоже, меня одурачил пример из какой-то документации libffi, а именно этот:

 #include <stdio.h>
 #include <ffi.h>

 int main()
 {
   ffi_cif cif;
   ffi_type *args[1];
   void *values[1];
   char *s;
   int rc;

   /* ... abbreviated ... */
       s = "This is cool!";
       ffi_call(&cif, puts, &rc, values);
       /* rc now holds the result of the call to puts */

   /* ... */
 }

Оказывается, это неправильно; в некоторых других документах libffi говорится, что возвращаемые значения должны быть захвачены с использованием типа ffi_arg (который, как ни странно, не используется для аргументов). Таким образом, приведенный выше пример должен, я думаю, делать что-то вроде этого:

ffi_arg rc_buf;
int rc;
/*...*/
s = "Turned out uncool, but we promise this is really cool now!";
ffi_call(&cif, puts, &rc_buf, values);
rc = (int) rc_buf;
person Kaz    schedule 27.05.2017