delve/pkg/proc/gdbserial/gdbserver_conn.go
Alessandro Arzilli 07e53f7cbb proc: fix interaction of RequestManualStop and conditional breakpoints (#876)
* proc: fix interaction of RequestManualStop and conditional breakpoints

A conditional breakpoint that is hit but has the condition evaluate to
false can block a RequestManualStop from working. If the conditional
breakpoint is set on an instruction that is executed very frequently by
multiple goroutines (or many conditional breakpoints are set) it could
prevent all calls to RequestManualStop from working.

This commit fixes the problem by changing proc.Continue to exit
unconditionally after a RequestManualStop is called.

* proc/gdbserial: fix ContinueOnce getting stuck on macOS

Fixes #902
2017-07-07 16:29:37 -07:00

1154 lines
28 KiB
Go

package gdbserial
import (
"bufio"
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/derekparker/delve/pkg/proc"
)
type gdbConn struct {
conn net.Conn
rdr *bufio.Reader
inbuf []byte
outbuf bytes.Buffer
manualStopMutex sync.Mutex
running bool
resumeChan chan<- struct{}
direction proc.Direction // direction of execution
packetSize int // maximum packet size supported by stub
regsInfo []gdbRegisterInfo // list of registers
pid int // cache process id
ack bool // when ack is true acknowledgment packets are enabled
multiprocess bool // multiprocess extensions are active
maxTransmitAttempts int // maximum number of transmit or receive attempts when bad checksums are read
threadSuffixSupported bool // thread suffix supported by stub
isDebugserver bool // true if the stub is debugserver
}
const (
regnamePC = "rip"
regnameCX = "rcx"
regnameSP = "rsp"
regnameBP = "rbp"
regnameFsBase = "fs_base"
regnameGsBase = "gs_base"
)
var ErrTooManyAttempts = errors.New("too many transmit attempts")
// GdbProtocolError is an error response (Exx) of Gdb Remote Serial Protocol
// or an "unsupported command" response (empty packet).
type GdbProtocolError struct {
context string
cmd string
code string
}
func (err *GdbProtocolError) Error() string {
cmd := err.cmd
if len(cmd) > 20 {
cmd = cmd[:20] + "..."
}
if err.code == "" {
return fmt.Sprintf("unsupported packet %s during %s", cmd, err.context)
}
return fmt.Sprintf("protocol error %s during %s for packet %s", err.code, err.context, cmd)
}
func isProtocolErrorUnsupported(err error) bool {
gdberr, ok := err.(*GdbProtocolError)
if !ok {
return false
}
return gdberr.code == ""
}
// GdbMalformedThreadIDError is returned when a the stub responds with a
// thread ID that does not conform with the Gdb Remote Serial Protocol
// specification.
type GdbMalformedThreadIDError struct {
tid string
}
func (err *GdbMalformedThreadIDError) Error() string {
return fmt.Sprintf("malformed thread ID %q", err.tid)
}
const (
qSupportedSimple = "$qSupported:swbreak+;hwbreak+;no-resumed+;xmlRegisters=i386"
qSupportedMultiprocess = "$qSupported:multiprocess+;swbreak+;hwbreak+;no-resumed+;xmlRegisters=i386"
)
func (conn *gdbConn) handshake() error {
conn.ack = true
conn.packetSize = 256
conn.rdr = bufio.NewReader(conn.conn)
// This first ack packet is needed to start up the connection
conn.sendack('+')
conn.disableAck()
// Try to enable thread suffixes for the command 'g' and 'p'
if _, err := conn.exec([]byte("$QThreadSuffixSupported"), "init"); err != nil {
if isProtocolErrorUnsupported(err) {
conn.threadSuffixSupported = false
} else {
return err
}
} else {
conn.threadSuffixSupported = true
}
if !conn.threadSuffixSupported {
features, err := conn.qSupported(true)
if err != nil {
return err
}
conn.multiprocess = features["multiprocess"]
// for some reason gdbserver won't let us read target.xml unless first we
// select a thread.
if conn.multiprocess {
conn.exec([]byte("$Hgp0.0"), "init")
} else {
conn.exec([]byte("$Hgp0"), "init")
}
} else {
// execute qSupported with the multiprocess feature disabled (the
// interaction of thread suffixes and multiprocess is not documented), we
// only need this call to configure conn.packetSize.
if _, err := conn.qSupported(false); err != nil {
return err
}
}
// Attempt to figure out the name of the processor register.
// We either need qXfer:features:read (gdbserver/rr) or qRegisterInfo (lldb)
if err := conn.readRegisterInfo(); err != nil {
if isProtocolErrorUnsupported(err) {
if err := conn.readTargetXml(); err != nil {
return err
}
} else {
return err
}
}
// We either need:
// * QListThreadsInStopReply + qThreadStopInfo (i.e. lldb-server/debugserver),
// * or a stub that runs the inferior in single threaded mode (i.e. rr).
// Otherwise we'll have problems handling breakpoints in multithreaded programs.
if _, err := conn.exec([]byte("$QListThreadsInStopReply"), "init"); err != nil {
gdberr, ok := err.(*GdbProtocolError)
if !ok {
return err
}
if gdberr.code != "" {
return err
}
}
return nil
}
// qSupported interprets qSupported responses.
func (conn *gdbConn) qSupported(multiprocess bool) (features map[string]bool, err error) {
q := qSupportedSimple
if multiprocess {
q = qSupportedMultiprocess
}
respBuf, err := conn.exec([]byte(q), "init/qSupported")
if err != nil {
return nil, err
}
resp := strings.Split(string(respBuf), ";")
features = make(map[string]bool)
for _, stubfeature := range resp {
if len(stubfeature) <= 0 {
continue
} else if equal := strings.Index(stubfeature, "="); equal >= 0 {
if stubfeature[:equal] == "PacketSize" {
if n, err := strconv.ParseInt(stubfeature[equal+1:], 16, 64); err == nil {
conn.packetSize = int(n)
}
}
} else if stubfeature[len(stubfeature)-1] == '+' {
features[stubfeature[:len(stubfeature)-1]] = true
}
}
return features, nil
}
// disableAck disables protocol acks.
func (conn *gdbConn) disableAck() error {
_, err := conn.exec([]byte("$QStartNoAckMode"), "init/disableAck")
if err == nil {
conn.ack = false
}
return err
}
// gdbTarget is a struct type used to parse target.xml
type gdbTarget struct {
Includes []gdbTargetInclude `xml:"xi include"`
Registers []gdbRegisterInfo `xml:"reg"`
}
type gdbTargetInclude struct {
Href string `xml:"href,attr"`
}
type gdbRegisterInfo struct {
Name string `xml:"name,attr"`
Bitsize int `xml:"bitsize,attr"`
Offset int
Regnum int `xml:"regnum,attr"`
}
// readTargetXml reads target.xml file from stub using qXfer:features:read,
// then parses it requesting any additional files.
// The schema of target.xml is described by:
// https://github.com/bminor/binutils-gdb/blob/61baf725eca99af2569262d10aca03dcde2698f6/gdb/features/gdb-target.dtd
func (conn *gdbConn) readTargetXml() (err error) {
conn.regsInfo, err = conn.readAnnex("target.xml")
if err != nil {
return err
}
var offset int
var pcFound, cxFound, spFound bool
regnum := 0
for i := range conn.regsInfo {
if conn.regsInfo[i].Regnum == 0 {
conn.regsInfo[i].Regnum = regnum
} else {
regnum = conn.regsInfo[i].Regnum
}
conn.regsInfo[i].Offset = offset
offset += conn.regsInfo[i].Bitsize / 8
switch conn.regsInfo[i].Name {
case regnamePC:
pcFound = true
case regnameCX:
cxFound = true
case regnameSP:
spFound = true
}
regnum++
}
if !pcFound {
return errors.New("could not find RIP register")
}
if !spFound {
return errors.New("could not find RSP register")
}
if !cxFound {
return errors.New("could not find RCX register")
}
return nil
}
// readRegisterInfo uses qRegisterInfo to read register information (used
// when qXfer:feature:read is not supported).
func (conn *gdbConn) readRegisterInfo() (err error) {
regnum := 0
var pcFound, cxFound, spFound bool
for {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$qRegisterInfo%x", regnum)
respbytes, err := conn.exec(conn.outbuf.Bytes(), "register info")
if err != nil {
if regnum == 0 {
return err
}
break
}
var regname string
var offset int
var bitsize int
var contained bool
resp := string(respbytes)
for {
semicolon := strings.Index(resp, ";")
keyval := resp
if semicolon >= 0 {
keyval = resp[:semicolon]
}
colon := strings.Index(keyval, ":")
if colon >= 0 {
name := keyval[:colon]
value := keyval[colon+1:]
switch name {
case "name":
regname = value
case "offset":
offset, _ = strconv.Atoi(value)
case "bitsize":
bitsize, _ = strconv.Atoi(value)
case "container-regs":
contained = true
}
}
if semicolon < 0 {
break
}
resp = resp[semicolon+1:]
}
if contained {
regnum++
continue
}
switch regname {
case regnamePC:
pcFound = true
case regnameCX:
cxFound = true
case regnameSP:
spFound = true
}
conn.regsInfo = append(conn.regsInfo, gdbRegisterInfo{Regnum: regnum, Name: regname, Bitsize: bitsize, Offset: offset})
regnum++
}
if !pcFound {
return errors.New("could not find RIP register")
}
if !spFound {
return errors.New("could not find RSP register")
}
if !cxFound {
return errors.New("could not find RCX register")
}
return nil
}
func (conn *gdbConn) readAnnex(annex string) ([]gdbRegisterInfo, error) {
tgtbuf, err := conn.qXfer("features", annex)
if err != nil {
return nil, err
}
var tgt gdbTarget
if err := xml.Unmarshal(tgtbuf, &tgt); err != nil {
return nil, err
}
for _, incl := range tgt.Includes {
regs, err := conn.readAnnex(incl.Href)
if err != nil {
return nil, err
}
tgt.Registers = append(tgt.Registers, regs...)
}
return tgt.Registers, nil
}
func (conn *gdbConn) readExecFile() (string, error) {
outbuf, err := conn.qXfer("exec-file", "")
if err != nil {
return "", err
}
return string(outbuf), nil
}
// qXfer executes a 'qXfer' read with the specified kind (i.e. feature,
// exec-file, etc...) and annex.
func (conn *gdbConn) qXfer(kind, annex string) ([]byte, error) {
out := []byte{}
for {
buf, err := conn.exec([]byte(fmt.Sprintf("$qXfer:%s:read:%s:%x,fff", kind, annex, len(out))), "target features transfer")
if err != nil {
return nil, err
}
out = append(out, buf[1:]...)
if buf[0] == 'l' {
break
}
}
return out, nil
}
// setBreakpoint executes a 'Z' (insert breakpoint) command of type '0' and kind '1'
func (conn *gdbConn) setBreakpoint(addr uint64) error {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$Z0,%x,1", addr)
_, err := conn.exec(conn.outbuf.Bytes(), "set breakpoint")
return err
}
// clearBreakpoint executes a 'z' (remove breakpoint) command of type '0' and kind '1'
func (conn *gdbConn) clearBreakpoint(addr uint64) error {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$z0,%x,1", addr)
_, err := conn.exec(conn.outbuf.Bytes(), "clear breakpoint")
return err
}
// kill executes a 'k' (kill) command.
func (conn *gdbConn) kill() error {
resp, err := conn.exec([]byte{'$', 'k'}, "kill")
if err == io.EOF {
// The stub is allowed to shut the connection on us immediately after a
// kill. This is not an error.
conn.conn.Close()
conn.conn = nil
return proc.ProcessExitedError{Pid: conn.pid}
}
if err != nil {
return err
}
_, _, err = conn.parseStopPacket(resp, "", nil)
return err
}
// detach executes a 'D' (detach) command.
func (conn *gdbConn) detach() error {
if conn.conn == nil {
// Already detached
return nil
}
_, err := conn.exec([]byte{'$', 'D'}, "detach")
conn.conn.Close()
conn.conn = nil
return err
}
// readRegisters executes a 'g' (read registers) command.
func (conn *gdbConn) readRegisters(threadID string, data []byte) error {
if !conn.threadSuffixSupported {
if err := conn.selectThread('g', threadID, "registers read"); err != nil {
return err
}
}
conn.outbuf.Reset()
conn.outbuf.WriteString("$g")
conn.appendThreadSelector(threadID)
resp, err := conn.exec(conn.outbuf.Bytes(), "registers read")
if err != nil {
return err
}
for i := 0; i < len(resp); i += 2 {
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
data[i/2] = uint8(n)
}
return nil
}
// writeRegisters executes a 'G' (write registers) command.
func (conn *gdbConn) writeRegisters(threadID string, data []byte) error {
if !conn.threadSuffixSupported {
if err := conn.selectThread('g', threadID, "registers write"); err != nil {
return err
}
}
conn.outbuf.Reset()
conn.outbuf.WriteString("$G")
for _, b := range data {
fmt.Fprintf(&conn.outbuf, "%02x", b)
}
conn.appendThreadSelector(threadID)
_, err := conn.exec(conn.outbuf.Bytes(), "registers write")
return err
}
// readRegister executes 'p' (read register) command.
func (conn *gdbConn) readRegister(threadID string, regnum int, data []byte) error {
if !conn.threadSuffixSupported {
if err := conn.selectThread('g', threadID, "registers write"); err != nil {
return err
}
}
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$p%x", regnum)
conn.appendThreadSelector(threadID)
resp, err := conn.exec(conn.outbuf.Bytes(), "register read")
if err != nil {
return err
}
for i := 0; i < len(resp); i += 2 {
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
data[i/2] = uint8(n)
}
return nil
}
// writeRegister executes 'P' (write register) command.
func (conn *gdbConn) writeRegister(threadID string, regnum int, data []byte) error {
if !conn.threadSuffixSupported {
if err := conn.selectThread('g', threadID, "registers write"); err != nil {
return err
}
}
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$P%x=", regnum)
for _, b := range data {
fmt.Fprintf(&conn.outbuf, "%02x", b)
}
conn.appendThreadSelector(threadID)
_, err := conn.exec(conn.outbuf.Bytes(), "register write")
return err
}
// resume executes a 'vCont' command on all threads with action 'c' if sig
// is 0 or 'C' if it isn't.
func (conn *gdbConn) resume(sig uint8, tu *threadUpdater) (string, uint8, error) {
if conn.direction == proc.Forward {
conn.outbuf.Reset()
if sig == 0 {
fmt.Fprint(&conn.outbuf, "$vCont;c")
} else {
fmt.Fprintf(&conn.outbuf, "$vCont;C%02x", sig)
}
} else {
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
return "", 0, err
}
conn.outbuf.Reset()
fmt.Fprint(&conn.outbuf, "$bc")
}
conn.manualStopMutex.Lock()
if err := conn.send(conn.outbuf.Bytes()); err != nil {
conn.manualStopMutex.Unlock()
return "", 0, err
}
conn.running = true
conn.manualStopMutex.Unlock()
defer func() {
conn.manualStopMutex.Lock()
conn.running = false
conn.manualStopMutex.Unlock()
}()
if conn.resumeChan != nil {
close(conn.resumeChan)
conn.resumeChan = nil
}
return conn.waitForvContStop("resume", "-1", tu)
}
// step executes a 'vCont' command on the specified thread with 's' action.
func (conn *gdbConn) step(threadID string, tu *threadUpdater) (string, uint8, error) {
if conn.direction == proc.Forward {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$vCont;s:%s", threadID)
} else {
if err := conn.selectThread('c', threadID, "step"); err != nil {
return "", 0, err
}
conn.outbuf.Reset()
fmt.Fprint(&conn.outbuf, "$bs")
}
if err := conn.send(conn.outbuf.Bytes()); err != nil {
return "", 0, err
}
return conn.waitForvContStop("singlestep", threadID, tu)
}
var threadBlockedError = errors.New("thread blocked")
func (conn *gdbConn) waitForvContStop(context string, threadID string, tu *threadUpdater) (string, uint8, error) {
count := 0
failed := false
for {
conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval))
resp, err := conn.recv(nil, context)
conn.conn.SetReadDeadline(time.Time{})
if neterr, isneterr := err.(net.Error); isneterr && neterr.Timeout() {
// Debugserver sometimes forgets to inform us that inferior stopped,
// sending this status request after a timeout helps us get unstuck.
// Debugserver will not respond to this request unless inferior is
// already stopped.
if conn.isDebugserver {
conn.send([]byte("$?"))
}
if count > 1 && context == "singlestep" {
failed = true
conn.sendCtrlC()
}
count++
} else if failed {
return "", 0, threadBlockedError
} else if err != nil {
return "", 0, err
} else {
repeat, sp, err := conn.parseStopPacket(resp, threadID, tu)
if !repeat {
return sp.threadID, sp.sig, err
}
}
}
}
type stopPacket struct {
threadID string
sig uint8
reason string
}
// executes 'vCont' (continue/step) command
func (conn *gdbConn) parseStopPacket(resp []byte, threadID string, tu *threadUpdater) (repeat bool, sp stopPacket, err error) {
switch resp[0] {
case 'T':
if len(resp) < 3 {
return false, stopPacket{}, fmt.Errorf("malformed response for vCont %s", string(resp))
}
sig, err := strconv.ParseUint(string(resp[1:3]), 16, 8)
if err != nil {
return false, stopPacket{}, fmt.Errorf("malformed stop packet: %s", string(resp))
}
sp.sig = uint8(sig)
if logGdbWire && logGdbWireFullStopPacket {
fmt.Fprintf(os.Stderr, "full stop packet: %s\n", string(resp))
}
buf := resp[3:]
for buf != nil {
colon := bytes.Index(buf, []byte{':'})
if colon < 0 {
break
}
key := buf[:colon]
buf = buf[colon+1:]
semicolon := bytes.Index(buf, []byte{';'})
var value []byte
if semicolon < 0 {
value = buf
buf = nil
} else {
value = buf[:semicolon]
buf = buf[semicolon+1:]
}
switch string(key) {
case "thread":
sp.threadID = string(value)
case "threads":
if tu != nil {
tu.Add(strings.Split(string(value), ","))
tu.Finish()
}
case "reason":
sp.reason = string(value)
}
}
return false, sp, nil
case 'W', 'X':
// process exited, next two character are exit code
semicolon := bytes.Index(resp, []byte{';'})
if semicolon < 0 {
semicolon = len(resp)
}
status, _ := strconv.ParseUint(string(resp[1:semicolon]), 16, 8)
return false, stopPacket{}, proc.ProcessExitedError{Pid: conn.pid, Status: int(status)}
case 'N':
// we were singlestepping the thread and the thread exited
sp.threadID = threadID
return false, sp, nil
case 'O':
data := make([]byte, 0, len(resp[1:])/2)
for i := 1; i < len(resp); i += 2 {
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
data = append(data, uint8(n))
}
os.Stdout.Write(data)
return true, sp, nil
default:
return false, sp, fmt.Errorf("unexpected response for vCont %c", resp[0])
}
}
const ctrlC = 0x03 // the ASCII character for ^C
// executes a ctrl-C on the line
func (conn *gdbConn) sendCtrlC() error {
if logGdbWire {
fmt.Println("<- interrupt")
}
_, err := conn.conn.Write([]byte{ctrlC})
return err
}
// queryProcessInfo executes a qProcessInfoPID (if pid != 0) or a qProcessInfo (if pid == 0)
func (conn *gdbConn) queryProcessInfo(pid int) (map[string]string, error) {
conn.outbuf.Reset()
if pid != 0 {
fmt.Fprintf(&conn.outbuf, "$qProcessInfoPID:%d", pid)
} else {
fmt.Fprint(&conn.outbuf, "$qProcessInfo")
}
resp, err := conn.exec(conn.outbuf.Bytes(), "process info for pid")
if err != nil {
return nil, err
}
pi := make(map[string]string)
for len(resp) > 0 {
semicolon := bytes.Index(resp, []byte{';'})
keyval := resp
if semicolon >= 0 {
keyval = resp[:semicolon]
resp = resp[semicolon+1:]
}
colon := bytes.Index(keyval, []byte{':'})
if colon < 0 {
continue
}
key := string(keyval[:colon])
value := string(keyval[colon+1:])
switch key {
case "name":
name := make([]byte, len(value)/2)
for i := 0; i < len(value); i += 2 {
n, _ := strconv.ParseUint(string(value[i:i+2]), 16, 8)
name[i/2] = byte(n)
}
pi[key] = string(name)
default:
pi[key] = value
}
}
return pi, nil
}
// executes qfThreadInfo/qsThreadInfo commands
func (conn *gdbConn) queryThreads(first bool) (threads []string, err error) {
// https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html
conn.outbuf.Reset()
if first {
conn.outbuf.WriteString("$qfThreadInfo")
} else {
conn.outbuf.WriteString("$qsThreadInfo")
}
resp, err := conn.exec(conn.outbuf.Bytes(), "thread info")
if err != nil {
return nil, err
}
switch resp[0] {
case 'l':
return nil, nil
case 'm':
// parse list...
default:
return nil, errors.New("malformed qfThreadInfo response")
}
var pid int
resp = resp[1:]
for {
tidbuf := resp
comma := bytes.Index(tidbuf, []byte{','})
if comma >= 0 {
tidbuf = tidbuf[:comma]
}
if conn.multiprocess && pid == 0 {
dot := bytes.Index(tidbuf, []byte{'.'})
if dot >= 0 {
pid, _ = strconv.Atoi(string(tidbuf[1:dot]))
}
}
threads = append(threads, string(tidbuf))
if comma < 0 {
break
}
resp = resp[comma+1:]
}
if conn.multiprocess && pid > 0 {
conn.pid = pid
}
return threads, nil
}
func (conn *gdbConn) selectThread(kind byte, threadID string, context string) error {
if conn.threadSuffixSupported {
panic("selectThread when thread suffix is supported")
}
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$H%c%s", kind, threadID)
_, err := conn.exec(conn.outbuf.Bytes(), context)
return err
}
func (conn *gdbConn) appendThreadSelector(threadID string) {
if !conn.threadSuffixSupported {
return
}
fmt.Fprintf(&conn.outbuf, ";thread:%s;", threadID)
}
// executes 'm' (read memory) command
func (conn *gdbConn) readMemory(data []byte, addr uintptr) error {
size := len(data)
data = data[:0]
for size > 0 {
conn.outbuf.Reset()
// gdbserver will crash if we ask too many bytes... not return an error, actually crash
sz := size
if dataSize := (conn.packetSize - 4) / 2; sz > dataSize {
sz = dataSize
}
size = size - sz
fmt.Fprintf(&conn.outbuf, "$m%x,%x", addr+uintptr(len(data)), sz)
resp, err := conn.exec(conn.outbuf.Bytes(), "memory read")
if err != nil {
return err
}
for i := 0; i < len(resp); i += 2 {
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
data = append(data, uint8(n))
}
}
return nil
}
func writeAsciiBytes(w io.Writer, data []byte) {
for _, b := range data {
fmt.Fprintf(w, "%02x", b)
}
}
// executes 'M' (write memory) command
func (conn *gdbConn) writeMemory(addr uintptr, data []byte) (written int, err error) {
conn.outbuf.Reset()
//TODO(aarzilli): do not send packets larger than conn.PacketSize
fmt.Fprintf(&conn.outbuf, "$M%x,%x:", addr, len(data))
writeAsciiBytes(&conn.outbuf, data)
_, err = conn.exec(conn.outbuf.Bytes(), "memory write")
if err != nil {
return 0, err
}
return len(data), nil
}
func (conn *gdbConn) allocMemory(sz uint64) (uint64, error) {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$_M%x,rwx", sz)
resp, err := conn.exec(conn.outbuf.Bytes(), "memory allocation")
if err != nil {
return 0, err
}
return strconv.ParseUint(string(resp), 16, 64)
}
// threadStopInfo executes a 'qThreadStopInfo' and returns the reason the
// thread stopped.
func (conn *gdbConn) threadStopInfo(threadID string) (sig uint8, reason string, err error) {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$qThreadStopInfo%s", threadID)
resp, err := conn.exec(conn.outbuf.Bytes(), "thread stop info")
if err != nil {
return 0, "", err
}
_, sp, err := conn.parseStopPacket(resp, "", nil)
if err != nil {
return 0, "", err
}
return sp.sig, sp.reason, nil
}
// restart executes a 'vRun' command.
func (conn *gdbConn) restart(pos string) error {
conn.outbuf.Reset()
fmt.Fprint(&conn.outbuf, "$vRun;")
if pos != "" {
fmt.Fprint(&conn.outbuf, ";")
writeAsciiBytes(&conn.outbuf, []byte(pos))
}
_, err := conn.exec(conn.outbuf.Bytes(), "restart")
return err
}
// qRRCmd executes a qRRCmd command
func (conn *gdbConn) qRRCmd(args ...string) (string, error) {
if len(args) == 0 {
panic("must specify at least one argument for qRRCmd")
}
conn.outbuf.Reset()
fmt.Fprint(&conn.outbuf, "$qRRCmd")
for _, arg := range args {
fmt.Fprint(&conn.outbuf, ":")
writeAsciiBytes(&conn.outbuf, []byte(arg))
}
resp, err := conn.exec(conn.outbuf.Bytes(), "qRRCmd")
if err != nil {
return "", err
}
data := make([]byte, 0, len(resp)/2)
for i := 0; i < len(resp); i += 2 {
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
data = append(data, uint8(n))
}
return string(data), nil
}
// exec executes a message to the stub and reads a response.
// The details of the wire protocol are described here:
// https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
func (conn *gdbConn) exec(cmd []byte, context string) ([]byte, error) {
if err := conn.send(cmd); err != nil {
return nil, err
}
return conn.recv(cmd, context)
}
var hexdigit = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
func (conn *gdbConn) send(cmd []byte) error {
if len(cmd) == 0 || cmd[0] != '$' {
panic("gdb protocol error: command doesn't start with '$'")
}
// append checksum to packet
cmd = append(cmd, '#')
sum := checksum(cmd)
cmd = append(cmd, hexdigit[sum>>4])
cmd = append(cmd, hexdigit[sum&0xf])
attempt := 0
for {
if logGdbWire {
if len(cmd) > logGdbWireMaxLen {
fmt.Printf("<- %s...\n", string(cmd[:logGdbWireMaxLen]))
} else {
fmt.Printf("<- %s\n", string(cmd))
}
}
_, err := conn.conn.Write(cmd)
if err != nil {
return err
}
if !conn.ack {
break
}
if conn.readack() {
break
}
if attempt > conn.maxTransmitAttempts {
return ErrTooManyAttempts
}
attempt++
}
return nil
}
func (conn *gdbConn) recv(cmd []byte, context string) (resp []byte, err error) {
attempt := 0
for {
var err error
resp, err = conn.rdr.ReadBytes('#')
if err != nil {
return nil, err
}
// read checksum
_, err = conn.rdr.Read(conn.inbuf[:2])
if err != nil {
return nil, err
}
if logGdbWire {
out := resp
partial := false
if idx := bytes.Index(out, []byte{'\n'}); idx >= 0 {
out = resp[:idx]
partial = true
}
if len(out) > logGdbWireMaxLen {
out = out[:logGdbWireMaxLen]
partial = true
}
if !partial {
fmt.Printf("-> %s%s\n", string(resp), string(conn.inbuf[:2]))
} else {
fmt.Printf("-> %s...\n", string(out))
}
}
if !conn.ack {
break
}
if resp[0] == '%' {
// If the first character is a % (instead of $) the stub sent us a
// notification packet, this is weird since we specifically claimed that
// we don't support notifications of any kind, but it should be safe to
// ignore regardless.
continue
}
if checksumok(resp, conn.inbuf[:2]) {
conn.sendack('+')
break
}
if attempt > conn.maxTransmitAttempts {
conn.sendack('+')
return nil, ErrTooManyAttempts
}
attempt++
conn.sendack('-')
}
conn.inbuf, resp = wiredecode(resp, conn.inbuf)
if len(resp) == 0 || resp[0] == 'E' {
cmdstr := ""
if cmd != nil {
cmdstr = string(cmd)
}
return nil, &GdbProtocolError{context, cmdstr, string(resp)}
}
return resp, nil
}
// Readack reads one byte from stub, returns true if the byte is '+'
func (conn *gdbConn) readack() bool {
b, err := conn.rdr.ReadByte()
if err != nil {
return false
}
if logGdbWire {
fmt.Printf("-> %s\n", string(b))
}
return b == '+'
}
// Sendack executes an ack character, c must be either '+' or '-'
func (conn *gdbConn) sendack(c byte) {
if c != '+' && c != '-' {
panic(fmt.Errorf("sendack(%c)", c))
}
conn.conn.Write([]byte{c})
if logGdbWire {
fmt.Printf("<- %s\n", string(c))
}
}
// wiredecode decodes the contents of in into buf.
// If buf is nil it will be allocated ex-novo, if the size of buf is not
// enough to hold the decoded contents it will be grown.
// Returns the newly allocated buffer as newbuf and the message contents as
// msg.
func wiredecode(in, buf []byte) (newbuf, msg []byte) {
if buf != nil {
buf = buf[:0]
} else {
buf = make([]byte, 0, 256)
}
start := 1
for i := 0; i < len(in); i++ {
switch ch := in[i]; ch {
case '{': // escape
if i+1 >= len(in) {
buf = append(buf, ch)
} else {
buf = append(buf, in[i+1]^0x20)
}
case ':':
buf = append(buf, ch)
if i == 3 {
// we just read the sequence identifier
start = i + 1
}
case '#': // end of packet
return buf, buf[start:]
case '*': // runlenght encoding marker
if i+1 >= len(in) || i == 0 {
buf = append(buf, ch)
} else {
n := in[i+1] - 29
r := buf[len(buf)-1]
for j := uint8(0); j < n; j++ {
buf = append(buf, r)
}
i++
}
default:
buf = append(buf, ch)
}
}
return buf, buf[start:]
}
// Checksumok checks that checksum is a valid checksum for packet.
func checksumok(packet, checksumBuf []byte) bool {
if packet[0] != '$' {
return false
}
sum := checksum(packet)
tgt, err := strconv.ParseUint(string(checksumBuf), 16, 8)
if err != nil {
return false
}
return sum == uint8(tgt)
}
func checksum(packet []byte) (sum uint8) {
for i := 1; i < len(packet); i++ {
if packet[i] == '#' {
return sum
}
sum += packet[i]
}
return sum
}