@@ -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
704704def 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
873933if __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