0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonでQEMUモニタを操作する

Last updated at Posted at 2022-07-23

概要

QEMUモニタの操作(メモリダンプ等)を自動化する方法を考えていたところ、Pythonのsubprocessモジュールを使用すれば実現できそうだったので実際に試してみました。

とりあえずの目標として、QEMUモニタをインタラクティブに操作するPythonスクリプトを作成します。

QEMUの起動

qemu_test.py
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モニタをインタラクティブに操作する方法を検討します。

標準入力への書き込み

標準入力への書き込みは以下のコードで実現できます。

qemu_test.py
cmd = "入力するコマンド"

proc.stdin.write(cmd+'\n')
proc.stdin.flush()

Popen.stdin.flush()を行わないと書き込みが標準入力へ反映されないそうなので、注意が必要です。

標準出力の読み込み

標準出力の読み込み処理の実装がやや面倒だったので、順を追って説明します。

問題点

通常、シェルにコマンドを入力してQEMUを起動した場合は以下のように出力されます。

bash
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) プロンプトの直前までの標準出力の読み出しを実現できます。

コード全容

最終的には以下のようなコードになりました。

qemu_test.py
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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?