GCC Toolchain

Что такое тулчейн

Компиляторы — одна из самых сложных и интересных областей программирования. К счастью, у пользователей Linux есть отличная возможность изучать ее не только в теории, но и на практике. В любом дистрибутиве Linux можно найти GCC (GNU Compiler Collection) — набор компиляторов промышленного качества для нескольких популярных языков программирования, разработанный в рамках проекта GNU. Разумеется, исходный код GCC открыт и доступен для изучения всем желающим. Однако первые проблемы подстерегают новичков задолго до погружения в премудрости синтаксического анализа и генерации кода. Просто собрать GCC — задача не из легких.

Что такое тулчейн

Казалось бы, сборка проектов — стандартная и давно отработанная процедура. А тем, кто заинтересовался устройством GCC, рассказывать про знаменитую триаду ./configure && make && make install просто смешно. Даже богатый набор конфигурационных ключей и зависимости от вспомогательных библиотек обычно не сильно усложняют сборку. В сухой теории так оно и есть, но древо жизни GCC не просто зеленеет, а пышно цветет и колосится.

Дело в том, что GCC может нормально работать только в составе так называемого тулчейна [англ. toolchain — цепочка инструментов]. Этот англицизм, уже укоренившийся в русскоязычном профессиональном сообществе, подразумевает каскадно соединенный набор инструментов, решающий общую задачу — преобразование программы, написанной на языке программирования высокого уровня, в исполняемый файл. GCC — лишь одно из звеньев этой цепочки, которому, при всей его важности, необходимо кооперироваться с другими. В этом источник большинства проблем сборки тулчейнов. Как их решить, будет показано ниже, а сейчас кратко перечислим основные компоненты тулчейна, для простоты пока опуская некоторые вспомогательные библиотеки. » GCC — набор компиляторов с языков высокого уровня (С, C++, Ada, Fortran) в ассемблер.

  • binutils — инструменты для манипуляций с объектными файлами и компиляции ассемблерного кода в машинный.
  • libc — стандартная библиотека языка С.
  • Заголовочные файлы ядра Linux.
  • Набор отладочных инструментов (отладчик GDB; библиотеки для поиска утечек памяти duma, dmalloc; утилиты-трассировщики strace и ltrace). Строго говоря, это необязательные компоненты, однако в реальных проектах они могут оказаться очень полезны.

Типы тулчейнов

GCC поддерживает несколько десятков процессорных архитектур. ARM, x86, MIPS, SPARK, PowerPC — только самые известные из них. Процесс сборки тулчейна осложняется еще и тем, что в общем случае в него вовлечены три машины:

  • build [англ. строить] — машина, на которой выполняется сборка тулчейна.
  • host [англ. хозяин] — машина, на которой собранный тулчейн будет работать.
  • target [англ. цель] — машина, для которой этот тулчейн будет генерировать код.

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

  • Native-тулчейн [англ. native — родной], когда build = host = target. Все три архитектуры одинаковы. В качестве примера можно привести тулчейн, имеющийся в составе любого дистрибутива Linux. Он собран на машине с архитектурой x86, на ней же работает и для нее же компилирует программы.
  • Cross-тулчейн [англ. cross — крест, перекрестный], когда build = host!= target. Тулчейн собирается и работает на машине с одной архитектурой, а генерирует код для машин с другой. Классический пример — кросс-тулчейны x86/ARM, активно применяемые при программировании для встроенных систем. Такой подход позволяет программисту комфортно работать и быстро компилировать код на мощном настольном компьютере, а слабую ARM-машину использовать только для тестирования уже готовой программы.
  • Cross-native-тулчейн, когда build!= host = target.
  • Canadian-тулчейн [англ. Canadian — канадский; намек на канадскую трехпартийную политическую систему], когда build!= host!= target.

Тулчейны первых двух типов используются наиболее часто; остальные имеют более узкие сферы применения.

Crosstool-NG

Столкнувшись с проблемой, всегда полезно поинтересоваться, что в подобной ситуации делают другие. Может быть, уже есть готовое решение. Сборка тулчейнов — как раз такой случай. Для ее автоматизации есть специальный инструмент — Crosstool-NG, проект с открытым исходным кодом, который уже много лет существует и активно развивается. Не вдаваясь в детали, можно считать, что он состоит из двух частей:

  • Заимствованная из ядра Linux конфигурационная система; она позволяет задать для собираемого тулчейна необходимые параметры.
  • Набор скриптов командной оболочки для выполнения сборки в соответствии с выбранной конфигурацией.

Пользовательский интерфейс Crosstool-NG прост. Тем, кто хоть раз собирал ядро Linux, он вряд ли покажется незнакомым. Главная проблема — разобраться в назначении параметров. К счастью, Crosstool-NG содержит множество готовых конфигураций, так что даже новичку есть с чего начать свои эксперименты. Кроме того, в архиве с исходным кодом (подкаталог docs) можно найти довольно содержательную документацию, а все этапы сборки тулчейна аккуратно заносятся в журнал. Это дает отличную возможность детально разобраться в процессе сборки, не изучая исходный код Crosstool-NG.

Пробуем Crosstool-NG

Попробуем собрать тулчейн. Пока, конечно, с помощью магии Crosstool-NG. Затем принципиальные противники чародейства и волшебства смогут проанализировать полученный журнал и научиться делать то же самое вручную.

Скачаем и распакуем последнюю версию Crosstool-NG:

Как и большинство других проектов, Crosstool-NG имеет зависимости, поэтому сначала необходимо установить несколько пакетов ПО. В Ubuntu 16.04 LTS это можно сделать так:

Сборка не отличается оригинальностью:

Ключ enable-local означает, что пользователь не собирается устанавливать Crosstool-NG в систему. Это никак не ограничивает функциональность, просто все команды придется запускать из каталога ./crosstool-ng-1.23.0. Зато для полного удаления Crosstool-NG достаточно будет лишь удалить этот каталог.

Всё, Crosstool-NG готов к работе. Но прежде чем начать его использовать, стоит познакомиться с соглашением именования тулчейнов. Оно довольно простое. Название тулчейна должно состоять из четырех компонентов, разделенных дефисами: <arch>-<vendor>-<OS>-<libc/abi>, где

  • <arch> — процессорная архитектура (arm, armv7, x86_64, mips). » <vendor> — название производителя в произвольной форме (часто пишут unknown или вообще опускают).
  • <OS> — операционная система. Здесь обычно встречаются два варианта: linux и none. С первым всё ясно, а второй используется для обозначения тулчейнов специального вида. Их еще называют bare metal [англ. голое железо]. С их помощью собирают загрузчики или ПО для устройств без ОС. Для компиляции прикладных программ, работающих под управлением Linux, такие тулчейны непригодны.
  • <libc/abi> — описание используемой стандартной библиотеки (glibc, uClibc, newlib, musl) и ABI (Application Binary Interface, от англ. двоичный интерфейс приложений). ABI — это набор соглашений для доступа приложения к ОС и другим низкоуровневым сервисам. Например, в названиях ARM тулчейнов довольно часто встречается gnueabi. Это означает, что они собраны с библиотекой glibc (GNU C Library) и поддержкой EABI (Embedded ABI, от англ. двоичный интерфейс встраиваемых приложений).

Итак, возвращаемся к Crosstool-NG. Попробуем собрать cross-тулчейн x86/ARM. Посмотрим список доступных примеров конфигураций и возьмем один из них за основу:

Кажется, arm-unknown-linux-gnueabi — неплохой вариант.

А так можно посмотреть краткую справку о его параметрах:

Если всё устраивает, выбираем его:

При необходимости можно запустить систему конфигурации, чтобы подробнее изучить параметры или откорректировать их:

Теперь остается только запустить сборку и дождаться ее окончания:

Это может занять несколько десятков минут, в зависимости от мощности рабочего ПК.

Что получилось

Если последняя команда завершилась без ошибок, Crosstool-NG успешно справился с задачей. А это значит, что в ${HOME}/x-tools/arm-unknown-linux-gnueabi (путь можно изменить в системе конфигурации перед сборкой) лежит готовый тулчейн. К сожалению, содержимое этого каталога простотой не отличается. Команда find . | wc -l насчитает более 7500 файлов и каталогов, а потому имеет смысл немного в них разобраться.

Начнем с самого интересного. Файл build.log.bz2 — это подробный журнал, по которому легко восстановить каждую команду, необходимую для сборки тулчейна.

Каталог bin можно назвать пользовательским интерфейсом тулчейна. В нем лежат компиляторы и полный набор утилит из пакета binutils. Вся полезная работа выполняется через них. У имени каждой программы есть префикс <имя тулчейна>-. Например, компилятор gcc в данном случае будет называться arm-unknown-linux-gnueabi-gcc. Часто для удобства путь к этому каталогу добавляют в переменную окружения $PATH. Префикс позволяет избежать конфликта имен при одновременном использовании нескольких тулчейнов.

Каталог arm-unknown-linux-gnueabi/sysroot содержит полный набор системных заголовочных файлов, а также библиотек и программ, собранных под целевую архитектуру (в данном случае, ARM). Они реализованы в разных компонентах тулчейна и необходимы для сборки любого прикладного ПО. Кроме того, они обязательно должны присутствовать в корневой файловой системе (КФС) целевой машины. Фактически, sysroot — это почти готовая КФС. Достаточно добавить в нее busybox (набор UNIX-утилит командной строки, реализованный в виде одного исполняемого файла; часто используется во встраиваемых системах) и ядро Linux, чтобы получить минимальный дистрибутив, пригодный для запуска на машине с подходящей процессорной архитектурой.

Каталог arm-unknown-linux-gnueabi/debug-root можно считать дополнением к sysroot. Он содержит программы и библиотеки, предназначенные для отладки. Например, библиотеку libduma.so для поиска утечек памяти, трассировщики strace и ltrace или отладчик gdb.

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

Как использовать

Попробуем новый тулчейн в деле. Начнем с простейшего примера — “hello, world” на С++.

Пусть его исходный код хранится в файле main.cpp. Поскольку он в нашем проекте единственный, никакие сборочные системы не понадобятся. Даже Makefile не нужен. Достаточно просто вызвать компилятор C++:

Однако в данном случае используется g++ из системного native-тулчейна, а следовательно, получается исполняемый файл hw для архитектуры x86_64 (если на рабочем ПК установлена 64-разрядная ОС). Это легко проверить командой file ./hw, которая наряду с прочей информацией о файле печатает и его целевую процессорную архитектуру. Ну и, конечно же, программа hw должна запускаться и печатать свое приветствие миру на рабочем ПК.

Чтобы собрать тот же код для архитектуры ARM, достаточно лишь заменить g++ на собранный ранее кросс-компилятор arm-unknown-linux-gnueabi-g++:

Путь к новому тулчейну не был добавлен в переменную окружения PATH, а потому его приходится явно указывать при вызове кросс-компилятора. Это, возможно, выглядит несколько громоздко, но на первых порах так понятней. Теперь полученный исполняемый файл hw не запускается на рабочем компьютере, ведь у него другая целевая архитектура. В этом легко удостовериться, вызвав команду file ./hw.

Возьмем пример посложнее: небольшой, но полноценный проект “GNU sed”. Скачаем его исходный код:

Распакуем полученный архив:

Это классический проект GNU, для сборки которого используется система autotools (еще ее называют GNU Build System). Такие проекты, как известно, собираются тремя командами ./configure && make && make install. Однако это самый простой вариант, когда всем конфигурационным ключам присваиваются значения по умолчанию, сборка выполняется в каталоге с исходным кодом (а значит, там же создаются все промежуточные файлы, необходимые в процессе сборки), а готовое ПО устанавливается в системные каталоги. Разумеется, перспектива затереть системную утилиту sed не радует. Поэтому придется использовать чуть более сложный вариант сборки.

Допустим, исходный код sed был распакован в каталог /home/ dmitry/tc/sed/. Создадим здесь подкаталоги build и install:

Из первого будем запускать скрипт configure, а потому все промежуточные файлы будут создаваться именно там. Второй укажем в ключе —prefix, который задает путь установки собранного ПО.

Результат сборки — утилиту sed — можно найти в ./install/ bin. Она, конечно, собрана с помощью системного native-тулчейна, а потому ее целевая архитектура совпадает с архитектурой рабочего ПК. Это можно проверить уже упоминавшейся выше командой file. Разумеется, ./install/bin/sed должна без труда запускаться. Например, вот простейший тест — замена во входном тексте ‘123′ символа 2 на A:

Как же собрать sed для машин с архитектурой ARM? Ответ на этот вопрос нетрудно найти в справочной информации скрипта configure. Получить ее можно так:

Оказывается, решение довольно простое: переменная окружения CC задает компилятор, а ключ —host — целевую архитектуру.

Очистим каталоги build и install, а затем попробуем собрать sed еще раз.

Проверяем целевую архитектуру собранной программы sed:

Действительно, ARM.

Алгоритм сборки тулчейна

Как уже было сказано, Crosstool-NG ведет подробный журнал, анализ которого позволяет восстановить точную последовательность выполненных команд и детально разобраться в процедуре сборки тулчейна. Однако не стоит с ходу погружаться в его изучение. Обилие низкоуровневых деталей может помешать «за деревьями разглядеть лес». Сначала гораздо полезнее получить общее представление об алгоритме сборки. Он отлично описан в документации Crosstool-NG (docs/9_Toolchain_Construction.md). Этот раздел статьи — лишь краткий пересказ изложенных там идей. Конечно, при наличии свободного времени и хотя бы минимальных знаний английского языка лучше обратиться к оригиналу.

Компоненты тулчейна зависят друг от друга. Следовательно, важен порядок их сборки. Но это еще полбеды, главная проблема — в циклической зависимости между GCC и libc. GCC должен уметь использовать libc, а для сборки libc необходим GCC. «Проблема курицы и яйца», как говорят любители метафор. С первой зависимостью ничего сделать нельзя, а вот от второй легко избавиться. Это возможно благодаря тому, что для сборки libc вполне подходит ограниченная версия GCC, которую можно собрать специально для этой цели. В терминологии Crosstool-NG она называется “pass-2 core C compiler”. К сожалению, этому вспомогательному компилятору тоже нужна libc, но не вся, а лишь ее небольшая часть: заголовочные и стартовые файлы. Стартовые файлы по-другому называют CRT или “С runtime” (от англ. run time — период выполнения). Для их сборки можно использовать еще более урезанную версию GCC, которая от libc совершенно не зависит. Ее в Crosstool-NG называют “pass-1 core C compiler”.

Кроме того, надо не забыть учесть еще несколько зависимостей. Во-первых, для libc необходимы заголовочные файлы ядра Linux. Во-вторых, GCC генерирует код ассемблера, для компиляции которого в машинный код требуется binutils. В-третьих, GCC зависит от вспомогательных библиотек GMP, MPFR и MPC, а при определенной конфигурации — еще и от PPL, ISL, CLooG и libelf. Некоторые из них также зависят друг от друга, а потому обязательно должны собираться в указанном порядке. Это сложные математические библиотеки. Их детальное рассмотрение выходит за рамки данной статьи. Подробное обсуждение этой темы без погружения в архитектуру GCC и теорию построения компиляторов совершенно невозможно. Тем не менее, ниже назначение каждой библиотеки будет кратко описано. Однако эту информацию скорее стоит рассматривать лишь как материал для формирования поисковых запросов.

В итоге получается такая последовательность сборки:

  1. GMP (GNU Multi-Precision Library) — библиотека для вычислений с числами произвольной точности.
  2. MPFR (Multiple-Precision Floating-point computations with correct Rounding) — библиотека для вычислений с плавающей запятой произвольной точности и корректным округлением. Зависит от GMP.
  3. MPC (Multiple-Precision Complex) — библиотека для вычислений с комплексными числами произвольной точности. Зависитот GMP и MPFR.
  4. PPL (Parma Polyhedra Library) — библиотека для работы с полиэдрами (родилась в университете итальянского города Парма). Зависит от GMP.
  5. ISL (Integer Set Library) — библиотека для манипулирования целочисленными множествами и отношениями между ними.
  6. CLooG (Chunky Loop Generator) — генератор циклов в полиэдральной модели. Библиотека, используемая GCC для оптимизации генерации кода. Она является частью проекта Chunky [англ. плотный, коренастый], исследовательского инструмента, применяемого для улучшения локальности данных.
  7. libelf — библиотека для работы с форматом ELF (основной формат исполняемых файлов в ОС Linux).
  8. binutils.
  9. pass-1 core C compiler.
  10. заголовочные файлы ядра Linux.
  11. заголовочные и стартовые файлы libc.
  12. pass-2 core C compiler.
  13. полная версия libc.
  14. полная версия GCC.

Это основные компоненты тулчейна. Их сборка дает полноценный компилятор. При необходимости с его помощью нетрудно собрать дополнительные компоненты. Например, отладочные инструменты (gdb, duma, dmalloc, strace, ltrace).

Журнал

Теперь можно заняться журналом. Он хранится в каталоге с собранным тулчейном: ${HOME}/x-tools/arm-unknown-linux-gnueabi/ build.log.bz2. Это обычный сжатый текстовый файл, который без труда можно распаковать утилитой bunzip2 и открыть в любом текстовом редакторе:

В этом файле почти 300 тысяч строк. Поэтому на первый взгляд идея заняться его изучением не кажется перспективной. Однако даже беглый просмотр показывает, что значительная часть его содержимого может быть опущена — например, вывод команд wget, tar, ./configure и make, использующихся при загрузке, распаковке и компиляции компонентов тулчейна. С другой стороны, самые важные строки — исполняемые команды — снабжены специальным префиксом ==> Execution, что очень облегчает их поиск.

К сожалению, рамки данного урока не позволяют подробно проанализировать журнал и заняться сборкой тулчейна вручную. Да и, вероятно, далеко не всем интересно читать занудные списки конфигурационных параметров тех или иных компонентов тулчей-на. Всё равно ничего не запомнишь, пока своими руками с ними не повозишься. Думаю, желающие без труда смогут найти в журнале интересующие их детали самостоятельно.

Заключение

GCC-тулчейны — чрезвычайно обширная, сложная и запутанная тема. На нескольких страницах достойно осветить ее во всех подробностях совершенно нереально. Поэтому можно вполне справедливо отметить, что статья получилась поверхностной и практически каждый абзац оставляет больше вопросов, чем дает ответов. Тем не менее, надеюсь, она послужит неплохим обзором для новичков, а также стимулом для дальнейшего углубления своих познаний.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий