@@ -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
702702def 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
863921if __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