Что означает «чистый буфер» в glib c? - PullRequest
2 голосов
/ 09 апреля 2020

Я обнаружил, что приведенные ниже коды вызывают утечку кучи, если я проверяю ее с помощью tcmallo c Средство проверки кучи в драконовском режиме, но утечка не обнаруживается с помощью LSan
(я предполагаю, что внутреннее выделение в glib c подавлено в LSan)

#include <string.h>
#include <netdb.h>

int foo() {
    struct addrinfo hints, *res;
    memset(&hints, 0, sizeof hints);

    getaddrinfo("www.example.com", 0, &hints, &res);

    freeaddrinfo(res);
}

int main() {
    foo();
}

Я проверил немного больше и обнаружил, что getaddrinfo() использует буфер очистки в glib c внутренне
и подозреваю, что этот буфер очистки вызывает утечки памяти
(хотя это и не вредно)

Но, к сожалению, нет полного объяснения
, и он говорит только о том, что "рабочий буфер является буфером переменного размера с размещением в стеке по умолчанию" ;;

Что именно делает буфер очистки?

Вы можете сослаться glibc/include/scratch_buffer.h здесь

Ответы [ 2 ]

1 голос
/ 14 апреля 2020

Внутренне все интерфейсы NSS (из которых getaddrinfo - один) выглядят как gethostbyname_r:

   int gethostbyname_r(const char *name,
           struct hostent *ret, char *buf, size_t buflen,
           struct hostent **result, int *h_errnop);

вызывающий абонент предоставляет буфер для данных результата через buf, buflen байтов. Если окажется, что этого буфера недостаточно по размеру, функция завершается с ошибкой ERANGE. Ожидается, что вызывающая сторона увеличит буфер (перераспределит его каким-то образом) и вызовет функцию с другими параметрами, такими же. Это повторяется до тех пор, пока буфер не станет достаточно большим и функция завершится успешно (или функция завершится сбоем по какой-либо другой причине). Это длинная история о том, как мы получили этот странный интерфейс, но это те интерфейсы, которые мы имеем сегодня. getaddrinfo выглядит по-другому, но реализации внутренней поддержки очень похожи на функцию publi c gethostbyname_r.

Поскольку идиома повторных попыток с большим буфером так распространена во всем коде NSS , struct scratch_buffer было введено. (Раньше было довольно смешанное c сочетание фиксированных размеров буфера, alloca, alloca с malloc отступом и т. Д.) struct scratch_buffer объединяет используемый в стеке буфер фиксированного размера, который используется для первого вызова NSS. Если это не удается с ERANGE, вызывается scratch_buffer_grow, который переключается на буфер кучи и при последующих вызовах выделяет больший буфер кучи. scratch_buffer_free освобождает буфер кучи, если он есть.

В вашем примере утечки, которые tcmalloc сообщает, не связаны с чистыми буферами. (У нас, конечно, были такие ошибки в getaddrinfo, особенно на непонятных путях ошибок, но текущий код должен быть в основном нормальным.) Порядок ссылок также не является проблемой, потому что, очевидно, tcmalloc активен, иначе вы бы не получили никаких отчеты об утечках.

Причина, по которой вы видите утечки с tcmalloc (но не с другими инструментами, такими как valgrind), заключается в том, что tcmalloc не вызывает функцию magi c __libc_freeres, которая была специально добавлено для проверки кучи. Обычно, когда процесс завершается, glib c не освобождает все внутренние выделения, потому что ядро ​​все равно освободит эту память. Большинство подсистем регистрируют там распределения каким-либо образом с __libc_freeres. В примере getaddrinfo я вижу следующие еще выделенные ресурсы:

  • Результаты анализа /etc/resolv.conf (конфигурация DNS системы).
  • Результаты анализа /etc/nsswitch.conf ( Конфигурация NSS).
  • Различные структуры данных загрузчика динамического c, возникающие в результате внутренних вызовов dlopen (для загрузки модулей служб NSS.
  • Кэш для записи поддержки системы IPv4 / IPv6 в getaddrinfo.

Вы можете легко увидеть эти распределения, если запустите свой пример под valgrind, используя такую ​​команду:

valgrind --leak-check=full --show-reachable=yes --run-libc-freeres=no

Ключевая часть - --run-libc-freeres=no, который инструктирует valgrind , а не , вызывать __libc_freeres, что он делает по умолчанию. Если вы пропустите этот параметр, valgrind не будет сообщать о каких-либо утечках памяти.

1 голос
/ 09 апреля 2020

Из README google-perftools:

Чтобы перехватить все утечки кучи, tcmallo c должен быть связан last в вашем исполняемом файле. Средство проверки кучи может неверно характеризовать некоторые обращения к памяти в библиотеках, перечисленных после него в строке ссылки. Например, он может сообщить об этих библиотеках как утечка памяти, когда они не. (См. Исходный код для получения более подробной информации.)

И, как правило, lib c связывается последним.

Буфер или пустое пространство - это термин, довольно часто используемый для предварительного выделенная память (потому что время запуска обычно имеет меньшее значение, чем производительность во время выполнения), которая будет использоваться для всех видов вещей. Я не знаю точного использования этого в glib c, но я просто предполагаю, что им нужен буфер для их внутренних вычислений. Вместо того, чтобы размещать на лету, они просто используют предварительно выделенный «чистый» буфер.

LSan поддерживает подавление некоторых утечек, но вы должны сами проверить, активны ли и какие подавления в вашей сборке.

Что касается драконовского режима: я сильно подозреваю, что рабочий буфер выделяется перед вашей main функцией и освобождается после нее. В этом случае HeapChecker сообщит об этом. Не беспокойся об этом.

...