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