Skip to content

Commit

Permalink
bootparam: refactor boot param with reference to kernel source
Browse files Browse the repository at this point in the history
Lack of e820 entry setup was causing memory shortages in the fedora 31 kernel
and initrd loading. The offset in e820_map[E820MAX] was in an unintended
position, so I adjusted the padding and added unit tests. Also, when booting
from protected mode, all parameters except SetupHeader (offset 0x1f1) had to
be rebuilt from zero filling. With this fix, it is now possible to launch the
init process using the fedora 31 kernel and initrd.

Signed-off-by: Nobuhiro MIKI <[email protected]>
  • Loading branch information
bobuhiro11 committed Feb 28, 2021
1 parent ec916bf commit 6712e09
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 151 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ bzImage: linux.config linux.tar.xz

.PHONY: run
run: initrd bzImage
go run .
go run . -p "console=ttyS0 earlyprintk=serial debug ignore_loglevel"

.PHONY: run-system-kernel
run-system-kernel:
# Implemented based on fedora's default path.
# Other distributions need to be considered.
go run . -p "console=ttyS0 rdinit=/bin/sh pci=off earlyprintk=serial nokaslr" \
go run . -p "console=ttyS0 pci=off earlyprintk=serial nokaslr rdinit=/bin/sh" \
-k $(shell ls -t /boot/vmlinuz*.x86_64 | head -n 1) \
-i $(shell ls -t /boot/initramfs*.x86_64.img | head -n 1)

Expand Down
162 changes: 162 additions & 0 deletions bootparam/bootparam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package bootparam

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io/ioutil"
"unsafe"
)

const (
MagicSignature = 0x53726448

LoadedHigh = uint8(1 << 0)
KeepSegments = uint8(1 << 6)
CanUseHeap = uint8(1 << 7)

EddMbrSigMax = 16
E820Max = 128
E820Ram = 1
E820Reserved = 2

RealModeIvtBegin = 0x00000000
EBDAStart = 0x0009fc00
VGARAMBegin = 0x000a0000
MBBIOSBegin = 0x000f0000
MBBIOSEnd = 0x000fffff
)

type E820Entry struct {
Addr uint64
Size uint64
Type uint32
}

// The so-called "zeropage"
// https://www.kernel.org/doc/html/latest/x86/boot.html
// https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/bootparam.h
type BootParam struct {
Padding [0x1e8]uint8
E820Entries uint8
EddbufEntries uint8
EddMbrSigBufEntries uint8
KdbStatus uint8
Padding2 [5]uint8
Hdr SetupHeader
Padding3 [0x290 - 0x1f1 - unsafe.Sizeof(SetupHeader{})]uint8

// Required to adjust the offset of E820Map to 0x2D0.
Padding4 [0x3d]uint8

EddMbrSigBuffer [EddMbrSigMax]uint8
E820Map [E820Max]E820Entry
}

type SetupHeader struct {
SetupSects uint8
RootFlags uint16
SysSize uint32
RAMSize uint16
VidMode uint16
RootDev uint16
BootFlag uint16
Jump uint16
Header uint32
Version uint16
ReadModeSwitch uint32
StartSysSeg uint16
KernelVersion uint16
TypeOfLoader uint8
LoadFlags uint8
SetupMoveSize uint16
Code32Start uint32
RamdiskImage uint32
RamdiskSize uint32
BootsectKludge uint32
HeapEndPtr uint16
ExtLoaderVer uint8
ExtLoaderType uint8
CmdlinePtr uint32
InitrdAddrMax uint32
KernelAlignment uint32
RelocatableKernel uint8
MinAlignment uint8
XloadFlags uint16
CmdlineSize uint32
HardwareSubarch uint32
HardwareSubarchData uint64
PayloadOffset uint32
PayloadLength uint32
SetupData uint64
PrefAddress uint64
InitSize uint32
HandoverOffset uint32
KernelInfoOffset uint32
}

var ErrorSignatureNotMatch = errors.New("signature not match in bzImage")

var ErrorOldProtocolVersion = errors.New("old protocol version")

func New(bzImagePath string) (*BootParam, error) {
b := &BootParam{}

bzImage, err := ioutil.ReadFile(bzImagePath)
if err != nil {
return b, err
}

// In 64-bit boot protocol, the first step in loading a Linux kernel should be
// to setup the boot parameters (struct boot_params, traditionally known as
// "zero page"). The memory for struct boot_params could be allocated anywhere
// (even above 4G) and initialized to all zero. Then, the setup header at
// offset 0x01f1 of kernel image on should be loaded into struct boot_params
// and examined.
//
// refs: https://www.kernel.org/doc/html/latest/x86/boot.html#id1
reader := bytes.NewReader(bzImage[0x01f1:])
if err := binary.Read(reader, binary.LittleEndian, &(b.Hdr)); err != nil {
return b, err
}

if err := b.isValid(); err != nil {
return b, err
}

return b, nil
}

func (b *BootParam) isValid() error {
if b.Hdr.Header != MagicSignature {
return ErrorSignatureNotMatch
}

// Currently, only Protocol 2.15 is supported.
if b.Hdr.Version != 0x020f {
return fmt.Errorf("%w: 0x%x", ErrorOldProtocolVersion, b.Hdr.Version)
}

return nil
}

func (b *BootParam) AddE820Entry(addr, size uint64, typ uint32) {
i := b.E820Entries
b.E820Map[i] = E820Entry{
Addr: addr,
Size: size,
Type: typ,
}
b.E820Entries = i + 1
}

func (b *BootParam) Bytes() ([]byte, error) {
buf := new(bytes.Buffer)

if err := binary.Write(buf, binary.LittleEndian, b); err != nil {
return []byte{}, err
}

return buf.Bytes(), nil
}
70 changes: 70 additions & 0 deletions bootparam/bootparam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package bootparam_test

import (
"bytes"
"encoding/binary"
"testing"

"github.com/nmi/gokvm/bootparam"
)

func TestNew(t *testing.T) {
t.Parallel()

if _, err := bootparam.New("../bzImage"); err != nil {
t.Fatal(err)
}
}

func TestNewNotbzImage(t *testing.T) {
t.Parallel()

if _, err := bootparam.New("../README.md"); err == nil {
t.Fatal(err)
}
}

func TestBytes(t *testing.T) {
t.Parallel()

b, _ := bootparam.New("../bzImage")

if _, err := b.Bytes(); err != nil {
t.Fatal(err)
}
}

func TestAddE820Entry(t *testing.T) {
t.Parallel()

b, _ := bootparam.New("../bzImage")
b.AddE820Entry(
0x1234567812345678,
0xabcdefabcdefabcd,
bootparam.E820Ram,
)

rawBootParam, _ := b.Bytes()
if rawBootParam[0x1E8] != 1 {
t.Fatalf("invalid e820_entries: %d", rawBootParam[0x1E8])
}

actual := bootparam.E820Entry{}
reader := bytes.NewReader(rawBootParam[0x2D0:])

if err := binary.Read(reader, binary.LittleEndian, &actual); err != nil {
t.Fatal(err)
}

if actual.Addr != 0x1234567812345678 {
t.Fatalf("invalid e820 addr: %v", actual.Addr)
}

if actual.Size != 0xabcdefabcdefabcd {
t.Fatalf("invalid e820 size: %v", actual.Size)
}

if actual.Type != bootparam.E820Ram {
t.Fatalf("invalid e820 type: %v", actual.Type)
}
}
95 changes: 0 additions & 95 deletions bootproto/bootproto.go

This file was deleted.

33 changes: 0 additions & 33 deletions bootproto/bootproto_test.go

This file was deleted.

Loading

0 comments on commit 6712e09

Please sign in to comment.