Обзор «вспомогательных» утилит из 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 байта.

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

В следующей статье мы рассмотрим оставшиеся утилиты из набора.
  • +7
  • 29 февраля 2012, 15:38
  • e_mc2

Комментарии (17)

RSS свернуть / развернуть
Ещё бы про скрипты линкера было полезно.
0
Ну, скрипты линкера это отдельная тема.

Пока хочу рассказать о незаслуженно забытых (хотя местами бесполезных) утилитах из стандартного набора.

Просто я когда-то загорелся идеей использовать профайлер из toolchain'а. На тематических форумах писали, что он не работает, но я до последнего отказывался в это верить :) Думал: раз разработчики из раза в раз помещают утилиту gprof в состав сборки – значит она зачем-то нужна. Зачем ее помещать в сборку, если она бесполезна? Углубился в дизассемблированный код, перепробовал все известные мне toolchain'ы и убедился – утилита абсолютно бесполезна :(
0
Если очень хочется то профайлер можно сделать самому. Простой способ (для linux есть какой-то профайлер не требующий пересборки, раглядывая его код я и узнал о таком методе), берется таймер и по его срабатыванию дампится/парсится верх стека, та функция/процесс которая больше всего загружает cpu имеет больше всего шансов оказатся сдампленной.
0
Ну, в конечном итоге, я почти также и выкрутился. Просто изначально хотелось использовать «штатное» средство.
0
На тематических форумах писали, что он не работает, но я до последнего отказывался в это верить :) Думал: раз разработчики из раза в раз помещают утилиту gprof в состав сборки – значит она зачем-то нужна. Зачем ее помещать в сборку, если она бесполезна?

Как раз сейчас мне попался документик где написана причина:

Profling is enabled by means of the -pg compiler option. In this mode, the compiler inserts a call to __gnu_mcount_nc into every function prologue. However, no implementation of __gnu_mcount_nc is provided (to do so would be impossible without knowledge of the execution environment). You must provide your own implementation of __gnu_mcount_nc.
0
Перепроверил – Вы однозначно правы. С опцией -pg последняя сборка yagarto вставляет вызовы __gnu_mcount_nc в каждую функцию. Я поводил тесты около года назад и вызовов не видел. Либо я тогда где-то налажал (что скорее всего), либо ключ –pg некорректно работал в старой версии компилятора.
В любом случае – появился шанс запустить штатный профайлер. Если получится – напишу отдельную статью.
0
addr2line — по идее полезна что бы узнать строку, когда удастся словить по какому адресу рухнуло приложение
щастье в ней :)
0
objcopy и objdump очень даже полезные утилитки. Первая преобразует формат бинарика из elf в hex и bin и обратно, умеет выдёргивать отдельные секции. Вторая дампит секции в человеко-читаемом виде, в частности умеет дизасемблировать код.
0
Да, утилиты безусловно полезные. О них пойдет речь в седеющей статье.
0
На практике, в программировании под МК, библиотеки обычно распространяются в исходниках, которые программист просто включает в свой проект. Однако статические библиотеки иногда полезны. Например, таким образом можно спрятать свой «быдлокод» :)
А еще с ними удобней работать. Вот только под МК они плохо подходят по двум причинам:
1) Как правило, из-за недостатка ресурсов библиотеки для МК конфигурируются дефайнами. А при этом их приходится перекомпилировать.
2) Иногда набор команд зависит от серии МК (особенно этим PIC'и отличаются), поэтому под каждый МК приходится компилировать отдельно.

А код оттуда извлечь не так сложно — МК обычно имеют довольно немного памяти и его реально дизассемблировать и разобрать по косточкам. Метаинфа из объектников в этом существенно поможет.
0
  • avatar
  • Vga
  • 29 февраля 2012, 20:03
Утащил в софт.
0
GCC toolchain не только arm
0
GCC toolchain не только arm
Верно. Я же писал «Для примера возьмем yagarto (хотя в данном случае это не принципиально)». Просто продемонстрировал работу утилит на том, что было под рукой. Все вышесказанное актуально, например, для WINAVR.
0
Господа,
а что общего/разного между блаблабла-g++ и блаблабла-c++?
0
Общего блаблабла++
разного g/c
0
Из man g++:
«g++ is a program that calls GCC and treats .c, .h and .i files as C ++ source files instead of C source files unless -x is used, and automatically specifies linking against the C ++ library. This program is also useful when precompiling a C header file with a .h extension for use in C ++ compilations. On many systems, g++ is also installed with the name c++
0
Спасибо. Теперь понял.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.