土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

QEMUで組み込みLinux(aarch64+virtボード)でネットワーク調査とツールのクロスコンパイル

前回 は、VirtualBox + Ubuntu 22.04 のネットワークの理解を進めました。

今回は、ここで作った QEMU を使って、VirtualBox + Ubuntu 22.04 とのネットワークの疎通を確認していきます。

それでは、やっていきます。

はじめに

「QEMUを動かす」の記事一覧です。良かったら参考にしてください。

QEMUを動かすの記事一覧
・第1回:STM32(ARM Cortex-M)をQEMUで動かす(環境構築編)
・第2回:STM32(ARM Cortex-M)をQEMUで動かす(ソースコード確認編)
・第3回:STM32(ARM Cortex-M)をQEMUで動かす(スタートアップルーチン編)
・第4回:STM32(ARM Cortex-M)をQEMUで動かす(リンカスクリプト編)
・第5回:STM32(ARM Cortex-M)のELFファイルの内容を確認する
・第6回:STM32(ARM Cortex-M)のELFファイル⇔バイナリの変換を行う
・第7回:STM32(ARM Cortex-M)のバイナリから構築したELFファイルをQEMUで動かす
・第8回:QEMUのビルドに必要なxpm(xPack Project Manager)について学ぶ
・第9回:QEMUをソースからビルドして動かす
・第10回:QEMUのソースコードを変更してSTM32の動作を変える
・第11回:QEMUに似たRenodeというOSSの組込みデバイスエミュレータを試す
・第12回:QEMUに似たRenodeでSTM32をGDBを使ってデバッグする
・第13回:QEMUに似たRenodeでSTM32をバイナリファイルで動かす
・第14回:QEMUに似たRenodeをソースからビルドする
・第15回:QEMUに似たRenodeでVSCodeを使ってデバッグする
・第16回:QEMUに似たRenodeでVSCodeを使ってRenode自体をデバッグする
・第17回:QEMUで組み込みLinux(Buildroot+BusyBox)をやってみる
・第18回:QEMUで組み込みLinux(Buildroot+BusyBox)をやってみる、の補足
・第19回:QEMUで組み込みLinux(Buildroot+BusyBox)にU-Bootを追加する
・第20回:QEMUで組み込みLinux(Buildroot+BusyBox+U-Boot)で起動する
・第21回:QEMUで組み込みLinux(Buildroot+BusyBox+U-Boot)をinitramfsで起動する
・第22回:QEMUで組み込みLinux(Buildroot+virtボード)で ARM64bitを起動する
・第23回:QEMUで組み込みLinux(aarch64+virtボード)でネットワーク調査とツールのクロスコンパイル ← 今回

以下は、Buildroot の Documentation のリンクです。

https://buildroot.org/downloads/manual/manual.html

ここで使用する環境は、VirtualBox に入れた Ubuntu 22.04 です。

Buildroot を使います。Buildroot に含まれている U-Boot と、QEMU を使います。

ツール 設定値
Buildroot qemu_aarch64_virt_defconfig
U-Boot qemu_arm64_defconfig
QEMU virt(virt-8.2)

IPv6 の無効化

まずは、パケットキャプチャで見にくくなるので、VirtualBox + Ubuntu 22.04 の環境で、IPv6 を無効化します。

IPv6 を無効化する方法は、一時的に IPv6 を無効化し、再起動したら元に戻る方法と、再起動しても IPv6 が無効のままになる方法があります。

現在の状況を確認します。

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:54:d5:cb brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s3
       valid_lft 86331sec preferred_lft 86331sec
    inet6 fe80::eb16:b65:e9c5:ddc9/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:c0:67:7d brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.101/24 brd 192.168.56.255 scope global dynamic noprefixroute enp0s8
       valid_lft 531sec preferred_lft 531sec
    inet6 fe80::2d19:efaa:a12d:f974/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:6c:c6:1a:3c brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

inet6 が表示されているので、現在は IPv6 が有効な状態です。

まず、一時的に IPv6 を無効化する方法です。これは再起動すると元に戻ります。

sysctl コマンドを使います。

sysctl コマンドは、カーネルのパラメータを変更するコマンドです。以下のように、-w オプションを使って、パラメータを設定すると、その設定が即座にシステムに反映されます。

$ sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
$ sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
$ sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1

一方、永続的に IPv6 を無効化する方法は、2つあります。

1つは、/etc/sysctl.conf に設定を書き込んで、起動時に実行されるスクリプトを作成して、/etc/sysctl.conf を反映させるという方法がありますが、なんか自然な方法ではない気がするので、ここではもう1つの方法で無効化します。

ブートローダーの grub の設定ファイル(/etc/default/grub)で、カーネルパラメータとして、追加で、ipv6.disable=1 を渡す方法です。

/etc/default/grub を編集して、カーネルパラメータに ipv6.disable=1 を追加します。

-GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
+GRUB_CMDLINE_LINUX_DEFAULT="quiet splash ipv6.disable=1"

追加できたら、grub を更新します。

$ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.5.0-41-generic
Found initrd image: /boot/initrd.img-6.5.0-41-generic
Found linux image: /boot/vmlinuz-6.5.0-35-generic
Found initrd image: /boot/initrd.img-6.5.0-35-generic
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done

更新できたら、再起動します。

再度、現在の状態を確認します。

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:54:d5:cb brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s3
       valid_lft 86275sec preferred_lft 86275sec
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:c0:67:7d brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.101/24 brd 192.168.56.255 scope global dynamic noprefixroute enp0s8
       valid_lft 475sec preferred_lft 475sec
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:6c:c6:1a:3c brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

inet6 が表示されなくなりました。IPv6 の無効化については以上です。

tcpdumpのインストール

前回は、Wireshark を使いましたが、コマンドラインで確認するために、tcpdump もインストールしておきます。

$ sudo apt install tcpdump

tcpdump の使い方は、root 権限が必要なので、sudo を付けて実行します。

また、対象とするネットワークインターフェースを -i オプションで指定します。

これでパケットを確認することができますが、見やすくするために、いくつかオプションを使います。

オプション 説明
-n ホストアドレスを名前に変換しない
-nn ホストアドレスに加えて、ポート番号も名前に変換しない
-t タイムスタンプを表示しない

-n と -nn は、実際に使ってみて、違いが分かりませんでした。

ここでは、-n と -t を使います。

VirtualBox + Ubuntu 22.04 のインターネットに繋がるインタフェース(enp0s3)で tcpdump を起動しておき、QEMU から http://www.google.co.jp に wget コマンドを実行したところをキャプチャしてみます。

まず、QEMU 側です。wget コマンドの -S オプションは、サーバ側のレスポンスを表示します。

# wget -S http://www.google.co.jp
Connecting to www.google.co.jp (142.251.222.35:80)
  HTTP/1.1 200 OK
  Date: Mon, 15 Jul 2024 03:42:07 GMT
  Expires: -1
  Cache-Control: private, max-age=0
  Content-Type: text/html; charset=Shift_JIS
  Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-zMcYJVB4lYrRFDRhLYbOHQ' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
  P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
  Server: gws
  X-XSS-Protection: 0
  X-Frame-Options: SAMEORIGIN
  Set-Cookie: AEC=AVYB7codc8r_4hcLjRGK8ssLHyqdH-1AI-Dpk-JGz4vq9sNl1vihwclMgQ; expires=Sat, 11-Jan-2025 03:42:07 GMT; path=/; domain=.google.co.jp; Secure; HttpOnly; SameSite=lax
  Set-Cookie: NID=515=mBDHsj1Jrb6pnJeOb602tO0agFHDUcXdJ3ZMg9kGbp_K7XiJaWr17yawIkAj-Aczp6tsj3kIhBYVuMu34KBWF1Bkb-m-9odbcyF5o228MjIXPCYMfCTH0wTRXGYcTOmyPh17HRoLNp-4qRWcObcyxLyTPLy_L-4Uo7mNUajmRh8; expires=Tue, 14-Jan-2025 03:42:07 GMT; path=/; domain=.google.co.jp; HttpOnly
  Accept-Ranges: none
  Vary: Accept-Encoding
  Connection: close
  Transfer-Encoding: chunked

wget: can't open 'index.html': File exists

Date 情報が確認できます。この情報で時刻合わせが出来たりします。

次に、VirtualBox + Ubuntu 22.04 側で、インターネットに繋がるインタフェースを tcpdump を実行した内容です。

$ sudo tcpdump -tn -i enp0s3
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 10.0.2.15.44635 > 192.168.3.1.53: 43604+ [1au] A? www.google.co.jp. (45)
IP 10.0.2.15.40942 > 192.168.3.1.53: 35084+ [1au] AAAA? www.google.co.jp. (45)
IP 192.168.3.1.53 > 10.0.2.15.44635: 43604 1/4/9 A 172.217.174.99 (319)
IP 192.168.3.1.53 > 10.0.2.15.40942: 35084 1/4/9 AAAA 2404:6800:4004:80c::2003 (331)
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [S], seq 2027278432, win 64240, options [mss 1460,sackOK,TS val 3004422288 ecr 0,nop,wscale 7], length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [S.], seq 14400001, ack 2027278433, win 65535, options [mss 1460], length 0
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 1, win 64240, length 0
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [P.], seq 1:80, ack 1, win 64240, length 79: HTTP: GET / HTTP/1.1
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [.], ack 80, win 65535, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [.], seq 1:2921, ack 80, win 65535, length 2920: HTTP: HTTP/1.1 200 OK
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 2921, win 62780, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [.], seq 2921:7301, ack 80, win 65535, length 4380: HTTP
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 7301, win 61320, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [.], seq 7301:13141, ack 80, win 65535, length 5840: HTTP
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 13141, win 61320, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [P.], seq 13141:14109, ack 80, win 65535, length 968: HTTP
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 14109, win 62780, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [P.], seq 14109:16933, ack 80, win 65535, length 2824: HTTP
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 16933, win 62780, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [P.], seq 16933:19757, ack 80, win 65535, length 2824: HTTP
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 19757, win 62780, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [P.], seq 19757:21581, ack 80, win 65535, length 1824: HTTP
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [.], ack 21581, win 62780, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [F.], seq 21581, ack 80, win 65535, length 0
IP 10.0.2.15.36010 > 172.217.174.99.80: Flags [F.], seq 80, ack 21582, win 62780, length 0
IP 172.217.174.99.80 > 10.0.2.15.36010: Flags [.], ack 81, win 65535, length 0
^C
26 packets captured
26 packets received by filter
0 packets dropped by kernel

先頭で、http://www.google.co.jp を DNS で問い合わせて、IP アドレス 172.217.174.99 を取得しています。

その後は、取得した IP アドレスを使って、http プロトコルで、データ(index.html)を取得しています。

tcpdump の使い方は以上になります。

QEMUのネットワークの確認

まず、IPアドレスと、ルーティングテーブルを確認します。

# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop qlen 1000
    link/ether f2:22:5f:2c:0a:eb brd ff:ff:ff:ff:ff:ff
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
    link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fec0::5054:ff:fe12:3456/64 scope site dynamic flags 100
       valid_lft 86114sec preferred_lft 14114sec
    inet6 fe80::5054:ff:fe12:3456/64 scope link
       valid_lft forever preferred_lft forever
4: sit0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0

# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         _gateway        0.0.0.0         UG    0      0        0 eth0
10.0.2.0        *               255.255.255.0   U     0      0        0 eth0

続いて、外部とのネットワークの接続を確認します。

# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=255 time=11.169 ms
64 bytes from 8.8.8.8: seq=1 ttl=255 time=6.995 ms
^C
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 6.995/9.082/11.169 ms

# ping google.com
PING google.com (172.217.174.110): 56 data bytes
64 bytes from 172.217.174.110: seq=0 ttl=255 time=15.283 ms
64 bytes from 172.217.174.110: seq=1 ttl=255 time=14.880 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 14.880/15.081/15.283 ms

問題なく、インターネットに繋がることと、DNS による名前解決が行われていることが確認できました。

routeコマンドの _gateway は、具体的なアドレスが知りたいですね。

# ping _gateway
PING _gateway (10.0.2.2): 56 data bytes
64 bytes from 10.0.2.2: seq=0 ttl=255 time=0.533 ms
64 bytes from 10.0.2.2: seq=1 ttl=255 time=0.480 ms
^C
--- _gateway ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.480/0.506/0.533 ms

なるほど、10.0.2.2 でした。

VirtualBox と同じネットワークアドレスなので、とてもややこしいです。

VirtualBox では、ルーターが 10.0.2.2 で、起動した Ubuntu 22.04 の IPアドレスが 10.0.2.15 です。

一方、QEMU では、同じく、ルーターが 10.0.2.2 で、起動した Buildroot の IPアドレスが 10.0.2.15 です。

IPアドレスが更新されたりしないかな?と思って、DHCPクライアントで、IPアドレスを削除してみました。

# udhcpc -R -i eth0
udhcpc: started, v1.36.1
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
deleting routers
adding dns 10.0.2.3

リリースのはずですが、すぐに再取得されました。

しかし、おかげで、QEMU のネットワークの情報が分かりました。

ルーターは 10.0.2.2 は合ってますが、DNS サーバが 10.0.2.3 のようです。

# lsof
1       /bin/busybox    0       /dev/console
1       /bin/busybox    1       /dev/console
1       /bin/busybox    2       /dev/console
51      /bin/busybox    0       socket:[41]
51      /bin/busybox    1       /dev/null
51      /bin/busybox    2       /dev/null
51      /bin/busybox    3       /tmp/messages
55      /bin/busybox    0       /dev/null
55      /bin/busybox    1       /dev/null
55      /bin/busybox    2       /dev/null
55      /bin/busybox    3       socket:[42]
95      /bin/busybox    0       /dev/null
95      /bin/busybox    1       /dev/null
95      /bin/busybox    2       /dev/null
95      /bin/busybox    3       pipe:[60]
95      /bin/busybox    4       pipe:[60]
97      /bin/busybox    0       /dev/console
97      /bin/busybox    1       /dev/console
97      /bin/busybox    2       /dev/console
97      /bin/busybox    10      /dev/tty

# which lsof
/usr/bin/lsof

# ls -alF /usr/bin/lsof
lrwxrwxrwx    1 root     root            17 Jul 13 09:09 /usr/bin/lsof -> ../../bin/busybox*

Buildroot では、普通の lsof コマンドは持ってなくて、BusyBox が持っている簡易的な lsof コマンドのようです。

よって、PID、実行ファイルパス、オープンしたファイルぐらいしか表示されません。

listen してるポートとそれをオープンしたファイルが知りたかったのですが、BusyBox の lsofコマンドでは難しいようです。

lsofコマンドをインストールする

では、lsofコマンドのソースコードをダウンロードしてきて、クロスコンパイルして、Buildroot で使えるようにしてみたいと思います。

lsofコマンドの GitHub のリリースページです。

github.com

最新バージョンは、lsof 4.99.3 のようです。

ではダウンロードします。

$ wget https://github.com/lsof-org/lsof/archive/refs/tags/4.99.3.tar.gz
$ tar zxvf 4.99.3.tar.gz
$ cd lsof-4.99.3/

続いて、コンフィグを行う必要がありそうですが、まずは、INSTALL ファイルを軽く眺めてみます。

うーん、configure を行ってください、と書かれてありますが、ファイル名が Configure(先頭が大文字)のファイルしかありません。怪しいな、、と思ったので、00README というファイルを見てみます。こちらは、Configure を実行しなさい、と書かれています。

Configure linux と実行すれば良さそうです。

lsofコマンドをネイティブでコンパイルする

まずは、ネイティブでビルド(x86)で試して、うまくいったら、クロスコンパイルをやっていきます。

$ ./Configure linux

実行すると、いくつか質問されますが、よく分からないので、とりあえず、全てリターンを押しました。

Makefile が出来たようなので、あとは make すればいいはずです。

$ make -j $(nproc)

無事に実行ファイル(lsof)が作られたようですので、試します。

$ ./lsof -i
COMMAND  PID    USER FD   TYPE DEVICE SIZE/OFF NODE NAME
ssh     5768 daisuke 3u  IPv4  41976      0t0  TCP daisuke-VirtualBox:55128->192.168.4.1:ssh (ESTABLISHED)
(省略)

Ubuntu に既に入ってる方の lsof の出力と同じような感じだったので、大丈夫そうです。

lsofコマンドをクロスコンパイルする

次は、クロスコンパイルしていきます。

Web で情報を探したり、ChatGPT に聞いてみたのですが、Makefile の変数を直接書き換える方法しかなさそうです。

Makefile の先頭に、CC=cc とありますので、ここをクロスコンパイラに置き換えます。

-CC=  cc
+CC=    arm-linux-gnueabi-gcc
$ make clean
$ make -j $(nproc)
(省略)
/usr/lib/gcc-cross/arm-linux-gnueabi/11/../../../../arm-linux-gnueabi/bin/ld: ./lib/liblsof.a: error adding symbols: file format not recognized
collect2: error: ld returned 1 exit status
make: *** [Makefile:64: lsof] エラー 1

make が出力した内容を見ると、lib フォルダに移動してからは、クロスコンパイラが使われていませんでした。

ということで、lib フォルダの Makefile も書き換えます。

-CC=  cc
+CC=    arm-linux-gnueabi-gcc
-AR=   ar cr ${LIB} ${OBJ}
+AR=    arm-linux-gnueabi-ar cr ${LIB} ${OBJ}
-RANLIB=   ranlib ${LIB}
+RANLIB=    arm-linux-gnueabi-ranlib ${LIB}

では、make します。

$ make clean
$ make -j $(nproc)
(省略)
arm-linux-gnueabi-gcc -o lsof dfile.o dmnt.o dnode.o dprint.o dproc.o dsock.o dstore.o arg.o main.o print.o store.o usage.o util.o -L./lib -llsof  -ltirpc -lselinux
/usr/lib/gcc-cross/arm-linux-gnueabi/11/../../../../arm-linux-gnueabi/bin/ld: -ltirpc が見つかりません: そのようなファイル やディレクトリはありません
/usr/lib/gcc-cross/arm-linux-gnueabi/11/../../../../arm-linux-gnueabi/bin/ld: -lselinux が見つかりません: そのようなファイ ルやディレクトリはありません
collect2: error: ld returned 1 exit status
make: *** [Makefile:64: lsof] エラー 1

クロスコンパイラ用の libtirpc.so と libselinux.so が見つからない、というエラーのようです。

Ubuntu には、他のアーキテクチャのライブラリも apt でインストールすることが出来るようです。

流れとしては、まず、アーキテクチャを追加します。その後、/etc/apt/sources.list を編集して、apt update でエラーが出ないようにします。そこまで出来たら、必要なライブラリをインストールします。

まず、現在のデフォルトのアーキテクチャを確認します。

$ dpkg --print-architecture
amd64

PC の 64bit と設定されていますね。

次に、追加したアーキテクチャを確認します。

$ dpkg --print-foreign-architectures
i386

PC の 32bit が設定されていました。

そこに、今回のクロスコンパイラ(32bit ARM)の設定を追加します。

$ sudo dpkg --add-architecture armhf

$ dpkg --print-foreign-architectures
i386
armhf

追加することが出来ました。

ここで、apt update を行うと、エラーが出ました。

/etc/apt/sources.list の編集が必要なようです。

現在の sources.list の内容です(コメントは削除しました)。

$ cat /etc/apt/sources.list
deb http://jp.archive.ubuntu.com/ubuntu/ jammy main restricted
deb http://jp.archive.ubuntu.com/ubuntu/ jammy-updates main restricted
deb http://jp.archive.ubuntu.com/ubuntu/ jammy universe
deb http://jp.archive.ubuntu.com/ubuntu/ jammy-updates universe
deb http://jp.archive.ubuntu.com/ubuntu/ jammy multiverse
deb http://jp.archive.ubuntu.com/ubuntu/ jammy-updates multiverse
deb http://jp.archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu jammy-security main restricted
deb http://security.ubuntu.com/ubuntu jammy-security universe
deb http://security.ubuntu.com/ubuntu jammy-security multiverse

この状態だと、全行について、現在の全てのアーキテクチャを探そうとします。しかし、今回追加した armhf は別の URL なので、apt update で、エラーが出てしまいます。

そこで、各行について、適用するアーキテクチャを指定するように変更が必要なようです。

また、armhf 用の URL を追加します。

$ sudo nano /etc/apt/sources.list
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy main restricted
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy-updates main restricted
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy universe
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy-updates universe
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy multiverse
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy-updates multiverse
deb [arch=amd64,i386] http://jp.archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse
deb [arch=amd64,i386] http://security.ubuntu.com/ubuntu jammy-security main restricted
deb [arch=amd64,i386] http://security.ubuntu.com/ubuntu jammy-security universe
deb [arch=amd64,i386] http://security.ubuntu.com/ubuntu jammy-security multiverse

deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse

こうすることで、apt update がエラーなく実行できるようになりました。

では、ようやく、必要なライブラリをインストールしてみます。コロンの後にアーキテクチャを指定します。

$ sudo apt-get install libtirpc-dev:armhf libselinux1-dev:armhf

$ find /usr/ -name 'libtirpc*'
/usr/lib/x86_64-linux-gnu/libtirpc.so.3.0.0
/usr/lib/x86_64-linux-gnu/libtirpc.so
/usr/lib/x86_64-linux-gnu/pkgconfig/libtirpc.pc
/usr/lib/x86_64-linux-gnu/libtirpc.so.3
/usr/lib/x86_64-linux-gnu/libtirpc.a
/usr/lib/arm-linux-gnueabihf/libtirpc.so.3.0.0
/usr/lib/arm-linux-gnueabihf/libtirpc.so
/usr/lib/arm-linux-gnueabihf/pkgconfig/libtirpc.pc
/usr/lib/arm-linux-gnueabihf/libtirpc.so.3
/usr/lib/arm-linux-gnueabihf/libtirpc.a

$ find /usr/ -name 'libselinux*'
/usr/share/doc/libselinux1-dev
/usr/share/doc/libselinux1
!/usr/lib/x86_64-linux-gnu/libselinux.so.1
/usr/lib/x86_64-linux-gnu/libselinux.a
/usr/lib/x86_64-linux-gnu/libselinux.so
/usr/lib/x86_64-linux-gnu/pkgconfig/libselinux.pc
/usr/lib/arm-linux-gnueabihf/libselinux.so.1
/usr/lib/arm-linux-gnueabihf/libselinux.a
/usr/lib/arm-linux-gnueabihf/libselinux.so
/usr/lib/arm-linux-gnueabihf/pkgconfig/libselinux.pc

/usr 以下を検索すると、ちゃんとインストールしたライブラリが確認できました(長いので一部省略しました)。

それでは、このライブラリを使うように Makefile を変更します。

まず、トップディレクトリの Makefile です。

-CC=  cc
+CC=    arm-linux-gnueabihf-gcc

次に、lib ディレクトリの Makefile です。

-CC=  cc
+CC=    arm-linux-gnueabihf-gcc
-AR=   ar cr ${LIB} ${OBJ}
+AR=    arm-linux-gnueabihf-ar cr ${LIB} ${OBJ}
-RANLIB=   ranlib ${LIB}
+RANLIB=    arm-linux-gnueabihf-ranlib ${LIB}

make してみると、先ほどのエラーは無くなり、ビルドが完了しました。

早速、Buildroot に転送して実行してみます。

# ./lsof -i
./lsof: error while loading shared libraries: libselinux.so.1: cannot open shared object file: No such file or directory

エラーになりました。クロスコンパイラ環境にはインストールしましたが、実行環境にライブラリが無いということだと思います。

とりあえず、インストールした2つのライブラリの .so を Buildroot の lsof と同じ場所に転送して、実行してみます。

# LD_LIBRARY_PATH=. ./lsof -i
./lsof: error while loading shared libraries: libpcre2-8.so.0: cannot open shared object file: No such file or directory

うーん、ダメです。他のライブラリが無いということでしょうか。

ダメもとで、/usr/lib/arm-linux-gnueabihf/libpcre2-8.so.0 も Buildroot に転送して、実行してみると、次は、libc のバージョンが対応していない(クロス環境が新しい)というエラーが出ました。

仕方ないので、Ubuntu 22.04 から Ubuntu 18.04 に戻して、試してみます。

Ubuntu で、apt で入るクロスコンパイラって以下の2つがありますね。違いが分かっていません。

  • crossbuild-essential-armhf:dpkg --add-architecture armhf を実施した場合に、よく紹介されているクロスコンパイラ
  • gcc-arm-linux-gnueabihf(インストールするときは g++-arm-linux-gnueabihf を指定した方がいいらしい):検索すると、こちらの方がよくヒットする

今までは何も考えずに後者を使ってましたが、今回は前者を使ってみます。

$ sudo apt install crossbuild-essential-armhf
以下の追加パッケージがインストールされます:
  binutils binutils-arm-linux-gnueabihf binutils-common binutils-x86-64-linux-gnu cpp-7-arm-linux-gnueabihf
  cpp-arm-linux-gnueabihf dpkg-cross g++-7-arm-linux-gnueabihf g++-arm-linux-gnueabihf gcc-7-arm-linux-gnueabihf
  gcc-7-arm-linux-gnueabihf-base gcc-7-cross-base gcc-8-cross-base gcc-arm-linux-gnueabihf libasan4-armhf-cross
  libatomic1-armhf-cross libbinutils libc6-armhf-cross libc6-dev-armhf-cross libcilkrts5-armhf-cross libconfig-auto-perl
  libconfig-inifiles-perl libdebian-dpkgcross-perl libfile-homedir-perl libfile-which-perl libgcc-7-dev-armhf-cross
  libgcc1-armhf-cross libgomp1-armhf-cross libstdc++-7-dev-armhf-cross libstdc++6-armhf-cross libubsan0-armhf-cross
  libyaml-perl linux-libc-dev-armhf-cross

よく見ると、依存パッケージとして、gcc-arm-linux-gnueabihf が入っていました。ついでに、g++-arm-linux-gnueabihf も入っています。今度からは、crossbuild-essential-armhf(64bitの場合は crossbuild-essential-arm64)を入れることにします。

では、同様に、追加のライブラリをインストールし、コンフィグで linux を指定し、Makefile を同様に書き換えてビルドします。動的リンクなら問題なく完了しました。

一応、glibc のバージョンを確認します。

$ dpkg -l | grep libc6
ii  libc6:amd64                                2.27-3ubuntu1.6                                 amd64        GNU C Library: Shared libraries
ii  libc6:armhf                                2.27-3ubuntu1.6                                 armhf        GNU C Library: Shared libraries
ii  libc6-armhf-cross                          2.27-3ubuntu1cross1.1                           all          GNU C Library: Shared libraries (for cross-compiling)
ii  libc6-dbg:amd64                            2.27-3ubuntu1.6                                 amd64        GNU C Library: detached debugging symbols
ii  libc6-dev:amd64                            2.27-3ubuntu1.6                                 amd64        GNU C Library: Development Libraries and Header Files
ii  libc6-dev:armhf                            2.27-3ubuntu1.6                                 armhf        GNU C Library: Development Libraries and Header Files
ii  libc6-dev-armhf-cross                      2.27-3ubuntu1cross1.1                           all          GNU C Library: Development Libraries and Header Files (for cross-compiling)

だいぶ古いので大丈夫な気がします。

では、lsof のディレクトリと、クロスコンパイラのライブラリを圧縮して、Ubuntu 22.04 にコピーします。

Buildroot に転送して、実行してみました。動きました!

必要なツールをクロスコンパイルして Buildroot で動かすことは、なかなか難しいということが分かりました。

おわりに

今回は、QEMU のネットワークの調査とその準備、必要なツールのクロスコンパイルを行いました。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。