Skip to content

Commit 6c41476

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. Take the opportunity to optimize launch_qemu_gdb() a bit by pulling as many unnecessary changes out of the while loop as possible. 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 <nathan@kernel.org>
1 parent 044bea2 commit 6c41476

1 file changed

Lines changed: 187 additions & 121 deletions

File tree

boot-qemu.py

Lines changed: 187 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ def pretty_print_qemu_info(qemu):
698698
qemu_version_string = get_qemu_ver_string(qemu)
699699

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

703703

704704
def pretty_print_qemu_cmd(qemu_cmd):
@@ -723,29 +723,159 @@ def pretty_print_qemu_cmd(qemu_cmd):
723723
qemu_cmd_pretty += f' {element.split("/")[-1]}'
724724
else:
725725
qemu_cmd_pretty += f" {element}"
726-
print(f"$ {qemu_cmd_pretty.strip()}", flush=True)
726+
print(f"\n$ {qemu_cmd_pretty.strip()}", flush=True)
727727

728728

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

@@ -754,120 +884,50 @@ def launch_qemu(cfg):
754884
'Shared folder requested without an interactive session, ignoring...'
755885
)
756886
share_folder_with_guest = False
757-
if share_folder_with_guest and gdb:
758-
utils.yellow(
759-
'Shared folder requested during a debugging session, ignoring...')
760-
share_folder_with_guest = False
761887

762888
if share_folder_with_guest:
763889
shared_folder.mkdir(exist_ok=True, parents=True)
764890

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

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

872932

873933
if __name__ == '__main__':
@@ -877,4 +937,10 @@ def launch_qemu(cfg):
877937
config = setup_cfg(arguments)
878938
config = get_qemu_args(config)
879939

880-
launch_qemu(config)
940+
# Print information about the QEMU binary
941+
pretty_print_qemu_info(config['qemu_cmd'][0])
942+
943+
if config['gdb']:
944+
launch_qemu_gdb(config)
945+
else:
946+
launch_qemu_fg(config)

0 commit comments

Comments
 (0)