概要
QEMUモニタの操作(メモリダンプ等)を自動化する方法を考えていたところ、Pythonのsubprocess
モジュールを使用すれば実現できそうだったので実際に試してみました。
とりあえずの目標として、QEMUモニタをインタラクティブに操作するPythonスクリプトを作成します。
QEMUの起動
import subprocess
kernel_path = (イメージファイルへの絶対パス)
start_cmd = ['qemu-system-aarch64', '-cpu', 'cortex-a57', '-machine', 'virt',
'-kernel', kernel_path, '-monitor', 'stdio', '-nographic']
proc = subprocess.Popen(start_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, text=True, encoding='utf-8')
subprocess.Popen
クラスを利用してQEMUを起動します。
QEMU起動用のコマンドに色々オプションがついていますが、標準入出力を通してQEMUモニタを制御するための-monitor stdio
オプションは必須です。
また、標準入出力をwrite/readで読み書きするため、Popen
コンストラクタの引数stdin, stdoutにはそれぞれsubprocess.PIPE
を指定します。
QEMUモニタの操作
Popen
の各種メソッドを用いて、QEMUモニタをインタラクティブに操作する方法を検討します。
標準入力への書き込み
標準入力への書き込みは以下のコードで実現できます。
cmd = "入力するコマンド"
proc.stdin.write(cmd+'\n')
proc.stdin.flush()
Popen.stdin.flush()
を行わないと書き込みが標準入力へ反映されないそうなので、注意が必要です。
標準出力の読み込み
標準出力の読み込み処理の実装がやや面倒だったので、順を追って説明します。
問題点
通常、シェルにコマンドを入力してQEMUを起動した場合は以下のように出力されます。
QEMU 6.2.0 monitor - type 'help' for more information
(qemu)
調査の結果、上記の (qemu) プロンプトをPopen.stdout.readline()
で読み込もうとすると、
何らかのコマンドを入力しない限りハングアップしてしまうことが判明しました。
したがって、
-
Popen.stdout.readline()
で標準出力を逐次読み出そうとすると、 (qemu) プロンプトを読み出そうとした時点でスクリプトがハングアップする - 一方、 (qemu) プロンプトの直前まで標準出力を読み出そうにも、 (qemu) プロンプトの直前かどうかを判断する手立てが無い
といった理由から、入力したコマンドの結果をインタラクティブに取得するのが難しいという問題点があります。
解決策
強引な解決策ですが、QEMUモニタが基本的にステートレスであるのを利用して、何らかのコマンドをQEMUモニタへ入力したのちに無効なコマンド(例:hoge)を入力する処理を挟むことで解決しました。
QEMUモニタは無効なコマンドを入力された場合unknown command: '(コマンド)'
と出力するので、unknown command
を含んだ文字列を読み込んだ時点でPopen.stdout.readline()
による読み込みを停止することで、ハングアップを回避しつつ (qemu) プロンプトの直前までの標準出力の読み出しを実現できます。
コード全容
最終的には以下のようなコードになりました。
import subprocess
class QemuMonitor():
def __init__(self):
self.kernel_path = (イメージファイルへの絶対パス)
self.start_cmd = ['qemu-system-aarch64', '-cpu', 'cortex-a57', '-machine', 'virt',
'-kernel', self.kernel_path, '-monitor', 'stdio', '-nographic']
self.proc = subprocess.Popen(self.start_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, text=True, encoding='utf-8')
self._prepare_for_interactive()
def _prepare_for_interactive(self):
self.proc.stdout.readline()
def talk(self, cmd=''):
if cmd == '':
return 1
self.proc.stdin.write(cmd+'\n')
self.proc.stdin.flush()
if self.is_alive():
self.proc.stdin.write("hoge\n")
self.proc.stdin.flush()
count = 0
while(self.is_alive()):
line = self.proc.stdout.readline()
count += 1
if 'unknown command' in line:
if count == 2:
print(line, end="")
continue
else:
break
if '(qemu)' in line:
continue
print(line, end="")
def is_alive(self):
if self.proc.poll() == None:
return True
else:
return False
def kill_qemu(self):
if self.is_alive() == True:
self.proc.send_signal(2)
def __del__(self):
self.kill_qemu()
if __name__ == "__main__":
qm = QemuMonitor()
while(qm.is_alive()):
print("(qemu) ", end="")
cmd = input()
qm.talk(cmd)
このスクリプトによって、「QEMUモニタをインタラクティブに操作する」という一応の目標は達成できました。
QEMUモニタを極力再現するために手を加えた結果汚いコードになっていますが、リファクタリングは今後の課題とします。
このスクリプトを流用すれば、「QEMUモニタの処理を自動化する」という当初の目的は達成できそうです。
所感
Pythonのsubprocess
モジュールを使用すれば、QEMUモニタの処理を自動化できることが分かりました。
QEMUモニタだけでなく、インタラクティブな入出力を行う様々なプログラムの自動化にも応用が効くと思われます。
参考サイト
subprocess --- サブプロセス管理 — Python 3.10.4 ドキュメント
Popenでinteractiveシェルと通信する | tm23forest.com