LoginSignup
0
0

More than 3 years have passed since last update.

CentOS8 で rust の Raspberry Pi クロスコンパイルをやってみる

Last updated at Posted at 2020-06-17

Raspberry Pi のクロスコンパイル環境に関して、ホスト側に Ubunt を用いる方法であれば、色々な記事を見ることが出来ますが、他のホストの場合 Raspberry Pi のクロスコンパイラがパッケージ化されていなこともあり、そういう記事を見つけることが出来ませんでした。

そこで、ホスト側を CentOS8 として gcc、rust の クロスコンパイル環境を構築してみます。

注意

  • 2020/07/07 Raspberry Pi 3 側の GLIBCが /lib/arm-linux-gnueabihf/libc-2.28.so ですが、下記の手順では glibc-2.31 をビルドしている為、rust にて libcを使用する場合はクロスコンパイルでビルドが成功しても、Raspberry Pi 側で動かすと以下のようなエラーがでます。tokio、actix等を使用しても同様だと思われます。(確認不足ですみません。)
$ ./test
./test: /lib/arm-linux-gnueabihf/libm.so.6: version `GLIBC_2.29' not found (required by ./test)
  • 2020/07/07 上記に関しまして、glibc のバージョンを 2.31 から 2.28 に変更しましたら、Raspberry Pi 3 でも動作するようになりました。以下ので使用しているスクリプトの方も、2.28 に変更しました。

環境と方針

  • ホスト側は、CentOS8 とします。
  • ターゲット側は、Raspberry Pi 3B+ とします。
  • rust を CentOS8 に導入します。このとき、rust のクロスビルドでは、arm クロスコンパイル用 gcc が必要となります。
  • CentOS8 には arm クロスコンパイル用 gcc パッケージが無いので、ソースからビルドします。この時 glibc も必要となるため、一緒にコンパイルします。
  • この時ついでに gdb もビルドし、gdbserver を使ってリモートデバッグができるようにします。
  • 最後に rust でクロスコンパイルを行います。

arm クロスコンパイル用 gcc, glibc のビルドと gdb のビルド

クロスコンパイラの作成には以下を参考にしました。

gdbの作成には以下を参考にしました。

ビルドスクリプト

任意のディレクトリで、以下のスクリプトを実行しました。

#!/bin/bash

#
# reference
#  - https://solarianprogrammer.com/2018/05/06/building-gcc-cross-compiler-raspberry-pi/
#  - https://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/
#  - https://qiita.com/tetsu_koba/items/ddf93510b1f0997a8cd2
# Thank you.
#

### STEP.1

# URL_PREFIX="http://ftp.gnu.org/gnu"
# URL_PREFIX="http://ftpmirror.gnu.org"
URL_PREFIX="http://ftp.jaist.ac.jp/pub/GNU"

HOST=x86_64-pc-linux-gnu

# Raspberry Pi 3 32bit
TARGET=arm-linux-gnueabihf
TARGET_ARCH=armv6
KERNEL_ARCH=arm
KERNEL=kernel7

# Raspberry Pi Zero         (no test)
# KERNEL=kernel

# Raspberry Pi 4 32bit      (no test)
# KERNEL=kernel7l

# Raspberry Pi 3/4 64bit    (no test)
# TARGET=aarch64-linux-gnu
# TARGET_ARCH=aarch64
# KERNEL=kernel8
# KERNEL_ARCH=arm64

# MACHTYPE=x86_64-redhat-linux-gnu
MAKE_OPT=" -j 8 "

PREFIX=`pwd`
PREFIX_SRC=${PREFIX}/src
BUILD=_build

export PATH=${PREFIX}/bin:$PATH

echo ${PREFIX}
echo ${PREFIX_SRC}

test -d ${PREFIX_SRC}   || mkdir ${PREFIX_SRC}

### STEP.2

cd ${PREFIX_SRC}

if [ -z $DL_SKIP ]; then

wget ${URL_PREFIX}/binutils/binutils-2.34.tar.xz
wget ${URL_PREFIX}/glibc/glibc-2.28.tar.xz
wget ${URL_PREFIX}/gcc/gcc-8.4.0/gcc-8.4.0.tar.xz
wget ${URL_PREFIX}/gmp/gmp-6.2.0.tar.xz
wget ${URL_PREFIX}/mpfr/mpfr-4.0.2.tar.xz
wget ${URL_PREFIX}/mpc/mpc-1.1.0.tar.gz
wget http://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2
wget ${URL_PREFIX}/gdb/gdb-8.2.1.tar.xz

git clone --depth=1 https://github.com/raspberrypi/linux

tar xvJf binutils-2.34.tar.xz
tar xvJf glibc-2.28.tar.xz
tar xvJf gcc-8.4.0.tar.xz
tar xvJf gmp-6.2.0.tar.xz
tar xvJf mpfr-4.0.2.tar.xz
tar xvzf mpc-1.1.0.tar.gz
tar xvjf isl-0.18.tar.bz2
tar xvJf gdb-8.2.1.tar.xz

fi

SRC_BINUTILS=${PREFIX_SRC}/binutils-2.34
SRC_GCC=${PREFIX_SRC}/gcc-8.4.0
SRC_GMP=${PREFIX_SRC}/gmp-6.2.0
SRC_MPFR=${PREFIX_SRC}/mpfr-4.0.2
SRC_MPC=${PREFIX_SRC}/mpc-1.1.0
SRC_ISL=${PREFIX_SRC}/isl-0.18
SRC_GLIBC=${PREFIX_SRC}/glibc-2.28
SRC_GDB=${PREFIX_SRC}/gdb-8.2.1

### STEP.3

(
cd ${PREFIX_SRC}/linux
make ARCH=${KERNEL_ARCH} INSTALL_HDR_PATH=${PREFIX}/${TARGET} headers_install
)

### STEP.4

(
cd ${SRC_BINUTILS}

mkdir ${BUILD}
cd ${BUILD}

echo ${SRC_BINUTILS}

../configure \
 --prefix=${PREFIX} \
 --target=${TARGET} \
 --with-arch=${TARGET_ARCH} \
 --with-fpu=vfp \
 --with-float=hard \
 --disable-nls \
 --disable-multilib

make ${MAKE_OPT} && make install
)

### STEP.5

(
cd ${SRC_GCC}

ln -s ${SRC_GMP}  gmp
ln -s ${SRC_MPFR} mpfr
ln -s ${SRC_MPC}  mpc
ln -s ${SRC_ISL}  isl

mkdir ${BUILD}
cd ${BUILD}

../configure \
 --prefix=${PREFIX} \
 --host=${HOST} \
 --target=${TARGET} \
 --with-arch=${TARGET_ARCH} \
 --enable-languages=c,c++ \
 --with-fpu=vfp \
 --with-float=hard \
 --disable-nls \
 --disable-multilib

make ${MAKE_OPT} all-gcc && make install-gcc
)

### STEP.6

(
cd ${SRC_GLIBC}

mkdir ${BUILD}
cd ${BUILD}

../configure \
 --prefix=${PREFIX}/${TARGET} \
 --build=$MACHTYPE \
 --host=${TARGET} \
 --target=${TARGET} \
 --with-arch=${TARGET_ARCH} \
 --with-fpu=vfp \
 --with-float=hard \
 --with-headers=${PREFIX}/${TARGET}/include \
 --disable-multilib \
 libc_cv_forced_unwind=yes

make install-bootstrap-headers=yes install-headers
make -${MAKE_OPT} csu/subdir_lib
install csu/crt1.o csu/crti.o csu/crtn.o ${PREFIX}/${TARGET}/lib
${PREFIX}/bin/${TARGET}-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o ${PREFIX}/${TARGET}/lib/libc.so
touch ${PREFIX}/${TARGET}/include/gnu/stubs.h
)

### STEP.7

(
cd ${SRC_GCC}/${BUILD}
make -${MAKE_OPT} all-target-libgcc && make install-target-libgcc
)

### STEP.8

(
cd ${SRC_GLIBC}/${BUILD}
make -${MAKE_OPT} && make install
)

### STEP.9

(
cd ${SRC_GCC}/${BUILD}
make -${MAKE_OPT} && make install
)

### STEP.10

test -e /tmp/test1 && rm /tmp/test1
test -e /tmp/test2 && rm /tmp/test2

cat <<EOT > /tmp/test1.c
int main() { return 0; }
EOT
${PREFIX}/bin/${TARGET}-gcc /tmp/test1.c -o /tmp/test1
file /tmp/test1

# /tmp/test1: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped

cat <<EOT > /tmp/test2.cpp
int main() { return 0; }
EOT
${PREFIX}/bin/${TARGET}-g++ /tmp/test2.cpp -o /tmp/test2
file /tmp/test2

# /tmp/test2: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped


### STEP.11

(
cd ${SRC_GDB}

mkdir ${BUILD}
cd ${BUILD}

../configure \
 --prefix=${PREFIX} \
 --target=${TARGET} \
 --enable-tui

make ${MAKE_OPT} && make install
)

### STEP.12

(
cd ${SRC_GDB}

mkdir ${BUILD}_target
cd ${BUILD}_target

export LDFLAGS=-static

../configure \
 --prefix=${PREFIX} \
 --program-prefix=${TARGET}-target- \
 --host=${TARGET}

make ${MAKE_OPT} && make install
cp ${PREFIX}/bin/${TARGET}-target-gdbserver{,-ns}
${PREFIX}/bin/${TARGET}-strip ${PREFIX}/bin/${TARGET}-target-gdbserver
file ${PREFIX}/bin/${TARGET}-target-gdbserver{,-ns}
)


###
### You can delete the source directory if desired.
###
### $ rm -rf src 
###

細かい説明は、参考にした記事をご覧ください。

補足としましては、

  • スクリプト中の以下の部分を変更することで、他の Raspberry Pi や 64bitなどへ対応できるかもしれませが、試していません。
# Raspberry Pi 3 32bit
TARGET=arm-linux-gnueabihf
TARGET_ARCH=armv6
KERNEL_ARCH=arm
KERNEL=kernel7
  • 参考ページによると gcc9, gcc10 では Raspbian のカーネルヘッダで結構エラーが出るとのことで、gcc8.4 を採用しました。参考ページの方では、gcc8.4 で glibc を作った後、gcc10 のコンパイラを作るみたいなことをしていますので、もう少し手間をかければできるようです。

  • gdb-8.3, gdb-9.2 では、以下のエラーがでます。そのため gdb-8.2.1 を採用しました。

make[3]: ディレクトリ '/home/zuntan/cross_rp_3/src/gdb-8.3/_build_2/gdb' から出ます
  CXX    gdb.o
In file included from /home/zuntan/cross_rp_3/arm-linux-gnueabihf/include/wchar.h:849,
                 from build-gnulib/import/wchar.h:87,
                 from /home/zuntan/cross_rp_3/arm-linux-gnueabihf/include/c++/8.4.0/cwchar:44,
                 from /home/zuntan/cross_rp_3/arm-linux-gnueabihf/include/c++/8.4.0/bits/postypes.h:40,
                 from /home/zuntan/cross_rp_3/arm-linux-gnueabihf/include/c++/8.4.0/bits/char_traits.h:40,
                 from /home/zuntan/cross_rp_3/arm-linux-gnueabihf/include/c++/8.4.0/string:40,
                 from ../../gdb/common/common-utils.h:23,
                 from ../../gdb/common/common-defs.h:102,
                 from ../../gdb/defs.h:28,
                 from ../../gdb/gdb.c:19:
/home/zuntan/cross_rp_3/arm-linux-gnueabihf/include/bits/wchar2.h:448:3: エラー: #error "Assumed value of MB_LEN_MAX wrong"
 # error "Assumed value of MB_LEN_MAX wrong"
   ^~~~~
make[2]: *** [Makefile:1641: gdb.o] エラー 1
make[2]: ディレクトリ '/home/zuntan/cross_rp_3/src/gdb-8.3/_build_2/gdb' から出ます
make[1]: *** [Makefile:9127: all-gdb] エラー 2
make[1]: ディレクトリ '/home/zuntan/cross_rp_3/src/gdb-8.3/_build_2' から出ます
make: *** [Makefile:850: all] エラー 2

ビルド結果

実行後以下のようなファイルが出来上がります。

$ ls
arm-linux-gnueabihf  bin  include  lib  lib64  libexec  share  src
$ ls -1 bin/*
bin/arm-linux-gnueabihf-addr2line
bin/arm-linux-gnueabihf-ar
bin/arm-linux-gnueabihf-as
bin/arm-linux-gnueabihf-c++
bin/arm-linux-gnueabihf-c++filt
bin/arm-linux-gnueabihf-cpp
bin/arm-linux-gnueabihf-elfedit
bin/arm-linux-gnueabihf-g++
bin/arm-linux-gnueabihf-gcc
bin/arm-linux-gnueabihf-gcc-8.4.0
bin/arm-linux-gnueabihf-gcc-ar
bin/arm-linux-gnueabihf-gcc-nm
bin/arm-linux-gnueabihf-gcc-ranlib
bin/arm-linux-gnueabihf-gcov
bin/arm-linux-gnueabihf-gcov-dump
bin/arm-linux-gnueabihf-gcov-tool
bin/arm-linux-gnueabihf-gdb
bin/arm-linux-gnueabihf-gdb-add-index
bin/arm-linux-gnueabihf-gprof
bin/arm-linux-gnueabihf-ld
bin/arm-linux-gnueabihf-ld.bfd
bin/arm-linux-gnueabihf-nm
bin/arm-linux-gnueabihf-objcopy
bin/arm-linux-gnueabihf-objdump
bin/arm-linux-gnueabihf-ranlib
bin/arm-linux-gnueabihf-readelf
bin/arm-linux-gnueabihf-run
bin/arm-linux-gnueabihf-size
bin/arm-linux-gnueabihf-strings
bin/arm-linux-gnueabihf-strip
bin/arm-linux-gnueabihf-target-gcore
bin/arm-linux-gnueabihf-target-gdb
bin/arm-linux-gnueabihf-target-gdb-add-index
bin/arm-linux-gnueabihf-target-gdbserver
bin/arm-linux-gnueabihf-target-gdbserver-ns
bin/arm-linux-gnueabihf-target-run
  • bin/arm-linux-gnueabihf-target-* は arm 側の実行ファイルです。
  • bin/arm-linux-gnueabihf-target-gdbserver を Raspberry 側に転送して実行させます。なおちょっとでもバイナリサイズを小さくするため strip しています。
  • 上記以外は、arm向けクロスコンパイル用のツールです。

クロスコンパイルの確認

ビルドスクリプト中にもちょっとだけ含まれていますが、以下の操作で確認することが出来ます。

$ cat <<EOT > /tmp/test.cpp
#include <iostream>
using namespace std;
int main(){
    cout << "Hello world." << endl;
    return 0;
}
EOT
$ bin/arm-linux-gnueabihf-g++ /tmp/test.cpp -o /tmp/test
$ file /tmp/test
/tmp/test: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped

で、そのまま動かすと当然うごきません。

$ /tmp/test
-bash: /tmp/test: バイナリファイルを実行できません: 実行形式エラー

このファイルを RPi 3 にもっていきます

$ sftp xxx.xxx.xxx.xxx
sftp> put /tmp/test
Uploading /tmp/test to /home/xxxx/test

Raspberry Pi 3 で実行してみます。

$ ./test
Hello world.

ちゃんと動きました。

gdb によるリモートデバッグの確認

こちらを参考にしました。

sftpで arm-linux-gnueabihf-target-gdbserver を RPi 3 に転送します。

$ sftp xxx.xxx.xxx.xxx
sftp> put bin/arm-linux-gnueabihf-target-gdbserver
Uploading bin/arm-linux-gnueabihf-target-gdbserver to /home/xxxx/arm-linux-gnueabihf-target-gdbserve

RPi 3 側で、gdbserve を起動します。

$ ./arm-linux-gnueabihf-target-gdbserver --multi :5555
Listening on port 5555

ホスト側で gdb を起動し、以下のように入力します。(-target-がついてない物を使います)

$ bin/arm-linux-gnueabihf-gdb
(gdb) target extended-remote xxx.xxx.xxx.xxx:5555
(gdb) file /tmp/test
(gdb) remote put /tmp/test .
(gdb) set remote exec-file test
(gdb) start
(gdb) c

動きました。

RPi 3 側は以下のようになります。

$ ./arm-linux-gnueabihf-target-gdbserver --multi :5555
Listening on port 5555
Remote debugging from host xxx.xxx.xxx.xxx
Process /home/xxxxx/test created; pid = 4635
Hello world.

Child exited with status 0

ホスト側は以下のようになります。

(gdb) target extended-remote xxx.xxx.xxx.xxx:5555
Remote debugging using xxx.xxx.xxx.xxx:5555
(gdb) file /tmp/test
Reading symbols from /tmp/test...done.
(gdb) remote put /tmp/test .
Successfully sent file "/tmp/test".
(gdb) set remote exec-file test
(gdb) start
Temporary breakpoint 1 at 0x10710
Starting program: /tmp/test
Reading /lib/ld-linux-armhf.so.3 from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /lib/ld-linux-armhf.so.3 from remote target...
Reading /lib/85e699c11db06c7b24f74de2cdada3146442a8.debug from remote target...
Reading /lib/.debug/85e699c11db06c7b24f74de2cdada3146442a8.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so from remote target...
Reading /usr/lib/arm-linux-gnueabihf/libstdc++.so.6 from remote target...
Reading /lib/arm-linux-gnueabihf/libm.so.6 from remote target...
Reading /lib/arm-linux-gnueabihf/libgcc_s.so.1 from remote target...
Reading /lib/arm-linux-gnueabihf/libc.so.6 from remote target...
Reading /usr/lib/arm-linux-gnueabihf/0cce12f2cf78f277bea689e5b5192d1eb184ec.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/.debug/0cce12f2cf78f277bea689e5b5192d1eb184ec.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/a78aa48a65c3a7a324577388d8ba5c73262417.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/.debug/a78aa48a65c3a7a324577388d8ba5c73262417.debug from remote target...
Reading /lib/arm-linux-gnueabihf/22b5cf95895f5f4f69dae68ad9f99198e43121.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/22b5cf95895f5f4f69dae68ad9f99198e43121.debug from remote target...
Reading /lib/arm-linux-gnueabihf/74839a42e12b3d87c0da3ef5c5da4d5721db9f.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/74839a42e12b3d87c0da3ef5c5da4d5721db9f.debug from remote target...
Reading /lib/arm-linux-gnueabihf/dd27c16f5283e5c53dcbd1bbc3ef136e312d1b.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/dd27c16f5283e5c53dcbd1bbc3ef136e312d1b.debug from remote target...

Temporary breakpoint 1, 0x00010710 in main ()
(gdb) c
Continuing.
[Inferior 1 (process 4635) exited normally]

補足

src ディレクトリは、でかいので消しても可です。

$ du -sh *
294M    arm-linux-gnueabihf
262M    bin
364K    include
47M     lib
1.2M    lib64
648M    libexec
18M     share
6.5G    src

rust でのクロスコンパイル

こちらを参考にします。

  • rust を導入します。(他参照)

  • rustup で、armv7-unknown-linux-gnueabihf をインストールします。

$ rustup target add armv7-unknown-linux-gnueabihf
$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/junichi/.rustup

installed targets for active toolchain
--------------------------------------

armv7-unknown-linux-gnueabihf
x86_64-unknown-linux-gnu

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.43.1 (8d69840ab 2020-05-04)
  • とりあえず Hello World します。
$ cargo new --bin hello
     Created binary (application) `hello` package
$ cd hello/
$ ls
Cargo.toml  src
$ cargo run
   Compiling hello v0.1.0 (/home/junichi/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/hello`
Hello, world!
  • RPi 3 用のターゲット設定をします。
    • cross_rp_3 は gccをビルドしたディレクトリです。
    • liner = に指定する gcc のパスはフルパスにします。
$ mkdir .cargo
$ cat > .cargo/config << EOF
> [target.armv7-unknown-linux-gnueabihf]
> linker = "/home/xxxxxx/cross_rp_3/bin/arm-linux-gnueabihf-gcc"
> EOF
  • コンパイルします
$ cargo build --target armv7-unknown-linux-gnueabihf
   Compiling hello v0.1.0 (/home/junichi/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
  • 見てみます
file target/armv7-unknown-linux-gnueabihf/debug/hello
target/armv7-unknown-linux-gnueabihf/debug/hello: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped
  • sftp で RPi 3 にもっていきます
$ sftp xxx.xxx.xxx.xxx
sftp> put target/armv7-unknown-linux-gnueabihf/debug/hello
Uploading target/armv7-unknown-linux-gnueabihf/debug/hello to /home/xxxxx/hello
  • RPi 3 で実行します
 $ ./hello
Hello, world!

動きました。

リモートデバッグ

cargo run で、RPi 3 へ転送して実行、もしくはデバッグみたいなことが出来れば最高ですが、それは将来の課題とします。(力尽きました)

なお、作成した gdb を使って rust の出力結果をリモートデバッグすることは可能です。

$ /home/xxxxxx/cross_rp_3/bin/arm-linux-gnueabihf-gdb \
target/armv7-unknown-linux-gnueabihf/debug/hello

あとは、リモートデバッグの所で説明したのと同じことをすればいいです。
ブレークポイントやステップ実行も可能です。
ただし、rust-gdb と同じような pretty printing はできないです。

終わりに

Ubunt 使えばいいんですけどね。

ブログに続きあります。よろしくね

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0