github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/linux/load_linux_amd64.go (about) 1 // Copyright 2022 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package linux 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io/ioutil" 12 "os" 13 14 "github.com/mvdan/u-root-coreutils/pkg/boot/bzimage" 15 "github.com/mvdan/u-root-coreutils/pkg/boot/kexec" 16 "github.com/mvdan/u-root-coreutils/pkg/boot/purgatory" 17 "github.com/mvdan/u-root-coreutils/pkg/uio" 18 ) 19 20 const ( 21 bootParams = "/sys/kernel/boot_params/data" 22 ) 23 24 // KexecLoad loads a bzImage-formated Linux kernel file as the to-be-kexeced 25 // kernel with the given ramfs file and cmdline string. 26 // 27 // It uses the kexec_load system call. 28 func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { 29 bzimage.Debug = Debug 30 31 // A collection of vars used for processing the kernel for kexec 32 var err error 33 // bzimage is the deserialized bzImage from the kernel 34 // io.ReaderAt. 35 var bzimg bzimage.BzImage 36 // kmem is a struct holding kexec segments. 37 // 38 // It has routines to work with physical memory 39 // ranges. 40 var kmem *kexec.Memory 41 // TODO(10000TB): construct default params in go. 42 // 43 // boot_params directory is x86 specific. So for now, following code only 44 // works on x86. 45 // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-kernel-boot_params 46 bp, err := ioutil.ReadFile("/sys/kernel/boot_params/data") 47 if err != nil { 48 return fmt.Errorf("reading boot_param data: %w", err) 49 } 50 var lp = &bzimage.LinuxParams{} 51 if err := lp.UnmarshalBinary(bp); err != nil { 52 return fmt.Errorf("unmarshaling header: %w", err) 53 } 54 55 kb, err := uio.ReadAll(kernel) 56 if err != nil { 57 return fmt.Errorf("reading Linux kernel into memory: %w", err) 58 } 59 if err := bzimg.UnmarshalBinary(kb); err != nil { 60 return fmt.Errorf("parsing bzImage Linux kernel: %w", err) 61 } 62 63 if len(bzimg.KernelCode) < 1024 { 64 return fmt.Errorf("kernel code size smaller than 1024 bytes: %d", len(bzimg.KernelCode)) 65 } 66 67 kelf, err := bzimg.ELF() 68 if err != nil { 69 return fmt.Errorf("getting ELF from bzImage: %w", err) 70 } 71 kernelEntry := uintptr(kelf.Entry) 72 Debug("kernelEntry: %v", kernelEntry) 73 74 // Prepare segments. 75 kmem = &kexec.Memory{} 76 Debug("Try parsing memory map...") 77 // TODO(10000TB): refactor this call into initialization of 78 // kexec.Memory, as it does not depend on specific boot. 79 if err := kmem.ParseMemoryMap(); err != nil { 80 return fmt.Errorf("parse memory map: %v", err) 81 } 82 83 var relocatableKernel bool 84 if bzimg.Header.Protocolversion < 0x0205 { 85 return fmt.Errorf("bzImage boot protocol earlier thatn 2.05 is not supported currently: %v", bzimg.Header.Protocolversion) 86 } 87 relocatableKernel = bzimg.Header.RelocatableKernel != 0 88 // Only protected mode is currently supported. 89 // In protected mode, kernel need be relocatable, or it will need to fall 90 // to real mode executing. 91 if !relocatableKernel { 92 return errors.New("non-relocateable Kernels are not supported") 93 } 94 if _, err := kmem.LoadElfSegments(bytes.NewReader(bzimg.KernelCode)); err != nil { 95 return fmt.Errorf("loading kernel ELF segments: %w", err) 96 } 97 98 var ramfsRange kexec.Range 99 if ramfs != nil { 100 ramfsContents, err := ioutil.ReadAll(ramfs) 101 if err != nil { 102 return fmt.Errorf("unable to read initramfs: %w", err) 103 } 104 if ramfsRange, err = kmem.AddKexecSegment(ramfsContents); err != nil { 105 return fmt.Errorf("add initramfs segment: %w", err) 106 } 107 Debug("Added %d byte initramfs at %s", len(ramfsContents), ramfsRange) 108 lp.Initrdstart = uint32(ramfsRange.Start) 109 lp.Initrdsize = uint32(ramfsRange.Size) 110 } 111 112 Debug("Kernel cmdline to append: %s", cmdline) 113 if len(cmdline) > 0 { 114 var cmdlineRange kexec.Range 115 Debug("Add cmdline: %s", cmdline) 116 117 // Cmdline must be null-terminated. 118 cmdlineBytes := []byte(cmdline + "\x00") 119 if cmdlineRange, err = kmem.AddKexecSegment(cmdlineBytes); err != nil { 120 return fmt.Errorf("add cmdline segment: %v", err) 121 } 122 Debug("Added %d byte of cmdline at %s", len(cmdlineBytes), cmdlineRange) 123 lp.CLPtr = uint32(cmdlineRange.Start) // 2.02+ 124 lp.CmdLineSize = uint32(cmdlineRange.Size) // 2.06+ 125 } 126 127 // The kernel is a bzImage kernel if the protocol >= 2.00 and the 0x01 128 // bit (LOAD_HIGH) in the loadflags field is set. 129 // TODO(10000TB): check on loadflags. 130 linuxParam, err := lp.MarshalBinary() 131 if err != nil { 132 return fmt.Errorf("re-marshaling header: %w", err) 133 } 134 135 // TODO(10000TB): Free mem hole start end aligns by 136 // max(16, pagesize). 137 // 138 // Push alignment logic to kexec memory functions, e.g. a similar 139 // function to FindSpace. 140 setupRange, err := kmem.AddPhysSegment( 141 linuxParam, 142 kexec.RangeFromInterval( 143 uintptr(0x90000), 144 uintptr(len(linuxParam)), 145 ), 146 // TODO(10000TB): evaluate if we need to provide option to 147 // reserve from end. 148 // 149 // Our go code defaults to pick up a mem block of requested 150 // size from beginning, e.g. 151 // 152 // [Range.Start, Range.Start+memsz) 153 // 154 // Kexec userspace use the range from end, e.g. 155 // 156 // [Range.end-memsz+1, Range.end) 157 // 158 ) 159 if err != nil { 160 return fmt.Errorf("add real mode data and cmdline: %v", err) 161 } 162 163 Debug("Loaded real mode data and cmdline at: %v", setupRange) 164 165 // Verify purgatory loads higher than the parameters. 166 // TODO(10000TB): if rel_addr < setupRange.Start then return error. 167 168 // Load purgatory. 169 purgatoryEntry, err := purgatory.Load(kmem, kernelEntry, setupRange.Start) 170 if err != nil { 171 return err 172 } 173 Debug("purgatory entry: %v", purgatoryEntry) 174 175 // Load it. 176 if err := kexec.Load(purgatoryEntry, kmem.Segments, 0); err != nil { 177 return fmt.Errorf("kexec load(%v, %v, %d): %w", purgatoryEntry, kmem.Segments, 0, err) 178 } 179 return nil 180 }