Skip to content

Commit 2eca31e

Browse files
committed
boot-qemu.py: Break launch_qemu() into smaller functions
launch_qemu() has gotten quite unweildy in terms of indentation level and readability. Fix it by breaking it a part into several different functions, each of which has full documentation behind it to understand what they are doing and why they exist. There are a couple of minor changes around spacing in this, which came about due to changes to the order in which strings are printed. Signed-off-by: Nathan Chancellor <[email protected]>
1 parent 55adeb2 commit 2eca31e

File tree

1 file changed

+177
-113
lines changed

1 file changed

+177
-113
lines changed

boot-qemu.py

Lines changed: 177 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ def pretty_print_qemu_info(qemu):
696696
qemu_version_string = get_qemu_ver_string(qemu)
697697

698698
utils.green(f"QEMU location: \033[0m{qemu_dir}")
699-
utils.green(f"QEMU version: \033[0m{qemu_version_string}\n")
699+
utils.green(f"QEMU version: \033[0m{qemu_version_string}")
700700

701701

702702
def pretty_print_qemu_cmd(qemu_cmd):
@@ -721,29 +721,149 @@ def pretty_print_qemu_cmd(qemu_cmd):
721721
qemu_cmd_pretty += f' {element.split("/")[-1]}'
722722
else:
723723
qemu_cmd_pretty += f" {element}"
724-
print(f"$ {qemu_cmd_pretty.strip()}", flush=True)
724+
print(f"\n$ {qemu_cmd_pretty.strip()}", flush=True)
725725

726726

727-
def launch_qemu(cfg):
727+
def launch_qemu_gdb(cfg):
728728
"""
729-
Runs the QEMU command generated from get_qemu_args(), depending on whether
730-
or not the user wants to debug with GDB.
731-
732-
If debugging with GDB, QEMU is called with '-s -S' in the background then
733-
gdb_bin is called against 'vmlinux' connected to the target remote. This
734-
can be repeated multiple times.
735-
736-
Otherwise, QEMU is called with 'timeout' so that it is terminated if there
737-
is a problem while booting, passing along any error code that is returned.
729+
Spawn QEMU in the background with '-s -S' and call gdb_bin against
730+
'vmlinux' with the target remote command. This is repeated until the user
731+
quits.
738732
739733
Parameters:
740734
cfg (dict): The configuration dictionary generated with setup_cfg().
741735
"""
742-
interactive = cfg["interactive"]
743-
gdb = cfg["gdb"]
744736
gdb_bin = cfg["gdb_bin"]
745737
kernel_location = cfg["kernel_location"]
746738
qemu_cmd = cfg["qemu_cmd"]
739+
740+
if cfg['share_folder_with_guest']:
741+
utils.yellow(
742+
'Shared folder requested during a debugging session, ignoring...')
743+
744+
while True:
745+
utils.check_cmd("lsof")
746+
lsof = subprocess.run(["lsof", "-i:1234"],
747+
stdout=subprocess.DEVNULL,
748+
stderr=subprocess.DEVNULL,
749+
check=False)
750+
if lsof.returncode == 0:
751+
utils.die("Port 1234 is already in use, is QEMU running?")
752+
753+
utils.green("Starting QEMU with GDB connection on port 1234...")
754+
with subprocess.Popen(qemu_cmd + ["-s", "-S"]) as qemu_process:
755+
utils.green("Starting GDB...")
756+
utils.check_cmd(gdb_bin)
757+
gdb_cmd = [gdb_bin]
758+
gdb_cmd += [kernel_location.joinpath("vmlinux")]
759+
gdb_cmd += ["-ex", "target remote :1234"]
760+
subprocess.run(gdb_cmd, check=False)
761+
762+
utils.red("Killing QEMU...")
763+
qemu_process.kill()
764+
765+
answer = input("Re-run QEMU + gdb? [y/n] ")
766+
if answer.lower() == "n":
767+
break
768+
769+
770+
def find_virtiofsd(qemu_prefix):
771+
"""
772+
Find virtiofsd relative to qemu_prefix.
773+
774+
Parameters:
775+
qemu_prefix (Path): A Path object pointing to QEMU's installation prefix.
776+
777+
Returns:
778+
The full path to virtiofsd.
779+
"""
780+
virtiofsd_locations = [
781+
Path('libexec', 'virtiofsd'), # Default QEMU installation, Fedora
782+
Path('lib', 'qemu', 'virtiofsd'), # Arch Linux, Debian, Ubuntu
783+
]
784+
return utils.find_first_file(qemu_prefix, virtiofsd_locations)
785+
786+
787+
def get_and_call_sudo():
788+
"""
789+
Get the full path to a sudo binary and call it to gain sudo permission to
790+
run virtiofsd in the background. virtiofsd is spawned in the background so
791+
we cannot interact with it; getting permission beforehand allows everything
792+
to work properly.
793+
794+
Returns:
795+
The full path to a suitable sudo binary.
796+
"""
797+
if not (sudo := shutil.which('sudo')):
798+
raise Exception(
799+
'sudo is required to use virtiofsd but it could not be found!')
800+
utils.green(
801+
'Requesting sudo permission to run virtiofsd in the background...')
802+
subprocess.run([sudo, 'true'], check=True)
803+
return sudo
804+
805+
806+
def get_virtiofsd_cmd(qemu_path, socket_path):
807+
"""
808+
Generate a virtiofsd command suitable for running through
809+
subprocess.Popen().
810+
811+
This is the command as recommended by the virtio-fs website:
812+
https://virtio-fs.gitlab.io/howto-qemu.html
813+
814+
Parameters:
815+
qemu_path (Path): An absolute path to the QEMU binary being used.
816+
socket_path (Path): An absolute path to the socket file virtiofsd
817+
will use to communicate with QEMU.
818+
819+
Returns:
820+
The virtiofsd command as a list.
821+
"""
822+
sudo = get_and_call_sudo()
823+
virtiofsd = find_virtiofsd(qemu_path.resolve().parent.parent)
824+
825+
return [
826+
sudo,
827+
virtiofsd,
828+
f"--socket-group={grp.getgrgid(os.getgid()).gr_name}",
829+
f"--socket-path={socket_path}",
830+
'-o', f"source={shared_folder}",
831+
'-o', 'cache=always',
832+
] # yapf: disable
833+
834+
835+
def get_virtiofs_qemu_args(mem_path, qemu_mem, socket_path):
836+
"""
837+
Generate a list of arguments for QEMU to use virtiofs.
838+
839+
These are the arguments as recommended by the virtio-fs website:
840+
https://virtio-fs.gitlab.io/howto-qemu.html
841+
842+
Parameters:
843+
mem_path (Path): An absolute path to the memory file that virtiofs will
844+
be using.
845+
qemu_mem (str): The amount of memory QEMU will be using.
846+
socket_path (Path): An absolute path to the socket file virtiofsd
847+
will use to communicate with QEMU.
848+
"""
849+
return [
850+
'-chardev', f"socket,id=char0,path={socket_path}",
851+
'-device', 'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=shared',
852+
'-object', f"memory-backend-file,id=shm,mem-path={mem_path},share=on,size={qemu_mem}",
853+
'-numa', 'node,memdev=shm',
854+
] # yapf: disable
855+
856+
857+
def launch_qemu_fg(cfg):
858+
"""
859+
Spawn QEMU in the foreground, using 'timeout' if running non-interactively
860+
to see if the kernel successfully gets to userspace.
861+
862+
Parameters:
863+
cfg (dict): The configuration dictionary generated with setup_cfg().
864+
"""
865+
interactive = cfg["interactive"]
866+
qemu_cmd = cfg["qemu_cmd"] + ["-serial", "mon:stdio"]
747867
share_folder_with_guest = cfg["share_folder_with_guest"]
748868
timeout = cfg["timeout"]
749869

@@ -752,112 +872,50 @@ def launch_qemu(cfg):
752872
'Shared folder requested without an interactive session, ignoring...'
753873
)
754874
share_folder_with_guest = False
755-
if share_folder_with_guest and gdb:
756-
utils.yellow(
757-
'Shared folder requested during a debugging session, ignoring...')
758-
share_folder_with_guest = False
759875

760876
if share_folder_with_guest:
761877
shared_folder.mkdir(exist_ok=True, parents=True)
762878

763-
# If shared folder was requested, we need to search for virtiofsd in
764-
# certain known locations.
765-
qemu_prefix = Path(qemu_cmd[0]).resolve().parent.parent
766-
virtiofsd_locations = [
767-
Path('libexec', 'virtiofsd'), # Default QEMU installation, Fedora
768-
Path('lib', 'qemu', 'virtiofsd'), # Arch Linux, Debian, Ubuntu
769-
]
770-
virtiofsd = utils.find_first_file(qemu_prefix, virtiofsd_locations)
771-
772-
if not (sudo := shutil.which('sudo')):
773-
raise Exception(
774-
'sudo is required to use virtiofsd but it could not be found!')
775-
utils.green(
776-
'Requesting sudo permission to run virtiofsd in the background...')
777-
subprocess.run([sudo, 'true'], check=True)
778-
779879
virtiofsd_log = base_folder.joinpath('.vfsd.log')
780880
virtiofsd_mem = base_folder.joinpath('.vfsd.mem')
781881
virtiofsd_socket = base_folder.joinpath('.vfsd.sock')
782-
virtiofsd_cmd = [
783-
sudo,
784-
virtiofsd,
785-
f"--socket-group={grp.getgrgid(os.getgid()).gr_name}",
786-
f"--socket-path={virtiofsd_socket}",
787-
'-o', f"source={shared_folder}",
788-
'-o', 'cache=always',
789-
] # yapf: disable
790-
791-
qemu_mem = qemu_cmd[qemu_cmd.index('-m') + 1]
792-
qemu_cmd += [
793-
'-chardev', f"socket,id=char0,path={virtiofsd_socket}",
794-
'-device', 'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=shared',
795-
'-object', f"memory-backend-file,id=shm,mem-path={virtiofsd_mem},share=on,size={qemu_mem}",
796-
'-numa', 'node,memdev=shm',
797-
] # yapf: disable
798-
799-
# Print information about the QEMU binary
800-
pretty_print_qemu_info(qemu_cmd[0])
801882

802-
if gdb:
803-
while True:
804-
utils.check_cmd("lsof")
805-
lsof = subprocess.run(["lsof", "-i:1234"],
806-
stdout=subprocess.DEVNULL,
807-
stderr=subprocess.DEVNULL,
808-
check=False)
809-
if lsof.returncode == 0:
810-
utils.die("Port 1234 is already in use, is QEMU running?")
811-
812-
utils.green("Starting QEMU with GDB connection on port 1234...")
813-
with subprocess.Popen(qemu_cmd + ["-s", "-S"]) as qemu_process:
814-
utils.green("Starting GDB...")
815-
utils.check_cmd(gdb_bin)
816-
gdb_cmd = [gdb_bin]
817-
gdb_cmd += [kernel_location.joinpath("vmlinux")]
818-
gdb_cmd += ["-ex", "target remote :1234"]
819-
subprocess.run(gdb_cmd, check=False)
820-
821-
utils.red("Killing QEMU...")
822-
qemu_process.kill()
823-
824-
answer = input("Re-run QEMU + gdb? [y/n] ")
825-
if answer.lower() == "n":
826-
break
827-
else:
828-
qemu_cmd += ["-serial", "mon:stdio"]
829-
830-
if not interactive:
831-
timeout_cmd = ["timeout", "--foreground", timeout]
832-
stdbuf_cmd = ["stdbuf", "-oL", "-eL"]
833-
qemu_cmd = timeout_cmd + stdbuf_cmd + qemu_cmd
834-
835-
pretty_print_qemu_cmd(qemu_cmd)
836-
null_cm = contextlib.nullcontext()
837-
with open(virtiofsd_log, 'w', encoding='utf-8') if share_folder_with_guest else null_cm as vfsd_log, \
838-
subprocess.Popen(virtiofsd_cmd, stderr=vfsd_log, stdout=vfsd_log) if share_folder_with_guest else null_cm as vfsd_process:
839-
try:
840-
subprocess.run(qemu_cmd, check=True)
841-
except subprocess.CalledProcessError as ex:
842-
if ex.returncode == 124:
843-
utils.red("ERROR: QEMU timed out!")
844-
else:
845-
utils.red("ERROR: QEMU did not exit cleanly!")
846-
# If virtiofsd is dead, it is pretty likely that it was the
847-
# cause of QEMU failing so add to the existing exception using
848-
# 'from'.
849-
if vfsd_process and vfsd_process.poll():
850-
vfsd_log_txt = virtiofsd_log.read_text(
851-
encoding='utf-8')
852-
raise Exception(
853-
f"virtiofsd failed with: {vfsd_log_txt}") from ex
854-
sys.exit(ex.returncode)
855-
finally:
856-
if vfsd_process:
857-
vfsd_process.kill()
858-
# Delete the memory to save space, it does not have to be
859-
# persistent
860-
virtiofsd_mem.unlink(missing_ok=True)
883+
virtiofsd_cmd = get_virtiofsd_cmd(Path(qemu_cmd[0]), virtiofsd_socket)
884+
885+
qemu_mem_val = qemu_cmd[qemu_cmd.index('-m') + 1]
886+
qemu_cmd += get_virtiofs_qemu_args(virtiofsd_mem, qemu_mem_val,
887+
virtiofsd_socket)
888+
889+
if not interactive:
890+
timeout_cmd = ["timeout", "--foreground", timeout]
891+
stdbuf_cmd = ["stdbuf", "-oL", "-eL"]
892+
qemu_cmd = timeout_cmd + stdbuf_cmd + qemu_cmd
893+
894+
pretty_print_qemu_cmd(qemu_cmd)
895+
null_cm = contextlib.nullcontext()
896+
with open(virtiofsd_log, 'w', encoding='utf-8') if share_folder_with_guest else null_cm as vfsd_log, \
897+
subprocess.Popen(virtiofsd_cmd, stderr=vfsd_log, stdout=vfsd_log) if share_folder_with_guest else null_cm as vfsd_process:
898+
try:
899+
subprocess.run(qemu_cmd, check=True)
900+
except subprocess.CalledProcessError as ex:
901+
if ex.returncode == 124:
902+
utils.red("ERROR: QEMU timed out!")
903+
else:
904+
utils.red("ERROR: QEMU did not exit cleanly!")
905+
# If virtiofsd is dead, it is pretty likely that it was the
906+
# cause of QEMU failing so add to the existing exception using
907+
# 'from'.
908+
if vfsd_process and vfsd_process.poll():
909+
vfsd_log_txt = virtiofsd_log.read_text(encoding='utf-8')
910+
raise Exception(
911+
f"virtiofsd failed with: {vfsd_log_txt}") from ex
912+
sys.exit(ex.returncode)
913+
finally:
914+
if vfsd_process:
915+
vfsd_process.kill()
916+
# Delete the memory to save space, it does not have to be
917+
# persistent
918+
virtiofsd_mem.unlink(missing_ok=True)
861919

862920

863921
if __name__ == '__main__':
@@ -867,4 +925,10 @@ def launch_qemu(cfg):
867925
config = setup_cfg(arguments)
868926
config = get_qemu_args(config)
869927

870-
launch_qemu(config)
928+
# Print information about the QEMU binary
929+
pretty_print_qemu_info(config['qemu_cmd'][0])
930+
931+
if config['gdb']:
932+
launch_qemu_gdb(config)
933+
else:
934+
launch_qemu_fg(config)

0 commit comments

Comments
 (0)