Сборка и загрузка программ: Операционные системы

Сборка и загрузка
программ
«Операционные системы»
НГУ
ФИТ-ФФ
Иртегов Д.В.
Начнем издалека
• Что такое память?
• Основная память компьютера – ОЗУ + ПЗУ
• Массив пронумерованных ячеек
• Ячейка – это один байт (у старых компьютеров, по словам)
• Номер ячейки называется ее адресом
• В первом приближении, основная память –
это память, которую вы можете адресовать указателем языка С
• Большинство компьютеров также имеет внешнюю (вторичную)
память
Внешняя память
• Это память, которую вы не можете адресовать указателями
• Магнитные ленты вообще не имеют адресации
(данные можно только читать все подряд)
• Диски, флэш-накопители – адресация по блокам
(512 байт или более)
• Чтобы работать с программами и данными во внешней памяти,
их надо загрузить (скопировать) в основную память
• ОС предоставляют удобные средства для этого
(файлы, страничную подкачку)
Сборка программ на языке C
• Формат командной строки
cc file1.c file2.c [-lgen] [-o out]
• Рекомендованный способ
cс –c file1.c
cc –c file2.c
cc file1.o file2.o
• Кроме перечисленных в командной строке файлов и библиотек,
сс по умолчанию добавляет библиотеку libc.so
(стандартную библиотеку языка C)
Еще про сборку программ на языке C
• В отличие от более новых языков (Java, Go), исходный код программы
на C не содержит указаний на то, какие внешние модули ему нужны
• Используемые внешние модули вы должны сами перечислить при
компиляции (если только это не стандартная библиотека)
• Include-файлы – это не сами внешние модули, это только описание их
интерфейсов (структур, типов, объявлений функций)
• Обычно, у каждого include-файла должны быть один или несколько
соответствующих .c-файлов с определениями функций
• У стандартной библиотеки языка C, обычно исходники вам напрямую
не доступны, есть только соответствующая объектная или разделяемая
библиотека
Что вообще такое программа?
Машинный код – последовательность команд,
лежащих в памяти
Команда – последовательность байт, состоящая
из кода операции и операндов
Например, команда b8 22 11 00 FF
B8 –код операции+операнд (movl %eax)
22 11 00 FF – операнд (константа 0xFF001122)
У фон-неймановских процессоров, есть так
называемые «команды перехода»
Аналог оператора goto
Используются для реализации циклов, if,
подпрограмм, переключения нитей и
процессов
Операнд команды перехода –
адрес в основной памяти, куда нужно перейти
Почему это важно?
• Программа содержит адреса
• Переменных
• Точек перехода
• При создании программы, надо знать, как она будет размещена в
памяти, чтобы правильно посчитать эти адреса
• Или нет?
Метки (символы ассемблера и линкера)
Метка – именованное значение
Метка – это место (адрес) в коде или данных
Метку можно использовать в операндах команд
Если значение метки по какой-то причине
поменяется, код переписывать не надо,
ассемблер сам пересчитает все операнды,
где использовалась метка
В программах на C, метки соответствуют точкам
входа функций, переменным
Также компилятор создает метки для
реализации условных операторов и циклов
Раздельная компиляция и сборка
Как пересчитать метки
при раздельной компиляции?
• Ассемблер этого сделать не может
• Он не знает, в каком месте программы окажется модуль
• Это делает линкер (сборщик)
• Ассемблер генерирует таблицы символов
• Таблица экспорта
• Таблица импорта
• Таблица перемещения
• на самом деле, это не таблица символов, в ней нету имен
• Линкер использует эти таблицы, чтобы пересчитать все символы
Маленькие хитрости
• Многие процессоры поддерживают относительную адресацию
• Относительная адресация – это когда операнд представляет
собой не адрес, а смещение относительно адреса команды
(значения счетчика команд)
• У x86 так устроены большинство команд перехода
• У ARM, также возможно обращаться к константам и данным
• Относительная адресация позволяет сильно сократить таблицу
перемещения
• Если таблицу перемещения удается сделать вообще пустой,
получается позиционно-независимый код
Объектный файл (модуль)
• Содержит бинарный код с неразрешенными ссылками
• Также содержит
• Заголовок (сигнатура, количество других секций и их размеры)
• Таблицу экспорта (символы, которые определены в этом модуле)
• Таблицу импорта (внешние символы, которые использует этот модуль,
+ для каждого символа, все точки, где на него ссылаются
• Таблицу перемещений (ссылки на внутренние метки,
которые надо пересчитать, чтобы привязать модуль к его реальному
положению в программе)
• Служебную и отладочную информацию
Программные секции
При создании метки, ассемблер знает, к какой
секции она относится
Линкер собирает код или данные каждой
секции в свое место, и учитывает это при
пересчете меток
Абсолютная и относительная загрузка
• Абсолютная загрузка: мы знаем, как программа будет размещена
в памяти
• Однопроцессные (однопрограммные) ОС
• Образы для прошивки в ПЗУ
• Виртуальная память (каждой программе выделяют свой образ)
• Относительная загрузка: мы не знаем, как программа будет
размещена в памяти
• Многопроцессные ОС без виртуальной памяти
• Динамическая сборка и загрузка
Абсолютные и относительные
исполняемые файлы
• Абсолютный (неперемещаемый) исполняемый файл:
• Содержит точную копию кода и начальные значения данных
• Просто копируется или отображается в память при загрузке
• Относительный (перемещаемый) файл
• Содержит код и данные с неразрешенными ссылками
• Имеет таблицу перемещения
• Ссылки нужно пересчитать при загрузке
Динамическая сборка
• Сборка в момент загрузки
• Разделяемые библиотеки:
• Все программы на C используют стандартную библиотеку
• Зачем держать ее в каждом исполняемом файле?
• Зачем много раз загружать ее в память?
• Если мы найдем ошибку в библиотеке, можно будет пропатчить эту
библиотеку, и все исполняемые файлы, которые ее используют,
волшебным образом получат этот патч
Динамическая загрузка
• Сборка и загрузка модулей после загрузки основной программы
(во время исполнения)
• Примеры:
• Фильтры экспорта-импорта в Word, Photoshop
• Плагины в Netscape/Firefox
• Модули Apache
• Обращения к коду на C из интерпретируемых/JiT языков (Python, Java)
• Модули ядра ОС
DLL hell
• Темная сторона динамической загрузки
• Динамически линкуемые модули взаимодействуют через ABI
• Требуется бинарная совместимость
• Точное совпадение форматов всех структур данных
• Добавили поле или изменили тип поля в структуре – сломали ABI
• Ломать совместимость иногда приходится –> версии ABI
• Конфликты версий
• Много разделяемых библиотек -> конфликты возникают
постоянно
Выходы из ситуации
• Не ломать ABI никогда
• IBM OS/360, OS/2
• Пересобирать все из исходников по месту
• *BSD, Gentoo
• «Все свое ношу с собой»
• Windows, Docker
• «Дистрибутивы» - наборы пакетов с совместимым ABI
• Debian, Red Hat, etc