diff options
| author | Alexey Kardashevskiy <aik@linux.ibm.com> | 2020-05-26 12:29:39 +1000 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-10-26 10:19:14 +0100 |
| commit | a7aac492ebbc53e5c7bc4b5bbaf55f428c54093f (patch) | |
| tree | 6e3fc0007132959e50469ccd2a7199f8e69b0569 /vm | |
| parent | a5924b471c98b54c2e097762ebe94089fa6a272e (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.go | 27 | ||||
| -rw-r--r-- | vm/qemu/qmp.go | 120 |
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 +} |
