2017-04-21 06:55:53 +00:00
|
|
|
package gdbserial
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2017-08-14 18:20:34 +00:00
|
|
|
"debug/macho"
|
|
|
|
"encoding/json"
|
2017-02-10 14:11:40 +00:00
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2017-04-21 06:55:53 +00:00
|
|
|
|
2019-01-04 18:39:25 +00:00
|
|
|
"github.com/go-delve/delve/pkg/logflags"
|
|
|
|
"github.com/go-delve/delve/pkg/proc"
|
2017-02-10 14:11:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type gdbConn struct {
|
|
|
|
conn net.Conn
|
|
|
|
rdr *bufio.Reader
|
|
|
|
|
|
|
|
inbuf []byte
|
|
|
|
outbuf bytes.Buffer
|
|
|
|
|
2022-03-21 19:42:37 +00:00
|
|
|
running bool
|
2017-02-10 14:11:40 +00:00
|
|
|
|
2017-05-05 22:17:52 +00:00
|
|
|
direction proc.Direction // direction of execution
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
packetSize int // maximum packet size supported by stub
|
|
|
|
regsInfo []gdbRegisterInfo // list of registers
|
|
|
|
|
2021-10-30 18:51:02 +00:00
|
|
|
workaroundReg *gdbRegisterInfo // used to work-around a register setting bug in debugserver, see use in gdbserver.go
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
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
|
2021-01-29 21:39:33 +00:00
|
|
|
xcmdok bool // x command can be used to transfer memory
|
2021-10-04 21:45:05 +00:00
|
|
|
goarch string
|
2021-12-07 17:21:53 +00:00
|
|
|
goos string
|
2018-06-14 18:12:11 +00:00
|
|
|
|
2021-10-14 18:06:14 +00:00
|
|
|
useXcmd bool // forces writeMemory to use the 'X' command
|
|
|
|
|
2023-02-14 17:46:35 +00:00
|
|
|
log logflags.Logger
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 == ""
|
|
|
|
}
|
|
|
|
|
2022-12-28 11:41:13 +00:00
|
|
|
// GdbMalformedThreadIDError is returned when the stub responds with a
|
2017-02-10 14:11:40 +00:00
|
|
|
// 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"
|
|
|
|
)
|
|
|
|
|
2021-04-05 18:23:32 +00:00
|
|
|
func (conn *gdbConn) handshake(regnames *gdbRegnames) error {
|
2017-02-10 14:11:40 +00:00
|
|
|
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)
|
2021-04-05 18:23:32 +00:00
|
|
|
regFound := map[string]bool{
|
|
|
|
regnames.PC: false,
|
|
|
|
regnames.SP: false,
|
|
|
|
regnames.BP: false,
|
|
|
|
regnames.CX: false,
|
|
|
|
}
|
|
|
|
if err := conn.readRegisterInfo(regFound); err != nil {
|
2017-02-10 14:11:40 +00:00
|
|
|
if isProtocolErrorUnsupported(err) {
|
2021-04-05 18:23:32 +00:00
|
|
|
if err := conn.readTargetXml(regFound); err != nil {
|
2017-02-10 14:11:40 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-04-05 18:23:32 +00:00
|
|
|
for n := range regFound {
|
|
|
|
if n == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !regFound[n] {
|
|
|
|
return fmt.Errorf("could not find %s register", n)
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-29 21:39:33 +00:00
|
|
|
if resp, err := conn.exec([]byte("$x0,0"), "init"); err == nil && string(resp) == "OK" {
|
|
|
|
conn.xcmdok = true
|
|
|
|
}
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
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 {
|
2023-07-13 18:30:32 +00:00
|
|
|
if len(stubfeature) == 0 {
|
2017-02-10 14:11:40 +00:00
|
|
|
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
|
2019-02-26 16:53:45 +00:00
|
|
|
Regnum int `xml:"regnum,attr"`
|
|
|
|
Group string `xml:"group,attr"`
|
2021-05-26 15:21:03 +00:00
|
|
|
|
|
|
|
ignoreOnWrite bool
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2021-04-05 18:23:32 +00:00
|
|
|
func setRegFound(regFound map[string]bool, name string) {
|
|
|
|
for n := range regFound {
|
|
|
|
if name == n {
|
|
|
|
regFound[n] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
// 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:
|
proc/gdbserver: ignore spurious stop packet from debugserver (#3021)
When we send an interrupt request to debugserver we, sometimes, get one
extra spurious stop packet back.
This stop packet gets interpreted as a response to a qThreadStopInfo request
we make causing the protocol to become desynchronized for a while, until
eventually some kind of error appears.
Here's an example of this problem, distilled from issue #3013:
1 <- $vCont;c#a8
2 <- interrupt
3 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
4 <- $qThreadStopInfo12efb8e#28
5 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
6 <- $qThreadStopInfo12efb8f#29
7 -> $T00thread:12efb8e;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
8 <- $qThreadStopInfo12efb90#f4
9 -> $T00thread:12efb8f;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
10 <- $qThreadStopInfo12efb91#f5
11 -> $T00thread:12efb90;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
12 <- $qThreadStopInfo12efb47#f6
13 -> $T00thread:12efb91;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
14 <- $qThreadStopInfo12efb8d#27
15 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
16 <- $p0;thread:12efb8e;#f5
17 -> $T00thread:12efb8d;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
response (3) is interpreted as the response to the vCont request at (1). We
then make a qThreadStopInfo request (4) and receive a stop packet in
response (5). Packet (5) is interpreted as the response to (4) but it
actually isn't, note how the thread ID is different, packet (5) is actually
a spurious stop packet sent by debug server. From response (5) onward the
protocol is desynchronized, none of the response we process are actually the
response to the preceding request.
This eventually causes a failure at packet (17) which debugserver sent as
the response to request (14) but we interpret as the response to (16).
Fixes #3013
2022-06-08 17:56:50 +00:00
|
|
|
//
|
|
|
|
// https://github.com/bminor/binutils-gdb/blob/61baf725eca99af2569262d10aca03dcde2698f6/gdb/features/gdb-target.dtd
|
2021-04-05 18:23:32 +00:00
|
|
|
func (conn *gdbConn) readTargetXml(regFound map[string]bool) (err error) {
|
2017-02-10 14:11:40 +00:00
|
|
|
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
|
2021-01-04 16:52:04 +00:00
|
|
|
|
2021-04-05 18:23:32 +00:00
|
|
|
setRegFound(regFound, conn.regsInfo[i].Name)
|
|
|
|
regnum++
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
2021-01-04 16:52:04 +00:00
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// readRegisterInfo uses qRegisterInfo to read register information (used
|
|
|
|
// when qXfer:feature:read is not supported).
|
2021-04-05 18:23:32 +00:00
|
|
|
func (conn *gdbConn) readRegisterInfo(regFound map[string]bool) (err error) {
|
2017-02-10 14:11:40 +00:00
|
|
|
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 {
|
2017-05-05 22:17:52 +00:00
|
|
|
if regnum == 0 {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
var regname string
|
|
|
|
var offset int
|
|
|
|
var bitsize int
|
|
|
|
var contained bool
|
2021-05-26 15:21:03 +00:00
|
|
|
var ignoreOnWrite bool
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
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
|
2021-05-26 15:21:03 +00:00
|
|
|
case "set":
|
2022-05-03 17:46:24 +00:00
|
|
|
if value == "Exception State Registers" || value == "AMX Registers" {
|
2021-05-26 15:21:03 +00:00
|
|
|
// debugserver doesn't like it if we try to write these
|
|
|
|
ignoreOnWrite = true
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if semicolon < 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
resp = resp[semicolon+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
if contained {
|
2021-10-30 18:51:02 +00:00
|
|
|
if regname == "xmm0" {
|
|
|
|
conn.workaroundReg = &gdbRegisterInfo{Regnum: regnum, Name: regname, Bitsize: bitsize, Offset: offset, ignoreOnWrite: ignoreOnWrite}
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
regnum++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-04-05 18:23:32 +00:00
|
|
|
setRegFound(regFound, regname)
|
2017-02-10 14:11:40 +00:00
|
|
|
|
2021-05-26 15:21:03 +00:00
|
|
|
conn.regsInfo = append(conn.regsInfo, gdbRegisterInfo{Regnum: regnum, Name: regname, Bitsize: bitsize, Offset: offset, ignoreOnWrite: ignoreOnWrite})
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
regnum++
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (conn *gdbConn) readAnnex(annex string) ([]gdbRegisterInfo, error) {
|
2018-05-29 15:01:51 +00:00
|
|
|
tgtbuf, err := conn.qXfer("features", annex, false)
|
2017-02-10 14:11:40 +00:00
|
|
|
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) {
|
2018-05-29 15:01:51 +00:00
|
|
|
outbuf, err := conn.qXfer("exec-file", "", true)
|
2017-02-10 14:11:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(outbuf), nil
|
|
|
|
}
|
|
|
|
|
2018-05-29 15:01:51 +00:00
|
|
|
func (conn *gdbConn) readAuxv() ([]byte, error) {
|
|
|
|
return conn.qXfer("auxv", "", true)
|
|
|
|
}
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
// qXfer executes a 'qXfer' read with the specified kind (i.e. feature,
|
|
|
|
// exec-file, etc...) and annex.
|
2018-05-29 15:01:51 +00:00
|
|
|
func (conn *gdbConn) qXfer(kind, annex string, binary bool) ([]byte, error) {
|
2017-02-10 14:11:40 +00:00
|
|
|
out := []byte{}
|
|
|
|
for {
|
2018-05-29 15:01:51 +00:00
|
|
|
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)
|
2017-02-10 14:11:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
out = append(out, buf[1:]...)
|
|
|
|
if buf[0] == 'l' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2021-10-14 18:06:14 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-10-04 21:45:05 +00:00
|
|
|
type breakpointType uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
swBreakpoint breakpointType = 0
|
|
|
|
hwBreakpoint breakpointType = 1
|
|
|
|
writeWatchpoint breakpointType = 2
|
|
|
|
readWatchpoint breakpointType = 3
|
|
|
|
accessWatchpoint breakpointType = 4
|
|
|
|
)
|
|
|
|
|
2021-01-04 16:52:04 +00:00
|
|
|
// setBreakpoint executes a 'Z' (insert breakpoint) command of type '0' and kind '1' or '4'
|
2021-10-04 21:45:05 +00:00
|
|
|
func (conn *gdbConn) setBreakpoint(addr uint64, typ breakpointType, kind int) error {
|
2017-02-10 14:11:40 +00:00
|
|
|
conn.outbuf.Reset()
|
2021-10-04 21:45:05 +00:00
|
|
|
fmt.Fprintf(&conn.outbuf, "$Z%d,%x,%d", typ, addr, kind)
|
2017-02-10 14:11:40 +00:00
|
|
|
_, err := conn.exec(conn.outbuf.Bytes(), "set breakpoint")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-01-04 16:52:04 +00:00
|
|
|
// clearBreakpoint executes a 'z' (remove breakpoint) command of type '0' and kind '1' or '4'
|
2021-10-04 21:45:05 +00:00
|
|
|
func (conn *gdbConn) clearBreakpoint(addr uint64, typ breakpointType, kind int) error {
|
2017-02-10 14:11:40 +00:00
|
|
|
conn.outbuf.Reset()
|
2021-10-04 21:45:05 +00:00
|
|
|
fmt.Fprintf(&conn.outbuf, "$z%d,%x,%d", typ, addr, kind)
|
2017-02-10 14:11:40 +00:00
|
|
|
_, 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
|
2018-08-31 18:08:18 +00:00
|
|
|
return proc.ErrProcessExited{Pid: conn.pid}
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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.
|
2022-03-21 19:42:37 +00:00
|
|
|
func (conn *gdbConn) resume(cctx *proc.ContinueOnceContext, threads map[int]*gdbThread, tu *threadUpdater) (stopPacket, error) {
|
2017-05-05 22:17:52 +00:00
|
|
|
if conn.direction == proc.Forward {
|
|
|
|
conn.outbuf.Reset()
|
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)
|
|
|
|
}
|
2017-05-05 22:17:52 +00:00
|
|
|
}
|
2020-02-11 01:31:54 +00:00
|
|
|
fmt.Fprintf(&conn.outbuf, ";c")
|
2017-02-10 14:11:40 +00:00
|
|
|
} else {
|
2017-05-05 22:17:52 +00:00
|
|
|
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
|
2021-10-04 21:45:05 +00:00
|
|
|
return stopPacket{}, err
|
2017-05-05 22:17:52 +00:00
|
|
|
}
|
|
|
|
conn.outbuf.Reset()
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, "$bc")
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
2022-03-21 19:42:37 +00:00
|
|
|
cctx.StopMu.Lock()
|
2017-02-10 14:11:40 +00:00
|
|
|
if err := conn.send(conn.outbuf.Bytes()); err != nil {
|
2022-03-21 19:42:37 +00:00
|
|
|
cctx.StopMu.Unlock()
|
2021-10-04 21:45:05 +00:00
|
|
|
return stopPacket{}, err
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
conn.running = true
|
2022-03-21 19:42:37 +00:00
|
|
|
cctx.StopMu.Unlock()
|
2017-02-10 14:11:40 +00:00
|
|
|
defer func() {
|
2022-03-21 19:42:37 +00:00
|
|
|
cctx.StopMu.Lock()
|
2017-02-10 14:11:40 +00:00
|
|
|
conn.running = false
|
2022-03-21 19:42:37 +00:00
|
|
|
cctx.StopMu.Unlock()
|
2017-02-10 14:11:40 +00:00
|
|
|
}()
|
2022-03-21 19:42:37 +00:00
|
|
|
if cctx.ResumeChan != nil {
|
|
|
|
close(cctx.ResumeChan)
|
|
|
|
cctx.ResumeChan = nil
|
2017-06-06 15:26:16 +00:00
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
return conn.waitForvContStop("resume", "-1", tu)
|
|
|
|
}
|
|
|
|
|
|
|
|
// step executes a 'vCont' command on the specified thread with 's' action.
|
2021-12-07 17:21:53 +00:00
|
|
|
func (conn *gdbConn) step(th *gdbThread, tu *threadUpdater, ignoreFaultSignal bool) error {
|
|
|
|
threadID := th.strID
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
if conn.direction != proc.Forward {
|
2017-05-05 22:17:52 +00:00
|
|
|
if err := conn.selectThread('c', threadID, "step"); err != nil {
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
return err
|
2017-05-05 22:17:52 +00:00
|
|
|
}
|
|
|
|
conn.outbuf.Reset()
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, "$bs")
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
if err := conn.send(conn.outbuf.Bytes()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-10-04 21:45:05 +00:00
|
|
|
_, err := conn.waitForvContStop("singlestep", threadID, tu)
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
return err
|
2017-05-05 22:17:52 +00:00
|
|
|
}
|
2021-12-07 17:21:53 +00:00
|
|
|
|
|
|
|
var _SIGBUS uint8
|
|
|
|
switch conn.goos {
|
|
|
|
case "linux":
|
|
|
|
_SIGBUS = 0x7
|
|
|
|
case "darwin":
|
|
|
|
_SIGBUS = 0xa
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unknown GOOS %s", conn.goos))
|
|
|
|
}
|
|
|
|
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
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
|
|
|
|
}
|
2019-08-13 18:14:47 +00:00
|
|
|
if tu != nil {
|
|
|
|
tu.Reset()
|
|
|
|
}
|
2021-10-04 21:45:05 +00:00
|
|
|
sp, err := conn.waitForvContStop("singlestep", threadID, tu)
|
|
|
|
sig = sp.sig
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
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
|
|
|
|
}
|
2021-12-07 17:21:53 +00:00
|
|
|
case _SIGILL, _SIGBUS, _SIGFPE:
|
|
|
|
// propagate these signals to inferior immediately
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
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:
|
2021-12-07 17:21:53 +00:00
|
|
|
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
|
gdbserial: propagate signals to target while stepping (#1624)
Propagate signals when stepping because debugserver will report them,
from the issue:
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $z0,105525d,1#c9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $vCont;s:c41c3#50
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $T1cthread:c41c3;threads:c41c3,c41d7,c41d8,c41d9,c41da;thread-pcs:105525d,7fffc464bf46,7fffc464bbf2,7fffc464bbf2,7fffc46...
2019-07-11T16:31:25+02:00 debug layer=gdbconn <- $Z0,105525d,1#a9
2019-07-11T16:31:25+02:00 debug layer=gdbconn -> $OK#00
in this case we request a single step on thread c41c3 but debugserver
reports instead a signal (in this case SIGWINCH).
Fixes #1610
2019-07-23 21:42:21 +00:00
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 16:52:04 +00:00
|
|
|
var errThreadBlocked = errors.New("thread blocked")
|
2017-07-07 23:29:37 +00:00
|
|
|
|
2021-10-04 21:45:05 +00:00
|
|
|
func (conn *gdbConn) waitForvContStop(context, threadID string, tu *threadUpdater) (stopPacket, error) {
|
2017-07-07 23:29:37 +00:00
|
|
|
count := 0
|
|
|
|
failed := false
|
2017-02-10 14:11:40 +00:00
|
|
|
for {
|
|
|
|
conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval))
|
2017-08-14 18:20:34 +00:00
|
|
|
resp, err := conn.recv(nil, context, false)
|
2017-02-10 14:11:40 +00:00
|
|
|
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("$?"))
|
|
|
|
}
|
2017-07-07 23:29:37 +00:00
|
|
|
if count > 1 && context == "singlestep" {
|
|
|
|
failed = true
|
|
|
|
conn.sendCtrlC()
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
} else if failed {
|
2021-10-04 21:45:05 +00:00
|
|
|
return stopPacket{}, errThreadBlocked
|
2017-02-10 14:11:40 +00:00
|
|
|
} else if err != nil {
|
2021-10-04 21:45:05 +00:00
|
|
|
return stopPacket{}, err
|
2017-02-10 14:11:40 +00:00
|
|
|
} else {
|
|
|
|
repeat, sp, err := conn.parseStopPacket(resp, threadID, tu)
|
|
|
|
if !repeat {
|
2021-10-04 21:45:05 +00:00
|
|
|
return sp, err
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type stopPacket struct {
|
2021-10-04 21:45:05 +00:00
|
|
|
threadID string
|
|
|
|
sig uint8
|
|
|
|
reason string
|
|
|
|
watchAddr uint64
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2021-10-04 21:45:05 +00:00
|
|
|
// Mach exception codes used to decode metype/medata keys in stop packets (necessary to support watchpoints with debugserver).
|
|
|
|
// See:
|
proc/gdbserver: ignore spurious stop packet from debugserver (#3021)
When we send an interrupt request to debugserver we, sometimes, get one
extra spurious stop packet back.
This stop packet gets interpreted as a response to a qThreadStopInfo request
we make causing the protocol to become desynchronized for a while, until
eventually some kind of error appears.
Here's an example of this problem, distilled from issue #3013:
1 <- $vCont;c#a8
2 <- interrupt
3 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
4 <- $qThreadStopInfo12efb8e#28
5 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
6 <- $qThreadStopInfo12efb8f#29
7 -> $T00thread:12efb8e;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
8 <- $qThreadStopInfo12efb90#f4
9 -> $T00thread:12efb8f;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
10 <- $qThreadStopInfo12efb91#f5
11 -> $T00thread:12efb90;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
12 <- $qThreadStopInfo12efb47#f6
13 -> $T00thread:12efb91;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
14 <- $qThreadStopInfo12efb8d#27
15 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
16 <- $p0;thread:12efb8e;#f5
17 -> $T00thread:12efb8d;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
response (3) is interpreted as the response to the vCont request at (1). We
then make a qThreadStopInfo request (4) and receive a stop packet in
response (5). Packet (5) is interpreted as the response to (4) but it
actually isn't, note how the thread ID is different, packet (5) is actually
a spurious stop packet sent by debug server. From response (5) onward the
protocol is desynchronized, none of the response we process are actually the
response to the preceding request.
This eventually causes a failure at packet (17) which debugserver sent as
the response to request (14) but we interpret as the response to (16).
Fixes #3013
2022-06-08 17:56:50 +00:00
|
|
|
//
|
|
|
|
// 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
|
2021-10-04 21:45:05 +00:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
// 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)
|
|
|
|
|
2018-03-31 16:03:27 +00:00
|
|
|
if logflags.GdbWire() && gdbWireFullStopPacket {
|
2018-07-02 09:00:32 +00:00
|
|
|
conn.log.Debugf("full stop packet: %s", string(resp))
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2021-10-04 21:45:05 +00:00
|
|
|
var metype int
|
|
|
|
var medata = make([]uint64, 0, 10)
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
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)
|
2021-10-04 21:45:05 +00:00
|
|
|
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]
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2018-08-31 18:08:18 +00:00
|
|
|
return false, stopPacket{}, proc.ErrProcessExited{Pid: conn.pid, Status: int(status)}
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
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 {
|
2018-06-14 18:12:11 +00:00
|
|
|
conn.log.Debug("<- interrupt")
|
2017-02-10 14:11:40 +00:00
|
|
|
_, 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 {
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, "$qProcessInfo")
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-09-09 17:36:15 +00:00
|
|
|
func (conn *gdbConn) readMemory(data []byte, addr uint64) error {
|
2021-01-29 21:39:33 +00:00
|
|
|
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 {
|
2017-04-18 14:24:45 +00:00
|
|
|
size := len(data)
|
|
|
|
data = data[:0]
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-09-09 17:36:15 +00:00
|
|
|
fmt.Fprintf(&conn.outbuf, "$m%x,%x", addr+uint64(len(data)), sz)
|
2017-02-10 14:11:40 +00:00
|
|
|
resp, err := conn.exec(conn.outbuf.Bytes(), "memory read")
|
|
|
|
if err != nil {
|
2017-04-18 14:24:45 +00:00
|
|
|
return err
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(resp); i += 2 {
|
|
|
|
n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8)
|
|
|
|
data = append(data, uint8(n))
|
|
|
|
}
|
|
|
|
}
|
2017-04-18 14:24:45 +00:00
|
|
|
return nil
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 21:39:33 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-05-05 22:17:52 +00:00
|
|
|
func writeAsciiBytes(w io.Writer, data []byte) {
|
|
|
|
for _, b := range data {
|
|
|
|
fmt.Fprintf(w, "%02x", b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 18:06:14 +00:00
|
|
|
// writeMemory writes memory using either 'M' or 'X'
|
2020-09-09 17:36:15 +00:00
|
|
|
func (conn *gdbConn) writeMemory(addr uint64, data []byte) (written int, err error) {
|
2021-10-14 18:06:14 +00:00
|
|
|
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) {
|
2018-08-03 15:17:01 +00:00
|
|
|
if len(data) == 0 {
|
|
|
|
// LLDB can't parse requests for 0-length writes and hangs if we emit them
|
|
|
|
return 0, nil
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
conn.outbuf.Reset()
|
|
|
|
//TODO(aarzilli): do not send packets larger than conn.PacketSize
|
|
|
|
fmt.Fprintf(&conn.outbuf, "$M%x,%x:", addr, len(data))
|
|
|
|
|
2017-05-05 22:17:52 +00:00
|
|
|
writeAsciiBytes(&conn.outbuf, data)
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
_, err = conn.exec(conn.outbuf.Bytes(), "memory write")
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return len(data), nil
|
|
|
|
}
|
|
|
|
|
2021-10-14 18:06:14 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
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.
|
2021-10-04 21:45:05 +00:00
|
|
|
func (conn *gdbConn) threadStopInfo(threadID string) (sp stopPacket, err error) {
|
2017-02-10 14:11:40 +00:00
|
|
|
conn.outbuf.Reset()
|
|
|
|
fmt.Fprintf(&conn.outbuf, "$qThreadStopInfo%s", threadID)
|
|
|
|
resp, err := conn.exec(conn.outbuf.Bytes(), "thread stop info")
|
|
|
|
if err != nil {
|
2021-10-04 21:45:05 +00:00
|
|
|
return stopPacket{}, err
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
2021-10-04 21:45:05 +00:00
|
|
|
_, sp, err = conn.parseStopPacket(resp, "", nil)
|
2017-02-10 14:11:40 +00:00
|
|
|
if err != nil {
|
2021-10-04 21:45:05 +00:00
|
|
|
return stopPacket{}, err
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
proc/gdbserver: ignore spurious stop packet from debugserver (#3021)
When we send an interrupt request to debugserver we, sometimes, get one
extra spurious stop packet back.
This stop packet gets interpreted as a response to a qThreadStopInfo request
we make causing the protocol to become desynchronized for a while, until
eventually some kind of error appears.
Here's an example of this problem, distilled from issue #3013:
1 <- $vCont;c#a8
2 <- interrupt
3 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
4 <- $qThreadStopInfo12efb8e#28
5 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
6 <- $qThreadStopInfo12efb8f#29
7 -> $T00thread:12efb8e;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
8 <- $qThreadStopInfo12efb90#f4
9 -> $T00thread:12efb8f;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
10 <- $qThreadStopInfo12efb91#f5
11 -> $T00thread:12efb90;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
12 <- $qThreadStopInfo12efb47#f6
13 -> $T00thread:12efb91;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
14 <- $qThreadStopInfo12efb8d#27
15 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
16 <- $p0;thread:12efb8e;#f5
17 -> $T00thread:12efb8d;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
response (3) is interpreted as the response to the vCont request at (1). We
then make a qThreadStopInfo request (4) and receive a stop packet in
response (5). Packet (5) is interpreted as the response to (4) but it
actually isn't, note how the thread ID is different, packet (5) is actually
a spurious stop packet sent by debug server. From response (5) onward the
protocol is desynchronized, none of the response we process are actually the
response to the preceding request.
This eventually causes a failure at packet (17) which debugserver sent as
the response to request (14) but we interpret as the response to (16).
Fixes #3013
2022-06-08 17:56:50 +00:00
|
|
|
if sp.threadID != threadID {
|
|
|
|
// When we send a ^C (manual stop request) and the process is close to
|
|
|
|
// stopping anyway, sometimes, debugserver will send back two stop
|
|
|
|
// packets. We need to ignore this spurious stop packet. Because the first
|
|
|
|
// thing we do after the stop is updateThreadList, which calls this
|
|
|
|
// function, this is relatively painless. We simply need to check that the
|
|
|
|
// stop packet we receive is for the thread we requested, if it isn't we
|
|
|
|
// can assume it is the spurious extra stop packet and simply ignore it.
|
|
|
|
// An example of a problematic interaction is in the commit message for
|
|
|
|
// this change.
|
|
|
|
// See https://github.com/go-delve/delve/issues/3013.
|
|
|
|
|
|
|
|
conn.conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
|
|
|
|
resp, err = conn.recv(conn.outbuf.Bytes(), "thread stop info", false)
|
|
|
|
conn.conn.SetReadDeadline(time.Time{})
|
|
|
|
if err != nil {
|
|
|
|
if neterr, isneterr := err.(net.Error); isneterr && neterr.Timeout() {
|
|
|
|
return stopPacket{}, fmt.Errorf("qThreadStopInfo mismatch, requested %s got %s", sp.threadID, threadID)
|
|
|
|
}
|
|
|
|
return stopPacket{}, err
|
|
|
|
}
|
|
|
|
_, sp, err = conn.parseStopPacket(resp, "", nil)
|
|
|
|
if err != nil {
|
|
|
|
return stopPacket{}, err
|
|
|
|
}
|
|
|
|
if sp.threadID != threadID {
|
|
|
|
return stopPacket{}, fmt.Errorf("qThreadStopInfo mismatch, requested %s got %s", sp.threadID, threadID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-04 21:45:05 +00:00
|
|
|
return sp, nil
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2017-05-05 22:17:52 +00:00
|
|
|
// restart executes a 'vRun' command.
|
|
|
|
func (conn *gdbConn) restart(pos string) error {
|
|
|
|
conn.outbuf.Reset()
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, "$vRun;")
|
2017-05-05 22:17:52 +00:00
|
|
|
if pos != "" {
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, ";")
|
2017-05-05 22:17:52 +00:00
|
|
|
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()
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, "$qRRCmd")
|
2017-05-05 22:17:52 +00:00
|
|
|
for _, arg := range args {
|
2017-06-29 18:15:59 +00:00
|
|
|
fmt.Fprint(&conn.outbuf, ":")
|
2017-05-05 22:17:52 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-08-14 18:20:34 +00:00
|
|
|
type imageList struct {
|
|
|
|
Images []imageDescription `json:"images"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type imageDescription struct {
|
2021-01-04 16:52:04 +00:00
|
|
|
LoadAddress uint64 `json:"load_address"`
|
|
|
|
Pathname string `json:"pathname"`
|
|
|
|
MachHeader machHeader `json:"mach_header"`
|
2017-08-14 18:20:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-01-29 21:39:33 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
// exec executes a message to the stub and reads a response.
|
|
|
|
// The details of the wire protocol are described here:
|
proc/gdbserver: ignore spurious stop packet from debugserver (#3021)
When we send an interrupt request to debugserver we, sometimes, get one
extra spurious stop packet back.
This stop packet gets interpreted as a response to a qThreadStopInfo request
we make causing the protocol to become desynchronized for a while, until
eventually some kind of error appears.
Here's an example of this problem, distilled from issue #3013:
1 <- $vCont;c#a8
2 <- interrupt
3 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
4 <- $qThreadStopInfo12efb8e#28
5 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
6 <- $qThreadStopInfo12efb8f#29
7 -> $T00thread:12efb8e;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
8 <- $qThreadStopInfo12efb90#f4
9 -> $T00thread:12efb8f;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
10 <- $qThreadStopInfo12efb91#f5
11 -> $T00thread:12efb90;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
12 <- $qThreadStopInfo12efb47#f6
13 -> $T00thread:12efb91;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
14 <- $qThreadStopInfo12efb8d#27
15 -> $T05thread:12efb47;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
16 <- $p0;thread:12efb8e;#f5
17 -> $T00thread:12efb8d;threads:12efb47,12efb8d,12efb8e,12efb8f,12efb90,12efb91;thread-pcs:10abe83,7ff81b20e2be,7ff81b20e3ea,...
response (3) is interpreted as the response to the vCont request at (1). We
then make a qThreadStopInfo request (4) and receive a stop packet in
response (5). Packet (5) is interpreted as the response to (4) but it
actually isn't, note how the thread ID is different, packet (5) is actually
a spurious stop packet sent by debug server. From response (5) onward the
protocol is desynchronized, none of the response we process are actually the
response to the preceding request.
This eventually causes a failure at packet (17) which debugserver sent as
the response to request (14) but we interpret as the response to (16).
Fixes #3013
2022-06-08 17:56:50 +00:00
|
|
|
//
|
|
|
|
// https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
|
2017-02-10 14:11:40 +00:00
|
|
|
func (conn *gdbConn) exec(cmd []byte, context string) ([]byte, error) {
|
|
|
|
if err := conn.send(cmd); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-08-14 18:20:34 +00:00
|
|
|
return conn.recv(cmd, context, false)
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-09 06:27:05 +00:00
|
|
|
const hexdigit = "0123456789abcdef"
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
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])
|
2017-02-10 14:11:40 +00:00
|
|
|
|
|
|
|
attempt := 0
|
|
|
|
for {
|
2018-03-31 16:03:27 +00:00
|
|
|
if logflags.GdbWire() {
|
|
|
|
if len(cmd) > gdbWireMaxLen {
|
2018-07-02 09:00:32 +00:00
|
|
|
conn.log.Debugf("<- %s...", string(cmd[:gdbWireMaxLen]))
|
2017-02-10 14:11:40 +00:00
|
|
|
} else {
|
2018-07-02 09:00:32 +00:00
|
|
|
conn.log.Debugf("<- %s", string(cmd))
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_, 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
|
|
|
|
}
|
|
|
|
|
2017-08-14 18:20:34 +00:00
|
|
|
func (conn *gdbConn) recv(cmd []byte, context string, binary bool) (resp []byte, err error) {
|
2017-02-10 14:11:40 +00:00
|
|
|
attempt := 0
|
|
|
|
for {
|
|
|
|
var err error
|
|
|
|
resp, err = conn.rdr.ReadBytes('#')
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// read checksum
|
2020-09-15 20:15:49 +00:00
|
|
|
_, err = io.ReadFull(conn.rdr, conn.inbuf[:2])
|
2017-02-10 14:11:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-31 16:03:27 +00:00
|
|
|
if logflags.GdbWire() {
|
2017-02-10 14:11:40 +00:00
|
|
|
out := resp
|
|
|
|
partial := false
|
2023-07-09 06:27:05 +00:00
|
|
|
if !binary {
|
|
|
|
if idx := bytes.Index(out, []byte{'\n'}); idx >= 0 {
|
|
|
|
out = resp[:idx]
|
|
|
|
partial = true
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
2018-03-31 16:03:27 +00:00
|
|
|
if len(out) > gdbWireMaxLen {
|
|
|
|
out = out[:gdbWireMaxLen]
|
2017-02-10 14:11:40 +00:00
|
|
|
partial = true
|
|
|
|
}
|
|
|
|
if !partial {
|
2020-09-15 20:15:49 +00:00
|
|
|
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]))
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
} else {
|
2020-09-15 20:15:49 +00:00
|
|
|
if binary {
|
|
|
|
conn.log.Debugf("-> %q...", string(out))
|
|
|
|
} else {
|
|
|
|
conn.log.Debugf("-> %s...", string(out))
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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('-')
|
|
|
|
}
|
|
|
|
|
2017-08-14 18:20:34 +00:00
|
|
|
if binary {
|
|
|
|
conn.inbuf, resp = binarywiredecode(resp, conn.inbuf)
|
|
|
|
} else {
|
|
|
|
conn.inbuf, resp = wiredecode(resp, conn.inbuf)
|
|
|
|
}
|
2017-02-10 14:11:40 +00:00
|
|
|
|
2020-09-15 20:15:49 +00:00
|
|
|
if len(resp) == 0 || (resp[0] == 'E' && !binary) || (resp[0] == 'E' && len(resp) == 3) {
|
2017-02-10 14:11:40 +00:00
|
|
|
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
|
|
|
|
}
|
2018-07-02 09:00:32 +00:00
|
|
|
conn.log.Debugf("-> %s", string(b))
|
2017-02-10 14:11:40 +00:00
|
|
|
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})
|
2018-07-02 09:00:32 +00:00
|
|
|
conn.log.Debugf("<- %s", string(c))
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
2017-08-14 18:20:34 +00:00
|
|
|
// escapeXor is the value mandated by the specification to escape characters
|
|
|
|
const escapeXor byte = 0x20
|
|
|
|
|
2017-02-10 14:11:40 +00:00
|
|
|
// 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 {
|
2017-08-14 18:20:34 +00:00
|
|
|
buf = append(buf, in[i+1]^escapeXor)
|
|
|
|
i++
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
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
|
2017-02-10 14:11:40 +00:00
|
|
|
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:]
|
|
|
|
}
|
|
|
|
|
2017-08-14 18:20:34 +00:00
|
|
|
// 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:]
|
|
|
|
}
|
|
|
|
|
2022-10-05 15:17:53 +00:00
|
|
|
// checksumok checks that checksum is a valid checksum for packet.
|
2017-02-10 14:11:40 +00:00
|
|
|
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
|
|
|
|
}
|
2021-01-04 16:52:04 +00:00
|
|
|
|
|
|
|
tgt8 := uint8(tgt)
|
|
|
|
|
|
|
|
return sum == tgt8
|
2017-02-10 14:11:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func checksum(packet []byte) (sum uint8) {
|
|
|
|
for i := 1; i < len(packet); i++ {
|
|
|
|
if packet[i] == '#' {
|
|
|
|
return sum
|
|
|
|
}
|
|
|
|
sum += packet[i]
|
|
|
|
}
|
|
|
|
return sum
|
|
|
|
}
|