👻

KV260(arm64)のUbuntu用のプログラムをクロスコンパイルする

に公開

はじめに

私は普段は Kria-KV260 上で、arm64(aarch64) の Ubuntu を使い、C++ や Rust などのセルフコンパイルで動かしています。

一方で、近年は AI を使った開発なども増えてきて、特に Rust での開発では、VS-Code Remote Development などを使って、KV260 に接続すると、メモリやパフォーマンスの不足を感じるようになってきてしまいました。

そこで、KV260 用のプログラムをホスト PC (x86_64) 上でクロスコンパイルして、KV260 上で動かす方法を試してみましたので備忘録です。

同じく arm64 の Raspberry Pi などでも応用できると思います。

WSL2 上の Ubuntu 22.04 LTS を使い、KV260 用の Rust プログラムをクロスコンパイルして、実際に動かすまでをまとめます。なお KV260 も Ubuntu 22.04 LTS を使っています。

クロスコンパイルの基本設定

WSL2 で C/C++ 環境を整える

これはいたって簡単で WSL2 上の Ubuntu で

sudo apt update
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

とすれば aarch64-linux-gnu-gccaarch64-linux-gnu-g++ が使えるようになります。

これらのクロスコンパイラでコンパイルしたものは基本的にそのまま KV260 上の Ubuntu にコピーすれば動きます。

Rust のクロスコンパイル環境を整える

こちらも簡単で、Rust がインストールされている前提で

rustup target add aarch64-unknown-linux-gnu

とし、対象の プロジェクトで .cargo/config.toml に

.cargo/config.toml
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

として、

cargo build --target aarch64-unknown-linux-gnu

とすれば、KV260 上で動くバイナリが生成されます。

毎回オプションを付けるのが面倒な場合は、.cargo/config.toml に

.cargo/config.toml
[build]
target = "aarch64-unknown-linux-gnu"

と書いておけば、通常の

cargo build

でクロスコンパイルされます。

OpenCV を使えるようにする

セルフコンパイルで C/C++ や Rust などから OpenCV が使いたい場合、KV260 の Ubuntu 上で

suso apt update
sudo apt install libopencv-dev

とすれば OpenCV のライブラリがインストールされ、OpenCV が使えるようになります。

一方で、apt install は便利な反面、実行時にリンクされるライブラリ環境などがあるためクロスコンパイルが面倒になります。

OpenCV 自体をソースからコンパイルして、ホスト側にインストールする方法もありますが、それなりに時間がかかるという課題もあります。

そこで何とか、クロス環境でも apt から取得される OpenCV を使えるようにします。

C/C++ で SYSROOT を使う

apt install 済みの KV260 から、ホストに環境をコピーして、コンパイル時に SYSROOT として指定する方法を試してみます。

KV260 から環境をコピーする

いろいろ試行錯誤したのですが KV260 の Ubuntu 上で

sudo tar czvf sysroot.tar.gz \
    -C / \
    lib \
    lib64 \
    usr/lib \
    usr/lib64 \
    usr/include \
    etc/alternatives

として環境をアーカイブして、WSL2 上にコピーし、WSL2 上で ~/kv260/ubuntu22_sysroot に展開しました。

mkdir -p ~/kv260/ubuntu22_sysroot
tar xzvf sysroot.tar.gz -C ~/kv260/ubuntu22_sysroot

もしくは rsync を使って下記のように持ってくることもできるようです。

mkdir -p ~/kv260/ubuntu22_sysroot
rsync -avz --relative \
    kria-kv260:/lib \
    kria-kv260:/lib64 \
    kria-kv260:/usr/lib \
    kria-kv260:/usr/lib64 \
    kria-kv260:/usr/include \
    kria-kv260:/etc/alternatives \
    ~/kv260/ubuntu22_sysroot/

なお、この際のアーカイブやコピーはシンボリックリンクはリンクのままコピーしないと、シンボリックリンクが循環していて、下手に実体コピーを指定すると無限の階層をコピーしようとしてしまうようです。

シンボリックリンクを張りなおす

こうやってコピーしてきたイメージのシンボリックリンクは、絶対パスになっているため、そのままではホスト側の /lib/usr/lib などを参照してしまい、正しく動きません。

そこで、下記のようなスクリプトでリンクの張り直しを行いました。

#!/bin/bash

SYSROOT=$HOME/kv260/ubuntu22_sysroot

find "$SYSROOT" -type l | while read -r link; do
  target=$(readlink "$link")
  if [[ "$target" = /* ]]; then
    # リンク先が絶対パスで且つ、$SYSROOTで始まらず、/dev/nullでもない場合
    if [[ "$target" != "$SYSROOT"* && "$target" != "/dev/null" ]]; then
      new_target="$SYSROOT$target"
      echo "update: $link"
      echo " $target$new_target"
      ln -sf "$new_target" "$link"
    fi
  fi
done

さらにどういうわけか、私の環境だとさらに、OpenCV が依存している libblas.so や liblapack.so のシンボリックリンクが存在しなかったため、手動で下記のようにシンボリックリンクを張り直しました。

cd ~/kv260/ubuntu22_sysroot/usr/lib/aarch64-linux-gnu/
ln -s libblas.so.3 libblas.so
ln -s liblapack.so.3 liblapack.so

これで、aarch64-linux-gnu-g++ などを使うときに --sysroot=~/kv260/ubuntu22_sysroot を指定すれば、KV260 上の Ubuntu と同じライブラリ環境を使ってコンパイルできるようになります。

OpenCV のコンパイルオプションを確認する

なお、セルフコンパイル時は OpenCV のコンパイルオプションを pkg-config --cflags opencv4 で、リンクオプションを pkg-config --libs opencv4 でコンパイルオプションに追加すればいいのですが、WSL2 上では仮にそちらにも OpenCV をインストールしていても、x86_64 用のオプションが返ってきてしまいます。

万全を期すなら、KV260 上でオプションを確認してから直接指定したほうが良いでしょう。

私の環境では下記のようになっていました。

$ pkg-config --cflags opencv4
-I/usr/include/opencv4
$ pkg-config --libs opencv4
-lopencv_stitching -lopencv_alphamat -lopencv_aruco -lopencv_barcode -lopencv_bgsegm -lopencv_bioinspired -lopencv_ccalib -lopencv_dnn_objdetect -lopencv_dnn_superres -lopencv_dpm -lopencv_face -lopencv_freetype -lopencv_fuzzy -lopencv_hdf -lopencv_hfs -lopencv_img_hash -lopencv_intensity_transform -lopencv_line_descriptor -lopencv_mcc -lopencv_quality -lopencv_rapid -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_structured_light -lopencv_phase_unwrapping -lopencv_superres -lopencv_optflow -lopencv_surface_matching -lopencv_tracking -lopencv_highgui -lopencv_datasets -lopencv_text -lopencv_plot -lopencv_ml -lopencv_videostab -lopencv_videoio -lopencv_viz -lopencv_wechat_qrcode -lopencv_ximgproc -lopencv_video -lopencv_xobjdetect -lopencv_objdetect -lopencv_calib3d -lopencv_imgcodecs -lopencv_features2d -lopencv_dnn -lopencv_flann -lopencv_xphoto -lopencv_photo -lopencv_imgproc -lopencv_core

仮想環境でクロスコンパイルする(Rust で cross を使う)

Rust でも SYSROOT を使う方法で OpenCV を使えないかも模索中ですが、Rust の場合 cross を使って仮想環境を作る方法がうまくいったので、そちらを紹介します。

cross のインストール

cross のインストールは

cargo install cross

で行います。

cross 用の Docker イメージを作る

そのあとに Cargo.toml と同じ場所に Cross.tomlDockerfile を下記のように作ります。

なお、DevContainer で試行錯誤した後に持ってきたものなので余計なものも残っているかもしれませんが、ひとまずは動くものとして参考にしてください。

Cross.toml
[target.aarch64-unknown-linux-gnu]
dockerfile = { file = "Dockerfile" }
Dockerfile
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN release=$(. /etc/os-release && echo "$UBUNTU_CODENAME") \
 && mv /etc/apt/sources.list /etc/apt/sources.list.amd64 \
 && printf "deb [arch=amd64] http://archive.ubuntu.com/ubuntu %s main universe multiverse restricted\n" "$release" > /etc/apt/sources.list \
 && printf "deb [arch=amd64] http://archive.ubuntu.com/ubuntu %s-updates main universe multiverse restricted\n" "$release" >> /etc/apt/sources.list \
 && printf "deb [arch=amd64] http://archive.ubuntu.com/ubuntu %s-backports main universe multiverse restricted\n" "$release" >> /etc/apt/sources.list \
 && printf "deb [arch=amd64] http://security.ubuntu.com/ubuntu %s-security main universe multiverse restricted\n" "$release" >> /etc/apt/sources.list \
 && rm -f /etc/apt/sources.list.d/* \
 && printf "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports %s main universe multiverse restricted\n" "$release" > /etc/apt/sources.list.d/arm64-ports.list \
 && printf "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports %s-updates main universe multiverse restricted\n" "$release" >> /etc/apt/sources.list.d/arm64-ports.list \
 && printf "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports %s-backports main universe multiverse restricted\n" "$release" >> /etc/apt/sources.list.d/arm64-ports.list \
 && printf "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports %s-security main universe multiverse restricted\n" "$release" >> /etc/apt/sources.list.d/arm64-ports.list \
 && dpkg --add-architecture arm64 \
 && apt-get update \
 && apt-get install -y --no-install-recommends \
        build-essential \
        crossbuild-essential-arm64 \
        pkg-config \
        cmake \
        ninja-build \
        libssl-dev \
        clang \
        libclang-dev \
        llvm-dev \
        libopencv-dev:arm64 \
        libopencv-contrib-dev:arm64 \
        libopencv-stitching-dev:arm64 \
        libopencv-calib3d-dev:arm64 \
        libopencv-dnn-dev:arm64 \
        libopencv-features2d-dev:arm64 \
        libopencv-flann-dev:arm64 \
        libopencv-highgui-dev:arm64 \
        libopencv-imgcodecs-dev:arm64 \
        libopencv-imgproc-dev:arm64 \
        libopencv-ml-dev:arm64 \
        libopencv-objdetect-dev:arm64 \
        libopencv-photo-dev:arm64 \
        libopencv-shape-dev:arm64 \
        libopencv-video-dev:arm64 \
        libopencv-videoio-dev:arm64 \
        libopencv-viz-dev:arm64 \
        libblas-dev:arm64 \
        liblapack-dev:arm64 \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

cross でビルドする

この2つのファイルを用意できたら、あとは

cross build --target aarch64-unknown-linux-gnu --release

のようにすれば、KV260 用の OpenCV を使った Rust プログラムをクロスコンパイルできます。

おわりに

arm64 用の仮想環境を作ることも考えたのですが、わりと面倒な上に、aarch64 のコンパイラを qemu などで動かすのもパフォーマンス観点で不利になってきますので、今回はネイティブコードのクロスコンパイラを使う方向で環境を作ってみました。

なお、最後に cross 用に作った仮想環境は DevConatainer などの仮想環境としても作ったところ、SYSROOT なしで C/C++ もビルド可能でした。

ネイティブの環境でも同じことをすれば、おそらくビルド可能になると思われますが、見ての通り /etc/apt/sources.list などシステムに手を入れるので、仮想環境がお勧めではあります。

一方で仮想環境では、今度は KV260 との接続性などで面倒なケースもあるため、用途に応じて使い分けるのが良いかと思います。

おまけ (armhf 用の Docker イメージ)

ZYBO Z7-20 などの armhf 用に cross を使う場合もやってみました。

ZYBO では ikwzm氏の FPGA-SoC-Debian12 を使っていますが、こちらは殆どトラブルなくすっきり整備できました。

下記のように Cross.tomlDockerfile を作れば良いようです。

Cross.toml
[target.arm-unknown-linux-gnueabihf]
dockerfile = { file = "Dockerfile" }
Dockerfile
FROM debian:12

ENV DEBIAN_FRONTEND=noninteractive

RUN dpkg --add-architecture armhf \
 && apt-get update \
 && apt-get install -y --no-install-recommends \
      build-essential \
      crossbuild-essential-armhf \
      pkg-config \
      cmake \
      ninja-build \
      libssl-dev \
      clang \
      libclang-dev \
      llvm-dev \
      libopencv-dev:armhf \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
.cargo/config.toml
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

[build]
target = "arm-unknown-linux-gnueabihf"

下記のようにしたら行けました。

cross build --target arm-unknown-linux-gnueabihf --release

なぜか --release が無いと失敗するのですが...

GitHubで編集を提案

Discussion