(2020/5/27)
【実験】Raspbianを使えちゃう様にするや【実験】Debian stretchのchroot環境構築でchrootを使ってraspbianやdebianのバイナリをPaPeRo i 本体で動かしましたが、patchelfというコマンドを使う方法でも似たようなことができそうだったので試してみました。今回は音声強化版パペロのTinkerBoardS(OS=TinkerOS Debian9 v2.0.11)のバイナリをPaPeRo i 本体で動かしてみます。
スタティックリンクなら動くかの確認
まず最初に確認のため、TinkerBoardSでソースhw.c
#include <stdio.h>
int main()
{
printf("hello world\n");
}
を以下の2通りでコンパイルし、
tinker $ gcc -o hwd hw.c
tinker $ gcc -o hws -static hw.c
できたhwd、hwsをPaPeRo i 本体に転送して動かしてみると
papero # ./hwd
/bin/sh: ./hwd: not found
papero # ./hws
hello world
と、-staticを付けたスタティックリンクの実行可能ファイルであれば動きますがダイナミックリンクでは動きません。動かしたいものをスタティックリンクでビルドすれば良い?という話ではなくて(可能ならそれでも良いかもしれませんが)、ここでは動かない原因を解決できればダイナミックリンクでもちゃんと動きそうだという確認ができたということになります。参考までに/proc/cpuinfoはそれぞれ以下の通りでした。
papero # cat /proc/cpuinfo
Processor : ARMv7 Processor rev 1 (v7l)
processor : 0
BogoMIPS : 1790.77
processor : 1
BogoMIPS : 1790.77
Features : swp half thumb fastmult vfp edsp neon vfpv3 tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x2
CPU part : 0xc09
CPU revision : 1
tinker $ cat /proc/cpuinfo
processor : 0
model name : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 48.00
Features : half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xc0d
CPU revision : 1
processor : 1
model name : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 48.00
Features : half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xc0d
CPU revision : 1
processor : 2
model name : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 48.00
Features : half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xc0d
CPU revision : 1
processor : 3
model name : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 48.00
Features : half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xc0d
CPU revision : 1
Hardware : Rockchip (Device Tree)
Revision : 0000
Python3.7.3の準備
せっかくなので新しいPythonを動かしてみることにしました。音声強化版パペロのTinkerBoardSではログインした状態でpyenvが有効になっており、試した時点でログイン直後のデフォルトがPython3.4.2、システムのPython3は3.5.3でした。この3.5.3でも良かったのですがせっかくあらかじめpyenv環境が整えられているので、この時点でpyenvで利用可能な最も新しいバージョンをインストールしました。
tinker $ pyenv insall --list
tinker $ pyenv insall 3.7.3
ソースからビルドされ、結果は/home/linaro/.pyenv/versions/3.7.3以下に置かれます。このディレクトリ以下のディスク使用量(du -s)は236MByte強でした。試しにPaPeRo i 本体で実行してみても、
papero # ./python3.7
/bin/sh: ./python3.7: not found
と当然ですがこのままでは実行できません。
ダイナミックリンクの実行可能ファイルを実行できる条件
ダイナミックリンクの実行可能ファイルは実行時に共有ライブラリとリンクされて実行されますが、これを行うのは通常/lib以下にあるダイナミックリンカ・ローダld-linux.soです。実行可能ファイルが実際に実行できるためには、
(1) 実行可能ファイルそのもの
(2) リンクされる共有ライブラリ
(3) ld-linux.so
が同一環境のものである必要があり、この場合で言えばPaPeRo i 本体のものとTinkerBoardSのものを混ぜることはできません。chrootを使うと全てTinkerBoardSの環境にそろえることができるので実行できるという事になります。chrootを使わずに実行することを考えると
(1) 実行可能ファイルそのものはシェルやスクリプトから直接指定する
(2) リンクされる共有ライブラリは環境変数LD_LIBRARY_PATHで場所を指定できる
ということでTinkerBoardSの環境にそろえられますから問題は(3)のld-linux.soになります。そもそも実行時にどうやってld-linux.soを探すかというと、
実行可能ファイルにフルパスが書いてある(コンパイル時に書かれる)
のだそうです。TinkerBoardSでコンパイルしたダイナミックリンクの実行可能ファイルには
/lib/ld-linux-armhf.so.3
が書かれています。PaPeRo i は/lib以下はリードオンリーなのでTinkerBoardSのld-linux.so(や共有ライブラリ)は書き込みできる/Extension以下に置くしかありません。結局TinkerBoardSの実行可能ファイルに書かれているld-linux.soのパスを、PaPeRo i 上にコピーした/Extension以下のパスに書き換えれば実行できそうだという事になります。
patchelfコマンド
PaPeRo i 本体には存在しませんがTinkerBoardSではpatchelfコマンドで実行可能ファイルのld-linux.soパスの書き換えができます。インストールは
sudo apt install patchelf
です。現在の値を表示するには
patchelf --print-interpreter 実行可能ファイル名
とします。書き換えるには
patchelf --set-interpreter ld-linux.soパス 実行可能ファイル名
とします。TinkerBoardSの共有ライブラリ等をPaPeRo i にコピーするルートをここでは
/Extension/tinker
に決めました。TinkerBoardS の
/lib
は50MByteほどなので全てPaPeRo i 本体の
/Extension/tinker/lib
にコピーしました。ld-linux.soは
/Extension/tinker/lib/ld-linux-armhf.so.3
にあるのでpython3.7をpython3.7-peにコピーした上でpatchelfで書き換えます。
tinker $ cd ~/.pyenv/versions/3.7.3/bin
tinker $ cp python3.7 ./python3.7-pe
tinker $ patchelf --print-interpreter ./python3.7-pe
tinker $ patchelf --set-interpreter /Extension/tinker/lib/ld-linux-armhf.so.3 ./python3.7-pe
そしてこの~/.pyenv/versions/3.7.3以下をPaPeRo i 本体のExtension/tinker/usr以下にコピーしました。
pythonを動かしてみる
このpatchelf版pythonを動かしてみると共有ライブラリが読めないエラーになります。これは/usr/lib/arm-linux-gnueabihf以下をコピーしていないためなのですが、TinkerBoardSの/usr/libはサイズが大きいためエラーになる共有ライブラリのみコピーしました。
papero # ls /Extension/tinker/usr/lib/arm-linux-gnueabihf/
libcrypto.so libcrypto.so.1.1 libffi.so.6.0.4 libssl.so.1.1
libcrypto.so.1.0.0 libffi.so libssl.so libstdc++.so.6
libcrypto.so.1.0.2 libffi.so.6 libssl.so.1.0.2 libstdc++.so.6.0.22
この状態でもう一度patchelf版pythonを起動すると正常に起動します(環境変数の指定が必要です)。
papero # PATH=/Extension/tinker/bin:/Extension/tinker/sbin:/Extension/tinker/usr/bin:/Extension/tinker/usr/sbin LD_LIBRARY_PATH=/Extension/tinker/lib:/Extension/tinker/lib/arm-linux-gnueabihf:/Extension/tinker/usr/lib:/Extension/tinker/usr/lib/arm-linux-gnueabihf PYTHONPATH=/Extension/tinker/usr/lib/python3.7 /Extension/tinker/usr/bin/python3.7-pe
Python 3.7.3 (default, Apr 28 2020, 10:06:02)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
数値計算やリストや辞書の操作を問題なく実行でき、またpython3.7 -m pipでパッケージのインストールもできます。これでPaPeRo i 本体でPython3.7を動かすことができました、と言えればよかったのですが、手元にある実用的なプログラムを動かそうとすると正常に動作しません。これは使用するパッケージによってはパッケージ内部で、
(1) unameなどコマンドを実行した結果を参照する
(2) 環境変数は見ず/lib以下の共有ライブラリを探して動的ロードしようとする
といった事が行われるためで、通常の環境であれば正常に動作するのですがこの環境では動作しません。パッケージをカスタマイズして動かそうと試みたのですがやり切れず、何をするにも手がかかり実用的な環境とは言えませんでした。
使い道
pythonの様に内部でコマンド実行や共有ライブラリの動的ロードを行う様なプログラムを動かす方法としてはpatchelfでの実行は実用的でないことが分かりましたが、例えばgrep(PaPeRo i 本体のものはBusyBoxの機能制限版)とかpatchelf自体の様なシンプルなコマンドであれば使用に問題は無さそうです。これらをPaPeRo i 本体用にソースからコンパイルするのに較べれば少ない手間で実行できると言えます。chrootと比較すると利点は
- 消費するFlash容量が100MByte以下なので/Extensionに置ける (chrootではUSBメモリ必須)
- コマンド指定でPaPeRo i 本体用の実行可能ファイルもpatchelfしたTinkerBoardSの実行可能ファイルも両方実行可能 (chroot環境ではPaPeRo i 本体用の実行可能ファイルは実行できない)
欠点は繰り返しになりますが
- 動かせるプログラムがシンプルなものに限定される
という事になります。