Целью данной работы является получение кода, сгенерированного бэк-эндом компилятора LLVM MSP430
Введение
CLANG — это просто фронт-энд компилятора С для LLVM. LLVM — это фреймворк для разработки фронт-эндов, бэк-эндов компиляторов для разных целевых платформ. Этот фреймворк облегчает анализ путем приведения всех фронт-эндов к промежуточному представлению (IR). Все бэк-энды оперируют с этим общим представлением.
Одна из целевых платформ, которая меня интересует, это MSP430 — популярный встраиваемый микроконтроллер, используемый во многих микропотребляющих приложениях.
Текущая поддержка MSP430 весьма ограничена. Я надеюсь, что смогу помочь кому-нибудь, упорно фейс-палмя, чтобы сохранить их лицо.
Поехали
Текущая поддержка данной платформы предельно ограничена. Надеюсь, я смогу восполнить этот пробел. Ниже приведена упрощенная диаграмма того, как должна происходить сборка:
[Source Code (.cpp)] -> [clang] --.ll --> [llc] --.s --> [msp430-gcc] --> elf
Clang берет ваш исходный код и компилирует его, основываясь на предоставленных параметрах. По умолчанию он скрывает много чего под своей оболочкой. Все это не что иное, как попытка сделать процесс более приятным для разработчика. Другими словами, обычно вы никогда не заглянете в промежуточное представление IR, ассемблер или линковку.
Что упущено в документации по целевой платформе MSP430, так это факт, что clang + llvm на самом деле не будет генерировать для вас бинарник elf. Кодогенератор просто производит ассемблерный код для вашей платформы. Это ваша личная обязанность произвести линковку и генерацию файла elf.
Технически, вам не нужен весь msp430-gcc. Вам всего лишь нужны libc, скрипты линкера и binutils. С другой стороны, msp430-gcc делает полезное дело, позволяя вам не вспоминать все ссылки и зависимости, необходимые для генерации финального elf (vector tables, crt0,...etc). Так что воспользуемся им.
Рабочее окружение и первоначальное исследование
Машина, которая используется для работы — это macbook pro под управлением OSX 10.9.2. Дополнительно используется LLVM и msp430-gcc.
До msp430-gcc я использовал патченную версию gcc, выложенную на sourceforge.net. В поисках более свежей версии я обнаружил, что она больше не поддерживается, потому что Texas Instruments и RHEL берут это на себя.
Здорово, подумал я, TI собирается работать с RHEL, чтобы взять на себя разработку, больше не нужны будут проприетарные компиляторы под windows.
FACEPALM 1
TI не рекомендует к использованию собственное решение, используйте на свой страх и риск (see TI's compiler download). Oке-ей… Так на что я надеюсь?
FACEPALM 2
Хорошо, я могу согласиться, что они не доверяют собственному продукту, что и я чувствую большую часть времени. Пытаясь собрать это, я быстро обнаружил, что TI не поддерживает сборку на OS X. Это следствие использования xgcc и того факта, что некоторые флаги не поддерживаются OS X.
Следует заметить, что работа идет, нужно лишь убрать лишние параметры и добавить некоторые хаки. Я ленив, и допиливание существующей системы сборки наводило тоску. Что ж, давайте посмотрим другие варианты, раз уж сказали, чтобы не доверять данному решению.
Что я сделал
Технически мне не нужен фронт-энд, мне всего лишь нужен линкер/ассемблер. Я предполагал, что есть brew/fink/ports путь. Я хотел использовать окружение gcc4.7 и использовать brew, но ничего не нашел.
Так что я создал свой собственный форк (основанный на sampsyo/homebrew-mspgcc). Он включает gcc, libc, gdb, mcu, binutils и mspdebug:
brew tap wbennett/homebrew-mspgcc
brew install wbennett/mspgcc/msp430-binutils
brew install wbennett/mspgcc/msp430-gcc
brew install wbennett/mspgcc/msp430-libc
brew install wbennett/mspgcc/msp430-mcu
brew install wbennett/mspgcc/msp430-gdb
brew install mspdebug
Вах… Теперь у нас есть gcc.
Мое лицо горит
Все становится жертвой цифрового распада, но особенное страдание доставляет open source софт. В частности, многочисленные хинты, представленные в тредах помощи и степень релевантности вашим запросам, по отношению к периоду полураспада информации, в них предоставленной.
Если вы ищете в интернетах информацию, как собрать в clang для платформы msp430, напрямую вы ничего не найдете. Вы найдете немного обнадеживающие наборы параметров, непотребные объемы документации и возможно, предложения использовать некоторые опции, о которых вы не думали:
$>clang -ccc-host-triple msp430-elf
clang-3.5: error: unsupported option '-ccc-host-triple'
clang-3.5: error: no such file or directory: 'msp430-elf'
clang-3.5: error: no input files
Отлично, это не работает.
Я могу только предположить, что это из-за старой версии LLVM. Может быть, я должен был использовать llc после того, как clang превратил его в LLVM байткод?.. Хорошо, посмотрим, какие варианты ещё остались.
Попробуем тестовое приложение:
#include "msp430.h"
#include "stdlib.h"
int main(void)
{
//disable watchdog
WDCTL = WDTPW + WDTHOLD;
//do something silly
auto i = 1;
auto result = i+1;
//avoid warnings
if(result == 2)
return 2;
//loop forever
while(true){}
//never reach this
return 0;
}
#compile application to llvm assembler
~/local/bin/clang++ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/
-D__MSP430F5438__ -g -S -emit-llvm -std=c++11 -Wall -c src/simpleadd.cpp -o src/simpleadd.ll
Отлично! Выглядит, как нормальный LL
; ModuleID = 'src/simpleadd.cpp'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.9.0"
@"1__WDTCTL" = external global i32
; Function Attrs: nounwind ssp uwtable
define i32 @main() #0 {
%1 = alloca i32, align 4
%i = alloca i32, align 4
%result = alloca i32, align 4
store i32 0, i32* %1
store volatile i32 23168, i32* @"1__WDTCTL", align 4
store i32 1, i32* %i, align 4
%2 = load i32* %i, align 4
%3 = add nsw i32 %2, 1
store i32 %3, i32* %result, align 4
%4 = load i32* %result, align 4
%5 = icmp eq i32 %4, 2
br i1 %5, label %6, label %7
; <label>:6 ; preds = %0
ret i32 2
; <label>:7 ; preds = %0
br label %8
; <label>:8 ; preds = %7, %8
br label %8
}
attributes #0 = { nounwind ssp uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 "}
Давайте уже переведем это в какой-нибудь ассемблер для msp430, что llc может нам предложить?
$>~/local/bin/llc --help
...
-mcpu=<cpu-name> - Target a specific cpu type (-mcpu=help for details)
#ok more specific plz
$>~/local/bin/llc -mcpu=help
...
amdfam10 - Select the amdfam10 processor.
athlon - Select the athlon processor.
athlon-4 - Select the athlon-4 processor.
athlon-fx - Select the athlon-fx processor.
athlon-mp - Select the athlon-mp processor.
athlon-tbird - Select the athlon-tbird processor.
athlon-xp - Select the athlon-xp processor.
athlon64 - Select the athlon64 processor.
athlon64-sse3 - Select the athlon64-sse3 processor.
...
Почему мы не можем использовать msp430? Я клянусь, что где-то читал, что существует частично поддерживаемая платформа, называемая msp430.
Воспользуемся помощью.
$>~/local/bin/llc --help
...
-march=<string> - Architecture to generate code for (see --version)
#ok
$>~/local/bin/llc --version
LLVM (http://llvm.org/):
LLVM version 3.5svn
DEBUG build.
Built Mar 1 2014 (22:49:35).
Default target: x86_64-apple-darwin13.1.0
Host CPU: core-avx-i
Registered Targets:
aarch64 - AArch64 (ARM 64-bit little endian target)
aarch64_be - AArch64 (ARM 64-bit big endian target)
arm - ARM
cpp - C++ backend
hexagon - Hexagon
mips - Mips
mips64 - Mips64 [experimental]
mips64el - Mips64el [experimental]
mipsel - Mipsel
msp430 - MSP430 [experimental]
nvptx - NVIDIA PTX 32-bit
nvptx64 - NVIDIA PTX 64-bit
ppc32 - PowerPC 32
ppc64 - PowerPC 64
ppc64le - PowerPC 64 LE
r600 - AMD GPUs HD2XXX-HD6XXX
sparc - Sparc
sparcv9 - Sparc V9
systemz - SystemZ
thumb - Thumb
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
xcore - XCore
Удивительно, но он поддерживается. Давайте сгенерируем msp430 ассемблер!
~/local/bin/llc -march=msp430 src/simpleadd.ll -o src/simpleadd.s
Не уверен, почему он разместил main в секции text, ну да бог с ним.
.file "src/simpleadd.ll"
.text
.globl main
.align 2
.type main,@function
main: ; @main
; BB#0:
push.w r4
mov.w r1, r4
sub.w #12, r1
mov.w #0, -2(r4)
mov.w #0, -4(r4)
mov.w #0, &__WDTCTL+2
mov.w #23168, &__WDTCTL
mov.w #0, -6(r4)
mov.w #1, -8(r4)
mov.w #0, -10(r4)
mov.w #2, -12(r4)
mov.w #0, r12
cmp.w #0, r12
jne .LBB0_2
; BB#1:
mov.w #2, r14
mov.w #0, r15
add.w #12, r1
pop.w r4
ret
.LBB0_2: ; =>This Inner Loop Header: Depth=1
jmp .LBB0_2
.Ltmp0:
.size main, .Ltmp0-main
.ident "clang version 3.5 "
Давайте сгененируем бинарник.
$>msp430-gcc -Wall -D_GNU_ASSEMBLER_ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/
-mmcu=msp430f5438 -mcpu=430 -x assembler -Wa,-gstabs -c src/simpleadd.s -o src/simpleadd.o
#let's hint our entry point (-e main), because it's weird to be placing main in the text section
#let's also generate a map to see how everything is linked
$>msp430-gcc -mmcu=msp430f5438 -mcpu=430 -Wl,-Map=a.out.map src/simpleadd.o -o a.out -e main
Без паники, похоже, мы на верном пути.
К сожалению, facepalm 3. Мое лицо красное и в синяках. После просмотра map файла видим, что линкер делает нечто странное:
*(.init .init.*)
791 *(.init0)
792 .init0 0x0000000000005c00 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_re#
793 0x0000000000005c00 _reset_vector__
794 *(.init1)
795 .init1 0x0000000000005c00 0xc /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__w#
796 0x0000000000005c00 __watchdog_support
797 *(.init2)
798 .init2 0x0000000000005c0c 0x4 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__i#
799 0x0000000000005c0c __init_stack
800 *(.init3)
801 .init3 0x0000000000005c10 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__l#
802 0x0000000000005c10 __low_level_init
803 *(.init4)
804 .init4 0x0000000000005c10 0x18 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_co#
805 0x0000000000005c10 __do_copy_data
806 .init4 0x0000000000005c28 0x16 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_cl#
807 0x0000000000005c28 __do_clear_bss
808 *(.init5)
809 *(.init6)
810 *(.init7)
811 *(.init8)
812 *(.init9)
813 *(.fini9)
814 .fini9 0x0000000000005c3e 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__s#
815 0x0000000000005c3e __stop_progExec__
816 *(.fini8)
817 *(.fini7)
818 *(.fini6)
819 *(.fini5)
820 *(.fini4)
821 *(.fini3)
822 *(.fini2)
823 *(.fini1)
824 *(.fini0)
825 .fini0 0x0000000000005c3e 0x6 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_en#
826 0x0000000000005c3e _endless_loop__
....
869 .text.crt0 0x0000000000005c48 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/crt0ivtbl64.o
870 .text 0x0000000000005c48 0x40 src/simpleadd.o
871 0x0000000000005c48 main
Линкер пропустил запись о переходе на main в секции .init9. Ладно, дам подсказку.
После инспекции ассемблера видно, что clang не понимает, как линкуется программа, или ему все равно:
...
.file "src/simpleadd.ll"
.text
.globl main
.align 2
.type main,@function
main: ; @main
...
Хорошо Mr.Clang, думаю, я могу вам немного помочь. Чтобы указать, где я хочу, чтобы компилятор поместить код, я должен использовать атрибут, а именно:
__attribute__((section(".init9")),aligned(2)).
Библиотека Crt0 используется для выполнения процедур стартапа, прежде чем переходить к основной программе. Кроме того, она обрабатывает поведение после возврата вашей программы из функции main. Поскольку микроконтроллер не может технически «остановиться», он войдет в бесконечный цикл после возвращения из main.
Шаги, которые будут выполняться до того, как запустится основная программа (линкованная с Crt0), следующие:
- Вектор сброса
- Конфигурация системы вочдога
- Инициализация стека
- Низкоуровневая инициализация
- Копирование данных
- Очистка ОЗУ
- Вызов конструкторов/деструкторов
- Вызов main
- Обработка возврата из main
Новый код:
__attribute__((section(".init9"),aligned(2)))
int main(void)
{
...
Попробуем собрать:
$>~/local/bin/clang++ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/
-D__MSP430F5438__ -S -emit-llvm -std=c++11 -Wall -c src/simpleadd.cpp -o src/simpleadd.ll
...
rc/simpleadd.cpp:4:24: error: argument to 'section' attribute is not valid for this target: mach-o section specifier requires a
segment and section separated by a comma
__attribute__((section(".init9"),aligned(2)))
Facepalm 4, Mr. Clang начинает выводить меня из себя.
Возвращаемся к --help. Я внимательно читаю, чтобы найти, где я, возможно, что-то упустил.
В конце концов, нашел.
$>~/local/bin/clang++ --help
...
--target=<value> Generate code for the given target
...
Согласен, мой косяк. Прошу прощения. Ну и как мне определить, какая цель правильная?
Попробуем по образу предыдущих запросов:
~/local/bin/clang++ --target=help
clang-3.5: error: no input files
#version maybe?
~/local/bin/clang++ --version
clang version 3.5
Target: x86_64-apple-darwin13.1.0
Thread model: posix
Неудачно… А что, если попробовать очевидное:
~/local/bin/clang++ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/
-D__MSP430F5438__ -S -emit-llvm -std=c++11 --target=msp430
-Wall -c src/simpleadd.cpp -o src/simpleadd.ll
Бабах!
Наконец-то сборщик работает как от него и требуется, и я могу запустить программу на устройстве.
Результирующий .ll:
; ModuleID = 'src/simpleadd.cpp'
target datalayout = "e-m:e-p:16:16-i32:16:32-n8:16"
target triple = "msp430"
@"1__WDTCTL" = external global i16
; Function Attrs: nounwind
define i16 @main() #0 section ".init9" align 2 {
%1 = alloca i16, align 2
%i = alloca i16, align 2
%result = alloca i16, align 2
store i16 0, i16* %1
store volatile i16 23168, i16* @"1__WDTCTL", align 2
store i16 1, i16* %i, align 2
%2 = load i16* %i, align 2
%3 = add nsw i16 %2, 1
store i16 %3, i16* %result, align 2
%4 = load i16* %result, align 2
%5 = icmp eq i16 %4, 2
br i1 %5, label %6, label %7
; <label>:6 ; preds = %0
ret i16 2
; <label>:7 ; preds = %0
br label %8
; <label>:8 ; preds = %7, %8
br label %8
}
attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 "}
Результирующий .s:
.file "src/simpleadd.ll"
.section .init9,"ax",@progbits
.globl main
.align 1
.type main,@function
main: ; @main
; BB#0:
push.w r4
mov.w r1, r4
sub.w #6, r1
mov.w #0, -2(r4)
mov.w #23168, &__WDTCTL
mov.w #1, -4(r4)
mov.w #2, -6(r4)
mov.w #2, r12
cmp.w #2, r12
jne .LBB0_2
; BB#1:
mov.w #2, r15
add.w #6, r1
pop.w r4
ret
.LBB0_2: ; =>This Inner Loop Header: Depth=1
jmp .LBB0_2
.Ltmp0:
.size main, .Ltmp0-main
.ident "clang version 3.5 "
Обратите внимание, что main теперь правильно размещен в результирующем .map:
792 .init0 0x0000000000005c00 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_re#
793 0x0000000000005c00 _reset_vector__
794 *(.init1)
795 .init1 0x0000000000005c00 0xc /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__w#
796 0x0000000000005c00 __watchdog_support
797 *(.init2)
798 .init2 0x0000000000005c0c 0x4 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__i#
799 0x0000000000005c0c __init_stack
800 *(.init3)
801 .init3 0x0000000000005c10 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__l#
802 0x0000000000005c10 __low_level_init
803 *(.init4)
804 .init4 0x0000000000005c10 0x18 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_co#
805 0x0000000000005c10 __do_copy_data
806 .init4 0x0000000000005c28 0x16 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_cl#
807 0x0000000000005c28 __do_clear_bss
808 *(.init5)
809 *(.init6)
810 *(.init7)
811 *(.init8)
812 *(.init9)
813 .init9 0x0000000000005c3e 0x2c src/simpleadd.o
814 0x0000000000005c3e main
815 *(.fini9)
816 .fini9 0x0000000000005c6a 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__s#
817 0x0000000000005c6a __stop_progExec__
Теперь я могу продолжить свою работу. Надеюсь, это поможет уменьшить количество фейспалмов, проявившихся за пределами данной статьи.
Об авторе:
Вильям Беннетт
Я старший инженер-программист в страховой компании. Я провожу ночи, работая над компиляторами и менеджерами рантайма для встраиваемых систем. Мне нравится возиться и собирать разные вещи.
От себя:
Учитывая, что английским я в основном пользуюсь для чтения мануалов и даташитов, есть вероятность не вполне правильного перевода некоторых моментов, так что если есть замечания, прошу поправить. Лично я давно собирался попробовать именно связку LLVM-mspgcc, но что-то всегда мешало, а тут эта статья. Если все пойдет хорошо, планирую повторить на Ubuntu-msp430-gcc-mspdebug-TI LaunchPad.
Также, в тексте есть некоторые моменты, связанные с особенностями OS X, которые я тоже могу не знать (вернее, не могу знать). Так что опять же, уточнения и конструктивная критика приветствуются.
На этом пока все, спасибо за ваше внимание.
Автор: MrYuran