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



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 のビルド

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

- [Building GCC as a cross compiler for Raspberry Pi](https://solarianprogrammer.com/2018/05/06/building-gcc-cross-compiler-raspberry-pi/)
- [How to Build a GCC Cross-Compiler](https://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/)

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

- [ARM Linuxのリモートデバッグ用にgdb 8.0.1 をソースからビルドする](https://qiita.com/tetsu_koba/items/ddf93510b1f0997a8cd2)



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

```
#!/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 によるリモートデバッグの確認

[こちら](https://qiita.com/iwatake2222/items/5b20558f8ab3f27ca4a4)を参考にしました。

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 でのクロスコンパイル

[こちら](https://qiita.com/drafts/872cfd057759e3002c20/edit)を参考にします。

- 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 使えばいいんですけどね。

[ブログに続きあります。よろしくね](https://zuntansan.hatenablog.jp/entry/2020/06/17/212345)