delve/pkg/proc/gdbserial/gdbserver_conn.go

1506 lines
38 KiB
Go
Raw Normal View History

package gdbserial
import (
"bufio"
"bytes"
"debug/macho"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
"github.com/sirupsen/logrus"
)
type gdbConn struct {
conn net.Conn
rdr *bufio.Reader
inbuf []byte
outbuf bytes.Buffer
running bool
direction proc.Direction // direction of execution
packetSize int // maximum packet size supported by stub
regsInfo []gdbRegisterInfo // list of registers
workaroundReg *gdbRegisterInfo // used to work-around a register setting bug in debugserver, see use in gdbserver.go
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
xcmdok bool // x command can be used to transfer memory
goarch string
goos string
useXcmd bool // forces writeMemory to use the 'X' command
log *logrus.Entry
}
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(regnames *gdbRegnames) 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)
regFound := map[string]bool{
regnames.PC: false,
regnames.SP: false,
regnames.BP: false,
regnames.CX: false,
}
if err := conn.readRegisterInfo(regFound); err != nil {
if isProtocolErrorUnsupported(err) {
if err := conn.readTargetXml(regFound); err != nil {
return err
}
} else {
return err
}
}
for n := range regFound {
if n == "" {
continue
}
if !regFound[n] {
return fmt.Errorf("could not find %s register", n)
}
}
// 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
}
}
if resp, err := conn.exec([]byte("$x0,0"), "init"); err == nil && string(resp) == "OK" {
conn.xcmdok = true
}
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"`
Group string `xml:"group,attr"`
ignoreOnWrite bool
}
func setRegFound(regFound map[string]bool, name string) {
for n := range regFound {
if name == n {
regFound[n] = true
}
}
}
// 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(regFound map[string]bool) (err error) {
conn.regsInfo, err = conn.readAnnex("target.xml")
if err != nil {
return err
}
var offset int
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
setRegFound(regFound, conn.regsInfo[i].Name)
regnum++
}
return nil
}
// readRegisterInfo uses qRegisterInfo to read register information (used
// when qXfer:feature:read is not supported).
func (conn *gdbConn) readRegisterInfo(regFound map[string]bool) (err error) {
regnum := 0
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
var ignoreOnWrite 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
case "set":
if value == "Exception State Registers" {
// debugserver doesn't like it if we try to write these
ignoreOnWrite = true
}
}
}
if semicolon < 0 {
break
}
resp = resp[semicolon+1:]
}
if contained {
if regname == "xmm0" {
conn.workaroundReg = &gdbRegisterInfo{Regnum: regnum, Name: regname, Bitsize: bitsize, Offset: offset, ignoreOnWrite: ignoreOnWrite}
}
regnum++
continue
}
setRegFound(regFound, regname)
conn.regsInfo = append(conn.regsInfo, gdbRegisterInfo{Regnum: regnum, Name: regname, Bitsize: bitsize, Offset: offset, ignoreOnWrite: ignoreOnWrite})
regnum++
}
return nil
}
func (conn *gdbConn) readAnnex(annex string) ([]gdbRegisterInfo, error) {
tgtbuf, err := conn.qXfer("features", annex, false)
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", "", true)
if err != nil {
return "", err
}
return string(outbuf), nil
}
func (conn *gdbConn) readAuxv() ([]byte, error) {
return conn.qXfer("auxv", "", true)
}
// qXfer executes a 'qXfer' read with the specified kind (i.e. feature,
// exec-file, etc...) and annex.
func (conn *gdbConn) qXfer(kind, annex string, binary bool) ([]byte, error) {
out := []byte{}
for {
cmd := []byte(fmt.Sprintf("$qXfer:%s:read:%s:%x,fff", kind, annex, len(out)))
err := conn.send(cmd)
if err != nil {
return nil, err
}
buf, err := conn.recv(cmd, "target features transfer", binary)
if err != nil {
return nil, err
}
out = append(out, buf[1:]...)
if buf[0] == 'l' {
break
}
}
return out, nil
}
// qXferWrite executes a 'qXfer' write with the specified kind and annex.
func (conn *gdbConn) qXferWrite(kind, annex string) error {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$qXfer:%s:write:%s:0:", kind, annex)
//TODO(aarzilli): if we ever actually need to write something with qXfer,
//this will need to be implemented properly. At the moment it is only used
//for a fake write to the siginfo kind, to end a diversion in 'rr'.
_, err := conn.exec(conn.outbuf.Bytes(), "qXfer")
return err
}
type breakpointType uint8
const (
swBreakpoint breakpointType = 0
hwBreakpoint breakpointType = 1
writeWatchpoint breakpointType = 2
readWatchpoint breakpointType = 3
accessWatchpoint breakpointType = 4
)
// setBreakpoint executes a 'Z' (insert breakpoint) command of type '0' and kind '1' or '4'
func (conn *gdbConn) setBreakpoint(addr uint64, typ breakpointType, kind int) error {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$Z%d,%x,%d", typ, addr, kind)
_, err := conn.exec(conn.outbuf.Bytes(), "set breakpoint")
return err
}
// clearBreakpoint executes a 'z' (remove breakpoint) command of type '0' and kind '1' or '4'
func (conn *gdbConn) clearBreakpoint(addr uint64, typ breakpointType, kind int) error {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$z%d,%x,%d", typ, addr, kind)
_, 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.ErrProcessExited{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
}
*: Go 1.14 support branch (#1727) * tests: misc test fixes for go1.14 - math.go is now ambiguous due to changes to the go runtime so specify that we mean our own math.go in _fixtures - go list -m requires vendor-mode to be disabled so pass '-mod=' to it in case user has GOFLAGS=-mod=vendor - update version of go/packages, required to work with go 1.14 (and executed go mod vendor) - Increased goroutine migration in one development version of Go 1.14 revealed a problem with TestCheckpoints in command_test.go and rr_test.go. The tests were always wrong because Restart(checkpoint) doesn't change the current thread but we can't assume that when the checkpoint was taken the current goroutine was running on the same thread. * goversion: update maximum supported version * Makefile: disable testing lldb-server backend on linux with Go 1.14 There seems to be some incompatibility with lldb-server version 6.0.0 on linux and Go 1.14. * proc/gdbserial: better handling of signals - if multiple signals are received simultaneously propagate all of them to the target threads instead of only one. - debugserver will drop an interrupt request if a target thread simultaneously receives a signal, handle this situation. * dwarf/line: normalize backslashes for windows executables Starting with Go 1.14 the compiler sometimes emits backslashes as well as forward slashes in debug_line, normalize everything to / for conformity with the behavior of previous versions. * proc/native: partial support for Windows async preempt mechanism See https://github.com/golang/go/issues/36494 for a description of why full support for 1.14 under windows is problematic. * proc/native: disable Go 1.14 async preemption on Windows See https://github.com/golang/go/issues/36494
2020-02-11 01:31:54 +00:00
// resume execution of the target process.
// If the current direction is proc.Backward this is done with the 'bc' command.
// If the current direction is proc.Forward this is done with the vCont command.
// The threads argument will be used to determine which signal to use to
// resume each thread. If a thread has sig == 0 the 'c' action will be used,
// otherwise the 'C' action will be used and the value of sig will be passed
// to it.
func (conn *gdbConn) resume(cctx *proc.ContinueOnceContext, threads map[int]*gdbThread, tu *threadUpdater) (stopPacket, error) {
if conn.direction == proc.Forward {
conn.outbuf.Reset()
*: Go 1.14 support branch (#1727) * tests: misc test fixes for go1.14 - math.go is now ambiguous due to changes to the go runtime so specify that we mean our own math.go in _fixtures - go list -m requires vendor-mode to be disabled so pass '-mod=' to it in case user has GOFLAGS=-mod=vendor - update version of go/packages, required to work with go 1.14 (and executed go mod vendor) - Increased goroutine migration in one development version of Go 1.14 revealed a problem with TestCheckpoints in command_test.go and rr_test.go. The tests were always wrong because Restart(checkpoint) doesn't change the current thread but we can't assume that when the checkpoint was taken the current goroutine was running on the same thread. * goversion: update maximum supported version * Makefile: disable testing lldb-server backend on linux with Go 1.14 There seems to be some incompatibility with lldb-server version 6.0.0 on linux and Go 1.14. * proc/gdbserial: better handling of signals - if multiple signals are received simultaneously propagate all of them to the target threads instead of only one. - debugserver will drop an interrupt request if a target thread simultaneously receives a signal, handle this situation. * dwarf/line: normalize backslashes for windows executables Starting with Go 1.14 the compiler sometimes emits backslashes as well as forward slashes in debug_line, normalize everything to / for conformity with the behavior of previous versions. * proc/native: partial support for Windows async preempt mechanism See https://github.com/golang/go/issues/36494 for a description of why full support for 1.14 under windows is problematic. * proc/native: disable Go 1.14 async preemption on Windows See https://github.com/golang/go/issues/36494
2020-02-11 01:31:54 +00:00
fmt.Fprintf(&conn.outbuf, "$vCont")
for _, th := range threads {
if th.sig != 0 {
fmt.Fprintf(&conn.outbuf, ";C%02x:%s", th.sig, th.strID)
}
}
*: Go 1.14 support branch (#1727) * tests: misc test fixes for go1.14 - math.go is now ambiguous due to changes to the go runtime so specify that we mean our own math.go in _fixtures - go list -m requires vendor-mode to be disabled so pass '-mod=' to it in case user has GOFLAGS=-mod=vendor - update version of go/packages, required to work with go 1.14 (and executed go mod vendor) - Increased goroutine migration in one development version of Go 1.14 revealed a problem with TestCheckpoints in command_test.go and rr_test.go. The tests were always wrong because Restart(checkpoint) doesn't change the current thread but we can't assume that when the checkpoint was taken the current goroutine was running on the same thread. * goversion: update maximum supported version * Makefile: disable testing lldb-server backend on linux with Go 1.14 There seems to be some incompatibility with lldb-server version 6.0.0 on linux and Go 1.14. * proc/gdbserial: better handling of signals - if multiple signals are received simultaneously propagate all of them to the target threads instead of only one. - debugserver will drop an interrupt request if a target thread simultaneously receives a signal, handle this situation. * dwarf/line: normalize backslashes for windows executables Starting with Go 1.14 the compiler sometimes emits backslashes as well as forward slashes in debug_line, normalize everything to / for conformity with the behavior of previous versions. * proc/native: partial support for Windows async preempt mechanism See https://github.com/golang/go/issues/36494 for a description of why full support for 1.14 under windows is problematic. * proc/native: disable Go 1.14 async preemption on Windows See https://github.com/golang/go/issues/36494
2020-02-11 01:31:54 +00:00
fmt.Fprintf(&conn.outbuf, ";c")
} else {
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
return stopPacket{}, err
}
conn.outbuf.Reset()
fmt.Fprint(&conn.outbuf, "$bc")
}
cctx.StopMu.Lock()
if err := conn.send(conn.outbuf.Bytes()); err != nil {
cctx.StopMu.Unlock()
return stopPacket{}, err
}
conn.running = true
cctx.StopMu.Unlock()
defer func() {
cctx.StopMu.Lock()
conn.running = false
cctx.StopMu.Unlock()
}()
if cctx.ResumeChan != nil {
close(cctx.ResumeChan)
cctx.ResumeChan = nil
}
return conn.waitForvContStop("resume", "-1", tu)
}
// step executes a 'vCont' command on the specified thread with 's' action.
func (conn *gdbConn) step(th *gdbThread, tu *threadUpdater, ignoreFaultSignal bool) error {
threadID := th.strID
if conn.direction != proc.Forward {
if err := conn.selectThread('c', threadID, "step"); err != nil {
return err
}
conn.outbuf.Reset()
fmt.Fprint(&conn.outbuf, "$bs")
if err := conn.send(conn.outbuf.Bytes()); err != nil {
return err
}
_, err := conn.waitForvContStop("singlestep", threadID, tu)
return err
}
var _SIGBUS uint8
switch conn.goos {
case "linux":
_SIGBUS = 0x7
case "darwin":
_SIGBUS = 0xa
default:
panic(fmt.Errorf("unknown GOOS %s", conn.goos))
}
var sig uint8 = 0
for {
conn.outbuf.Reset()
if sig == 0 {
fmt.Fprintf(&conn.outbuf, "$vCont;s:%s", threadID)
} else {
fmt.Fprintf(&conn.outbuf, "$vCont;S%02x:%s", sig, threadID)
}
if err := conn.send(conn.outbuf.Bytes()); err != nil {
return err
}
if tu != nil {
tu.Reset()
}
sp, err := conn.waitForvContStop("singlestep", threadID, tu)
sig = sp.sig
if err != nil {
return err
}
switch sig {
case faultSignal:
if ignoreFaultSignal { // we attempting to read the TLS, a fault here should be ignored
return nil
}
case _SIGILL, _SIGBUS, _SIGFPE:
// propagate these signals to inferior immediately
case interruptSignal, breakpointSignal, stopSignal:
return nil
case childSignal: // stop on debugserver but SIGCHLD on lldb-server/linux
if conn.isDebugserver {
return nil
}
case debugServerTargetExcBadAccess, debugServerTargetExcBadInstruction, debugServerTargetExcArithmetic, debugServerTargetExcEmulation, debugServerTargetExcSoftware, debugServerTargetExcBreakpoint:
if ignoreFaultSignal {
return nil
}
return machTargetExcToError(sig)
default:
// delay propagation of any other signal to until after the stepping is done
th.sig = sig
sig = 0
}
}
}
var errThreadBlocked = errors.New("thread blocked")
func (conn *gdbConn) waitForvContStop(context, threadID string, tu *threadUpdater) (stopPacket, error) {
count := 0
failed := false
for {
conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval))
resp, err := conn.recv(nil, context, false)
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 stopPacket{}, errThreadBlocked
} else if err != nil {
return stopPacket{}, err
} else {
repeat, sp, err := conn.parseStopPacket(resp, threadID, tu)
if !repeat {
return sp, err
}
}
}
}
type stopPacket struct {
threadID string
sig uint8
reason string
watchAddr uint64
}
// Mach exception codes used to decode metype/medata keys in stop packets (necessary to support watchpoints with debugserver).
// See:
// https://opensource.apple.com/source/xnu/xnu-4570.1.46/osfmk/mach/exception_types.h.auto.html
// https://opensource.apple.com/source/xnu/xnu-4570.1.46/osfmk/mach/i386/exception.h.auto.html
// https://opensource.apple.com/source/xnu/xnu-4570.1.46/osfmk/mach/arm/exception.h.auto.html
const (
_EXC_BREAKPOINT = 6 // mach exception type for hardware breakpoints
_EXC_I386_SGL = 1 // mach exception code for single step on x86, for some reason this is also used for watchpoints
_EXC_ARM_DA_DEBUG = 0x102 // mach exception code for debug fault on arm/arm64
)
// 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 logflags.GdbWire() && gdbWireFullStopPacket {
conn.log.Debugf("full stop packet: %s", string(resp))
}
var metype int
var medata = make([]uint64, 0, 10)
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)
case "watch", "awatch", "rwatch":
sp.watchAddr, err = strconv.ParseUint(string(value), 16, 64)
if err != nil {
return false, stopPacket{}, fmt.Errorf("malformed stop packet: %s (wrong watch address)", string(resp))
}
case "metype":
// mach exception type (debugserver extension)
metype, _ = strconv.Atoi(string(value))
case "medata":
// mach exception data (debugserver extension)
d, _ := strconv.ParseUint(string(value), 16, 64)
medata = append(medata, d)
}
}
// Debugserver does not report watchpoint stops in the standard way preferring
// instead the semi-undocumented metype/medata keys.
// These values also have different meanings depending on the CPU architecture.
switch conn.goarch {
case "amd64":
if metype == _EXC_BREAKPOINT && len(medata) >= 2 && medata[0] == _EXC_I386_SGL {
sp.watchAddr = medata[1] // this should be zero if this is really a single step stop and non-zero for watchpoints
}
case "arm64":
if metype == _EXC_BREAKPOINT && len(medata) >= 2 && medata[0] == _EXC_ARM_DA_DEBUG {
sp.watchAddr = medata[1]
}
}
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.ErrProcessExited{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 {
conn.log.Debug("<- 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)
}
func (conn *gdbConn) readMemory(data []byte, addr uint64) error {
if conn.xcmdok && len(data) > conn.packetSize {
return conn.readMemoryBinary(data, addr)
}
return conn.readMemoryHex(data, addr)
}
// executes 'm' (read memory) command
func (conn *gdbConn) readMemoryHex(data []byte, addr uint64) 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+uint64(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
}
// executes 'x' (binary read memory) command
func (conn *gdbConn) readMemoryBinary(data []byte, addr uint64) error {
size := len(data)
data = data[:0]
for len(data) < size {
conn.outbuf.Reset()
sz := size - len(data)
fmt.Fprintf(&conn.outbuf, "$x%x,%x", addr+uint64(len(data)), sz)
if err := conn.send(conn.outbuf.Bytes()); err != nil {
return err
}
resp, err := conn.recv(conn.outbuf.Bytes(), "binary memory read", true)
if err != nil {
return err
}
data = append(data, resp...)
}
return nil
}
func writeAsciiBytes(w io.Writer, data []byte) {
for _, b := range data {
fmt.Fprintf(w, "%02x", b)
}
}
// writeMemory writes memory using either 'M' or 'X'
func (conn *gdbConn) writeMemory(addr uint64, data []byte) (written int, err error) {
if conn.useXcmd {
return conn.writeMemoryBinary(addr, data)
}
return conn.writeMemoryHex(addr, data)
}
// executes 'M' (write memory) command
func (conn *gdbConn) writeMemoryHex(addr uint64, data []byte) (written int, err error) {
if len(data) == 0 {
// LLDB can't parse requests for 0-length writes and hangs if we emit them
return 0, nil
}
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) writeMemoryBinary(addr uint64, data []byte) (written int, err error) {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$X%x,%x:", addr, len(data))
for _, b := range data {
switch b {
case '#', '$', '}':
conn.outbuf.WriteByte('}')
conn.outbuf.WriteByte(b ^ escapeXor)
default:
conn.outbuf.WriteByte(b)
}
}
_, 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) (sp stopPacket, 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 stopPacket{}, err
}
_, sp, err = conn.parseStopPacket(resp, "", nil)
if err != nil {
return stopPacket{}, err
}
return sp, 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
}
type imageList struct {
Images []imageDescription `json:"images"`
}
type imageDescription struct {
LoadAddress uint64 `json:"load_address"`
Pathname string `json:"pathname"`
MachHeader machHeader `json:"mach_header"`
}
type machHeader struct {
FileType macho.Type `json:"filetype"`
}
// getLoadedDynamicLibraries executes jGetLoadedDynamicLibrariesInfos which
// returns the list of loaded dynamic libraries
func (conn *gdbConn) getLoadedDynamicLibraries() ([]imageDescription, error) {
cmd := []byte("$jGetLoadedDynamicLibrariesInfos:{\"fetch_all_solibs\":true}")
if err := conn.send(cmd); err != nil {
return nil, err
}
resp, err := conn.recv(cmd, "get dynamic libraries", true)
if err != nil {
return nil, err
}
var images imageList
err = json.Unmarshal(resp, &images)
return images.Images, err
}
type memoryRegionInfo struct {
start uint64
size uint64
permissions string
name string
}
func decodeHexString(in []byte) (string, bool) {
out := make([]byte, 0, len(in)/2)
for i := 0; i < len(in); i += 2 {
v, err := strconv.ParseUint(string(in[i:i+2]), 16, 8)
if err != nil {
return "", false
}
out = append(out, byte(v))
}
return string(out), true
}
func (conn *gdbConn) memoryRegionInfo(addr uint64) (*memoryRegionInfo, error) {
conn.outbuf.Reset()
fmt.Fprintf(&conn.outbuf, "$qMemoryRegionInfo:%x", addr)
resp, err := conn.exec(conn.outbuf.Bytes(), "qMemoryRegionInfo")
if err != nil {
return nil, err
}
mri := &memoryRegionInfo{}
buf := resp
for len(buf) > 0 {
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 "start":
start, err := strconv.ParseUint(string(value), 16, 64)
if err != nil {
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (start): %v in %s", err, string(resp))
}
mri.start = start
case "size":
size, err := strconv.ParseUint(string(value), 16, 64)
if err != nil {
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (size): %v in %s", err, string(resp))
}
mri.size = size
case "permissions":
mri.permissions = string(value)
case "name":
namestr, ok := decodeHexString(value)
if !ok {
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (name): %s", string(resp))
}
mri.name = namestr
case "error":
errstr, ok := decodeHexString(value)
if !ok {
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (error): %s", string(resp))
}
return nil, fmt.Errorf("qMemoryRegionInfo error: %s", errstr)
}
}
return mri, 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, false)
}
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)
2020-03-18 22:45:29 +00:00
cmd = append(cmd, hexdigit[sum>>4], hexdigit[sum&0xf])
attempt := 0
for {
if logflags.GdbWire() {
if len(cmd) > gdbWireMaxLen {
conn.log.Debugf("<- %s...", string(cmd[:gdbWireMaxLen]))
} else {
conn.log.Debugf("<- %s", 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, binary bool) (resp []byte, err error) {
attempt := 0
for {
var err error
resp, err = conn.rdr.ReadBytes('#')
if err != nil {
return nil, err
}
// read checksum
_, err = io.ReadFull(conn.rdr, conn.inbuf[:2])
if err != nil {
return nil, err
}
if logflags.GdbWire() {
out := resp
partial := false
if idx := bytes.Index(out, []byte{'\n'}); idx >= 0 && !binary {
out = resp[:idx]
partial = true
}
if len(out) > gdbWireMaxLen {
out = out[:gdbWireMaxLen]
partial = true
}
if !partial {
if binary {
conn.log.Debugf("-> %q%s", string(resp), string(conn.inbuf[:2]))
} else {
conn.log.Debugf("-> %s%s", string(resp), string(conn.inbuf[:2]))
}
} else {
if binary {
conn.log.Debugf("-> %q...", string(out))
} else {
conn.log.Debugf("-> %s...", 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('-')
}
if binary {
conn.inbuf, resp = binarywiredecode(resp, conn.inbuf)
} else {
conn.inbuf, resp = wiredecode(resp, conn.inbuf)
}
if len(resp) == 0 || (resp[0] == 'E' && !binary) || (resp[0] == 'E' && len(resp) == 3) {
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
}
conn.log.Debugf("-> %s", 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})
conn.log.Debugf("<- %s", string(c))
}
// escapeXor is the value mandated by the specification to escape characters
const escapeXor byte = 0x20
// 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]^escapeXor)
i++
}
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:]
2018-03-20 10:05:35 +00:00
case '*': // runlength 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:]
}
// binarywiredecode is like wiredecode but decodes the wire encoding for
// binary packets, such as the 'x' and 'X' packets as well as all the json
// packets used by lldb/debugserver.
func binarywiredecode(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]^escapeXor)
i++
}
case '#': // end of packet
return buf, buf[start:]
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
}
tgt8 := uint8(tgt)
return sum == tgt8
}
func checksum(packet []byte) (sum uint8) {
for i := 1; i < len(packet); i++ {
if packet[i] == '#' {
return sum
}
sum += packet[i]
}
return sum
}