Кроме пошагового отладчика gdb
, при работе с памятью существуют дополнительные инструменты, предназначенные для выявления проблем.
Набор инструментов valgrind
использует контролируемое выполнение инструкций программы, модифицируя её код перед выполнением на физическом процессоре.
Основные инструменты:
memcheck
- диагностика проблем с памятью: неверные указатели на кучу, повторное освобождение, чтение неинициализированных данных и забытое освобождение памяти.callgrind
- диагностика производительности выполняемой программы.
Для запуска программы под valgrind необходимо собрать программу с отладочной информацией (опция компиляции -g
), в противном случае вывод valgrind будет не информативным.
Запуск:
> valgrind --tool=ИНСТРУМЕНТ program.jpg ARG1 ARG2 ... ARGn
В случае использования инструмента callgrind
, после выполнения программы генерируется файл callgrind.out
в формате XML, который можно визуализировать с помощью KCacheGrind (из KDE во всех современных дистрибутивах Linux), либо его кросс-платформенном аналоге QCacheGrind.
Требует для своей работы свежие версии clang
или gcc
, и позволяют выполнять инструментальный контроль во время выполнения программы значительно быстрее, чем valgrind
.
Реализуются на уровне генерации кода и замены некоторых функций, например malloc
/free
на реализации с дополнительными проверками.
Основные санитайзеры:
- AddressSanitizer (
-fsanitize=address
) - диагностирует ситуации утечек памяти, двойного освобождения памяти, выхода за границу стека или кучи, и использования указателей на стек после завершения работы функции. - MemorySanitizer (
-fsanitize=memory
) - диагностика ситуаций чтения неинициализированных данных. Требует, чтобы программа, и как и все используемые ею библиотеки, были скомпилированы в позиционно-независимый код. - UndefinedBehaviourSanitizer (
-fsanitize=undefined
) - диагностика неопределенного поведения в целочисленной арифметике: битовые сдвиги, знаковое переполнение, и т.д.
#include <sys/mman.h>
void *mmap(
void *addr, /* рекомендуемый адрес отображения */
size_t length, /* размер отображения */
int prot, /* аттрибуты доступа */
int flags, /* флаги совместного отображения */
int fd, /* файловый декскриптор фала */
off_t offset /* смещение относительно начала файла */
);
int munmap(void *addr, size_t length) /* освободить отображение */
Системный вызов mmap
предназначен для создания в виртуальном адресном пространстве процесса доступной области по определенному адресу. Эта область может быть как связана с определенным файлом (ранее открытым), так и располагаться в оперативной памяти. Второй способ использования обычно реализуется в функциях malloc
/calloc
.
Память можно выделять только постранично. Для большинства архитектур размер одной страницы равен 4Кб, хотя процессоры архитектуры x86_64 поддерживают страницы большего размера: 2Мб и 1Гб.
В общем случае, никогда нельзя полагаться на то, что размер страницы равен 4096 байт. Его можно узнать с помощью команды getconf
или функции sysconf
:
# Bash
> getconf PAGE_SIZE
4096
/* Си */
#include <unistd.h>
long page_size = sysconf(_SC_PAGE_SIZE);
Параметр offset
(если используется файл) обязан быть кратным размеру страницы; параметр length
- нет, но ядро системы округляет это значение до размера страницы в большую сторону. Параметр addr
(рекомендуемый адрес) может быть равным NULL
, - в этом случае ядро само назначает адрес в виртуальном адресном пространстве.
При использовании отображения на файл, параметр length
имеет значение длины отображаемых данных; в случае, если размер файла меньше размера страницы, или отображается его последний небольшой фрагмент, то оставшаяся часть страницы заполняется нулями.
Страницы памяти могут флаги аттрибутов доступа:
- чтение
PROT_READ
; - запись
PROT_WRITE
; - выполнение
PROT_EXE
; - ничего
PROT_NONE
.
В случае использования отображения на файл, он должен быть открыт на чтение или запись в соответствии с требуемыми аттрибутами доступа.
Флаги mmap
:
MAP_FIXED
- требует, чтобы память была выделена по указаному в первом аргументе адресу; без этого флага ядро может выбрать адрес, наиболее близкий к указанному.MAP_ANONYMOUS
- выделить страницы в оперативной памяти, а не связать с файлом.MAP_SHARED
- выделить страницы, разделяемые с другими процессами; в случае с отображением на файл - синхронизировать изменения так, чтобы они были доступны другим процессам.MAP_PRIVATE
- в противоположностьMAP_SHARED
, не делать отображение доступным другим процессам. В случае отображения на файл, он доступен для чтения, а созданные процессом изменения, в файл не сохраняются.