bash スクリプト内で dbgsh 経由で BitVisor のコマンドを実行する

# はじめに
BitVisor Advent Calendar 4 日目(タイムゾーン: AoE) です。

BitVisor には dbgsh と呼ばれる対話式のシェルがあり、これはゲスト OS から呼び出すことができます。これを使えばゲスト OS から BitVisor のログを出力したり、`msgregister()` で登録した関数を呼び出せたりして、デバッグや実験で便利です。
しかし、対話シェルであるために、bash スクリプトなどから呼び出すには少し工夫が必要です。今回はその方法を紹介できればと思います。
(注1: 今回紹介するのは技術的には bash のテクニックになります。BitVisor 固有の話ではありません、あしからずご了承ください🙇‍♂️)
(注2: 基本的に bash でしか動作確認していないので bash と明記していますが、どこが bash 固有の機能なのかは特に確認していません。もしかしたら他のシェルでも動作するかもしれません。)

## msgregister で登録した関数を呼び出す。
@mmi さんが書かれた[こちらの記事](https://qiita.com/mmi/items/561ff1ed5923e6a0f1a0)の最後の方で解説されている通り、BitVisor のカーネル内で `msgregister()` を使って登録した関数は、dbgsh の `sendint` コマンドなどで呼び出せます。
これを bash のワンライナーで書くと以下のようにできます。

```bash

$ echo -e 'sendint\nfree\n\nexit\n' | ./tools/dbgsh/dbgsh
stty: 'standard input': Inappropriate ioctl for device
> sendint
sendint> free
send 0 to free
sendint>
> exit
stty: 'standard input': Inappropriate ioctl for device
$
```

(`stty: 'standard input': Inappropriate ioctl for device` の警告が出ないようにする方法は少し調べてみたんですが見つけられませんでした... 知っている方がいれば教えてください。一応この警告が出ていても動作は問題なさそうです。)

もう少し可読性のある書き方だとこんな感じです。

```bash
$ cat <<EOF | ./tools/dbgsh/dbgsh
sendint
free

exit
EOF
#出力省略
```

この例はワンライナーですが、これをスクリプトに入れて実行することも可能です。
ちなみに sendint で呼び出している `free` は BitVisor のメモリ使用量をログに書き出すものです。

### 解説
どちらのスクリプトもパイプの前で dbgsh 内でユーザーが入力する内容をテキストとして出力するもので、それをパイプを通して dbgsh に渡しているというものです。
注意として、

* `echo` コマンドは `-e` オプションを付けないと `\n` などのエスケープ文字を開業に変えてくれません。
* `time` と入力した後には **2 回** 改行を入力する必要があります。これは `sendint` のプロンプトから抜けるためです。
* ちゃんと `exit` コマンドを実行しましょう。exit しないと次に同様のワンライナーを実行した際に dbgsh が中途半端な状態になってうまく動かない可能性があります。

## dbgsh 経由で bash スクリプトから BitVisor のログを出力する
上記の sendint の例と同様にコマンドを投げればいいのですが、ログの表示ではもう少し工夫する点があります。
とりあえず例を見てみましょう。

```bash
$ echo -e 'log\nfffffffffffqexit\n'| ./tools/dbgsh/dbgsh;
stty: 'standard input': Inappropriate ioctl for device
> log
Starting BitVisor...
Copyright (c) 2007, 2008 University of Tsukuba
All rights reserved.
ACPI DMAR not found.
FACS address 0xCD022080 0xCD022040
Module not found.
Processor 0 (BSP)
..................................................                                                  oooooooooooooooooooooooooooooooooooooooooooooooooo
Using VMX.
Processor 0 3299991424 Hz (Invariant TSC)
Loading drivers.
PCI device concealer registered
PCI device monitor registered
PCI: finding devices...
PCI: 17 devices found
MCFG [0] 0000:00-3F (F8000000,4000000)
Starting a virtual machine.
[00:1F.6] New PCI device found.
Processor 1 (AP)
Processor 3 (AP)
Processor 2 (AP)
Processor 3 3299996608 Hz (Invariant TSC)
Processor 1 3300000896 Hz (Invariant TSC)
Processor 2 3300000448 Hz (Invariant TSC)
time 24535135985
time 24849966374
time 27071149645
time 27083488874
time 27088495409
time 27098768926
time 27122310759
time 27169041001
time 27180289137
time 27181213335
time 27193770241
time 27203807380
25697 pages (102788 KiB) free
> exit
```
(ちなみに time というログは以下のコードの false を true にすると使える msg handler で出力できます https://sourceforge.net/p/bitvisor/code/ci/7eb4586136f36faa856da0011438d81476fe2322/tree/core/time.c#l202)

```bash
$ cat <<EOF | ./tools/dbgsh/dbgsh
log
ffffffffffffffffffffq
exit

EOF
#(出力省略)
```
### 解説
dbgsh で `log` を実行しているところまではよいと思いますが、`ffffffffffffq` は何でしょうか? これは dbgsh の log でページ送りするためのキー入力 (`f` でページ送りします) と log 表示を終了するためのキー入力 (`q` でログ出力を終了します)になります。このログ表示の際の `f` と `q` のキー入力は改行なしで動きますので改行はいれていません (more や less コマンドと同じです)
`f` の数は適当ですが、多すぎて動かなくなることはないのでログをすべて出力するのに必要そうな適当な数の `f` を入れておくとよいです。

### ちなみに
@hdk_2 さんの[以前の記事](https://qiita.com/hdk_2/items/ce488b3c04afefa7a785) で tools/log を使ったログの取得方法があります。継続的にログを取得する必要がある場合には tools/log を使った方が便利そうです。Linux のログの機構でタイムスタンプが付いたり Linux のログと合わせて見れそうなのが良さそうです(実は使ったことないのですが...)

一方、今回の方法はとりあえず雑に一度ログをダンプできればいいという用途には手っ取り早くてよいかもしれません。リダイレクトすればファイルにも書き出せます。ただ、少し制御文字が少し混じってしまいますが...

## ssh 経由で実行
最後に、ssh 経由で dbgsh 内のコマンドを実行する方法です。
基本的には上のワンライナーを ssh 経由で実行すればいいのですが、エスケープがわけわからんことになります。自分でもどうしてこうなったのは記憶が定かではありませんが、以前使っていたスクリプトから抜粋したものを書き残しておこうと思います。

```bash
remote_run() {
  cmd=$1
  host="user@10.20.30.40"
  eval ssh $host $cmd
}
remote_run "'( echo -e '\''sendint\nfree\n\nexit\n'\'' | /home/user/bitvisor/tools/dbgsh/dbgsh )'" || true
```

ちなみにここで呼び出している time というのは core/time.c で定義されているもので、ログに時間 (たぶん TCS の値) を出力するものです。これは time_init_msg() 内にある if 文の条件を false から true に変えてビルドすれば使えます。
https://sourceforge.net/p/bitvisor/code/ci/7eb4586136f36faa856da0011438d81476fe2322/tree/core/time.c#l202

## 関連技術?
今回は bash でやりましたが、`expect` というコマンドを使って対話シェルとのやり取りを自動化する方法もあります。こちらの方が応答内容によって処理を替えられたりするので柔軟ですが、いかんせん自動化用のスクリプトを書くのが面倒です。決まった順番でコマンドを投げるだけなら今回の方法で十分かと思います。また、ansible でも対話シェルとのやり取りを自動化できると聞いたことがあります(内部では expect を使っているのかな?) 詳しくは知らないので興味があれば調べてみてください。

# おわりに
dbgsh のコマンドをスクリプトから実行する方法をご紹介しました。結構便利だなと個人的には思っているので、ぜひぜひ使ってみてください。
ちなみに今回の bash のテクニックは telnet などの対話シェルでもだいたい似たように使えます (そちらはググると色々出てくると思います)。ネットワークスイッチや QEMU monitor に雑にコマンドを放り込むときに便利です。