Ответ 1
Мой вопрос заключается в том, будет ли код в этих библиотеках выделять память в той же куче, что и основное приложение, или они используют свою собственную кучу?
Если библиотека использует тот же malloc/free
как приложение (например, от glibc
) - тогда да, программа и все библиотеки будут использовать одну кучу.
Если библиотека использует mmap
напрямую, она может выделять память, которая не является памятью, используемой самой программой.
Так, например, некоторая функция в .so файле вызывает malloc, будет ли она использовать тот же кучный менеджер, что и приложение или другое?
Если функция из .so вызывает malloc, этот malloc такой же, как malloc, вызванный из программы. Вы можете увидеть журнал привязки символов в Linux/glibc ( > 2.1) с помощью
LD_DEBUG=bindings ./your_program
Да, несколько экземпляров менеджеров кучи (с конфигурацией по умолчанию) не могут сосуществовать, не зная друг о друге (проблема заключается в сохранении размера кучи brk, синхронизированного между экземплярами). Но возможна конфигурация, когда несколько экземпляров могут сосуществовать.
Большинство классических реализаций malloc (ptmalloc *, dlmalloc и т.д.) могут использовать два метода получения памяти из системы: brk
и mmap
. Brk - классическая куча, которая является линейной и может расти или сокращаться. Mmap позволяет получать много памяти в любом месте; и вы можете вернуть эту память обратно в систему (бесплатно) в любом порядке.
При создании malloc метод brk может быть отключен. Затем malloc будет эмулировать линейную кучу, используя только mmap
или даже отключит классическую линейную кучу, и все распределения будут сделаны из несмежных mmaped fragmens.
Итак, некоторые библиотеки могут иметь собственный менеджер памяти, например. malloc
скомпилирован с brk
отключен или с менеджером памяти не-malloc. Этот менеджер должен иметь имена функций, отличные от malloc
и free
, например malloc1
и free1
, или не должен показывать/экспортировать эти имена в динамический компоновщик.
Также, как насчет глобальных данных в этих общих памяти. Где он лежит? Я знаю, что приложение лежит в сегменте bss и данных, но не знает, где это для этих общих файлов объектов.
Вы должны думать как о программе, так и о файлах ELF. Каждый файл ELF имеет "заголовки программ" (readelf -l elf_file
). Способ загрузки данных из ELF в память зависит от типа заголовка программы. Если тип "LOAD
", соответствующая часть файла будет конфиденциально mmap
ed (Sic!) В память. Обычно имеется 2 сегмента LOAD; первый для кода с флагами R + X (read + execute), а второй - для данных с флагами R + W (чтение + запись). Обе секции .bss
и .data
(глобальные данные) помещаются в сегмент типа LOAD с флажком с поддержкой записи.
В обеих исполняемых и разделяемых библиотеках есть сегменты LOAD. Некоторые сегменты имеют memory_size > file_size. Это означает, что сегмент будет расширен в памяти; первая часть будет заполнена данными из файла ELF, а вторая часть размера (memory_size-file_size) будет заполнена нулем (для разделов *bss
), используя mmap(/dev/zero)
и memset(0)
Когда Kernel или Dynamic компоновщик загружает ELF файл в память, они не будут думать о совместном использовании. Например, вы хотите запустить ту же программу дважды. Первый процесс будет загружать часть файла ELF только для чтения с помощью mmap; второй процесс будет делать тот же mmap (если aslr активен - второй mmap будет в другом виртуальном адресе). Задача кэша страниц (подсистема VFS) хранить одну копию данных в физической памяти (с COPY-on-WRITE aka COW); и mmap будет просто устанавливать сопоставления из виртуального адреса в каждом процессе в единое физическое местоположение. Если какой-либо процесс изменит страницу памяти; он будет скопирован при записи в уникальную частную физическую память.
Код загрузки находится в glibc/elf/dl-load.c
(_dl_map_object_from_fd
) для ld.so и linux-kernel/fs/binfmt_elf.c
для загрузчика ядра ELF (elf_map
, load_elf_binary
). Сделайте поиск PT_LOAD
.
Таким образом, глобальные данные и данные bss всегда конфиденциально обрабатываются в каждом процессе и защищены COW.
Кучи и стек выделяются во время выполнения с помощью brk + mmap (куча) и ядра ОС автоматически в виде brk-типа (для стека основного потока). Дополнительные стеки потоков распределяются с помощью mmap
в pthread_create
.