aboutsummaryrefslogtreecommitdiffstats
path: root/vm
diff options
context:
space:
mode:
authorAlexey Kardashevskiy <aik@linux.ibm.com>2020-05-26 12:29:39 +1000
committerDmitry Vyukov <dvyukov@google.com>2020-10-26 10:19:14 +0100
commita7aac492ebbc53e5c7bc4b5bbaf55f428c54093f (patch)
tree6e3fc0007132959e50469ccd2a7199f8e69b0569 /vm
parenta5924b471c98b54c2e097762ebe94089fa6a272e (diff)
vm/qemu: dump vCPU registers when crashed/hung using QMP
QEMU provides an interface to read/change the VM state. There are: 1. JSON based "QMP" protocol; 2. human monitor protocol - "HMP" - a user-friendly QEMU console. The type of protocol is selected by the "mode" switch. QMP and HMP implement quite different set of commands (although there is some intersection) but QMP also implements a wrapper for HMP - HMP does not implement a proxy for QMP. This adds a TCP socket for QMP (another option would be a UNIX socket) to QEMU and uses it for dumping vCPU(s) registers (via HMP's "info registers") from instance::Diagnose() which is invoked when VM is considered having issues. This implements a QMP handshake and stores the connection handle in the instance. An example of a typical trafic is below: R {"QMP": {"version": {"qemu": {"micro": 50, "minor": 1, "major": 5}, \ "package": ""}, "capabilities": ["oob"]}} S {"execute": "qmp_capabilities"} R {"return": {}} S {"execute": "human-monitor-command", "arguments": \ {"command-line": "info status"}} R {"return": "VM status: running\r\n"} Signed-off-by: Alexey Kardashevskiy <aik@linux.ibm.com>
Diffstat (limited to 'vm')
-rw-r--r--vm/qemu/qemu.go27
-rw-r--r--vm/qemu/qmp.go120
2 files changed, 143 insertions, 4 deletions
diff --git a/vm/qemu/qemu.go b/vm/qemu/qemu.go
index 95d136217..adae5d823 100644
--- a/vm/qemu/qemu.go
+++ b/vm/qemu/qemu.go
@@ -4,8 +4,10 @@
package qemu
import (
+ "encoding/json"
"fmt"
"io"
+ "net"
"os"
"os/exec"
"path/filepath"
@@ -77,6 +79,10 @@ type instance struct {
sshkey string
sshuser string
port int
+ monport int
+ mon net.Conn
+ monEnc *json.Encoder
+ monDec *json.Decoder
rpipe io.ReadCloser
wpipe io.WriteCloser
qemu *exec.Cmd
@@ -369,13 +375,19 @@ func (inst *instance) Close() {
if inst.wpipe != nil {
inst.wpipe.Close()
}
+ if inst.mon != nil {
+ inst.mon.Close()
+ }
}
func (inst *instance) boot() error {
inst.port = vmimpl.UnusedTCPPort()
+ inst.monport = vmimpl.UnusedTCPPort()
args := []string{
"-m", strconv.Itoa(inst.cfg.Mem),
"-smp", strconv.Itoa(inst.cfg.CPU),
+ "-chardev", fmt.Sprintf("socket,id=SOCKSYZ,server,nowait,host=localhost,port=%v", inst.monport),
+ "-mon", "chardev=SOCKSYZ,mode=control",
"-display", "none",
"-serial", "stdio",
"-no-reboot",
@@ -608,11 +620,18 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
}
func (inst *instance) Diagnose() ([]byte, bool) {
- select {
- case inst.diagnose <- true:
- default:
+ ret := []byte(fmt.Sprintf("%s Registers:\n", time.Now().Format("15:04:05 ")))
+ for cpu := 0; cpu < inst.cfg.CPU; cpu++ {
+ regs, err := inst.hmp("info registers", cpu)
+ if err == nil {
+ ret = append(ret, []byte(fmt.Sprintf("info registers vcpu %v\n", cpu))...)
+ ret = append(ret, []byte(regs)...)
+ } else {
+ log.Logf(0, "VM-%v failed reading regs: %v", inst.index, err)
+ ret = append(ret, []byte(fmt.Sprintf("Failed reading regs: %v\n", err))...)
+ }
}
- return nil, false
+ return ret, false
}
// nolint: lll
diff --git a/vm/qemu/qmp.go b/vm/qemu/qmp.go
new file mode 100644
index 000000000..a0ff727ff
--- /dev/null
+++ b/vm/qemu/qmp.go
@@ -0,0 +1,120 @@
+// Copyright 2020 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package qemu
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+)
+
+type qmpVersion struct {
+ Package string
+ QEMU struct {
+ Major int
+ Micro int
+ Minor int
+ }
+}
+
+type qmpBanner struct {
+ QMP struct {
+ Version qmpVersion
+ }
+}
+
+type qmpCommand struct {
+ Execute string `json:"execute"`
+ Arguments interface{} `json:"arguments,omitempty"`
+}
+
+type hmpCommand struct {
+ Command string `json:"command-line"`
+ CPU int `json:"cpu-index"`
+}
+
+type qmpResponse struct {
+ Error struct {
+ Class string
+ Desc string
+ }
+ Return interface{}
+}
+
+func (inst *instance) qmpConnCheck() error {
+ if inst.mon != nil {
+ return nil
+ }
+
+ addr := fmt.Sprintf("127.0.0.1:%v", inst.monport)
+ conn, err := net.Dial("tcp", addr)
+ if err != nil {
+ return err
+ }
+
+ monDec := json.NewDecoder(conn)
+ monEnc := json.NewEncoder(conn)
+
+ var banner qmpBanner
+ if err := monDec.Decode(&banner); err != nil {
+ return err
+ }
+
+ inst.monEnc = monEnc
+ inst.monDec = monDec
+ if _, err := inst.doQmp(&qmpCommand{Execute: "qmp_capabilities"}); err != nil {
+ inst.monEnc = nil
+ inst.monDec = nil
+ return err
+ }
+ inst.mon = conn
+
+ return nil
+}
+
+func (inst *instance) qmpRecv() (*qmpResponse, error) {
+ qmp := new(qmpResponse)
+ err := inst.monDec.Decode(qmp)
+
+ return qmp, err
+}
+
+func (inst *instance) doQmp(cmd *qmpCommand) (*qmpResponse, error) {
+ if err := inst.monEnc.Encode(cmd); err != nil {
+ return nil, err
+ }
+ return inst.qmpRecv()
+}
+
+func (inst *instance) qmp(cmd *qmpCommand) (interface{}, error) {
+ if err := inst.qmpConnCheck(); err != nil {
+ return nil, err
+ }
+ resp, err := inst.doQmp(cmd)
+ if err != nil {
+ return resp.Return, err
+ }
+ if resp.Error.Desc != "" {
+ return resp.Return, fmt.Errorf("error %v", resp.Error)
+ }
+ if resp.Return == nil {
+ return nil, fmt.Errorf(`no "return" nor "error" in [%v]`, resp)
+ }
+ return resp.Return, nil
+}
+
+func (inst *instance) hmp(cmd string, cpu int) (string, error) {
+ req := &qmpCommand{
+ Execute: "human-monitor-command",
+ Arguments: &hmpCommand{
+ Command: cmd,
+ CPU: cpu,
+ },
+ }
+ resp, err := inst.qmp(req)
+ if err != nil {
+ return "", err
+ }
+ return resp.(string), nil
+}