Pull to refresh

Непереводимая игра слов по поводу CLANG, LLVM и msp430

Reading time 11 min
Views 20K
Original author: William Bennett
Целью данной работы является получение кода, сгенерированного бэк-эндом компилятора LLVM MSP430

Введение


CLANG — это просто фронт-энд компилятора С для LLVM. LLVM — это фреймворк для разработки фронт-эндов, бэк-эндов компиляторов для разных целевых платформ. Этот фреймворк облегчает анализ путем приведения всех фронт-эндов к промежуточному представлению (IR). Все бэк-энды оперируют с этим общим представлением.

Одна из целевых платформ, которая меня интересует, это MSP430 — популярный встраиваемый микроконтроллер, используемый во многих микропотребляющих приложениях.

Текущая поддержка MSP430 весьма ограничена. Я надеюсь, что смогу помочь кому-нибудь, кто слишком фейс-палмит, сохранить лицо. 1

Поехали


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

[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"

@"\01__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* @"\01__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, ну да бог с ним. 2
    .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), следующие:
  1. Вектор сброса
  2. Конфигурация системы вочдога
  3. Инициализация стека
  4. Низкоуровневая инициализация
  5. Копирование данных
  6. Очистка ОЗУ
  7. Вызов конструкторов/деструкторов
  8. Вызов main
  9. Обработка возврата из 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"

@"\01__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* @"\01__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, которые я тоже могу не знать (вернее, не могу знать). Так что опять же, уточнения и конструктивная критика приветствуются.

На этом пока все, спасибо за ваше внимание.

1 — подсказали, что так более удачно. Пожалуй, соглашусь.
Tags:
Hubs:
+30
Comments 7
Comments Comments 7

Articles