GrabDuck

Обзор «вспомогательных» утилит из GCC toolchain. Часть 1. / Софт для электронщика / ...

:

Думаю каждый, кто использует GCC, знает, что представляет из себя GCC toolchain. В данный комплект, помимо собственно компиляторов и линкера, входит ряд «вспомогательных» утилит из пакета GNU binutils. Эти утилиты отлично описаны в контексте применения в UNIX системах. А вот о «тонкостях» применения этих утилит при корос-компиляции под МК — информации немного. Предлагаю восполнить данный пробел.

Прежде всего, давайте вспомним, как выглядит «типичная» сборка GCC toolchain. Для примера возьмем yagarto (хотя в данном случае это не принципиально). Вот содержание каталога bin:


arm-none-eabi-addr2line.exe
arm-none-eabi-ar.exe
arm-none-eabi-as.exe
arm-none-eabi-c++.exe
arm-none-eabi-c++filt.exe
arm-none-eabi-cpp.exe
arm-none-eabi-elfedit.exe
arm-none-eabi-g++.exe
arm-none-eabi-gcc-4.6.0.exe
arm-none-eabi-gcc.exe
arm-none-eabi-gcov.exe
arm-none-eabi-gdb.exe
arm-none-eabi-gprof.exe
arm-none-eabi-ld.bfd.exe
arm-none-eabi-ld.exe
arm-none-eabi-nm.exe
arm-none-eabi-objcopy.exe
arm-none-eabi-objdump.exe
arm-none-eabi-ranlib.exe
arm-none-eabi-readelf.exe
arm-none-eabi-run.exe
arm-none-eabi-size.exe
arm-none-eabi-strings.exe
arm-none-eabi-strip.exe

«arm-none-eabi-» — это префикс, который позволяет отличить один установленный набор утилит для корос-компиляции от другого. На одном ПК может быть установлено несколько разных toolchain'ов под разные платформы и чтобы избежать конфликта имен (и не играться с переопределением PATH) все инструменты toolchain’a принято называть с определенного префикса. Например, в WINAVR название всех утилит начинается с «avr-» либо с «avr32-» (для семейств AVR и AVR32 соответственно).

Инструменты, предназначенные непосредственно для генерации кода (as, gcc, g++, ld), мы трогать не будем – это тема для отдельных статей. Также, пока, опустим отладчик (gdb). Давайте по порядку пройдемся по оставшимся утилитам.

addr2line

Как следует из названия, данная утилита позволяет получить номер сроки в С файле по абсолютному адресу в коде. На входе утилита принимает имя ELF-файла и абсолютный адрес.

Например:

arm-none-eabi-addr2line.exe -e LcdTest.elf 0x08001400

Результат:

D:\Tmp\LcdTest\Debug/../cmsis_boot/system_stm32f10x.c:666

Теперь мы знаем, что по адресу 0x08001400 в нашей прошивке содержится код, полученный при компиляции строки 666 файла system_stm32f10x.c.

Практическая полезность данной утилиты (IMHO) сомнительна. Для того чтобы утилита работала ELF файл должен содержать отладочную информацию (опция –g в GCC).

ar

Данная утилита предназначена для создания статических библиотек (*.а). На самом деле, статическая библиотека – это просто архив из объектных файлов (*.о). Соответственно, «ar» в названии утилиты – сокращение от archiver.

Например, мы хотим создать библиотеку с набором функций реализованных в файлах LED.c Font.c. Сначала компилируем эти файлы и получаем объектные файлы LED.o Font.o соответственно.

Теперь вызываем утилиту ar с ключом «q» (q – быстрое добавление в архив).

arm-none-eabi-ar.exe q liblcd.a Font.o LCD.o

liblcd.a – это имя библиотеки, которую мы хотим создать. Если библиотека с таким именем уже существует – то Font.o LCD.o будут добавлены в существующую библиотеку. В противном случае – будет создана новая библиотека.

Теперь можно подключить библиотеку к проекту, указав линкеру опцию –llcd.

Здесь следует обратить внимание, что отцы основатели (K&R) заложили следующую логику: имя библиотеки всегда начинается с префикса «lib», а в параметры линкера передается название библиотеки БЕЗ префикса и расширения. Вот такая «фича».

Рассмотрим еще несколько полезных ключей данной утилиты.

Ключ «t» позволяет просмотреть содержание архива (библиотеки):

arm-none-eabi-ar.exe t liblcd.a

Результат:

Font.o
LCD.o

Ключ «x» позволяет распаковать архив – извлечь объектные файлы из библиотеки.

arm-none-eabi-ar.exe x liblcd.a

Иногда это полезно при изучении сторонних библиотек.

На практике, в программировании под МК, библиотеки обычно распространяются в исходниках, которые программист просто включает в свой проект. Однако статические библиотеки иногда полезны. Например, таким образом можно спрятать свой «быдлокод» :)

c++filt

Утилита предназначена для декодирования имен С++ методов. Дело в том, что в С++ допускается «перегрузка» методов класса. В одном классе могут быть несколько методов с одинаковым именем (но они должны отличаться числом/типом принимаемых аргументов). При создании объектного файла компилятор кодирует имена методов определенным образом, в результате закодированные имена становятся уникальными (не повторятся в пределах объектного файла), но теряется «читабельность» имен. Вот утилита c++filt и позволяет декодировать такие имена.

Например, мы видим, что объектный файл содержит символ (метод/функцию) «getCount__7AverageFv». Вызываем c++filt и передаем ей на вход этот «шифр».

arm-none-eabi-c++filt.exe getCount__7AverageFv

Результат:

Average::getCount(void)

Все просто и понятно :)

elfedit

Утилита позволяет модифицировать некоторые поля в заголовке ELF файла. В нашем контексте – вещь абсолютно бесполезная.
gcov и gprof

Утилиты предназначены для анализа выполнения кода в рантайме. Другими словами – profiler.

Сразу скажу – во всех известных мне toolchain'ах эта, безусловно полезная, вещь не работает :(

Идея в следующем – мы компилируем нашу программу с опцией компилятора «-pg».
При этом компилятор генерирует дополнительный код при входе в каждую функцию. Этот код обирает статистику вызовов по каждой функции (сколько раз вызывалась функция, суммарное время выполнения функции).
Далее мы запускаем нашу программу, и вся статистика выгружается в специальный файл. Полученный файл мы скармливаем gprof и получаем детальный отчет по каждой функции – сколько раз она вызывалась, сколько времени выполнялась и т. д.

Например, из отчета следует, что функция А() выполнялась 90% времени от общего времени выполнения программы. Ура! Вот он кандидат для оптимизации №1! Вообще, profiler – очень полезная вещь при оптимизации.

Но, как уже было сказано, вся эта красота в нашем применении не работает. Компилятор просто не генерирует тот самый дополнительный код для сбора статистики. В этом я убедился, дизассемблируя код собранный с опцией «-pg». Об этом также пишут на форумах.

UPD: После комментария grand1987 решил еще раз все перепроверить. Оказалось компилятор (по крайней мере из yagarto ) генерирует дополнительный код (вставляет вызовы __gnu_mcount_nc() в начале каждой функции).
Попробую написать реализацию __gnu_mcount_nc() и собрать статистику.
Огромное спасибо grand1987, и извиняюсь за то, что ввел читателей в заблуждение.

nm

Утилита для анализа объектных файлов.

Сразу рассмотрим пример с ключом «-S» (S – показать размер для каждого символа):

arm-none-eabi-nm.exe -S LCD.o

Результат:

00000000 00000016 t ClrCS
00000000 00000016 t ClrRS
00000000 00000016 t ClrReset
00000000 00000016 t ClrWR
U GPIO_Init
U GPIO_ResetBits
U GPIO_SetBits
U GPIO_WriteBit
00000000 00000052 T LCD_Clear
00000000 0000004e t LCD_Delay
00000000 000000ba T LCD_DrawChar
00000000 00000158 T LCD_DrawCircle
00000000 000000d4 T LCD_DrawLine
00000000 0000003e T LCD_DrawStr
00000000 000003be T LCD_Init
00000000 00000056 t LCD_PortOutDir
00000000 0000006a t LCD_PortWrite
00000000 0000002e t LCD_SetCursor
00000000 0000002a T LCD_SetLine
00000000 00000032 T LCD_SetPoint
00000000 00000026 t LCD_WriteData
00000000 00000026 t LCD_WriteIndex
00000000 0000001a T LCD_WriteLine
00000000 00000026 t LCD_WriteReg
U RCC_APB2PeriphClockCmd
00000000 00000016 t SetRD
00000000 00000016 t SetRS
00000000 00000016 t SetReset
00000000 00000016 t SetWR

Мы видим все символы содержащиеся в данном объектном фале. Во второй колоне показан размер символа (переменной или функции) в HEX, далее идет тип символа (t – «text», функция НЕ экспортируемая из файла, static-функция; T – «text», функция экспортируемая из файла; U – внешя зависимость), далее идет собственно имя символа.

Например: 00000000 00000016 t ClrCS означает, что в данном обектном файле содержится не експортируемая (недоступная извне) функция с именем ClrCS, реализация функция занимает 16 HEX = 22 байта.

Данная утилита вполне юзабельна, более детально ознакомится с возможностями утилиты можно здесь.

В следующей статье мы рассмотрим оставшиеся утилиты из набора.