├── .gitignore ├── e2e └── fixtures │ ├── loop.asm │ ├── a.asm │ └── add.asm ├── go.mod ├── go.sum ├── README.md ├── cmd ├── asmbuilder │ └── main.go └── barevm │ └── main.go ├── asmbuilder └── builder.go ├── vm.go ├── ioctl_linux.go ├── rundata.go ├── vcpurun.go └── vcpu.go /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | .vscode 3 | -------------------------------------------------------------------------------- /e2e/fixtures/loop.asm: -------------------------------------------------------------------------------- 1 | .code16 2 | .globl _start 3 | 4 | _start: 5 | jmp _start 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hankjacobs/go-kvm 2 | 3 | require ( 4 | github.com/davecgh/go-spew v1.1.1 5 | golang.org/x/sys v0.0.0-20190105165716-badf5585203e 6 | ) 7 | -------------------------------------------------------------------------------- /e2e/fixtures/a.asm: -------------------------------------------------------------------------------- 1 | .code16 2 | .globl _start 3 | 4 | _start: 5 | mov $0x61, %al 6 | mov $0x217, %dx 7 | out %al, (%dx) 8 | mov $10, %al 9 | out %al, (%dx) 10 | hlt 11 | -------------------------------------------------------------------------------- /e2e/fixtures/add.asm: -------------------------------------------------------------------------------- 1 | .code16 2 | .globl _start 3 | 4 | _start: 5 | mov $0x3f8, %dx 6 | add %bl, %al 7 | add $'0', %al 8 | out %al, (%dx) 9 | mov $'\n', %al 10 | out %al, (%dx) 11 | hlt 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | golang.org/x/sys v0.0.0-20190105165716-badf5585203e h1:34JZ+d5RsKTBgJnMpK4m6gzXRZ6H99pKveztnOI3+JA= 4 | golang.org/x/sys v0.0.0-20190105165716-badf5585203e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kvm 2 | 3 | A pure go implemention of the [KVM API](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt). 4 | 5 | ## THIS IS A WORK IN PROGRESS 6 | 7 | What does that mean? 8 | - It probably doesn't work 9 | - If it does, you probably shouldn't use it (yet) 10 | - If you do, the API is subject to change and things will most likely break 11 | - I would love feedback around the design of the API 12 | 13 | The first goal of this project is to be able to run an extremely trivial VM. See this [blog post](https://david942j.blogspot.com/2018/10/note-learning-kvm-implement-your-own.html) for what I am using as a reference implementation and as a first goal. The `barevm` command uses the `kvm` package to build the VM outlined in the above blog post. 14 | 15 | Run using 16 | 17 | ```bash 18 | go run cmd/barevm/main.go 19 | ``` -------------------------------------------------------------------------------- /cmd/asmbuilder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/hankjacobs/kvm/asmbuilder" 10 | ) 11 | 12 | func main() { 13 | flag.Parse() 14 | 15 | args := flag.Args() 16 | 17 | fail := func(msg string) { 18 | fmt.Println("error: ", msg) 19 | os.Exit(1) 20 | } 21 | 22 | if len(args) != 2 { 23 | fail("please set a input file and an output file") 24 | } 25 | 26 | asm, err := ioutil.ReadFile(args[0]) 27 | if err != nil { 28 | fail(fmt.Sprintf("failed reading file %s: %v", args[0], err)) 29 | } 30 | 31 | bin, err := asmbuilder.Build(asm) 32 | if err != nil { 33 | fail(fmt.Sprintf("failed building asm: %v", err)) 34 | } 35 | 36 | err = ioutil.WriteFile(args[1], bin, 755) 37 | if err != nil { 38 | fail(fmt.Sprintf("failed writing file %s: %v", args[1], err)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /asmbuilder/builder.go: -------------------------------------------------------------------------------- 1 | package asmbuilder 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | ) 10 | 11 | // Build builds the asm and outputs a byte array suitable for being executed by kvm 12 | func Build(asm []byte) ([]byte, error) { 13 | td, err := ioutil.TempDir("", "asmbuilder") 14 | if err != nil { 15 | return nil, err 16 | } 17 | defer os.RemoveAll(td) 18 | 19 | opath := filepath.Join(td, "asm.o") 20 | cmd := exec.Command("as", "-32", "-o", opath, "--") 21 | cmd.Stdin = bytes.NewReader(asm) 22 | cmd.Stderr = os.Stderr 23 | err = cmd.Run() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | binary := bytes.Buffer{} 29 | cmd = exec.Command("ld", "-m", "elf_i386", "--oformat=binary", "-e", "_start", "-o", "/dev/stdout", opath) 30 | cmd.Stdout = &binary 31 | cmd.Stderr = os.Stderr 32 | err = cmd.Run() 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return binary.Bytes(), nil 38 | } 39 | -------------------------------------------------------------------------------- /vm.go: -------------------------------------------------------------------------------- 1 | package kvm 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | "unsafe" 7 | ) 8 | 9 | var osIoctl ioctler = &osIoctler{} 10 | 11 | // VM is a KVM VM 12 | type VM struct { 13 | Fd uintptr 14 | MMapSize int 15 | } 16 | 17 | // CreateVM creates a new VM 18 | func CreateVM() (*VM, error) { 19 | file, err := os.OpenFile("/dev/kvm", os.O_RDWR|syscall.O_CLOEXEC, 0) 20 | if err != nil { 21 | return nil, err 22 | } 23 | defer file.Close() 24 | 25 | kvmfd := file.Fd() 26 | vmfd, err := osIoctl.ioctl(kvmfd, ioctlKVMCreateVM, 0) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | mmapsize, err := osIoctl.ioctl(kvmfd, ioctlKVMGetVCPUMMAPSize, 0) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | vm := VM{ 37 | Fd: vmfd, 38 | MMapSize: int(mmapsize), 39 | } 40 | 41 | return &vm, nil 42 | } 43 | 44 | const ( 45 | // UserMemoryFlagLogDirtyPages maps to KVM_MEM_LOG_DIRTY_PAGES 46 | UserMemoryFlagLogDirtyPages = 1 << iota 47 | 48 | // UserMemoryReadOnly maps to KVM_MEM_READONLY 49 | UserMemoryReadOnly 50 | ) 51 | 52 | type kvmUserspaceMemoryRegion struct { 53 | Slot uint32 54 | Flags uint32 55 | GuestPhysAddr uint64 56 | MemorySize uint64 57 | UserspaceAddr uint64 58 | } 59 | 60 | // MapUserMemory maps memory to the specified slot with the given flags 61 | func (vm *VM) MapUserMemory(slot uint32, flags uint32, guestAddress uint64, memory []byte) error { 62 | userspaceAddr := unsafe.Pointer(&memory[0]) 63 | region := kvmUserspaceMemoryRegion{ 64 | Slot: slot, 65 | Flags: flags, 66 | GuestPhysAddr: guestAddress, 67 | MemorySize: uint64(len(memory)), 68 | UserspaceAddr: uint64(uintptr(userspaceAddr)), 69 | } 70 | 71 | _, err := osIoctl.ioctl(vm.Fd, ioctlKVMSetUserMemoryRegion, uintptr(unsafe.Pointer(®ion))) 72 | return err 73 | } 74 | -------------------------------------------------------------------------------- /ioctl_linux.go: -------------------------------------------------------------------------------- 1 | package kvm 2 | 3 | import ( 4 | "syscall" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | // KVM Ioctls 10 | const ( 11 | ioctlKVMGetAPIVersion = 0xAE00 12 | ioctlKVMCreateVM = 0xAE01 13 | ioctlKVMGetMSRIndexList = 0xC004AE02 14 | ioctlKVMS390EnableSIE = 0xAE06 15 | ioctlKVMCheckExtension = 0xAE03 16 | ioctlKVMGetVCPUMMAPSize = 0xAE04 17 | ioctlKVMGetSupportedCPUID = 0xC008AE05 18 | ioctlKVMGetEmulatedCPUID = 0xC008AE09 19 | ioctlKVMGetMSRFeatureIndexList = 0xC004AE0A 20 | ioctlKVMSetUserMemoryRegion = 0x4020AE46 21 | ioctlKVMCreateVCPU = 0xAE41 22 | ioctlKVMGetRegs = 0x8090AE81 23 | ioctlKVMSetRegs = 0x4090ae82 24 | ioctlKVMGetSRegs = 0x8138AE83 25 | ioctlKVMSetSRegs = 0x4138AE84 26 | ioctlKVMRun = 0xAE80 27 | ) 28 | 29 | // https://github.com/golang/sys/blob/master/unix/syscall_unix.go#L33 30 | func errnoErr(e syscall.Errno) error { 31 | switch e { 32 | case 0: 33 | return nil 34 | // are these cases even relevant for KVM? 35 | case unix.EAGAIN: 36 | return syscall.EAGAIN 37 | case unix.EINVAL: 38 | return syscall.EINVAL 39 | case unix.ENOENT: 40 | return syscall.ENOENT 41 | case unix.EINTR: 42 | return syscall.EINTR 43 | } 44 | return e 45 | } 46 | 47 | // ioctler is an interface capable of calling ioctl 48 | type ioctler interface { 49 | ioctl(fd uintptr, req uint, arg uintptr) (ret uintptr, err error) 50 | } 51 | 52 | // An osIoctler struct does OS calls to ioctl. 53 | type osIoctler struct{} 54 | 55 | func (osIoctler) ioctl(fd uintptr, req uint, arg uintptr) (ret uintptr, err error) { 56 | ret, _, errno := unix.Syscall( 57 | unix.SYS_IOCTL, 58 | uintptr(fd), 59 | uintptr(req), 60 | arg, 61 | ) 62 | if errno != 0 { 63 | err = errnoErr(errno) 64 | } 65 | 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /rundata.go: -------------------------------------------------------------------------------- 1 | package kvm 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | type runData struct { 9 | // in 10 | requestInterrupWindow uint8 11 | immediateExit uint8 12 | _ [6]uint8 13 | 14 | // out 15 | exitReason uint32 16 | readyForInterruptInjection uint8 17 | ifFlag uint8 18 | flags uint16 19 | 20 | // in (pre_kvm_run), out (post_kvm_run) 21 | cr8 uint64 22 | apicBase uint64 23 | 24 | // #ifdef __KVM_S390 25 | // /* the processor status word for s390 */ 26 | // __u64 psw_mask; /* psw upper half */ 27 | // __u64 psw_addr; /* psw lower half */ 28 | // #endif 29 | 30 | exitReasonDataUnion [256]byte 31 | 32 | kvmValidRegs uint64 33 | kvmDirtyRegs uint64 34 | 35 | synRegsUnion [2048]byte 36 | } 37 | 38 | func (k *runData) exitUnknown() ExitUnknown { 39 | exit := (*ExitUnknown)(unsafe.Pointer(&k.exitReasonDataUnion[0])) 40 | return *exit 41 | } 42 | 43 | func (k *runData) exitException() ExitException { 44 | exit := (*ExitException)(unsafe.Pointer(&k.exitReasonDataUnion[0])) 45 | return *exit 46 | } 47 | 48 | func (k *runData) exitIO() ExitIO { 49 | exit := (*ExitIO)(unsafe.Pointer(&k.exitReasonDataUnion[0])) 50 | return *exit 51 | } 52 | 53 | func (k *runData) exitIOData() []byte { 54 | io := k.exitIO() 55 | dataStart := unsafe.Pointer(uintptr(unsafe.Pointer(k)) + uintptr(io.DataOffset)) 56 | 57 | var srcData []byte 58 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&srcData)) 59 | hdr.Data = uintptr(dataStart) 60 | hdr.Len = int(io.Size) 61 | hdr.Cap = int(io.Size) 62 | 63 | data := make([]byte, len(srcData)) 64 | copy(data, srcData) 65 | 66 | return data 67 | } 68 | 69 | func (k *runData) exitFailEntry() ExitFailEntry { 70 | exit := (*ExitFailEntry)(unsafe.Pointer(&k.exitReasonDataUnion[0])) 71 | return *exit 72 | } 73 | 74 | func (k *runData) exitInternalError() ExitInternalError { 75 | exit := (*ExitInternalError)(unsafe.Pointer(&k.exitReasonDataUnion[0])) 76 | return *exit 77 | } 78 | -------------------------------------------------------------------------------- /vcpurun.go: -------------------------------------------------------------------------------- 1 | package kvm 2 | 3 | type ExitReason int 4 | 5 | const ( 6 | ExitReasonUnknown ExitReason = iota 7 | ExitReasonException 8 | ExitReasonIO 9 | ExitReasonHypercall 10 | ExitReasonDebug 11 | ExitReasonHlt 12 | ExitReasonMmio 13 | ExitReasonIrqWindowOpen 14 | ExitReasonShutdown 15 | ExitReasonFailEntry 16 | ExitReasonIntr 17 | ExitReasonSetTpr 18 | ExitReasonTprAccess 19 | ExitReasonS390Sieic 20 | ExitReasonS390Reset 21 | ExitReasonDcr 22 | ExitReasonNmi 23 | ExitReasonInternalError 24 | ExitReasonOsi 25 | ExitReasonPaprHcall 26 | ExitReasonS390Ucontrol 27 | ExitReasonWatchdog 28 | ExitReasonS390Tsch 29 | ExitReasonEpr 30 | ExitReasonSystemEvent 31 | ExitReasonS390Stsi 32 | ExitReasonIoapicEoi 33 | ExitReasonHyperv 34 | ) 35 | 36 | type IODirection uint8 37 | 38 | const ( 39 | IODirectionIn IODirection = 0 40 | IODirectionOut IODirection = 1 41 | ) 42 | 43 | type ExitIO struct { 44 | Direction IODirection 45 | Size uint8 46 | Port uint16 47 | Count uint32 48 | DataOffset uint64 49 | } 50 | 51 | type ExitUnknown struct { 52 | HardwareExitReason uint64 53 | } 54 | 55 | type ExitFailEntry struct { 56 | HardwareEntryFailureReason uint64 57 | } 58 | 59 | type ExitException struct { 60 | Exception uint32 61 | ErrorCode uint32 62 | } 63 | 64 | type ExitInternalError struct { 65 | Suberror uint32 66 | Ndata uint32 67 | Data [16]uint64 68 | } 69 | 70 | func (c *VCPU) Run() (ExitReason, error) { 71 | _, err := osIoctl.ioctl(c.Fd, ioctlKVMRun, 0) 72 | return ExitReason(c.run.exitReason), err 73 | } 74 | 75 | func (c *VCPU) ExitUnknown() ExitUnknown { 76 | return c.run.exitUnknown() 77 | } 78 | 79 | func (c *VCPU) ExitException() ExitException { 80 | return c.run.exitException() 81 | } 82 | 83 | func (c *VCPU) ExitIO() ExitIO { 84 | return c.run.exitIO() 85 | } 86 | 87 | func (c *VCPU) ExitIOData() []byte { 88 | return c.run.exitIOData() 89 | } 90 | 91 | func (c *VCPU) ExitFailEntry() ExitFailEntry { 92 | return c.run.exitFailEntry() 93 | } 94 | 95 | func (c *VCPU) ExitInternalError() ExitInternalError { 96 | return c.run.exitInternalError() 97 | } 98 | -------------------------------------------------------------------------------- /vcpu.go: -------------------------------------------------------------------------------- 1 | package kvm 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func (vm *VM) CreateVCPU() (*VCPU, error) { 11 | fd, err := osIoctl.ioctl(vm.Fd, ioctlKVMCreateVCPU, 0) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | runMap, err := syscall.Mmap(int(fd), 0, vm.MMapSize, syscall.PROT_READ|syscall.PROT_WRITE, unix.MAP_SHARED) 17 | if err != nil { 18 | syscall.Close(int(fd)) 19 | return nil, err 20 | } 21 | 22 | run := (*runData)(unsafe.Pointer(&runMap[0])) 23 | 24 | return &VCPU{Fd: fd, run: run}, nil 25 | } 26 | 27 | type VCPU struct { 28 | Fd uintptr 29 | run *runData 30 | } 31 | 32 | type Registers struct { 33 | Rax, Rbx, Rcx, Rdx uint64 34 | Rsi, Rdi, Rsp, Rbp uint64 35 | R8, R9, R10, R11 uint64 36 | R12, R13, R14, R15 uint64 37 | Rip, Rflags uint64 38 | } 39 | 40 | func (c *VCPU) GetRegisters() (Registers, error) { 41 | regs := Registers{} 42 | _, err := osIoctl.ioctl(c.Fd, ioctlKVMGetRegs, uintptr(unsafe.Pointer(®s))) 43 | return regs, err 44 | } 45 | 46 | func (c *VCPU) SetRegisters(regs Registers) error { 47 | _, err := osIoctl.ioctl(c.Fd, ioctlKVMSetRegs, uintptr(unsafe.Pointer(®s))) 48 | return err 49 | } 50 | 51 | type Segment struct { 52 | Base uint64 53 | Limit uint32 54 | Selector uint16 55 | Type uint8 56 | Present, Dpl, Db, S, L, G, Avl uint8 57 | unusable uint8 58 | padding uint8 59 | } 60 | 61 | type Dtable struct { 62 | Base uint64 63 | Limit uint16 64 | padding [3]uint16 65 | } 66 | 67 | const NRInterrupts = 256 68 | 69 | type SRegisters struct { 70 | Cs, Ds, Es, Fs, Gs, Ss Segment 71 | Tr, Ldt Segment 72 | Gdt, Idt Dtable 73 | Cr0, Cr2, Cr3, Cr4, Cr8 uint64 74 | Efer uint64 75 | ApicBase uint64 76 | InterruptBitmap [(NRInterrupts + 63) / 64]uint64 77 | } 78 | 79 | func (c *VCPU) GetSRegisters() (SRegisters, error) { 80 | sregs := SRegisters{} 81 | _, err := osIoctl.ioctl(c.Fd, ioctlKVMGetSRegs, uintptr(unsafe.Pointer(&sregs))) 82 | return sregs, err 83 | } 84 | 85 | func (c *VCPU) SetSRegisters(sregs SRegisters) error { 86 | _, err := osIoctl.ioctl(c.Fd, ioctlKVMSetSRegs, uintptr(unsafe.Pointer(&sregs))) 87 | return err 88 | } 89 | -------------------------------------------------------------------------------- /cmd/barevm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "os/signal" 9 | "runtime" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/hankjacobs/kvm" 14 | "github.com/hankjacobs/kvm/asmbuilder" 15 | ) 16 | 17 | func main() { 18 | fBin := flag.Bool("bin", false, "Specified file is a binary. Assembler step will be skipped.") 19 | flag.Parse() 20 | 21 | args := flag.Args() 22 | if len(args) != 1 { 23 | fmt.Println("please specify asm file") 24 | os.Exit(1) 25 | } 26 | 27 | file, err := ioutil.ReadFile(args[0]) 28 | if err != nil { 29 | panic(err.Error()) 30 | } 31 | 32 | var bin []byte 33 | if *fBin { 34 | bin = file 35 | } else { 36 | bin, err = asmbuilder.Build(file) 37 | if err != nil { 38 | panic(err.Error()) 39 | } 40 | } 41 | 42 | doKvm(bin) 43 | } 44 | 45 | func doKvm(code []uint8) { 46 | // step 1 47 | vm, err := kvm.CreateVM() 48 | if err != nil { 49 | panic(err.Error()) 50 | } 51 | 52 | vmfd := vm.Fd 53 | fmt.Println("vmfd", vmfd) 54 | 55 | memSize := 0x40000000 56 | 57 | //void *mem = mmap(0, mem_size, PROT_READ|PROT_WRITE, 58 | // MAP_SHARED|MAP_ANONYMOUS, -1, 0); 59 | // step 3 60 | data, err := syscall.Mmap(-1, 0, memSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED|syscall.MAP_ANONYMOUS) 61 | if err != nil { 62 | panic(err.Error()) 63 | } 64 | defer syscall.Munmap(data) 65 | 66 | userEntry := 0x0 67 | copy(data[userEntry:], code) 68 | 69 | err = vm.MapUserMemory(0, 0, 0, data) 70 | if err != nil { 71 | panic(err.Error()) 72 | } 73 | 74 | // step 4 75 | vcpu, err := vm.CreateVCPU() 76 | if err != nil { 77 | panic(err.Error()) 78 | } 79 | 80 | // step 6 81 | regs, err := vcpu.GetRegisters() 82 | if err != nil { 83 | panic(err.Error()) 84 | } 85 | 86 | regs.Rip = uint64(userEntry) 87 | regs.Rsp = 0x200000 88 | regs.Rflags = 0x2 89 | regs.Rax = 0x2 90 | regs.Rbx = 0x2 91 | fmt.Printf("regs %+v\n", regs) 92 | 93 | err = vcpu.SetRegisters(regs) 94 | if err != nil { 95 | panic(err.Error()) 96 | } 97 | 98 | sregs, err := vcpu.GetSRegisters() 99 | if err != nil { 100 | panic(err.Error()) 101 | } 102 | 103 | sregs.Cs.Base = 0 104 | sregs.Cs.Selector = 0 105 | fmt.Printf("sregs %+v\n", sregs) 106 | err = vcpu.SetSRegisters(sregs) 107 | if err != nil { 108 | panic(err.Error()) 109 | } 110 | 111 | resume := make(chan struct{}) 112 | tid := 0 113 | wg := sync.WaitGroup{} 114 | eg := sync.WaitGroup{} 115 | wg.Add(1) 116 | eg.Add(1) 117 | go func() { 118 | runtime.LockOSThread() 119 | tid = syscall.Gettid() 120 | wg.Done() 121 | 122 | done := false 123 | 124 | for !done { 125 | exitReason, err := vcpu.Run() 126 | if err != nil { 127 | if err == syscall.EINTR { 128 | fmt.Println("paused") 129 | <-resume 130 | fmt.Println("resuming") 131 | continue 132 | } 133 | 134 | panic(err.Error()) 135 | } 136 | 137 | switch exitReason { 138 | case kvm.ExitReasonHlt: 139 | fmt.Println("Halted") 140 | done = true 141 | case kvm.ExitReasonIO: 142 | fmt.Printf("IO: %+v\n", vcpu.ExitIO()) 143 | case kvm.ExitReasonFailEntry: 144 | panic(vcpu.ExitFailEntry().HardwareEntryFailureReason) 145 | case kvm.ExitReasonInternalError: 146 | panic(vcpu.ExitInternalError().Suberror) 147 | case kvm.ExitReasonShutdown: 148 | fmt.Println("Shutdown") 149 | done = true 150 | default: 151 | panic(fmt.Sprintf("unhandled %d", exitReason)) 152 | } 153 | } 154 | 155 | eg.Done() 156 | }() 157 | 158 | wg.Wait() 159 | 160 | c := make(chan os.Signal, 1) 161 | signal.Notify(c, syscall.SIGUSR1) 162 | 163 | go func() { 164 | paused := false 165 | for { 166 | <-c 167 | if !paused { 168 | err = syscall.Kill(tid, syscall.SIGUSR2) 169 | if err != nil { 170 | panic(err.Error()) 171 | } 172 | paused = true 173 | } else { 174 | resume <- struct{}{} 175 | paused = false 176 | } 177 | } 178 | }() 179 | 180 | eg.Wait() 181 | fmt.Println("DONE... YAY") 182 | } 183 | --------------------------------------------------------------------------------