Skip to content

Commit

Permalink
kvm, machine, bootproto: refactoring
Browse files Browse the repository at this point in the history
Signed-off-by: Nobuhiro MIKI <[email protected]>
  • Loading branch information
bobuhiro11 committed Feb 12, 2021
1 parent bc3f788 commit b4df9a4
Show file tree
Hide file tree
Showing 6 changed files with 393 additions and 373 deletions.
4 changes: 4 additions & 0 deletions bootproto/bootproto.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (

const (
BootProtoMagicSignature = 0x53726448

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

// https://www.kernel.org/doc/html/latest/x86/boot.html
Expand Down
349 changes: 2 additions & 347 deletions kvm/kvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ package kvm

import (
"errors"
"fmt"
"io/ioutil"
"os"
"syscall"
"unsafe"

"github.com/nmi/gokvm/bootproto"
"github.com/nmi/gokvm/serial"
)

const (
Expand Down Expand Up @@ -58,6 +52,8 @@ const (
CPUIDSignature = 0x40000000
)

var ErrorUnexpectedEXITReason = errors.New("unexpected kvm exit reason")

type Regs struct {
RAX uint64
RBX uint64
Expand Down Expand Up @@ -304,344 +300,3 @@ func SetCPUID2(vcpuFd uintptr, kvmCPUID *CPUID) error {

return err
}

// InitialRegState GuestPhysAddr Binary files [+ offsets in the file]
//
// 0x00000000 +------------------+
// | |
// RSI --> 0x00010000 +------------------+ bzImage [+ 0]
// | |
// | boot protocol |
// | |
// +------------------+
// | |
// 0x00020000 +------------------+
// | |
// | cmdline |
// | |
// +------------------+
// | |
// RIP --> 0x00100000 +------------------+ bzImage [+ 512 x (setup_sects in boot protocol header)]
// | |
// | 64bit kernel |
// | |
// +------------------+
// | |
// 0x0f000000 +------------------+ initrd [+ 0]
// | |
// | initrd |
// | |
// +------------------+
// | |
// 0x40000000 +------------------+
const (
memSize = 1 << 30

bootParamAddr = 0x10000
cmdlineAddr = 0x20000

kernelAddr = 0x100000
initrdAddr = 0xf000000
)

// loadflags.
const (
LoadedHigh = uint8(1 << 0)
KASLRFlag = uint8(1 << 1)
QuietFlag = uint8(1 << 5)
KeepSegments = uint8(1 << 6)
CanUseHeap = uint8(1 << 7)
)

type LinuxGuest struct {
kvmFd, vmFd, vcpuFd uintptr
mem []byte
run *RunData
serial *serial.Serial
}

func NewLinuxGuest(bzImagePath, initPath string) (*LinuxGuest, error) {
g := &LinuxGuest{}

devKVM, err := os.OpenFile("/dev/kvm", os.O_RDWR, 0o644)
if err != nil {
panic(err)
}

g.kvmFd = devKVM.Fd()
g.vmFd, err = CreateVM(g.kvmFd)

if err != nil {
panic(err)
}

if err := SetTSSAddr(g.vmFd); err != nil {
panic(err)
}

if err := SetIdentityMapAddr(g.vmFd); err != nil {
panic(err)
}

if err := CreateIRQChip(g.vmFd); err != nil {
panic(err)
}

if err := CreatePIT2(g.vmFd); err != nil {
panic(err)
}

g.vcpuFd, err = CreateVCPU(g.vmFd)
if err != nil {
panic(err)
}

if err := g.initCPUID(); err != nil {
panic(err)
}

g.mem, err = syscall.Mmap(-1, 0, memSize,
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED|syscall.MAP_ANONYMOUS)
if err != nil {
panic(err)
}

mmapSize, err := GetVCPUMMmapSize(g.kvmFd)
if err != nil {
panic(err)
}

r, err := syscall.Mmap(int(g.vcpuFd), 0, int(mmapSize), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
panic(err)
}

g.run = (*RunData)(unsafe.Pointer(&r[0]))

err = SetUserMemoryRegion(g.vmFd, &UserspaceMemoryRegion{
Slot: 0, Flags: 0, GuestPhysAddr: 0, MemorySize: 1 << 30,
UserspaceAddr: uint64(uintptr(unsafe.Pointer(&g.mem[0]))),
})
if err != nil {
panic(err)
}

// Load initrd
initrd, err := ioutil.ReadFile(initPath)
if err != nil {
return g, err
}

for i := 0; i < len(initrd); i++ {
g.mem[initrdAddr+i] = initrd[i]
}

// Load cmdline
cmdline := "console=ttyS0"
for i, b := range []byte(cmdline) {
g.mem[cmdlineAddr+i] = b
}

g.mem[cmdlineAddr+len(cmdline)] = 0 // for null terminated string

// Load Boot Parameter
bootProto, err := bootproto.New(bzImagePath)
if err != nil {
return g, err
}

bootProto.VidMode = 0xFFFF
bootProto.TypeOfLoader = 0xFF
bootProto.RamdiskImage = initrdAddr
bootProto.RamdiskSize = uint32(len(initrd))
bootProto.LoadFlags |= CanUseHeap | LoadedHigh | KeepSegments
bootProto.HeapEndPtr = 0xFE00
bootProto.ExtLoaderVer = 0
bootProto.CmdlinePtr = cmdlineAddr
bootProto.CmdlineSize = uint32(len(cmdline) + 1)

bytes, err := bootProto.Bytes()
if err != nil {
return g, err
}

for i, b := range bytes {
g.mem[bootParamAddr+i] = b
}

// Load kernel
bzImage, err := ioutil.ReadFile(bzImagePath)
if err != nil {
return g, err
}

offset := int(bootProto.SetupSects+1) * 512 // copy to g.mem with offest setupsz

for i := 0; i < len(bzImage)-offset; i++ {
g.mem[kernelAddr+i] = bzImage[offset+i]
}

if err = g.initRegs(); err != nil {
return g, err
}

if err = g.initSregs(); err != nil {
return g, err
}

serialIRQCallback := func(irq, level uint32) {
if err := IRQLine(g.vmFd, irq, level); err != nil {
panic(err)
}
}

if g.serial, err = serial.New(serialIRQCallback); err != nil {
return g, err
}

return g, nil
}

func (g *LinuxGuest) GetInputChan() chan<- byte {
return g.serial.GetInputChan()
}

func (g *LinuxGuest) InjectSerialIRQ() {
g.serial.InjectIRQ()
}

func (g *LinuxGuest) initRegs() error {
regs, err := GetRegs(g.vcpuFd)
if err != nil {
return err
}

regs.RFLAGS = 2
regs.RIP = 0x100000
regs.RSI = 0x10000

if err := SetRegs(g.vcpuFd, regs); err != nil {
return err
}

return nil
}

func (g *LinuxGuest) initSregs() error {
sregs, err := GetSregs(g.vcpuFd)
if err != nil {
return err
}

// set all segment flat
sregs.CS.Base, sregs.CS.Limit, sregs.CS.G = 0, 0xFFFFFFFF, 1
sregs.DS.Base, sregs.DS.Limit, sregs.DS.G = 0, 0xFFFFFFFF, 1
sregs.FS.Base, sregs.FS.Limit, sregs.FS.G = 0, 0xFFFFFFFF, 1
sregs.GS.Base, sregs.GS.Limit, sregs.GS.G = 0, 0xFFFFFFFF, 1
sregs.ES.Base, sregs.ES.Limit, sregs.ES.G = 0, 0xFFFFFFFF, 1
sregs.SS.Base, sregs.SS.Limit, sregs.SS.G = 0, 0xFFFFFFFF, 1

sregs.CS.DB, sregs.SS.DB = 1, 1
sregs.CR0 |= 1 // protected mode

if err := SetSregs(g.vcpuFd, sregs); err != nil {
return err
}

return nil
}

func (g *LinuxGuest) initCPUID() error {
cpuid := CPUID{}
cpuid.Nent = 100

if err := GetSupportedCPUID(g.kvmFd, &cpuid); err != nil {
return err
}

// https://www.kernel.org/doc/html/latest/virt/kvm/cpuid.html
for i := 0; i < int(cpuid.Nent); i++ {
if cpuid.Entries[i].Function != CPUIDSignature {
continue
}

cpuid.Entries[i].Eax = CPUIDFeatures
cpuid.Entries[i].Ebx = 0x4b4d564b // KVMK
cpuid.Entries[i].Ecx = 0x564b4d56 // VMKV
cpuid.Entries[i].Edx = 0x4d // M
}

if err := SetCPUID2(g.vcpuFd, &cpuid); err != nil {
return err
}

return nil
}

var ErrorUnexpectedEXITReason = errors.New("unexpected kvm exit reason")

func (g *LinuxGuest) RunInfiniteLoop() error {
for {
isContinute, err := g.RunOnce()
if err != nil {
return err
}

if !isContinute {
return nil
}
}
}

func (g *LinuxGuest) RunOnce() (bool, error) {
if err := Run(g.vcpuFd); err != nil {
return false, err
}

switch g.run.ExitReason {
case EXITHLT:
fmt.Println("KVM_EXIT_HLT")

return false, nil
case EXITIO:
direction, size, port, count, offset := g.run.IO()
bytes := (*(*[100]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(g.run)) + uintptr(offset))))[0:size]

for i := 0; i < int(count); i++ {
if err := g.handleExitIO(direction, port, bytes); err != nil {
return false, err
}
}

return true, nil
default:
return false, fmt.Errorf("%w: %d", ErrorUnexpectedEXITReason, g.run.ExitReason)
}
}

func (g *LinuxGuest) handleExitIO(direction, port uint64, bytes []byte) error {
switch {
case 0x3c0 <= port && port <= 0x3da:
return nil // VGA
case 0x60 <= port && port <= 0x6F:
return nil // PS/2 Keyboard (Always 8042 Chip)
case 0x70 <= port && port <= 0x71:
return nil // CMOS clock
case 0x80 <= port && port <= 0x9F:
return nil // DMA Page Registers (Commonly 74L612 Chip)
case 0x2f8 <= port && port <= 0x2FF:
return nil // Serial port 2
case 0x3e8 <= port && port <= 0x3ef:
return nil // Serial port 3
case 0x2e8 <= port && port <= 0x2ef:
return nil // Serial port 4
case serial.COM1Addr <= port && port < serial.COM1Addr+8:
if direction == EXITIOIN {
return g.serial.In(port, bytes)
}

return g.serial.Out(port, bytes)
default:
return fmt.Errorf("%w: unexpected io port 0x%x", ErrorUnexpectedEXITReason, port)
}
}
Loading

0 comments on commit b4df9a4

Please sign in to comment.