RaspberryPi向けのクロスコンパイラ環境を作る


Raspberry Pi を買いました

先日 Raspberry Pi 3 Model b+ を購入しました。 USBキーボード、USBマウス、小型ディスプレイ、シリアル接続用のケーブルなどこまごまとしたものも含めると1.5万円くらいでした。

これまでは基本的にフロントエンド周りを色々触っていましたがひょんなことからコンパイラやOSに興味を持ったのでとりあえずベアメタルプログラミングでも始めてみようかなと思ってます。

ベアメタルプログラミングとは

要は、 Raspberry Pi のようなハードウェアをOS無しで制御するプログラムを書くことです。 そもそもベアメタルとは何も無い状態で、そこでプログラミングを行うのでベアメタルプログラミングと言うとかなんとか。

ちなみに仮想化周りでもベアメタルって名前が出てくるけど、似たような意味合いで、仮想化の場合はホストOSが無いという意味で使われています。 ハードディスクの上にホストOSを用意せずそのまま仮想マシンを乗っけるのです。

クロスコンパイラとは

ベアメタルな環境(?)の場合、コンパイラを置けません。 そういった環境向けに実行ファイルを作成する時に用いるのがクロスコンパイラです。

ベアメタルに限らず、何かしらの言語をコンパイルして実行ファイルを生成し、そのコンパイルした環境とは別の環境で実行ファイルを実行する際には、そのコンパイラはクロスコンパイラということになるようです。

クロスコンパイラ環境を作ってみよう

知識ゼロ且つ経験ゼロだったので紆余曲折しました。 最終的に crosstool-NG とコンパイラのソース落としてビルドの2パターンで Dockerfile を書きました。

crosstool-NG

crosstool-NG とは、クロスコンパイラやツールチェインの構築がめちゃめちゃ簡単に行えるツールです。(雑)

FROM ubuntu:14.04

RUN \
  apt-get update && \
  apt-get -y upgrade && \
  apt-get install -y  \
    wget \
    bash-completion \
    subversion && \
  apt-get install -y build-essential \
    gperf \
    bison \
    flex \
    texinfo \
    gawk \
    libtool \
    automake \
    libncurses5-dev \
    libexpat1-dev \
    python-dev \
    unzip \
    help2man && \
  rm -rf /var/lib/apt/lists/*

RUN \
  wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.23.0.tar.bz2 && \
  tar xf crosstool-ng-*.tar.* && \
  cd crosstool-ng-* && \
  ./configure && \
  make install

RUN \
  useradd -m crosstool-ng && \
  echo "crosstool-ng ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/crosstool-ng

USER crosstool-ng

WORKDIR /home/crosstool-ng

CMD ["bash"]

上記のような Dockerfile を作成し(どこかのリポジトリを参考にして書いたが参考元を見失った)、以下のコマンドで立ち上げます。

docker build crosstool-ng:latest .
docker run -d --name crosstool-ng-container -it crosstool-ng:latest

で、以下のコマンドでRaspberry Pi 3向けのクロスコンパイラ環境がサクッと完成します。

docker exec ct-ng list-samples
docker exec ct-ng armv8-rpi3-linux-gnueabihf
docker exec ct-ng build

ビルドに約1時間くらいかかりますのでご注意ください。

コンパイラのソース落としてビルド

Raspberry Pi 3 ベアメタルプログラミングのチュートリアルの環境構築と同じ手順です。

FROM ubuntu:14.04

## Install the packages
RUN apt-get update && \
    apt-get -y upgrade && \
    apt-get install -y  \
    wget

## Download the packages
RUN wget https://ftpmirror.gnu.org/binutils/binutils-2.30.tar.gz && \
    wget https://ftpmirror.gnu.org/gcc/gcc-8.1.0/gcc-8.1.0.tar.gz && \
    wget https://ftpmirror.gnu.org/mpfr/mpfr-4.0.1.tar.gz && \
    wget https://ftpmirror.gnu.org/gmp/gmp-6.1.2.tar.bz2 && \
    wget https://ftpmirror.gnu.org/mpc/mpc-1.1.0.tar.gz && \
    wget https://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 && \
    wget https://gcc.gnu.org/pub/gcc/infrastructure/cloog-0.18.1.tar.gz

## Download the checksums and signatures:
RUN wget https://ftpmirror.gnu.org/binutils/binutils-2.30.tar.gz.sig && \
    wget https://ftpmirror.gnu.org/gcc/gcc-8.1.0/gcc-8.1.0.tar.gz.sig && \
    wget https://ftpmirror.gnu.org/mpfr/mpfr-4.0.1.tar.gz.sig && \
    wget https://ftpmirror.gnu.org/gmp/gmp-6.1.2.tar.bz2.sig && \
    wget https://ftpmirror.gnu.org/mpc/mpc-1.1.0.tar.gz.sig && \
    wget https://gcc.gnu.org/pub/gcc/infrastructure/sha512.sum

## Unpack the packages
RUN for i in *.tar.gz; do tar -xzf $i; done && \
    for i in *.tar.bz2; do tar -xjf $i; done

## Clean up
RUN rm -f *.tar.* sha512.sum

## Linking
RUN cd binutils-* && \
    ln -s ../isl-* isl

RUN cd gcc-* && \
    ln -s ../isl-* isl && \
    ln -s ../mpfr-* mpfr && \
    ln -s ../gmp-* gmp && \
    ln -s ../mpc-* mpc && \
    ln -s ../cloog-* cloog

## Install the additional packages
RUN apt-get install -y \
    build-essential \
    gawk \
    libc6-dev \
    libgmp-dev \
    zlib1g-dev

## Compile the source of binutils
RUN mkdir aarch64-binutils && \
    cd aarch64-binutils && \
    ../binutils-*/configure --prefix=/usr/local/cross-compiler --target=aarch64-elf \
    --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit \
    --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-plugin \
    --disable-linker-build-id --enable-lto --enable-install-libiberty --with-linker-hash-style=gnu --with-gnu-ld\
    --enable-gnu-indirect-function --disable-multilib --disable-werror --enable-checking=release --enable-default-pie \
    --enable-default-ssp --enable-gnu-unique-object && \
    # make -j4 command returns exit code 2 because gcc version is confirmed by mistaken confirming version command in shell script.
    # But this code is no problem.
    make -j4 || true && \
    make install

## Compile the source of gcc
RUN mkdir aarch64-gcc && \
    cd aarch64-gcc && \
    ../gcc-*/configure --prefix=/usr/local/cross-compiler --target=aarch64-elf --enable-languages=c \
    --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit \
    --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-plugin \
    --disable-linker-build-id --enable-lto --enable-install-libiberty --with-linker-hash-style=gnu --with-gnu-ld\
    --enable-gnu-indirect-function --disable-multilib --disable-werror --enable-checking=release --enable-default-pie \
    --enable-default-ssp --enable-gnu-unique-object &&\
    make -j4 all-gcc && \
    make install-gcc

上記の環境を作らなくてもリポジトリ自体に kerner8.img のファイルが置いてあるので正直クロスコンパイラの環境を構築しなくてもなんとかなります。

プロセッサエミュレーター

Raspberry Pi を使ったベアメタルプログラミングの場合、クロスコンパイラ環境を用意して実行ファイルを生成し、 Raspberry Pi にSDカード経由で転送して、シリアル接続してURATの出力を確認するっていう作業がめちゃめちゃしんどいので、そんな時はプロセッサエミュレーターの出番のようです。

MacOS であれば Homebrew で簡単インストールできます。

brew install qemu

Raspberry Pi 3 と同じプロセッサを利用する場合、以下のコマンドで行けます。

qemu-system-aarch64 -M raspi3 -kernel イメージファイル  -serial stdio

qemu で動作確認して実機に転送して試してみるっていうのが主流かはわからないけど、一番手間が少なそう。

まとめ

ベアメタルプログラミングをとりあえず始めてみたけど、 UARTGPIO など知らない単語ばかり出てきて、初めて Web に触れた時と同じような感覚になってます。とにかく得るもの多くて面白いです。

始めたといっても、まだ初歩の初歩で UARTHelloWolrd しかしていないので早くLチカやディスプレイに何かを表示するなどやってみたい。