ELF — Executable and Linkable Format — формат исполнимых и компонуемых файлов. Помним, что у нас в программе есть секции, которые мы хотим какими-то разными способами подгрузить в память (.text как исполняемая и записываемая, .data — записываемая, но не исполняемая). Как положить это в память так, чтобы всем было удобно?

Записываем в файлы интересной структуры — с одной стороны заголовок Program header table — показывает какие части бинарника и как надо отображать в память рассказывает не про все части бинарника, Section header table — про все части бинарника, про некоторые будет указывать, что они имеют специальное служебное значение.

Untitled

Посмотрим на реальный ELF файл с помощью утилиты readelf.

Сначала посмотрим на саму программу:

Снимок экрана 2022-01-19 в 14.22.16.png

Снимок экрана 2022-01-19 в 14.23.12.png

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

Снимок экрана 2022-01-19 в 14.25.46.png

Посмотрим на заголовки сегментов программы, которые будут отображаться в память при загрузке. Видим секции (.data и .bss компановщик объединил в одну).

Также написаны точные адреса загрузки и флаги (RWE).

Почему в Offset такие красивые смещения? Это размер страницы памяти.

Снимок экрана 2022-01-19 в 14.28.22.png

В карте виртуальной памяти всё так же (правда часть схлопнулась в одну секцию). Там где просили нецелый адрес — 0x804b00d, соответствующее отображение захватило немного больший диапазон из памяти (операционная система иначе не умеет, нужен целый адрес)

Снимок экрана 2022-01-19 в 14.31.41.png

Смотрим на секцию .data — там действительно лежит число 17, которое мы попросили

Снимок экрана 2022-01-19 в 14.38.09.png

В .rodata тоже всё весьма ожидаемо:

Снимок экрана 2022-01-19 в 14.39.50.png

метка PROGBITS — секция занимает какое-то место в исполняемом файле, отобразим кусочки исполняемого файла в память

метка NOBITS — не занимает места в исполняемом файле

Флаг A — allocate, значит во время исполнения эти секции будут отображены в память.

Снимок экрана 2022-01-19 в 14.41.59.png

Какие-то секции ELF, которые в память не отображаются (флага A нет). Посмотрим на .symtab — явно это какая-то бинарная таблица с подозрительно знакомыми адресами

Снимок экрана 2022-01-19 в 14.53.57.png

Снимок экрана 2022-01-19 в 14.54.41.png

Ага, тут хранятся символы! Но не видно имён этих символов — строковые значения хранятся отдельно в .strtab

Снимок экрана 2022-01-19 в 14.55.55.png

Как организуется то, что разные бинарники в системе могут пользоваться одними и теми же библиотеками? Можем конечно складывать в бинарник всё, что ему требуется. Плохо тем, что библиотеки обновляются — нам бы хотелось, чтобы мы могли обновить библиотеку и все бинарники начали использовать новую версию. Печально ещё из-за памяти — будет работать жутко медленно.

Поэтому будем складывать разделяемый код в разделяемые динамические библиотеки.

Вот есть разделяемая библиотека — просто поверьте, что она такая. Как собрать этот код так, чтобы его могли использовать разные бинарники.

Запускаем компилятор с опцией -shared, и ещё с разными опциями, которые пригодятся: получается файл libshared.so (so — shared object): gcc -shared -fPIC -f -m32 -Wall -Werror shared.c -o libshared.so

Снимок экрана 2022-01-19 в 15.08.05.png

gcc -o main -g -m32 -Wall -Werror main.c -no-pie -fno-rie -L. -lshared -fno-stack-protector

Получился бинарник main, который скомпонован с динамической библиотекой libshared.so

Не найдено, потому что опять ищет в стандартных путях. Делаем export LD_LIBRARY_PATH = . и теперь он находит (не хочу вставлять отдельный скриншот, просто поверьте)

Снимок экрана 2022-01-19 в 15.19.45.png

Запустили .main и всё работает. Успешно обратились к разделяемой переменной, функции работают и что-то печатают.

Снимок экрана 2022-01-19 в 15.23.33.png

Посмотрим, что у памяти программы: как прежде, лежит бинарник, но также появились разделяемые библиотеки. Как мы работаем с переменными, определенными в бинарнике и разделяемой библиотеке? У библиотеки нет фиксированного места, в которое она выгружается в памяти, поэтому адреса загрузки динамической библиотеки появляются только при загрузке программы.

примерно 37ая минута