github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/boot/multiboot/multiboot.go (about)

     1  // Copyright 2018-2019 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 multiboot implements bootloading multiboot kernels as defined by
     6  // https://www.gnu.org/software/grub/manual/multiboot/multiboot.html.
     7  //
     8  // Package multiboot crafts kexec segments that can be used with the kexec_load
     9  // system call.
    10  package multiboot
    11  
    12  import (
    13  	"bytes"
    14  	"debug/elf"
    15  	"encoding/binary"
    16  	"fmt"
    17  	"io"
    18  	"log"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/u-root/u-root/pkg/boot/ibft"
    24  	"github.com/u-root/u-root/pkg/boot/kexec"
    25  	"github.com/u-root/u-root/pkg/boot/multiboot/internal/trampoline"
    26  	"github.com/u-root/u-root/pkg/ubinary"
    27  	"github.com/u-root/u-root/pkg/uio"
    28  )
    29  
    30  const bootloader = "u-root kexec"
    31  
    32  // Module describe a module by a ReaderAt and a `CmdLine`
    33  type Module struct {
    34  	Module  io.ReaderAt
    35  	Name    string
    36  	CmdLine string
    37  }
    38  
    39  // Modules is a range of module with a Closer interface
    40  type Modules []Module
    41  
    42  // multiboot defines parameters for working with multiboot kernels.
    43  type multiboot struct {
    44  	mem kexec.Memory
    45  
    46  	kernel  io.ReaderAt
    47  	modules []Module
    48  
    49  	cmdLine    string
    50  	bootloader string
    51  
    52  	// trampoline is a path to an executable blob, which contains a trampoline segment.
    53  	// Trampoline sets machine to a specific state defined by multiboot v1 spec.
    54  	// https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Machine-state.
    55  	trampoline string
    56  
    57  	header header
    58  
    59  	// infoAddr is a pointer to multiboot info.
    60  	infoAddr uintptr
    61  	// kernelEntry is a pointer to entry point of kernel.
    62  	kernelEntry uintptr
    63  	// EntryPoint is a pointer to trampoline.
    64  	entryPoint uintptr
    65  
    66  	info          info
    67  	loadedModules []module
    68  }
    69  
    70  var (
    71  	rangeTypes = map[kexec.RangeType]uint32{
    72  		kexec.RangeRAM:      1,
    73  		kexec.RangeDefault:  2,
    74  		kexec.RangeACPI:     3,
    75  		kexec.RangeNVS:      4,
    76  		kexec.RangeReserved: 2,
    77  	}
    78  )
    79  
    80  var sizeofMemoryMap = uint(binary.Size(MemoryMap{}))
    81  
    82  // MemoryMap represents a reserved range of memory passed via the multiboot Info header.
    83  type MemoryMap struct {
    84  	// Size is the size of the associated structure in bytes.
    85  	Size uint32
    86  	// BaseAddr is the starting address.
    87  	BaseAddr uint64
    88  	// Length is the size of the memory region in bytes.
    89  	Length uint64
    90  	// Type is the variety of address range represented.
    91  	Type uint32
    92  }
    93  
    94  // String returns a readable representation of a MemoryMap entry.
    95  func (m MemoryMap) String() string {
    96  	return fmt.Sprintf("[0x%x, 0x%x) (len: 0x%x, size: 0x%x, type: %d)", m.BaseAddr, m.BaseAddr+m.Length, m.Length, m.Size, m.Type)
    97  }
    98  
    99  type memoryMaps []MemoryMap
   100  
   101  // String returns a new-line-separated representation of the entire memory map.
   102  func (m memoryMaps) String() string {
   103  	var s []string
   104  	for _, mm := range m {
   105  		s = append(s, mm.String())
   106  	}
   107  	return strings.Join(s, "\n")
   108  }
   109  
   110  // Probe checks if `kernel` is multiboot v1 kernel.
   111  func Probe(kernel io.ReaderAt) error {
   112  	r := tryGzipFilter(kernel)
   113  	_, err := parseHeader(uio.Reader(r))
   114  	return err
   115  }
   116  
   117  // newMB returns a new multiboot instance.
   118  func newMB(kernel io.ReaderAt, cmdLine string, modules []Module) (*multiboot, error) {
   119  	// Trampoline should be a part of current binary.
   120  	p, err := os.Executable()
   121  	if err != nil {
   122  		return nil, fmt.Errorf("cannot find current executable path: %v", err)
   123  	}
   124  	trampoline, err := filepath.EvalSymlinks(p)
   125  	if err != nil {
   126  		return nil, fmt.Errorf("cannot eval symlinks for %v: %v", p, err)
   127  	}
   128  
   129  	return &multiboot{
   130  		kernel:     kernel,
   131  		modules:    modules,
   132  		cmdLine:    cmdLine,
   133  		trampoline: trampoline,
   134  		bootloader: bootloader,
   135  		mem:        kexec.Memory{},
   136  	}, nil
   137  }
   138  
   139  // Load parses and loads a multiboot `kernel` using kexec_load.
   140  //
   141  // debug turns on debug logging.
   142  //
   143  // Load can set up an arbitrary number of modules, and takes care of the
   144  // multiboot info structure, including the memory map.
   145  //
   146  // After Load is called, kexec.Reboot() is ready to be called any time to stop
   147  // Linux and execute the loaded kernel.
   148  func Load(debug bool, kernel io.ReaderAt, cmdline string, modules []Module, ibft *ibft.IBFT) error {
   149  	kernel = tryGzipFilter(kernel)
   150  	for i, mod := range modules {
   151  		modules[i].Module = tryGzipFilter(mod.Module)
   152  	}
   153  
   154  	m, err := newMB(kernel, cmdline, modules)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	if err := m.load(debug, ibft); err != nil {
   159  		return err
   160  	}
   161  	if err := kexec.Load(m.entryPoint, m.mem.Segments, 0); err != nil {
   162  		return fmt.Errorf("kexec.Load() error: %v", err)
   163  	}
   164  	return nil
   165  }
   166  
   167  // OpenModules open modules as files and fill a range of `Module` struct
   168  //
   169  // Each module is a path followed by optional command-line arguments, e.g.
   170  // []string{"./module arg1 arg2", "./module2 arg3 arg4"}.
   171  func OpenModules(cmds []string) (Modules, error) {
   172  	modules := make([]Module, len(cmds))
   173  	for i, cmd := range cmds {
   174  		modules[i].CmdLine = cmd
   175  		name := strings.Fields(cmd)[0]
   176  		modules[i].Name = name
   177  		f, err := os.Open(name)
   178  		if err != nil {
   179  			// TODO close already open files
   180  			return nil, fmt.Errorf("error opening module %v: %v", name, err)
   181  		}
   182  		modules[i].Module = f
   183  	}
   184  	return modules, nil
   185  }
   186  
   187  // LazyOpenModules assigns modules to be opened as files.
   188  //
   189  // Each module is a path followed by optional command-line arguments, e.g.
   190  // []string{"./module arg1 arg2", "./module2 arg3 arg4"}.
   191  func LazyOpenModules(cmds []string) Modules {
   192  	modules := make([]Module, 0, len(cmds))
   193  	for _, cmd := range cmds {
   194  		name := strings.Fields(cmd)[0]
   195  		modules = append(modules, Module{
   196  			CmdLine: cmd,
   197  			Name:    name,
   198  			Module:  uio.NewLazyFile(name),
   199  		})
   200  	}
   201  	return modules
   202  }
   203  
   204  // Close closes all Modules ReaderAt implementing the io.Closer interface
   205  func (m Modules) Close() error {
   206  	// poor error handling inspired from uio.multiCloser
   207  	var allErr error
   208  	for _, mod := range m {
   209  		if c, ok := mod.Module.(io.Closer); ok {
   210  			if err := c.Close(); err != nil {
   211  				allErr = err
   212  			}
   213  		}
   214  	}
   215  	return allErr
   216  }
   217  
   218  // load loads and parses multiboot information from m.kernel.
   219  func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error {
   220  	var err error
   221  	log.Println("Parsing multiboot header")
   222  	if m.header, err = parseHeader(uio.Reader(m.kernel)); err != nil {
   223  		return fmt.Errorf("error parsing headers: %v", err)
   224  	}
   225  
   226  	log.Printf("Getting kernel entry point")
   227  	if m.kernelEntry, err = getEntryPoint(m.kernel); err != nil {
   228  		return fmt.Errorf("error getting kernel entry point: %v", err)
   229  	}
   230  	log.Printf("Kernel entry point at %#x", m.kernelEntry)
   231  
   232  	log.Printf("Parsing ELF segments")
   233  	if err := m.mem.LoadElfSegments(m.kernel); err != nil {
   234  		return fmt.Errorf("error loading ELF segments: %v", err)
   235  	}
   236  
   237  	log.Printf("Parsing memory map")
   238  	if err := m.mem.ParseMemoryMap(); err != nil {
   239  		return fmt.Errorf("error parsing memory map: %v", err)
   240  	}
   241  
   242  	// Insert the iBFT now, since nothing else has been allocated and this
   243  	// is the most restricted allocation we're gonna have to make.
   244  	if ibft != nil {
   245  		ibuf := ibft.Marshal()
   246  
   247  		// The iBFT may sit between 512K and 1M in physical memory. The
   248  		// loaded OS finds it by scanning this region.
   249  		allowedRange := kexec.Range{
   250  			Start: 0x80000,
   251  			Size:  0x80000,
   252  		}
   253  		r, err := m.mem.ReservePhys(uint(len(ibuf)), allowedRange)
   254  		if err != nil {
   255  			return fmt.Errorf("reserving space for the iBFT in %s failed: %v", allowedRange, err)
   256  		}
   257  		log.Printf("iBFT was allocated at %s: %#v", r, ibft)
   258  		m.mem.Segments.Insert(kexec.NewSegment(ibuf, r))
   259  	}
   260  
   261  	log.Printf("Preparing multiboot info")
   262  	if m.infoAddr, err = m.addInfo(); err != nil {
   263  		return fmt.Errorf("error preparing multiboot info: %v", err)
   264  	}
   265  
   266  	log.Printf("Adding trampoline")
   267  	if m.entryPoint, err = m.addTrampoline(); err != nil {
   268  		return fmt.Errorf("error adding trampoline: %v", err)
   269  	}
   270  	log.Printf("Trampoline entry point at %#x", m.entryPoint)
   271  
   272  	if debug {
   273  		info, err := m.description()
   274  		if err != nil {
   275  			log.Printf("%v cannot create debug info: %v", DebugPrefix, err)
   276  		}
   277  		log.Printf("%v %v", DebugPrefix, info)
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  func getEntryPoint(r io.ReaderAt) (uintptr, error) {
   284  	f, err := elf.NewFile(r)
   285  	if err != nil {
   286  		return 0, err
   287  	}
   288  	return uintptr(f.Entry), err
   289  }
   290  
   291  func (m *multiboot) addInfo() (addr uintptr, err error) {
   292  	iw, err := m.newMultibootInfo()
   293  	if err != nil {
   294  		return 0, err
   295  	}
   296  	infoSize, err := iw.size()
   297  	if err != nil {
   298  		return 0, err
   299  	}
   300  
   301  	r, err := m.mem.FindSpace(infoSize)
   302  	if err != nil {
   303  		return 0, err
   304  	}
   305  
   306  	d, err := iw.marshal(r.Start)
   307  	if err != nil {
   308  		return 0, err
   309  	}
   310  	m.info = iw.info
   311  
   312  	m.mem.Segments.Insert(kexec.NewSegment(d, r))
   313  	return r.Start, nil
   314  }
   315  
   316  func (m multiboot) memoryMap() memoryMaps {
   317  	var ret memoryMaps
   318  	for _, r := range m.mem.Phys {
   319  		typ, ok := rangeTypes[r.Type]
   320  		if !ok {
   321  			typ = rangeTypes[kexec.RangeDefault]
   322  		}
   323  		v := MemoryMap{
   324  			// Size is really used for skipping to the next pair.
   325  			Size:     uint32(sizeofMemoryMap) - 4,
   326  			BaseAddr: uint64(r.Start),
   327  			Length:   uint64(r.Size),
   328  			Type:     typ,
   329  		}
   330  		ret = append(ret, v)
   331  	}
   332  	return ret
   333  }
   334  
   335  func (m *multiboot) addMmap() (addr uintptr, size uint, err error) {
   336  	mmap := m.memoryMap()
   337  	log.Printf("Memory map:\n%s", mmap)
   338  	d, err := mmap.marshal()
   339  	if err != nil {
   340  		return 0, 0, err
   341  	}
   342  	r, err := m.mem.AddKexecSegment(d)
   343  	if err != nil {
   344  		return 0, 0, err
   345  	}
   346  	return r.Start, uint(len(mmap)) * sizeofMemoryMap, nil
   347  }
   348  
   349  func (m multiboot) memoryBoundaries() (lower, upper uint32) {
   350  	const M1 = 1048576
   351  	const K640 = 640 * 1024
   352  	for _, r := range m.mem.Phys {
   353  		if r.Type != kexec.RangeRAM {
   354  			continue
   355  		}
   356  		end := uint32(r.Start) + uint32(r.Size)
   357  		// Lower memory starts at address 0, and upper memory starts at address 1 megabyte.
   358  		// The maximum possible value for lower memory is 640 kilobytes.
   359  		// The value returned for upper memory is maximally the address of the first upper memory hole minus 1 megabyte.
   360  		// It is not guaranteed to be this value.
   361  		if r.Start <= K640 && end > lower {
   362  			lower = end
   363  		}
   364  		if r.Start <= M1 && end > upper+M1 {
   365  			upper = end - M1
   366  		}
   367  	}
   368  	return
   369  }
   370  
   371  func min(a, b uint32) uint32 {
   372  	if a < b {
   373  		return a
   374  	}
   375  	return b
   376  }
   377  
   378  func (m *multiboot) newMultibootInfo() (*infoWrapper, error) {
   379  	mmapAddr, mmapSize, err := m.addMmap()
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	var inf info
   384  	if m.header.Flags&flagHeaderMemoryInfo != 0 {
   385  		lower, upper := m.memoryBoundaries()
   386  		inf = info{
   387  			Flags:      flagInfoMemMap | flagInfoMemory,
   388  			MemLower:   min(uint32(lower>>10), 0xFFFFFFFF),
   389  			MemUpper:   min(uint32(upper>>10), 0xFFFFFFFF),
   390  			MmapLength: uint32(mmapSize),
   391  			MmapAddr:   uint32(mmapAddr),
   392  		}
   393  	}
   394  
   395  	if len(m.modules) > 0 {
   396  		modAddr, err := m.addModules()
   397  		if err != nil {
   398  			return nil, err
   399  		}
   400  		inf.Flags |= flagInfoMods
   401  		inf.ModsAddr = uint32(modAddr)
   402  		inf.ModsCount = uint32(len(m.modules))
   403  	}
   404  
   405  	inf.CmdLine = sizeofInfo
   406  	inf.BootLoaderName = sizeofInfo + uint32(len(m.cmdLine)) + 1
   407  	inf.Flags |= flagInfoCmdLine | flagInfoBootLoaderName
   408  	return &infoWrapper{
   409  		info:           inf,
   410  		CmdLine:        m.cmdLine,
   411  		BootLoaderName: m.bootloader,
   412  	}, nil
   413  }
   414  
   415  // marshal writes out the exact bytes expected by the multiboot info header
   416  // specified in
   417  // https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format.
   418  func (m memoryMaps) marshal() ([]byte, error) {
   419  	buf := bytes.Buffer{}
   420  	err := binary.Write(&buf, ubinary.NativeEndian, m)
   421  	return buf.Bytes(), err
   422  }
   423  
   424  func (m *multiboot) addTrampoline() (entry uintptr, err error) {
   425  	// Trampoline setups the machine registers to desired state
   426  	// and executes the loaded kernel.
   427  	d, err := trampoline.Setup(m.trampoline, m.infoAddr, m.kernelEntry)
   428  	if err != nil {
   429  		return 0, err
   430  	}
   431  
   432  	r, err := m.mem.AddKexecSegment(d)
   433  	if err != nil {
   434  		return 0, err
   435  	}
   436  	return r.Start, nil
   437  }