@@ -661,16 +661,79 @@ def run(self):
661661 super ().run ()
662662
663663
664+ def guess_arch (kernel_arg ):
665+ # kernel_arg is either a path to the kernel build folder or a full kernel
666+ # location. If it is a file, we need to strip off the basename so that we
667+ # can easily navigate around with '..'.
668+ if (kernel_dir := kernel_arg ).is_file ():
669+ kernel_dir = kernel_dir .parent
670+
671+ # If kernel_location is the kernel build folder, vmlinux will be at
672+ # <kernel_dir>/vmlinux
673+ #
674+ # If kernel_location is a full kernel location, it could either be:
675+ # * <kernel_dir>/vmlinux (if the image is vmlinux)
676+ # * <kernel_dir>/../../../vmlinux (if the image is in arch/*/boot/)
677+ #
678+ # Note: 'required=False' just to provide our own exception.
679+ vmlinux_locations = ['vmlinux' , '../../../vmlinux' ]
680+ if not (vmlinux := utils .find_first_file (
681+ kernel_dir , vmlinux_locations , required = False )):
682+ raise RuntimeError (
683+ 'Architecture was not provided and vmlinux could not be found!' )
684+
685+ if not (file := shutil .which ('file' )):
686+ raise RuntimeError (
687+ "Architecture was not provided and 'file' is not installed!" )
688+
689+ # Get output of file
690+ file_out = subprocess .run ([file , vmlinux ],
691+ capture_output = True ,
692+ check = True ,
693+ text = True ).stdout .strip ()
694+
695+ # Unfortunately, 'file' is not terribly precise when it comes to
696+ # microarchitecture or architecture revisions. As such, there are certain
697+ # strings that are just ambiguous so we bail out and let the user tell us
698+ # exactly what architecture they were hoping to boot.
699+ file_rosetta = {
700+ 'ELF 32-bit LSB executable, ARM, EABI5' : 'ambiguous' , # could be any arm32
701+ 'ELF 64-bit LSB pie executable, ARM aarch64' : 'arm64' ,
702+ 'ELF 64-bit MSB pie executable, ARM aarch64' : 'arm64be' ,
703+ 'ELF 32-bit MSB executable, Motorola m68k, 68020' : 'm68k' ,
704+ 'ELF 32-bit MSB executable, MIPS, MIPS32' : 'mips' ,
705+ 'ELF 32-bit LSB executable, MIPS, MIPS32' : 'mipsel' ,
706+ 'ELF 32-bit MSB executable, PowerPC' : 'ambiguous' , # could be ppc32 or ppc32_mac
707+ 'ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, Power ELF V1 ABI' : 'ppc64' ,
708+ 'ELF 64-bit LSB executable, 64-bit PowerPC or cisco 7500, OpenPOWER ELF V2 ABI' : 'ppc64le' ,
709+ 'ELF 64-bit LSB executable, UCB RISC-V' : 'riscv' ,
710+ 'ELF 64-bit MSB executable, IBM S/390' : 's390' ,
711+ 'ELF 32-bit LSB executable, Intel 80386' : 'x86' ,
712+ 'ELF 64-bit LSB executable, x86-64' : 'x86_64' ,
713+ } # yapf: disable
714+ for string , value in file_rosetta .items ():
715+ if string in file_out :
716+ if value == 'ambiguous' :
717+ raise RuntimeError (
718+ f"'{ string } ' found in '{ file_out } ' but the architecture is ambiguous, please explicitly specify it via '-a'!"
719+ )
720+ return value
721+
722+ raise RuntimeError (
723+ f"Architecture could not be deduced from '{ file_out } ', please explicitly specify it via '-a' or add support for it to guess_arch()!"
724+ )
725+
726+
664727def parse_arguments ():
665728 parser = ArgumentParser (description = 'Boot a Linux kernel in QEMU' )
666729
667730 parser .add_argument (
668731 '-a' ,
669732 '--architecture' ,
670733 choices = SUPPORTED_ARCHES ,
671- help = 'The architecture to boot. Possible values are: %(choices)s' ,
672- metavar = 'ARCH' ,
673- required = True )
734+ help =
735+ "The architecture to boot. If omitted, value will be guessed based on 'vmlinux' if available. Possible values are: %(choices)s" ,
736+ metavar = 'ARCH' )
674737 parser .add_argument ('--efi' ,
675738 action = 'store_true' ,
676739 help = 'Boot kernel via UEFI (x86_64 only)' )
@@ -719,6 +782,13 @@ def parse_arguments():
719782if __name__ == '__main__' :
720783 args = parse_arguments ()
721784
785+ if not (kernel_location := Path (args .kernel_location ).resolve ()).exists ():
786+ raise FileNotFoundError (
787+ f"Supplied kernel location ('{ kernel_location } ') does not exist!" )
788+
789+ if not (arch := args .architecture ):
790+ arch = guess_arch (kernel_location )
791+
722792 arch_to_runner = {
723793 'arm' : ARMV7QEMURunner ,
724794 'arm32_v5' : ARMV5QEMURunner ,
@@ -738,11 +808,8 @@ def parse_arguments():
738808 'x86' : X86QEMURunner ,
739809 'x86_64' : X8664QEMURunner ,
740810 }
741- runner = arch_to_runner [args . architecture ]()
811+ runner = arch_to_runner [arch ]()
742812
743- if not (kernel_location := Path (args .kernel_location ).resolve ()).exists ():
744- raise FileNotFoundError (
745- f"Supplied kernel location ('{ kernel_location } ') does not exist!" )
746813 if kernel_location .is_file ():
747814 if args .gdb and kernel_location .name != 'vmlinux' :
748815 raise RuntimeError (
@@ -759,7 +826,7 @@ def parse_arguments():
759826 runner .efi = runner .supports_efi
760827 if not runner .efi :
761828 utils .yellow (
762- f"EFI boot requested on unsupported architecture ('{ args . architecture } '), ignoring..."
829+ f"EFI boot requested on unsupported architecture ('{ arch } '), ignoring..."
763830 )
764831
765832 if args .gdb :
0 commit comments