動機
ARMが提供するsemihosting機能を試してみたい。
semihosting機能とは
セミホスティングとは、ARM ターゲット上のアプリケーションコードから発行される入出力要求を、デバッガが実行されているホストコンピュータに伝達するメカニズムです。 例えば、このメカニズムを使用すると、printf() や scanf() などの C ライブラリ関数で、ターゲットシステム上の画面とキーボードではなく、ホストの画面とキーボードを使用することができます。
技術的には、特殊な命令をARM CPUが実行したときに、デバッガもしくはシミュレーション環境であればシミュレータがその命令をハンドルすることで、ホストマシン側のターミナルと入出力をつなぐ。ここで「特殊な命令」というのは、例えばラズパイなどで使われているCoretex-A53(ARMv8)の場合、aarch64モードではHLT 0xf000
と定められている。
上記に引用の通り、通常DS-5などの開発環境と一緒にsemihosting機能を実装したCライブラリ関数printf()やscanf()が提供される。
QEMUでは、こちらのようにsemihostingを利用してホストの入出力を使用している。
また、u-bootやlinux kernelでは、semihostingを使ったデバドラが実装されている。
他にも、linaroが公開しているnewlibのlibgrossではsemihosting機能で実装したシステムコールAPIが提供されている。
今回はもっともお手軽に、QEMU上でaarch64のユーザアプリケーションを動かすことで、semihosting機能を試してみる。
環境
- OS: Ubuntu16.04 x86_64
- CPU: Intel(R) Core(TM) i7-3517U CPU @ 1.90GHz x4
手順
1. 必要なパッケージのインストール
$ sudo apt install gcc-5-aarch64-linux-gnu qemu-user-static
2. テストコードのビルド&実行
$ git clone https://github.com/takeoverjp/semihosting.git
$ cd semihosting
$ make
aarch64-linux-gnu-gcc -g -O0 -o semihosting -static semihosting.c
$ ./semihosting
This string is output via semihosting.
非常に短いプログラムなので、全文を以下に記す。
stdio.hを使っていないし、システムコールも呼び出していないのに文字が出力できている点がポイント。
smh_writec関数で1文字セミホスティングで出力している。インラインアセンブリなので少しビビるかも知れないが、やってることは非常に単純。x0
レジスタに3(1文字出力を表す)を、x1
レジスタに出力したい文字コードを格納し、hlt 0xf000
命令を発行しているだけ。
static void
smh_writec (int c)
{
asm volatile ("mov x1, %0\n\t"
"mov x0, #3\n\t"
"hlt 0xf000\n\t"
: : "r" (&c) : "x0", "x1", "memory");
}
int
main (int argc, char *argv[])
{
const char str[] = "This string is output via semihosting.\n";
for (int i = 0; str[i] != '\0'; i++)
{
smh_writec (str[i]);
}
return 0;
}
もちろんセミホスティングには1文字出力以外にもたくさんの機能があるが、基本的にはsmh_writec()
でやっているように、x0
レジスタに機能種別を格納して、x1
, x2
, x3
レジスタに引数を格納し、hlt 0xf000
命令を発行するだけで利用できる。
前述のとおり、ドライバやライブラリに組み込まれるケースが多いと思われるので、あまり直接使う機会はないかもしれないが、仕組みがわかっていれば他には影響を与えず必要な箇所だけ部分的に使うことも簡単にできそうなので、覚えておきたい。
Tips
QEMUユーザモードエミュレーションにおけるgdbデバッグ
qemu-user-staticをインストールすると、armの実行ファイルが直接実行できるようになる。とても便利なのだが、なぜ直接実行できるかわかっていないと、gdbデバッグするときに少し混乱するかも知れない。
これは、binfmtという仕組みで実現している。binfmtとは、特定のバイナリパターンで始まるファイルを実行するときには、予め指定したインタプリタを使って実行するという仕組みだ。つまり、シェルスクリプトなどで指定するシェバンのもっと汎用的な仕組みだと思えばいい。
ただしシェバンとは違い、実行ファイル単体を見ても、どのインタプリタを使うかはわからない。下記のようにすれば、binfmtが選択するインタプリタを確認することができる。
$ update-binfmts --find semihosting
/usr/bin/qemu-aarch64-static
binfmtのルールを確認したいときは、/procから確認できる。
$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64-static
flags: OC
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff
話が少し逸れたが、つまりシミュレータである/usr/bin/qemu-aarch64-static
がsemihosting
というaarch64用の実行ファイルを逐次実行していくことになる。
このqemu-aarch64-staticに-g
オプションを指定すれば、aarch64-linux-gnu-gdbでリモートデバッグできる。
$ qemu-aarch64-static -g 4321 semihosting
$ aarch64-linux-gnu-gdb -i=mi semihosting
(gdb) target remote :4321
Remote debugging using :4321
0x00000000004004c8 in _start ()
また、qemu-aarch64-static --help
を見ればわかるが、QEMU_GDB
環境変数でも-g
オプションと同等の指定ができる。
$ QEMU_GDB=4321 ./semihosting
$ aarch64-linux-gnu-gdb -i=mi semihosting
(gdb) target remote :4321
Remote debugging using :4321
0x00000000004004c8 in _start ()