Update vendors

This commit is contained in:
MZI 2018-10-13 21:45:53 +08:00
parent bf5480b2df
commit db6c162b03
No known key found for this signature in database
GPG Key ID: 97BE7A905A0DA326
451 changed files with 139580 additions and 42578 deletions

24
vendor/github.com/cosiner/argv/LICENSE generated vendored Normal file
View File

@ -0,0 +1,24 @@
The MIT License (MIT)
Copyright (c) 2017 aihui zhu
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

34
vendor/github.com/cosiner/argv/argv.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
// Package argv parse command line string into arguments array using the bash syntax.
package argv
import "strings"
// ParseEnv parsing environment variables as key/value pair.
//
// Item will be ignored if one of the key and value is empty.
func ParseEnv(env []string) map[string]string {
var m map[string]string
for _, e := range env {
secs := strings.SplitN(e, "=", 2)
if len(secs) == 2 {
key := strings.TrimSpace(secs[0])
val := strings.TrimSpace(secs[1])
if key == "" || val == "" {
continue
}
if m == nil {
m = make(map[string]string)
}
m[key] = val
}
}
return m
}
// Argv split cmdline string as array of argument array by the '|' character.
//
// The parsing rules is same as bash. The environment variable will be replaced
// and string surround by '`' will be passed to reverse quote parser.
func Argv(cmdline []rune, env map[string]string, reverseQuoteParser ReverseQuoteParser) ([][]string, error) {
return NewParser(NewScanner(cmdline, env), reverseQuoteParser).Parse()
}

79
vendor/github.com/cosiner/argv/cmd.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
package argv
import (
"bytes"
"errors"
"io"
"os/exec"
"strings"
)
// Run execute cmdline string and return the output
func Run(cmdline []rune, env map[string]string) ([]rune, error) {
args, err := Argv(cmdline, env, Run)
if err != nil {
return nil, err
}
cmds, err := Cmds(args)
if err != nil {
return nil, err
}
output := bytes.NewBuffer(make([]byte, 0, 1024))
err = Pipe(nil, output, cmds...)
str := output.String()
str = strings.TrimSpace(str)
return []rune(str), err
}
// Cmds generate exec.Cmd for each command.
func Cmds(args [][]string) ([]*exec.Cmd, error) {
var cmds []*exec.Cmd
for _, argv := range args {
if len(argv) == 0 {
return nil, errors.New("invalid cmd")
}
cmds = append(cmds, exec.Command(argv[0], argv[1:]...))
}
return cmds, nil
}
// Pipe pipe previous command's stdout to next command's stdin, if in or
// out is nil, it will be ignored.
func Pipe(in io.Reader, out io.Writer, cmds ...*exec.Cmd) error {
l := len(cmds)
if l == 0 {
return nil
}
var err error
for i := 1; i < l; i++ {
cmds[i].Stdin, err = cmds[i-1].StdoutPipe()
if err != nil {
break
}
}
if err != nil {
return err
}
if in != nil {
cmds[0].Stdin = in
}
if out != nil {
cmds[l-1].Stdout = out
}
for i := range cmds {
err = cmds[i].Start()
if err != nil {
return err
}
}
for i := range cmds {
err = cmds[i].Wait()
if err != nil {
return err
}
}
return nil
}

222
vendor/github.com/cosiner/argv/parser.go generated vendored Normal file
View File

@ -0,0 +1,222 @@
package argv
import "errors"
type (
// ReverseQuoteParser parse strings quoted by '`' and return it's result. Commonly,
// it should run it os command.
ReverseQuoteParser func([]rune, map[string]string) ([]rune, error)
// Parser take tokens from Scanner, and do syntax checking, and generate the splitted arguments array.
Parser struct {
s *Scanner
tokbuf []Token
r ReverseQuoteParser
sections [][]string
currSection []string
currStrValid bool
currStr []rune
}
)
// NewParser create a cmdline string parser.
func NewParser(s *Scanner, r ReverseQuoteParser) *Parser {
if r == nil {
r = func(r []rune, env map[string]string) ([]rune, error) {
return r, nil
}
}
return &Parser{
s: s,
r: r,
}
}
func (p *Parser) nextToken() (Token, error) {
if l := len(p.tokbuf); l > 0 {
tok := p.tokbuf[l-1]
p.tokbuf = p.tokbuf[:l-1]
return tok, nil
}
return p.s.Next()
}
var (
// ErrInvalidSyntax was reported if there is a syntax error in command line string.
ErrInvalidSyntax = errors.New("invalid syntax")
)
func (p *Parser) unreadToken(tok Token) {
p.tokbuf = append(p.tokbuf, tok)
}
// Parse split command line string into arguments array.
//
// EBNF:
// Cmdline = Section [ Pipe Cmdline ]
// Section = [Space] SpacedSection { SpacedSection }
// SpacedSection = MultipleUnit [Space]
// MultipleUnit = Unit {Unit}
// Unit = String | ReverseQuote
func (p *Parser) Parse() ([][]string, error) {
err := p.cmdline()
if err != nil {
return nil, err
}
return p.sections, nil
}
func (p *Parser) cmdline() error {
err := p.section()
if err != nil {
return err
}
p.endSection()
tok, err := p.nextToken()
if err != nil {
return err
}
if tok.Type == TokEOF {
return nil
}
if !p.accept(tok.Type, TokPipe) {
return ErrInvalidSyntax
}
return p.cmdline()
}
func (p *Parser) section() error {
leftSpace, err := p.optional(TokSpace)
if err != nil {
return err
}
var isFirst = true
for {
unit, err := p.spacedSection()
if isFirst {
isFirst = false
} else {
if err == ErrInvalidSyntax {
break
}
}
if err != nil {
return err
}
p.appendUnit(leftSpace, unit)
leftSpace = unit.rightSpace
}
return nil
}
type unit struct {
rightSpace bool
toks []Token
}
func (p *Parser) spacedSection() (u unit, err error) {
u.toks, err = p.multipleUnit()
if err != nil {
return
}
u.rightSpace, err = p.optional(TokSpace)
return
}
func (p *Parser) multipleUnit() ([]Token, error) {
var (
toks []Token
isFirst = true
)
for {
tok, err := p.unit()
if isFirst {
isFirst = false
} else {
if err == ErrInvalidSyntax {
break
}
}
if err != nil {
return nil, err
}
toks = append(toks, tok)
}
return toks, nil
}
func (p *Parser) unit() (Token, error) {
tok, err := p.nextToken()
if err != nil {
return tok, err
}
if p.accept(tok.Type, TokString, TokReversequote) {
return tok, nil
}
p.unreadToken(tok)
return tok, ErrInvalidSyntax
}
func (p *Parser) optional(typ TokenType) (bool, error) {
tok, err := p.nextToken()
if err != nil {
return false, err
}
var ok bool
if ok = p.accept(tok.Type, typ); !ok {
p.unreadToken(tok)
}
return ok, nil
}
func (p *Parser) accept(t TokenType, types ...TokenType) bool {
for _, typ := range types {
if t == typ {
return true
}
}
return false
}
func (p *Parser) appendUnit(leftSpace bool, u unit) error {
if leftSpace {
p.currStr = p.currStr[:0]
}
for _, tok := range u.toks {
if tok.Type == TokReversequote {
val, err := p.r(tok.Value, p.s.envs())
if err != nil {
return err
}
p.currStr = append(p.currStr, val...)
} else {
p.currStr = append(p.currStr, tok.Value...)
}
}
p.currStrValid = true
if u.rightSpace {
p.currSection = append(p.currSection, string(p.currStr))
p.currStr = p.currStr[:0]
p.currStrValid = false
}
return nil
}
func (p *Parser) endSection() {
if p.currStrValid {
p.currSection = append(p.currSection, string(p.currStr))
}
p.currStr = p.currStr[:0]
p.currStrValid = false
if len(p.currSection) > 0 {
p.sections = append(p.sections, p.currSection)
p.currSection = nil
}
}

282
vendor/github.com/cosiner/argv/scanner.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
package argv
import "unicode"
// Scanner is a cmdline string scanner.
//
// It split cmdline string to tokens: space, string, pipe, reverse quote string.
type Scanner struct {
env map[string]string
text []rune
rpos int
dollarBuf []rune
}
// NewScanner create a scanner and init it's internal states.
func NewScanner(text []rune, env map[string]string) *Scanner {
return &Scanner{
text: text,
env: env,
}
}
func (s *Scanner) envs() map[string]string {
return s.env
}
const _RuneEOF = 0
func (s *Scanner) nextRune() rune {
if s.rpos >= len(s.text) {
return _RuneEOF
}
r := s.text[s.rpos]
s.rpos++
return r
}
func (s *Scanner) unreadRune(r rune) {
if r != _RuneEOF {
s.rpos--
}
}
func (s *Scanner) isEscapeChars(r rune) (rune, bool) {
switch r {
case 'a':
return '\a', true
case 'b':
return '\b', true
case 'f':
return '\f', true
case 'n':
return '\n', true
case 'r':
return '\r', true
case 't':
return '\t', true
case 'v':
return '\v', true
case '\\':
return '\\', true
case '$':
return '$', true
}
return r, false
}
func (s *Scanner) endEnv(r rune) bool {
if r == '_' || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
return false
}
return true
}
// TokenType is the type of tokens recognized by the scanner.
type TokenType uint32
// Token is generated by the scanner with a type and value.
type Token struct {
Type TokenType
Value []rune
}
const (
// TokString for string, single quoted string and double quoted string
TokString TokenType = iota + 1
// TokPipe is the '|' character
TokPipe
// TokReversequote is reverse quoted string
TokReversequote
// TokSpace represent space character sequence
TokSpace
// TokEOF means the input end.
TokEOF
)
func (s *Scanner) getEnv(name string) string {
return s.env[name]
}
func (s *Scanner) specialVar(r rune) (string, bool) {
switch r {
case '0', '*', '#', '@', '?', '$':
v, has := s.env[string(r)]
return v, has
default:
return "", false
}
}
func (s *Scanner) checkDollarStart(tok *Token, r rune, from, switchTo uint8) uint8 {
state := from
nr := s.nextRune()
if val, has := s.specialVar(nr); has {
if val != "" {
tok.Value = append(tok.Value, []rune(val)...)
}
} else if s.endEnv(nr) {
tok.Value = append(tok.Value, r)
s.unreadRune(nr)
} else {
state = switchTo
s.dollarBuf = append(s.dollarBuf[:0], nr)
}
return state
}
func (s *Scanner) checkDollarEnd(tok *Token, r rune, from, switchTo uint8) uint8 {
var state = from
if s.endEnv(r) {
tok.Value = append(tok.Value, []rune(s.getEnv(string(s.dollarBuf)))...)
state = switchTo
s.unreadRune(r)
} else {
s.dollarBuf = append(s.dollarBuf, r)
}
return state
}
// Next return next token, if it reach the end, TOK_EOF will be returned.
//
// Error is returned for invalid syntax such as unpaired quotes.
func (s *Scanner) Next() (Token, error) {
const (
Initial = iota + 1
Space
ReverseQuote
String
StringDollar
StringQuoteSingle
StringQuoteDouble
StringQuoteDoubleDollar
)
var (
tok Token
state uint8 = Initial
)
s.dollarBuf = s.dollarBuf[:0]
for {
r := s.nextRune()
switch state {
case Initial:
switch {
case r == _RuneEOF:
tok.Type = TokEOF
return tok, nil
case r == '|':
tok.Type = TokPipe
return tok, nil
case r == '`':
state = ReverseQuote
case unicode.IsSpace(r):
state = Space
s.unreadRune(r)
default:
state = String
s.unreadRune(r)
}
case Space:
if r == _RuneEOF || !unicode.IsSpace(r) {
s.unreadRune(r)
tok.Type = TokSpace
return tok, nil
}
case ReverseQuote:
switch r {
case _RuneEOF:
return tok, ErrInvalidSyntax
case '`':
tok.Type = TokReversequote
return tok, nil
default:
tok.Value = append(tok.Value, r)
}
case String:
switch {
case r == _RuneEOF || r == '|' || r == '`' || unicode.IsSpace(r):
tok.Type = TokString
s.unreadRune(r)
return tok, nil
case r == '\'':
state = StringQuoteSingle
case r == '"':
state = StringQuoteDouble
case r == '\\':
nr := s.nextRune()
if nr == _RuneEOF {
return tok, ErrInvalidSyntax
}
tok.Value = append(tok.Value, nr)
case r == '$':
state = s.checkDollarStart(&tok, r, state, StringDollar)
default:
tok.Value = append(tok.Value, r)
}
case StringDollar:
state = s.checkDollarEnd(&tok, r, state, String)
case StringQuoteSingle:
switch r {
case _RuneEOF:
return tok, ErrInvalidSyntax
case '\'':
state = String
case '\\':
nr := s.nextRune()
if escape, ok := s.isEscapeChars(nr); ok {
tok.Value = append(tok.Value, escape)
} else {
tok.Value = append(tok.Value, r)
s.unreadRune(nr)
}
default:
tok.Value = append(tok.Value, r)
}
case StringQuoteDouble:
switch r {
case _RuneEOF:
return tok, ErrInvalidSyntax
case '"':
state = String
case '\\':
nr := s.nextRune()
if nr == _RuneEOF {
return tok, ErrInvalidSyntax
}
if escape, ok := s.isEscapeChars(nr); ok {
tok.Value = append(tok.Value, escape)
} else {
tok.Value = append(tok.Value, r)
s.unreadRune(nr)
}
case '$':
state = s.checkDollarStart(&tok, r, state, StringQuoteDoubleDollar)
default:
tok.Value = append(tok.Value, r)
}
case StringQuoteDoubleDollar:
state = s.checkDollarEnd(&tok, r, state, StringQuoteDouble)
}
}
}
// Scan is a utility function help split input text as tokens.
func Scan(text []rune, env map[string]string) ([]Token, error) {
s := NewScanner(text, env)
var tokens []Token
for {
tok, err := s.Next()
if err != nil {
return nil, err
}
tokens = append(tokens, tok)
if tok.Type == TokEOF {
break
}
}
return tokens, nil
}

View File

@ -1,122 +0,0 @@
package line
import (
"bytes"
"encoding/binary"
"github.com/derekparker/delve/dwarf/util"
)
type DebugLinePrologue struct {
UnitLength uint32
Version uint16
Length uint32
MinInstrLength uint8
InitialIsStmt uint8
LineBase int8
LineRange uint8
OpcodeBase uint8
StdOpLengths []uint8
}
type DebugLineInfo struct {
Prologue *DebugLinePrologue
IncludeDirs []string
FileNames []*FileEntry
Instructions []byte
Lookup map[string]*FileEntry
}
type FileEntry struct {
Name string
DirIdx uint64
LastModTime uint64
Length uint64
}
type DebugLines []*DebugLineInfo
func (d *DebugLines) GetLineInfo(name string) *DebugLineInfo {
// Find in which table file exists and return it.
for _, l := range *d {
if _, ok := l.Lookup[name]; ok {
return l
}
}
return nil
}
func Parse(data []byte) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
)
// We have to parse multiple file name tables here.
for buf.Len() > 0 {
dbl := new(DebugLineInfo)
dbl.Lookup = make(map[string]*FileEntry)
parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf)
parseFileEntries(dbl, buf)
// Instructions size calculation breakdown:
// - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length.
// - dbl.Prologue.Length is the length of the prologue not including unit length, version or prologue length itself.
// - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)).
dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6))
lines = append(lines, dbl)
}
return lines
}
func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
p := new(DebugLinePrologue)
p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4))
p.Version = binary.LittleEndian.Uint16(buf.Next(2))
p.Length = binary.LittleEndian.Uint32(buf.Next(4))
p.MinInstrLength = uint8(buf.Next(1)[0])
p.InitialIsStmt = uint8(buf.Next(1)[0])
p.LineBase = int8(buf.Next(1)[0])
p.LineRange = uint8(buf.Next(1)[0])
p.OpcodeBase = uint8(buf.Next(1)[0])
p.StdOpLengths = make([]uint8, p.OpcodeBase-1)
binary.Read(buf, binary.LittleEndian, &p.StdOpLengths)
dbl.Prologue = p
}
func parseIncludeDirs(info *DebugLineInfo, buf *bytes.Buffer) {
for {
str, _ := util.ParseString(buf)
if str == "" {
break
}
info.IncludeDirs = append(info.IncludeDirs, str)
}
}
func parseFileEntries(info *DebugLineInfo, buf *bytes.Buffer) {
for {
entry := new(FileEntry)
name, _ := util.ParseString(buf)
if name == "" {
break
}
entry.Name = name
entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf)
info.FileNames = append(info.FileNames, entry)
info.Lookup[name] = entry
}
}

View File

@ -1,252 +0,0 @@
package line
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/derekparker/delve/dwarf/util"
)
type Location struct {
File string
Line int
Address uint64
Delta int
}
type StateMachine struct {
dbl *DebugLineInfo
file string
line int
address uint64
column uint
isStmt bool
basicBlock bool
endSeq bool
lastWasStandard bool
lastDelta int
}
type opcodefn func(*StateMachine, *bytes.Buffer)
// Special opcodes
const (
DW_LNS_copy = 1
DW_LNS_advance_pc = 2
DW_LNS_advance_line = 3
DW_LNS_set_file = 4
DW_LNS_set_column = 5
DW_LNS_negate_stmt = 6
DW_LNS_set_basic_block = 7
DW_LNS_const_add_pc = 8
DW_LNS_fixed_advance_pc = 9
)
// Extended opcodes
const (
DW_LINE_end_sequence = 1
DW_LINE_set_address = 2
DW_LINE_define_file = 3
)
var standardopcodes = map[byte]opcodefn{
DW_LNS_copy: copyfn,
DW_LNS_advance_pc: advancepc,
DW_LNS_advance_line: advanceline,
DW_LNS_set_file: setfile,
DW_LNS_set_column: setcolumn,
DW_LNS_negate_stmt: negatestmt,
DW_LNS_set_basic_block: setbasicblock,
DW_LNS_const_add_pc: constaddpc,
DW_LNS_fixed_advance_pc: fixedadvancepc,
}
var extendedopcodes = map[byte]opcodefn{
DW_LINE_end_sequence: endsequence,
DW_LINE_set_address: setaddress,
DW_LINE_define_file: definefile,
}
func newStateMachine(dbl *DebugLineInfo) *StateMachine {
return &StateMachine{dbl: dbl, file: dbl.FileNames[0].Name, line: 1}
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs.
func (dbl *DebugLines) AllPCsForFileLine(f string, l int) (pcs []uint64) {
var (
foundFile bool
lastAddr uint64
lineInfo = dbl.GetLineInfo(f)
sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
)
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if foundFile && sm.file != f {
return
}
if sm.line == l && sm.file == f && sm.address != lastAddr {
foundFile = true
pcs = append(pcs, sm.address)
line := sm.line
// Keep going until we're on a different line. We only care about
// when a line comes back around (i.e. for loop) so get to next line,
// and try to find the line we care about again.
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if line < sm.line {
break
}
}
}
}
return
}
var NoSourceError = errors.New("no source available")
func (dbl *DebugLines) AllPCsBetween(begin, end uint64, filename string) ([]uint64, error) {
lineInfo := dbl.GetLineInfo(filename)
if lineInfo == nil {
return nil, NoSourceError
}
var (
pcs []uint64
lastaddr uint64
sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
)
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if sm.address > end {
break
}
if sm.address >= begin && sm.address > lastaddr {
lastaddr = sm.address
pcs = append(pcs, sm.address)
}
}
return pcs, nil
}
func findAndExecOpcode(sm *StateMachine, buf *bytes.Buffer, b byte) {
switch {
case b == 0:
execExtendedOpcode(sm, b, buf)
case b < sm.dbl.Prologue.OpcodeBase:
execStandardOpcode(sm, b, buf)
default:
execSpecialOpcode(sm, b)
}
}
func execSpecialOpcode(sm *StateMachine, instr byte) {
var (
opcode = uint8(instr)
decoded = opcode - sm.dbl.Prologue.OpcodeBase
)
if sm.dbl.Prologue.InitialIsStmt == uint8(1) {
sm.isStmt = true
}
sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange))
sm.line += sm.lastDelta
sm.address += uint64(decoded / sm.dbl.Prologue.LineRange)
sm.basicBlock = false
sm.lastWasStandard = false
}
func execExtendedOpcode(sm *StateMachine, instr byte, buf *bytes.Buffer) {
_, _ = util.DecodeULEB128(buf)
b, _ := buf.ReadByte()
fn, ok := extendedopcodes[b]
if !ok {
panic(fmt.Sprintf("Encountered unknown extended opcode %#v\n", b))
}
sm.lastWasStandard = false
fn(sm, buf)
}
func execStandardOpcode(sm *StateMachine, instr byte, buf *bytes.Buffer) {
fn, ok := standardopcodes[instr]
if !ok {
panic(fmt.Sprintf("Encountered unknown standard opcode %#v\n", instr))
}
sm.lastWasStandard = true
fn(sm, buf)
}
func copyfn(sm *StateMachine, buf *bytes.Buffer) {
sm.basicBlock = false
}
func advancepc(sm *StateMachine, buf *bytes.Buffer) {
addr, _ := util.DecodeULEB128(buf)
sm.address += addr * uint64(sm.dbl.Prologue.MinInstrLength)
}
func advanceline(sm *StateMachine, buf *bytes.Buffer) {
line, _ := util.DecodeSLEB128(buf)
sm.line += int(line)
sm.lastDelta = int(line)
}
func setfile(sm *StateMachine, buf *bytes.Buffer) {
i, _ := util.DecodeULEB128(buf)
sm.file = sm.dbl.FileNames[i-1].Name
}
func setcolumn(sm *StateMachine, buf *bytes.Buffer) {
c, _ := util.DecodeULEB128(buf)
sm.column = uint(c)
}
func negatestmt(sm *StateMachine, buf *bytes.Buffer) {
sm.isStmt = !sm.isStmt
}
func setbasicblock(sm *StateMachine, buf *bytes.Buffer) {
sm.basicBlock = true
}
func constaddpc(sm *StateMachine, buf *bytes.Buffer) {
sm.address += (255 / uint64(sm.dbl.Prologue.LineRange))
}
func fixedadvancepc(sm *StateMachine, buf *bytes.Buffer) {
var operand uint16
binary.Read(buf, binary.LittleEndian, &operand)
sm.address += uint64(operand)
}
func endsequence(sm *StateMachine, buf *bytes.Buffer) {
sm.endSeq = true
}
func setaddress(sm *StateMachine, buf *bytes.Buffer) {
var addr uint64
binary.Read(buf, binary.LittleEndian, &addr)
sm.address = addr
}
func definefile(sm *StateMachine, buf *bytes.Buffer) {
var (
_, _ = util.ParseString(buf)
_, _ = util.DecodeULEB128(buf)
_, _ = util.DecodeULEB128(buf)
_, _ = util.DecodeULEB128(buf)
)
// Don't do anything here yet.
}

View File

@ -1,84 +0,0 @@
package op
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/derekparker/delve/dwarf/util"
)
const (
DW_OP_addr = 0x3
DW_OP_call_frame_cfa = 0x9c
DW_OP_plus = 0x22
DW_OP_consts = 0x11
DW_OP_plus_uconsts = 0x23
)
type stackfn func(*bytes.Buffer, []int64, int64) ([]int64, error)
var oplut = map[byte]stackfn{
DW_OP_call_frame_cfa: callframecfa,
DW_OP_plus: plus,
DW_OP_consts: consts,
DW_OP_addr: addr,
DW_OP_plus_uconsts: plusuconsts,
}
func ExecuteStackProgram(cfa int64, instructions []byte) (int64, error) {
stack := make([]int64, 0, 3)
buf := bytes.NewBuffer(instructions)
for opcode, err := buf.ReadByte(); err == nil; opcode, err = buf.ReadByte() {
fn, ok := oplut[opcode]
if !ok {
return 0, fmt.Errorf("invalid instruction %#v", opcode)
}
stack, err = fn(buf, stack, cfa)
if err != nil {
return 0, err
}
}
if len(stack) == 0 {
return 0, errors.New("empty OP stack")
}
return stack[len(stack)-1], nil
}
func callframecfa(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
if cfa == 0 {
return stack, fmt.Errorf("Could not retrieve CFA for current PC")
}
return append(stack, int64(cfa)), nil
}
func addr(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
return append(stack, int64(binary.LittleEndian.Uint64(buf.Next(8)))), nil
}
func plus(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
var (
slen = len(stack)
digits = stack[slen-2 : slen]
st = stack[:slen-2]
)
return append(st, digits[0]+digits[1]), nil
}
func plusuconsts(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
slen := len(stack)
num, _ := util.DecodeULEB128(buf)
stack[slen-1] = stack[slen-1] + int64(num)
return stack, nil
}
func consts(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
num, _ := util.DecodeSLEB128(buf)
return append(stack, num), nil
}

View File

@ -7,7 +7,7 @@ import (
"os/user" "os/user"
"path" "path"
yaml "gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
const ( const (
@ -20,7 +20,7 @@ type SubstitutePathRule struct {
// Directory path will be substituted if it matches `From`. // Directory path will be substituted if it matches `From`.
From string From string
// Path to which substitution is performed. // Path to which substitution is performed.
To string To string
} }
// Slice of source code path substitution rules. // Slice of source code path substitution rules.
@ -29,9 +29,24 @@ type SubstitutePathRules []SubstitutePathRule
// Config defines all configuration options available to be set through the config file. // Config defines all configuration options available to be set through the config file.
type Config struct { type Config struct {
// Commands aliases. // Commands aliases.
Aliases map[string][]string Aliases map[string][]string `yaml:"aliases"`
// Source code path substitution rules. // Source code path substitution rules.
SubstitutePath SubstitutePathRules `yaml:"substitute-path"` SubstitutePath SubstitutePathRules `yaml:"substitute-path"`
// MaxStringLen is the maximum string length that the commands print,
// locals, args and vars should read (in verbose mode).
MaxStringLen *int `yaml:"max-string-len,omitempty"`
// MaxArrayValues is the maximum number of array items that the commands
// print, locals, args and vars should read (in verbose mode).
MaxArrayValues *int `yaml:"max-array-values,omitempty"`
// If ShowLocationExpr is true whatis will print the DWARF location
// expression for its argument.
ShowLocationExpr bool `yaml:"show-location-expr"`
// Source list line-number color (3/4 bit color codes as defined
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
SourceListLineColor int `yaml:"source-list-line-color"`
} }
// LoadConfig attempts to populate a Config object from the config.yml file. // LoadConfig attempts to populate a Config object from the config.yml file.
@ -49,8 +64,11 @@ func LoadConfig() *Config {
f, err := os.Open(fullConfigFile) f, err := os.Open(fullConfigFile)
if err != nil { if err != nil {
createDefaultConfig(fullConfigFile) f, err = createDefaultConfig(fullConfigFile)
return nil if err != nil {
fmt.Printf("Error creating default config file: %v", err)
return nil
}
} }
defer func() { defer func() {
err := f.Close() err := f.Close()
@ -75,22 +93,37 @@ func LoadConfig() *Config {
return &c return &c
} }
func createDefaultConfig(path string) { func SaveConfig(conf *Config) error {
fullConfigFile, err := GetConfigFilePath(configFile)
if err != nil {
return err
}
out, err := yaml.Marshal(*conf)
if err != nil {
return err
}
f, err := os.Create(fullConfigFile)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(out)
return err
}
func createDefaultConfig(path string) (*os.File, error) {
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {
fmt.Printf("Unable to create config file: %v.", err) return nil, fmt.Errorf("Unable to create config file: %v.", err)
return
} }
defer func() {
err := f.Close()
if err != nil {
fmt.Printf("Closing config file failed: %v.", err)
}
}()
err = writeDefaultConfig(f) err = writeDefaultConfig(f)
if err != nil { if err != nil {
fmt.Printf("Unable to write default configuration: %v.", err) return nil, fmt.Errorf("Unable to write default configuration: %v.", err)
} }
return f, nil
} }
func writeDefaultConfig(f *os.File) error { func writeDefaultConfig(f *os.File) error {
@ -100,6 +133,11 @@ func writeDefaultConfig(f *os.File) error {
# This is the default configuration file. Available options are provided, but disabled. # This is the default configuration file. Available options are provided, but disabled.
# Delete the leading hash mark to enable an item. # Delete the leading hash mark to enable an item.
# Uncomment the following line and set your preferred ANSI foreground color
# for source line numbers in the (list) command (if unset, default is 34,
# dark blue) See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
# source-list-line-color: 34
# Provided aliases will be added to the default aliases for a given command. # Provided aliases will be added to the default aliases for a given command.
aliases: aliases:
# command: ["alias1", "alias2"] # command: ["alias1", "alias2"]
@ -111,6 +149,15 @@ aliases:
# commands. # commands.
substitute-path: substitute-path:
# - {from: path, to: path} # - {from: path, to: path}
# Maximum number of elements loaded from an array.
# max-array-values: 64
# Maximum loaded string length.
# max-string-len: 64
# Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
# show-location-expr: true
`) `)
return err return err
} }

View File

@ -0,0 +1,63 @@
package config
import (
"bytes"
"unicode"
)
// Like strings.Fields but ignores spaces inside areas surrounded
// by the specified quote character.
// To specify a single quote use backslash to escape it: '\''
func SplitQuotedFields(in string, quote rune) []string {
type stateEnum int
const (
inSpace stateEnum = iota
inField
inQuote
inQuoteEscaped
)
state := inSpace
r := []string{}
var buf bytes.Buffer
for _, ch := range in {
switch state {
case inSpace:
if ch == quote {
state = inQuote
} else if !unicode.IsSpace(ch) {
buf.WriteRune(ch)
state = inField
}
case inField:
if ch == quote {
state = inQuote
} else if unicode.IsSpace(ch) {
r = append(r, buf.String())
buf.Reset()
} else {
buf.WriteRune(ch)
}
case inQuote:
if ch == quote {
state = inField
} else if ch == '\\' {
state = inQuoteEscaped
} else {
buf.WriteRune(ch)
}
case inQuoteEscaped:
buf.WriteRune(ch)
state = inQuote
}
}
if buf.Len() != 0 {
r = append(r, buf.String())
}
return r
}

View File

@ -17,6 +17,7 @@ type CommonInformationEntry struct {
DataAlignmentFactor int64 DataAlignmentFactor int64
ReturnAddressRegister uint64 ReturnAddressRegister uint64
InitialInstructions []byte InitialInstructions []byte
staticBase uint64
} }
// Represents a Frame Descriptor Entry in the // Represents a Frame Descriptor Entry in the
@ -25,17 +26,14 @@ type FrameDescriptionEntry struct {
Length uint32 Length uint32
CIE *CommonInformationEntry CIE *CommonInformationEntry
Instructions []byte Instructions []byte
begin, end uint64 begin, size uint64
order binary.ByteOrder order binary.ByteOrder
} }
// Returns whether or not the given address is within the // Returns whether or not the given address is within the
// bounds of this frame. // bounds of this frame.
func (fde *FrameDescriptionEntry) Cover(addr uint64) bool { func (fde *FrameDescriptionEntry) Cover(addr uint64) bool {
if (addr - fde.begin) < fde.end { return (addr - fde.begin) < fde.size
return true
}
return false
} }
// Address of first location for this frame. // Address of first location for this frame.
@ -45,7 +43,7 @@ func (fde *FrameDescriptionEntry) Begin() uint64 {
// Address of last location for this frame. // Address of last location for this frame.
func (fde *FrameDescriptionEntry) End() uint64 { func (fde *FrameDescriptionEntry) End() uint64 {
return fde.begin + fde.end return fde.begin + fde.size
} }
// Set up frame for the given PC. // Set up frame for the given PC.
@ -53,43 +51,27 @@ func (fde *FrameDescriptionEntry) EstablishFrame(pc uint64) *FrameContext {
return executeDwarfProgramUntilPC(fde, pc) return executeDwarfProgramUntilPC(fde, pc)
} }
// Return the offset from the current SP that the return address is stored at.
func (fde *FrameDescriptionEntry) ReturnAddressOffset(pc uint64) (frameOffset, returnAddressOffset int64) {
frame := fde.EstablishFrame(pc)
return frame.cfa.offset, frame.regs[fde.CIE.ReturnAddressRegister].offset
}
type FrameDescriptionEntries []*FrameDescriptionEntry type FrameDescriptionEntries []*FrameDescriptionEntry
func NewFrameIndex() FrameDescriptionEntries { func NewFrameIndex() FrameDescriptionEntries {
return make(FrameDescriptionEntries, 0, 1000) return make(FrameDescriptionEntries, 0, 1000)
} }
type NoFDEForPCError struct { type ErrNoFDEForPC struct {
PC uint64 PC uint64
} }
func (err *NoFDEForPCError) Error() string { func (err *ErrNoFDEForPC) Error() string {
return fmt.Sprintf("could not find FDE for PC %#v", err.PC) return fmt.Sprintf("could not find FDE for PC %#v", err.PC)
} }
// Returns the Frame Description Entry for the given PC. // Returns the Frame Description Entry for the given PC.
func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) { func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) {
idx := sort.Search(len(fdes), func(i int) bool { idx := sort.Search(len(fdes), func(i int) bool {
if fdes[i].Cover(pc) { return fdes[i].Cover(pc) || fdes[i].Begin() >= pc
return true
}
if fdes[i].LessThan(pc) {
return false
}
return true
}) })
if idx == len(fdes) { if idx == len(fdes) || !fdes[idx].Cover(pc) {
return nil, &NoFDEForPCError{pc} return nil, &ErrNoFDEForPC{pc}
} }
return fdes[idx], nil return fdes[idx], nil
} }
func (frame *FrameDescriptionEntry) LessThan(pc uint64) bool {
return frame.End() <= pc
}

View File

@ -7,12 +7,14 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"github.com/derekparker/delve/dwarf/util" "github.com/derekparker/delve/pkg/dwarf/util"
) )
type parsefunc func(*parseContext) parsefunc type parsefunc func(*parseContext) parsefunc
type parseContext struct { type parseContext struct {
staticBase uint64
buf *bytes.Buffer buf *bytes.Buffer
entries FrameDescriptionEntries entries FrameDescriptionEntries
common *CommonInformationEntry common *CommonInformationEntry
@ -23,10 +25,10 @@ type parseContext struct {
// Parse takes in data (a byte slice) and returns a slice of // Parse takes in data (a byte slice) and returns a slice of
// commonInformationEntry structures. Each commonInformationEntry // commonInformationEntry structures. Each commonInformationEntry
// has a slice of frameDescriptionEntry structures. // has a slice of frameDescriptionEntry structures.
func Parse(data []byte, order binary.ByteOrder) FrameDescriptionEntries { func Parse(data []byte, order binary.ByteOrder, staticBase uint64) FrameDescriptionEntries {
var ( var (
buf = bytes.NewBuffer(data) buf = bytes.NewBuffer(data)
pctx = &parseContext{buf: buf, entries: NewFrameIndex()} pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase}
) )
for fn := parselength; buf.Len() != 0; { for fn := parselength; buf.Len() != 0; {
@ -45,12 +47,19 @@ func cieEntry(data []byte) bool {
} }
func parselength(ctx *parseContext) parsefunc { func parselength(ctx *parseContext) parsefunc {
var data = ctx.buf.Next(8) binary.Read(ctx.buf, binary.LittleEndian, &ctx.length)
ctx.length = binary.LittleEndian.Uint32(data[:4]) - 4 // take off the length of the CIE id / CIE pointer. if ctx.length == 0 {
// ZERO terminator
return parselength
}
if cieEntry(data[4:]) { var data = ctx.buf.Next(4)
ctx.common = &CommonInformationEntry{Length: ctx.length}
ctx.length -= 4 // take off the length of the CIE id / CIE pointer.
if cieEntry(data) {
ctx.common = &CommonInformationEntry{Length: ctx.length, staticBase: ctx.staticBase}
return parseCIE return parseCIE
} }
@ -61,8 +70,8 @@ func parselength(ctx *parseContext) parsefunc {
func parseFDE(ctx *parseContext) parsefunc { func parseFDE(ctx *parseContext) parsefunc {
r := ctx.buf.Next(int(ctx.length)) r := ctx.buf.Next(int(ctx.length))
ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) + ctx.staticBase
ctx.frame.end = binary.LittleEndian.Uint64(r[8:16]) ctx.frame.size = binary.LittleEndian.Uint64(r[8:16])
// Insert into the tree after setting address range begin // Insert into the tree after setting address range begin
// otherwise compares won't work. // otherwise compares won't work.

View File

@ -5,41 +5,31 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/derekparker/delve/dwarf/util" "github.com/derekparker/delve/pkg/dwarf/util"
) )
type CurrentFrameAddress struct {
register uint64
offset int64
expression []byte
rule byte
}
type DWRule struct { type DWRule struct {
rule byte Rule Rule
offset int64 Offset int64
newreg uint64 Reg uint64
expression []byte Expression []byte
} }
type FrameContext struct { type FrameContext struct {
loc uint64 loc uint64
order binary.ByteOrder order binary.ByteOrder
address uint64 address uint64
cfa CurrentFrameAddress CFA DWRule
regs map[uint64]DWRule Regs map[uint64]DWRule
initialRegs map[uint64]DWRule initialRegs map[uint64]DWRule
prevRegs map[uint64]DWRule prevRegs map[uint64]DWRule
buf *bytes.Buffer buf *bytes.Buffer
cie *CommonInformationEntry cie *CommonInformationEntry
RetAddrReg uint64
codeAlignment uint64 codeAlignment uint64
dataAlignment int64 dataAlignment int64
} }
func (fctx *FrameContext) CFAOffset() int64 {
return fctx.cfa.offset
}
// Instructions used to recreate the table from the .debug_frame data. // Instructions used to recreate the table from the .debug_frame data.
const ( const (
DW_CFA_nop = 0x0 // No ops DW_CFA_nop = 0x0 // No ops
@ -73,15 +63,19 @@ const (
) )
// Rules defined for register values. // Rules defined for register values.
type Rule byte
const ( const (
rule_undefined = iota RuleUndefined Rule = iota
rule_sameval RuleSameVal
rule_offset RuleOffset
rule_valoffset RuleValOffset
rule_register RuleRegister
rule_expression RuleExpression
rule_valexpression RuleValExpression
rule_architectural RuleArchitectural
RuleCFA // Value is rule.Reg + rule.Offset
RuleFramePointer // Value is stored at address rule.Reg + rule.Offset, but only if it's less than the current CFA, otherwise same value
) )
const low_6_offset = 0x3f const low_6_offset = 0x3f
@ -124,7 +118,8 @@ func executeCIEInstructions(cie *CommonInformationEntry) *FrameContext {
copy(initialInstructions, cie.InitialInstructions) copy(initialInstructions, cie.InitialInstructions)
frame := &FrameContext{ frame := &FrameContext{
cie: cie, cie: cie,
regs: make(map[uint64]DWRule), Regs: make(map[uint64]DWRule),
RetAddrReg: cie.ReturnAddressRegister,
initialRegs: make(map[uint64]DWRule), initialRegs: make(map[uint64]DWRule),
prevRegs: make(map[uint64]DWRule), prevRegs: make(map[uint64]DWRule),
codeAlignment: cie.CodeAlignmentFactor, codeAlignment: cie.CodeAlignmentFactor,
@ -142,9 +137,7 @@ func executeDwarfProgramUntilPC(fde *FrameDescriptionEntry, pc uint64) *FrameCon
frame.order = fde.order frame.order = fde.order
frame.loc = fde.Begin() frame.loc = fde.Begin()
frame.address = pc frame.address = pc
fdeInstructions := make([]byte, len(fde.Instructions)) frame.ExecuteUntilPC(fde.Instructions)
copy(fdeInstructions, fde.Instructions)
frame.ExecuteUntilPC(fdeInstructions)
return frame return frame
} }
@ -161,7 +154,7 @@ func (frame *FrameContext) ExecuteUntilPC(instructions []byte) {
frame.buf.Write(instructions) frame.buf.Write(instructions)
// We only need to execute the instructions until // We only need to execute the instructions until
// ctx.loc > ctx.addess (which is the address we // ctx.loc > ctx.address (which is the address we
// are currently at in the traced process). // are currently at in the traced process).
for frame.address >= frame.loc && frame.buf.Len() > 0 { for frame.address >= frame.loc && frame.buf.Len() > 0 {
executeDwarfInstruction(frame) executeDwarfInstruction(frame)
@ -262,7 +255,7 @@ func offset(frame *FrameContext) {
offset, _ = util.DecodeULEB128(frame.buf) offset, _ = util.DecodeULEB128(frame.buf)
) )
frame.regs[uint64(reg)] = DWRule{offset: int64(offset) * frame.dataAlignment, rule: rule_offset} frame.Regs[uint64(reg)] = DWRule{Offset: int64(offset) * frame.dataAlignment, Rule: RuleOffset}
} }
func restore(frame *FrameContext) { func restore(frame *FrameContext) {
@ -274,9 +267,9 @@ func restore(frame *FrameContext) {
reg := uint64(b & low_6_offset) reg := uint64(b & low_6_offset)
oldrule, ok := frame.initialRegs[reg] oldrule, ok := frame.initialRegs[reg]
if ok { if ok {
frame.regs[reg] = DWRule{offset: oldrule.offset, rule: rule_offset} frame.Regs[reg] = DWRule{Offset: oldrule.Offset, Rule: RuleOffset}
} else { } else {
frame.regs[reg] = DWRule{rule: rule_undefined} frame.Regs[reg] = DWRule{Rule: RuleUndefined}
} }
} }
@ -284,7 +277,7 @@ func setloc(frame *FrameContext) {
var loc uint64 var loc uint64
binary.Read(frame.buf, frame.order, &loc) binary.Read(frame.buf, frame.order, &loc)
frame.loc = loc frame.loc = loc + frame.cie.staticBase
} }
func offsetextended(frame *FrameContext) { func offsetextended(frame *FrameContext) {
@ -293,31 +286,31 @@ func offsetextended(frame *FrameContext) {
offset, _ = util.DecodeULEB128(frame.buf) offset, _ = util.DecodeULEB128(frame.buf)
) )
frame.regs[reg] = DWRule{offset: int64(offset) * frame.dataAlignment, rule: rule_offset} frame.Regs[reg] = DWRule{Offset: int64(offset) * frame.dataAlignment, Rule: RuleOffset}
} }
func undefined(frame *FrameContext) { func undefined(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf) reg, _ := util.DecodeULEB128(frame.buf)
frame.regs[reg] = DWRule{rule: rule_undefined} frame.Regs[reg] = DWRule{Rule: RuleUndefined}
} }
func samevalue(frame *FrameContext) { func samevalue(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf) reg, _ := util.DecodeULEB128(frame.buf)
frame.regs[reg] = DWRule{rule: rule_sameval} frame.Regs[reg] = DWRule{Rule: RuleSameVal}
} }
func register(frame *FrameContext) { func register(frame *FrameContext) {
reg1, _ := util.DecodeULEB128(frame.buf) reg1, _ := util.DecodeULEB128(frame.buf)
reg2, _ := util.DecodeULEB128(frame.buf) reg2, _ := util.DecodeULEB128(frame.buf)
frame.regs[reg1] = DWRule{newreg: reg2, rule: rule_register} frame.Regs[reg1] = DWRule{Reg: reg2, Rule: RuleRegister}
} }
func rememberstate(frame *FrameContext) { func rememberstate(frame *FrameContext) {
frame.prevRegs = frame.regs frame.prevRegs = frame.Regs
} }
func restorestate(frame *FrameContext) { func restorestate(frame *FrameContext) {
frame.regs = frame.prevRegs frame.Regs = frame.prevRegs
} }
func restoreextended(frame *FrameContext) { func restoreextended(frame *FrameContext) {
@ -325,9 +318,9 @@ func restoreextended(frame *FrameContext) {
oldrule, ok := frame.initialRegs[reg] oldrule, ok := frame.initialRegs[reg]
if ok { if ok {
frame.regs[reg] = DWRule{offset: oldrule.offset, rule: rule_offset} frame.Regs[reg] = DWRule{Offset: oldrule.Offset, Rule: RuleOffset}
} else { } else {
frame.regs[reg] = DWRule{rule: rule_undefined} frame.Regs[reg] = DWRule{Rule: RuleUndefined}
} }
} }
@ -335,32 +328,34 @@ func defcfa(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf) reg, _ := util.DecodeULEB128(frame.buf)
offset, _ := util.DecodeULEB128(frame.buf) offset, _ := util.DecodeULEB128(frame.buf)
frame.cfa.register = reg frame.CFA.Rule = RuleCFA
frame.cfa.offset = int64(offset) frame.CFA.Reg = reg
frame.CFA.Offset = int64(offset)
} }
func defcfaregister(frame *FrameContext) { func defcfaregister(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf) reg, _ := util.DecodeULEB128(frame.buf)
frame.cfa.register = reg frame.CFA.Reg = reg
} }
func defcfaoffset(frame *FrameContext) { func defcfaoffset(frame *FrameContext) {
offset, _ := util.DecodeULEB128(frame.buf) offset, _ := util.DecodeULEB128(frame.buf)
frame.cfa.offset = int64(offset) frame.CFA.Offset = int64(offset)
} }
func defcfasf(frame *FrameContext) { func defcfasf(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf) reg, _ := util.DecodeULEB128(frame.buf)
offset, _ := util.DecodeSLEB128(frame.buf) offset, _ := util.DecodeSLEB128(frame.buf)
frame.cfa.register = reg frame.CFA.Rule = RuleCFA
frame.cfa.offset = offset * frame.dataAlignment frame.CFA.Reg = reg
frame.CFA.Offset = offset * frame.dataAlignment
} }
func defcfaoffsetsf(frame *FrameContext) { func defcfaoffsetsf(frame *FrameContext) {
offset, _ := util.DecodeSLEB128(frame.buf) offset, _ := util.DecodeSLEB128(frame.buf)
offset *= frame.dataAlignment offset *= frame.dataAlignment
frame.cfa.offset = offset frame.CFA.Offset = offset
} }
func defcfaexpression(frame *FrameContext) { func defcfaexpression(frame *FrameContext) {
@ -369,8 +364,8 @@ func defcfaexpression(frame *FrameContext) {
expr = frame.buf.Next(int(l)) expr = frame.buf.Next(int(l))
) )
frame.cfa.expression = expr frame.CFA.Expression = expr
frame.cfa.rule = rule_expression frame.CFA.Rule = RuleExpression
} }
func expression(frame *FrameContext) { func expression(frame *FrameContext) {
@ -380,7 +375,7 @@ func expression(frame *FrameContext) {
expr = frame.buf.Next(int(l)) expr = frame.buf.Next(int(l))
) )
frame.regs[reg] = DWRule{rule: rule_expression, expression: expr} frame.Regs[reg] = DWRule{Rule: RuleExpression, Expression: expr}
} }
func offsetextendedsf(frame *FrameContext) { func offsetextendedsf(frame *FrameContext) {
@ -389,7 +384,7 @@ func offsetextendedsf(frame *FrameContext) {
offset, _ = util.DecodeSLEB128(frame.buf) offset, _ = util.DecodeSLEB128(frame.buf)
) )
frame.regs[reg] = DWRule{offset: offset * frame.dataAlignment, rule: rule_offset} frame.Regs[reg] = DWRule{Offset: offset * frame.dataAlignment, Rule: RuleOffset}
} }
func valoffset(frame *FrameContext) { func valoffset(frame *FrameContext) {
@ -398,7 +393,7 @@ func valoffset(frame *FrameContext) {
offset, _ = util.DecodeULEB128(frame.buf) offset, _ = util.DecodeULEB128(frame.buf)
) )
frame.regs[reg] = DWRule{offset: int64(offset), rule: rule_valoffset} frame.Regs[reg] = DWRule{Offset: int64(offset), Rule: RuleValOffset}
} }
func valoffsetsf(frame *FrameContext) { func valoffsetsf(frame *FrameContext) {
@ -407,7 +402,7 @@ func valoffsetsf(frame *FrameContext) {
offset, _ = util.DecodeSLEB128(frame.buf) offset, _ = util.DecodeSLEB128(frame.buf)
) )
frame.regs[reg] = DWRule{offset: offset * frame.dataAlignment, rule: rule_valoffset} frame.Regs[reg] = DWRule{Offset: offset * frame.dataAlignment, Rule: RuleValOffset}
} }
func valexpression(frame *FrameContext) { func valexpression(frame *FrameContext) {
@ -417,7 +412,7 @@ func valexpression(frame *FrameContext) {
expr = frame.buf.Next(int(l)) expr = frame.buf.Next(int(l))
) )
frame.regs[reg] = DWRule{rule: rule_valexpression, expression: expr} frame.Regs[reg] = DWRule{Rule: RuleValExpression, Expression: expr}
} }
func louser(frame *FrameContext) { func louser(frame *FrameContext) {

View File

@ -0,0 +1,107 @@
package godwarf
import (
"bytes"
"compress/zlib"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"fmt"
"io"
)
// GetDebugSectionElf returns the data contents of the specified debug
// section, decompressing it if it is compressed.
// For example GetDebugSectionElf("line") will return the contents of
// .debug_line, if .debug_line doesn't exist it will try to return the
// decompressed contents of .zdebug_line.
func GetDebugSectionElf(f *elf.File, name string) ([]byte, error) {
sec := f.Section(".debug_" + name)
if sec != nil {
return sec.Data()
}
sec = f.Section(".zdebug_" + name)
if sec == nil {
return nil, fmt.Errorf("could not find .debug_%s section", name)
}
b, err := sec.Data()
if err != nil {
return nil, err
}
return decompressMaybe(b)
}
// GetDebugSectionPE returns the data contents of the specified debug
// section, decompressing it if it is compressed.
// For example GetDebugSectionPE("line") will return the contents of
// .debug_line, if .debug_line doesn't exist it will try to return the
// decompressed contents of .zdebug_line.
func GetDebugSectionPE(f *pe.File, name string) ([]byte, error) {
sec := f.Section(".debug_" + name)
if sec != nil {
return peSectionData(sec)
}
sec = f.Section(".zdebug_" + name)
if sec == nil {
return nil, fmt.Errorf("could not find .debug_%s section", name)
}
b, err := peSectionData(sec)
if err != nil {
return nil, err
}
return decompressMaybe(b)
}
func peSectionData(sec *pe.Section) ([]byte, error) {
b, err := sec.Data()
if err != nil {
return nil, err
}
if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size {
b = b[:sec.VirtualSize]
}
return b, nil
}
// GetDebugSectionMacho returns the data contents of the specified debug
// section, decompressing it if it is compressed.
// For example GetDebugSectionMacho("line") will return the contents of
// __debug_line, if __debug_line doesn't exist it will try to return the
// decompressed contents of __zdebug_line.
func GetDebugSectionMacho(f *macho.File, name string) ([]byte, error) {
sec := f.Section("__debug_" + name)
if sec != nil {
return sec.Data()
}
sec = f.Section("__zdebug_" + name)
if sec == nil {
return nil, fmt.Errorf("could not find .debug_%s section", name)
}
b, err := sec.Data()
if err != nil {
return nil, err
}
return decompressMaybe(b)
}
func decompressMaybe(b []byte) ([]byte, error) {
if len(b) < 12 || string(b[:4]) != "ZLIB" {
// not compressed
return b, nil
}
dlen := binary.BigEndian.Uint64(b[4:12])
dbuf := make([]byte, dlen)
r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
if err != nil {
return nil, err
}
if _, err := io.ReadFull(r, dbuf); err != nil {
return nil, err
}
if err := r.Close(); err != nil {
return nil, err
}
return dbuf, nil
}

View File

@ -6,16 +6,44 @@
// The format is heavily biased toward C, but for simplicity // The format is heavily biased toward C, but for simplicity
// the String methods use a pseudo-Go syntax. // the String methods use a pseudo-Go syntax.
package dwarf // Borrowed from golang.org/x/debug/dwarf/type.go
package godwarf
import ( import (
"debug/dwarf"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"github.com/derekparker/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/util"
)
const (
AttrGoKind dwarf.Attr = 0x2900
AttrGoKey dwarf.Attr = 0x2901
AttrGoElem dwarf.Attr = 0x2902
AttrGoEmbeddedField dwarf.Attr = 0x2903
AttrGoRuntimeType dwarf.Attr = 0x2904
)
// Basic type encodings -- the value for AttrEncoding in a TagBaseType Entry.
const (
encAddress = 0x01
encBoolean = 0x02
encComplexFloat = 0x03
encFloat = 0x04
encSigned = 0x05
encSignedChar = 0x06
encUnsigned = 0x07
encUnsignedChar = 0x08
encImaginaryFloat = 0x09
) )
// A Type conventionally represents a pointer to any of the // A Type conventionally represents a pointer to any of the
// specific Type structures (CharType, StructType, etc.). // specific Type structures (CharType, StructType, etc.).
//TODO: remove this use dwarf.Type
type Type interface { type Type interface {
Common() *CommonType Common() *CommonType
String() string String() string
@ -29,7 +57,7 @@ type CommonType struct {
ByteSize int64 // size of value of this type, in bytes ByteSize int64 // size of value of this type, in bytes
Name string // name that can be used to refer to type Name string // name that can be used to refer to type
ReflectKind reflect.Kind // the reflect kind of the type. ReflectKind reflect.Kind // the reflect kind of the type.
Offset Offset // the offset at which this type was read Offset dwarf.Offset // the offset at which this type was read
} }
func (c *CommonType) Common() *CommonType { return c } func (c *CommonType) Common() *CommonType { return c }
@ -158,6 +186,7 @@ type StructField struct {
ByteSize int64 ByteSize int64
BitOffset int64 // within the ByteSize bytes at ByteOffset BitOffset int64 // within the ByteSize bytes at ByteOffset
BitSize int64 // zero if not a bit field BitSize int64 // zero if not a bit field
Embedded bool
} }
func (t *StructType) String() string { func (t *StructType) String() string {
@ -329,31 +358,19 @@ func (t *ChanType) String() string {
return "chan " + t.ElemType.String() return "chan " + t.ElemType.String()
} }
// typeReader is used to read from either the info section or the
// types section.
type typeReader interface {
Seek(Offset)
Next() (*Entry, error)
clone() typeReader
offset() Offset
// AddressSize returns the size in bytes of addresses in the current
// compilation unit.
AddressSize() int
}
// Type reads the type at off in the DWARF ``info'' section. // Type reads the type at off in the DWARF ``info'' section.
func (d *Data) Type(off Offset) (Type, error) { func ReadType(d *dwarf.Data, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) {
return d.readType("info", d.Reader(), off, d.typeCache) return readType(d, "info", d.Reader(), off, typeCache)
} }
func getKind(e *Entry) reflect.Kind { func getKind(e *dwarf.Entry) reflect.Kind {
integer, _ := e.Val(AttrGoKind).(int64) integer, _ := e.Val(AttrGoKind).(int64)
return reflect.Kind(integer) return reflect.Kind(integer)
} }
// readType reads a type from r at off of name using and updating a // readType reads a type from r at off of name using and updating a
// type cache. // type cache.
func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Offset]Type) (Type, error) { func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) {
if t, ok := typeCache[off]; ok { if t, ok := typeCache[off]; ok {
return t, nil return t, nil
} }
@ -364,10 +381,10 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
addressSize := r.AddressSize() addressSize := r.AddressSize()
if e == nil || e.Offset != off { if e == nil || e.Offset != off {
return nil, DecodeError{name, off, "no type at offset"} return nil, dwarf.DecodeError{name, off, "no type at offset"}
} }
// Parse type from Entry. // Parse type from dwarf.Entry.
// Must always set typeCache[off] before calling // Must always set typeCache[off] before calling
// d.Type recursively, to handle circular types correctly. // d.Type recursively, to handle circular types correctly.
var typ Type var typ Type
@ -375,7 +392,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
nextDepth := 0 nextDepth := 0
// Get next child; set err if error happens. // Get next child; set err if error happens.
next := func() *Entry { next := func() *dwarf.Entry {
if !e.Children { if !e.Children {
return nil return nil
} }
@ -390,10 +407,6 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
err = err1 err = err1
return nil return nil
} }
if kid == nil {
err = DecodeError{name, r.offset(), "unexpected end of DWARF entries"}
return nil
}
if kid.Tag == 0 { if kid.Tag == 0 {
if nextDepth > 0 { if nextDepth > 0 {
nextDepth-- nextDepth--
@ -411,20 +424,19 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
} }
// Get Type referred to by Entry's attr. // Get Type referred to by dwarf.Entry's attr.
// Set err if error happens. Not having a type is an error. // Set err if error happens. Not having a type is an error.
typeOf := func(e *Entry, attr Attr) Type { typeOf := func(e *dwarf.Entry, attr dwarf.Attr) Type {
tval := e.Val(attr) tval := e.Val(attr)
var t Type var t Type
switch toff := tval.(type) { switch toff := tval.(type) {
case Offset: case dwarf.Offset:
if t, err = d.readType(name, r.clone(), toff, typeCache); err != nil { if t, err = readType(d, name, d.Reader(), toff, typeCache); err != nil {
return nil return nil
} }
case uint64: case uint64:
if t, err = d.sigToType(toff); err != nil { err = dwarf.DecodeError{name, e.Offset, "DWARFv4 section debug_types unsupported"}
return nil return nil
}
default: default:
// It appears that no Type means "void". // It appears that no Type means "void".
return new(VoidType) return new(VoidType)
@ -433,7 +445,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
switch e.Tag { switch e.Tag {
case TagArrayType: case dwarf.TagArrayType:
// Multi-dimensional array. (DWARF v2 §5.4) // Multi-dimensional array. (DWARF v2 §5.4)
// Attributes: // Attributes:
// AttrType:subtype [required] // AttrType:subtype [required]
@ -444,16 +456,16 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
// TagSubrangeType or TagEnumerationType giving one dimension. // TagSubrangeType or TagEnumerationType giving one dimension.
// dimensions are in left to right order. // dimensions are in left to right order.
t := new(ArrayType) t := new(ArrayType)
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.ReflectKind = getKind(e) t.ReflectKind = getKind(e)
typ = t typ = t
typeCache[off] = t typeCache[off] = t
if t.Type = typeOf(e, AttrType); err != nil { if t.Type = typeOf(e, dwarf.AttrType); err != nil {
goto Error goto Error
} }
if bytes, ok := e.Val(AttrStride).(int64); ok { if bytes, ok := e.Val(dwarf.AttrStride).(int64); ok {
t.StrideBitSize = 8 * bytes t.StrideBitSize = 8 * bytes
} else if bits, ok := e.Val(AttrStrideSize).(int64); ok { } else if bits, ok := e.Val(dwarf.AttrStrideSize).(int64); ok {
t.StrideBitSize = bits t.StrideBitSize = bits
} else { } else {
// If there's no stride specified, assume it's the size of the // If there's no stride specified, assume it's the size of the
@ -467,11 +479,11 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
// TODO(rsc): Can also be TagEnumerationType // TODO(rsc): Can also be TagEnumerationType
// but haven't seen that in the wild yet. // but haven't seen that in the wild yet.
switch kid.Tag { switch kid.Tag {
case TagSubrangeType: case dwarf.TagSubrangeType:
count, ok := kid.Val(AttrCount).(int64) count, ok := kid.Val(dwarf.AttrCount).(int64)
if !ok { if !ok {
// Old binaries may have an upper bound instead. // Old binaries may have an upper bound instead.
count, ok = kid.Val(AttrUpperBound).(int64) count, ok = kid.Val(dwarf.AttrUpperBound).(int64)
if ok { if ok {
count++ // Length is one more than upper bound. count++ // Length is one more than upper bound.
} else { } else {
@ -486,8 +498,8 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
t.Type = &ArrayType{Type: t.Type, Count: count} t.Type = &ArrayType{Type: t.Type, Count: count}
} }
ndim++ ndim++
case TagEnumerationType: case dwarf.TagEnumerationType:
err = DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"} err = dwarf.DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}
goto Error goto Error
} }
} }
@ -496,7 +508,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
t.Count = -1 t.Count = -1
} }
case TagBaseType: case dwarf.TagBaseType:
// Basic type. (DWARF v2 §5.1) // Basic type. (DWARF v2 §5.1)
// Attributes: // Attributes:
// AttrName: name of base type in programming language of the compilation unit [required] // AttrName: name of base type in programming language of the compilation unit [required]
@ -504,15 +516,15 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
// AttrByteSize: size of type in bytes [required] // AttrByteSize: size of type in bytes [required]
// AttrBitOffset: for sub-byte types, size in bits // AttrBitOffset: for sub-byte types, size in bits
// AttrBitSize: for sub-byte types, bit offset of high order bit in the AttrByteSize bytes // AttrBitSize: for sub-byte types, bit offset of high order bit in the AttrByteSize bytes
name, _ := e.Val(AttrName).(string) name, _ := e.Val(dwarf.AttrName).(string)
enc, ok := e.Val(AttrEncoding).(int64) enc, ok := e.Val(dwarf.AttrEncoding).(int64)
if !ok { if !ok {
err = DecodeError{name, e.Offset, "missing encoding attribute for " + name} err = dwarf.DecodeError{name, e.Offset, "missing encoding attribute for " + name}
goto Error goto Error
} }
switch enc { switch enc {
default: default:
err = DecodeError{name, e.Offset, "unrecognized encoding attribute value"} err = dwarf.DecodeError{name, e.Offset, "unrecognized encoding attribute value"}
goto Error goto Error
case encAddress: case encAddress:
@ -525,7 +537,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
// clang writes out 'complex' instead of 'complex float' or 'complex double'. // clang writes out 'complex' instead of 'complex float' or 'complex double'.
// clang also writes out a byte size that we can use to distinguish. // clang also writes out a byte size that we can use to distinguish.
// See issue 8694. // See issue 8694.
switch byteSize, _ := e.Val(AttrByteSize).(int64); byteSize { switch byteSize, _ := e.Val(dwarf.AttrByteSize).(int64); byteSize {
case 8: case 8:
name = "complex float" name = "complex float"
case 16: case 16:
@ -548,11 +560,11 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
Basic() *BasicType Basic() *BasicType
}).Basic() }).Basic()
t.Name = name t.Name = name
t.BitSize, _ = e.Val(AttrBitSize).(int64) t.BitSize, _ = e.Val(dwarf.AttrBitSize).(int64)
t.BitOffset, _ = e.Val(AttrBitOffset).(int64) t.BitOffset, _ = e.Val(dwarf.AttrBitOffset).(int64)
t.ReflectKind = getKind(e) t.ReflectKind = getKind(e)
case TagClassType, TagStructType, TagUnionType: case dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType:
// Structure, union, or class type. (DWARF v2 §5.5) // Structure, union, or class type. (DWARF v2 §5.5)
// Also Slices and Strings (Go-specific). // Also Slices and Strings (Go-specific).
// Attributes: // Attributes:
@ -586,26 +598,26 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
typeCache[off] = typ typeCache[off] = typ
switch e.Tag { switch e.Tag {
case TagClassType: case dwarf.TagClassType:
t.Kind = "class" t.Kind = "class"
case TagStructType: case dwarf.TagStructType:
t.Kind = "struct" t.Kind = "struct"
case TagUnionType: case dwarf.TagUnionType:
t.Kind = "union" t.Kind = "union"
} }
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.StructName, _ = e.Val(AttrName).(string) t.StructName, _ = e.Val(dwarf.AttrName).(string)
t.Incomplete = e.Val(AttrDeclaration) != nil t.Incomplete = e.Val(dwarf.AttrDeclaration) != nil
t.Field = make([]*StructField, 0, 8) t.Field = make([]*StructField, 0, 8)
var lastFieldType Type var lastFieldType Type
var lastFieldBitOffset int64 var lastFieldBitOffset int64
for kid := next(); kid != nil; kid = next() { for kid := next(); kid != nil; kid = next() {
if kid.Tag == TagMember { if kid.Tag == dwarf.TagMember {
f := new(StructField) f := new(StructField)
if f.Type = typeOf(kid, AttrType); err != nil { if f.Type = typeOf(kid, dwarf.AttrType); err != nil {
goto Error goto Error
} }
switch loc := kid.Val(AttrDataMemberLoc).(type) { switch loc := kid.Val(dwarf.AttrDataMemberLoc).(type) {
case []byte: case []byte:
// TODO: Should have original compilation // TODO: Should have original compilation
// unit here, not unknownFormat. // unit here, not unknownFormat.
@ -613,28 +625,28 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
// Empty exprloc. f.ByteOffset=0. // Empty exprloc. f.ByteOffset=0.
break break
} }
b := makeBuf(d, unknownFormat{}, "location", 0, loc) b := util.MakeBuf(d, util.UnknownFormat{}, "location", 0, loc)
op := b.uint8() op_ := op.Opcode(b.Uint8())
switch op { switch op_ {
case opPlusUconst: case op.DW_OP_plus_uconst:
// Handle opcode sequence [DW_OP_plus_uconst <uleb128>] // Handle opcode sequence [DW_OP_plus_uconst <uleb128>]
f.ByteOffset = int64(b.uint()) f.ByteOffset = int64(b.Uint())
b.assertEmpty() b.AssertEmpty()
case opConsts: case op.DW_OP_consts:
// Handle opcode sequence [DW_OP_consts <sleb128> DW_OP_plus] // Handle opcode sequence [DW_OP_consts <sleb128> DW_OP_plus]
f.ByteOffset = b.int() f.ByteOffset = b.Int()
op = b.uint8() op_ = op.Opcode(b.Uint8())
if op != opPlus { if op_ != op.DW_OP_plus {
err = DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op)} err = dwarf.DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op_)}
goto Error goto Error
} }
b.assertEmpty() b.AssertEmpty()
default: default:
err = DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op)} err = dwarf.DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op_)}
goto Error goto Error
} }
if b.err != nil { if b.Err != nil {
err = b.err err = b.Err
goto Error goto Error
} }
case int64: case int64:
@ -642,10 +654,11 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
haveBitOffset := false haveBitOffset := false
f.Name, _ = kid.Val(AttrName).(string) f.Name, _ = kid.Val(dwarf.AttrName).(string)
f.ByteSize, _ = kid.Val(AttrByteSize).(int64) f.ByteSize, _ = kid.Val(dwarf.AttrByteSize).(int64)
f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64) f.BitOffset, haveBitOffset = kid.Val(dwarf.AttrBitOffset).(int64)
f.BitSize, _ = kid.Val(AttrBitSize).(int64) f.BitSize, _ = kid.Val(dwarf.AttrBitSize).(int64)
f.Embedded, _ = kid.Val(AttrGoEmbeddedField).(bool)
t.Field = append(t.Field, f) t.Field = append(t.Field, f)
bito := f.BitOffset bito := f.BitOffset
@ -662,35 +675,35 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
} }
if t.Kind != "union" { if t.Kind != "union" {
b, ok := e.Val(AttrByteSize).(int64) b, ok := e.Val(dwarf.AttrByteSize).(int64)
if ok && b*8 == lastFieldBitOffset { if ok && b*8 == lastFieldBitOffset {
// Final field must be zero width. Fix array length. // Final field must be zero width. Fix array length.
zeroArray(lastFieldType) zeroArray(lastFieldType)
} }
} }
case TagConstType, TagVolatileType, TagRestrictType: case dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType:
// Type modifier (DWARF v2 §5.2) // Type modifier (DWARF v2 §5.2)
// Attributes: // Attributes:
// AttrType: subtype // AttrType: subtype
t := new(QualType) t := new(QualType)
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.ReflectKind = getKind(e) t.ReflectKind = getKind(e)
typ = t typ = t
typeCache[off] = t typeCache[off] = t
if t.Type = typeOf(e, AttrType); err != nil { if t.Type = typeOf(e, dwarf.AttrType); err != nil {
goto Error goto Error
} }
switch e.Tag { switch e.Tag {
case TagConstType: case dwarf.TagConstType:
t.Qual = "const" t.Qual = "const"
case TagRestrictType: case dwarf.TagRestrictType:
t.Qual = "restrict" t.Qual = "restrict"
case TagVolatileType: case dwarf.TagVolatileType:
t.Qual = "volatile" t.Qual = "volatile"
} }
case TagEnumerationType: case dwarf.TagEnumerationType:
// Enumeration type (DWARF v2 §5.6) // Enumeration type (DWARF v2 §5.6)
// Attributes: // Attributes:
// AttrName: enum name if any // AttrName: enum name if any
@ -703,14 +716,14 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
t.ReflectKind = getKind(e) t.ReflectKind = getKind(e)
typ = t typ = t
typeCache[off] = t typeCache[off] = t
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.EnumName, _ = e.Val(AttrName).(string) t.EnumName, _ = e.Val(dwarf.AttrName).(string)
t.Val = make([]*EnumValue, 0, 8) t.Val = make([]*EnumValue, 0, 8)
for kid := next(); kid != nil; kid = next() { for kid := next(); kid != nil; kid = next() {
if kid.Tag == TagEnumerator { if kid.Tag == dwarf.TagEnumerator {
f := new(EnumValue) f := new(EnumValue)
f.Name, _ = kid.Val(AttrName).(string) f.Name, _ = kid.Val(dwarf.AttrName).(string)
f.Val, _ = kid.Val(AttrConstValue).(int64) f.Val, _ = kid.Val(dwarf.AttrConstValue).(int64)
n := len(t.Val) n := len(t.Val)
if n >= cap(t.Val) { if n >= cap(t.Val) {
val := make([]*EnumValue, n, n*2) val := make([]*EnumValue, n, n*2)
@ -722,23 +735,23 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
} }
} }
case TagPointerType: case dwarf.TagPointerType:
// Type modifier (DWARF v2 §5.2) // Type modifier (DWARF v2 §5.2)
// Attributes: // Attributes:
// AttrType: subtype [not required! void* has no AttrType] // AttrType: subtype [not required! void* has no AttrType]
// AttrAddrClass: address class [ignored] // AttrAddrClass: address class [ignored]
t := new(PtrType) t := new(PtrType)
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.ReflectKind = getKind(e) t.ReflectKind = getKind(e)
typ = t typ = t
typeCache[off] = t typeCache[off] = t
if e.Val(AttrType) == nil { if e.Val(dwarf.AttrType) == nil {
t.Type = &VoidType{} t.Type = &VoidType{}
break break
} }
t.Type = typeOf(e, AttrType) t.Type = typeOf(e, dwarf.AttrType)
case TagSubroutineType: case dwarf.TagSubroutineType:
// Subroutine type. (DWARF v2 §5.7) // Subroutine type. (DWARF v2 §5.7)
// Attributes: // Attributes:
// AttrType: type of return value if any // AttrType: type of return value if any
@ -749,11 +762,11 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
// AttrType: type of parameter // AttrType: type of parameter
// TagUnspecifiedParameter: final ... // TagUnspecifiedParameter: final ...
t := new(FuncType) t := new(FuncType)
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.ReflectKind = getKind(e) t.ReflectKind = getKind(e)
typ = t typ = t
typeCache[off] = t typeCache[off] = t
if t.ReturnType = typeOf(e, AttrType); err != nil { if t.ReturnType = typeOf(e, dwarf.AttrType); err != nil {
goto Error goto Error
} }
t.ParamType = make([]Type, 0, 8) t.ParamType = make([]Type, 0, 8)
@ -762,17 +775,17 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
switch kid.Tag { switch kid.Tag {
default: default:
continue continue
case TagFormalParameter: case dwarf.TagFormalParameter:
if tkid = typeOf(kid, AttrType); err != nil { if tkid = typeOf(kid, dwarf.AttrType); err != nil {
goto Error goto Error
} }
case TagUnspecifiedParameters: case dwarf.TagUnspecifiedParameters:
tkid = &DotDotDotType{} tkid = &DotDotDotType{}
} }
t.ParamType = append(t.ParamType, tkid) t.ParamType = append(t.ParamType, tkid)
} }
case TagTypedef: case dwarf.TagTypedef:
// Typedef (DWARF v2 §5.3) // Typedef (DWARF v2 §5.3)
// Also maps and channels (Go-specific). // Also maps and channels (Go-specific).
// Attributes: // Attributes:
@ -802,17 +815,17 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
typ = t typ = t
} }
typeCache[off] = typ typeCache[off] = typ
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
t.Type = typeOf(e, AttrType) t.Type = typeOf(e, dwarf.AttrType)
case TagUnspecifiedType: case dwarf.TagUnspecifiedType:
// Unspecified type (DWARF v3 §5.2) // Unspecified type (DWARF v3 §5.2)
// Attributes: // Attributes:
// AttrName: name // AttrName: name
t := new(UnspecifiedType) t := new(UnspecifiedType)
typ = t typ = t
typeCache[off] = t typeCache[off] = t
t.Name, _ = e.Val(AttrName).(string) t.Name, _ = e.Val(dwarf.AttrName).(string)
} }
if err != nil { if err != nil {
@ -822,7 +835,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
typ.Common().Offset = off typ.Common().Offset = off
{ {
b, ok := e.Val(AttrByteSize).(int64) b, ok := e.Val(dwarf.AttrByteSize).(int64)
if !ok { if !ok {
b = -1 b = -1
switch t := typ.(type) { switch t := typ.(type) {
@ -836,6 +849,9 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
b = t.Type.Size() b = t.Type.Size()
case *PtrType: case *PtrType:
b = int64(addressSize) b = int64(addressSize)
case *FuncType:
// on Go < 1.10 function types do not have a DW_AT_byte_size attribute.
b = int64(addressSize)
} }
} }
typ.Common().ByteSize = b typ.Common().ByteSize = b

View File

@ -0,0 +1,152 @@
package line
import (
"bytes"
"encoding/binary"
"path/filepath"
"github.com/derekparker/delve/pkg/dwarf/util"
)
type DebugLinePrologue struct {
UnitLength uint32
Version uint16
Length uint32
MinInstrLength uint8
InitialIsStmt uint8
LineBase int8
LineRange uint8
OpcodeBase uint8
StdOpLengths []uint8
}
type DebugLineInfo struct {
Prologue *DebugLinePrologue
IncludeDirs []string
FileNames []*FileEntry
Instructions []byte
Lookup map[string]*FileEntry
Logf func(string, ...interface{})
// stateMachineCache[pc] is a state machine stopped at pc
stateMachineCache map[uint64]*StateMachine
// lastMachineCache[pc] is a state machine stopped at an address after pc
lastMachineCache map[uint64]*StateMachine
// staticBase is the address at which the executable is loaded, 0 for non-PIEs
staticBase uint64
}
type FileEntry struct {
Path string
DirIdx uint64
LastModTime uint64
Length uint64
}
type DebugLines []*DebugLineInfo
// ParseAll parses all debug_line segments found in data
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
)
// We have to parse multiple file name tables here.
for buf.Len() > 0 {
lines = append(lines, Parse("", buf, logfn, staticBase))
}
return lines
}
// Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64) *DebugLineInfo {
dbl := new(DebugLineInfo)
dbl.Logf = logfn
dbl.staticBase = staticBase
dbl.Lookup = make(map[string]*FileEntry)
if compdir != "" {
dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
}
dbl.stateMachineCache = make(map[uint64]*StateMachine)
dbl.lastMachineCache = make(map[uint64]*StateMachine)
parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf)
parseFileEntries(dbl, buf)
// Instructions size calculation breakdown:
// - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length.
// - dbl.Prologue.Length is the length of the prologue not including unit length, version or prologue length itself.
// - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)).
dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6))
return dbl
}
func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
p := new(DebugLinePrologue)
p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4))
p.Version = binary.LittleEndian.Uint16(buf.Next(2))
p.Length = binary.LittleEndian.Uint32(buf.Next(4))
p.MinInstrLength = uint8(buf.Next(1)[0])
p.InitialIsStmt = uint8(buf.Next(1)[0])
p.LineBase = int8(buf.Next(1)[0])
p.LineRange = uint8(buf.Next(1)[0])
p.OpcodeBase = uint8(buf.Next(1)[0])
p.StdOpLengths = make([]uint8, p.OpcodeBase-1)
binary.Read(buf, binary.LittleEndian, &p.StdOpLengths)
dbl.Prologue = p
}
func parseIncludeDirs(info *DebugLineInfo, buf *bytes.Buffer) {
for {
str, _ := util.ParseString(buf)
if str == "" {
break
}
info.IncludeDirs = append(info.IncludeDirs, str)
}
}
func parseFileEntries(info *DebugLineInfo, buf *bytes.Buffer) {
for {
entry := readFileEntry(info, buf, true)
if entry.Path == "" {
break
}
info.FileNames = append(info.FileNames, entry)
info.Lookup[entry.Path] = entry
}
}
func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry {
entry := new(FileEntry)
entry.Path, _ = util.ParseString(buf)
if entry.Path == "" && exitOnEmptyPath {
return entry
}
entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf)
if !filepath.IsAbs(entry.Path) {
if entry.DirIdx >= 0 && entry.DirIdx < uint64(len(info.IncludeDirs)) {
entry.Path = filepath.Join(info.IncludeDirs[entry.DirIdx], entry.Path)
}
}
return entry
}

View File

@ -0,0 +1,450 @@
package line
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/derekparker/delve/pkg/dwarf/util"
)
type Location struct {
File string
Line int
Address uint64
Delta int
}
type StateMachine struct {
dbl *DebugLineInfo
file string
line int
address uint64
column uint
isStmt bool
basicBlock bool
endSeq bool
lastDelta int
prologueEnd bool
epilogueBegin bool
// valid is true if the current value of the state machine is the address of
// an instruction (using the terminology used by DWARF spec the current
// value of the state machine should be appended to the matrix representing
// the compilation unit)
valid bool
started bool
buf *bytes.Buffer // remaining instructions
opcodes []opcodefn
definedFiles []*FileEntry // files defined with DW_LINE_define_file
lastAddress uint64
lastFile string
lastLine int
}
type opcodeKind uint8
const (
specialOpcode opcodeKind = iota
standardOpcode
extendedOpcode
)
type opcodefn func(*StateMachine, *bytes.Buffer)
// Special opcodes
const (
DW_LNS_copy = 1
DW_LNS_advance_pc = 2
DW_LNS_advance_line = 3
DW_LNS_set_file = 4
DW_LNS_set_column = 5
DW_LNS_negate_stmt = 6
DW_LNS_set_basic_block = 7
DW_LNS_const_add_pc = 8
DW_LNS_fixed_advance_pc = 9
DW_LNS_prologue_end = 10
DW_LNS_epilogue_begin = 11
)
// Extended opcodes
const (
DW_LINE_end_sequence = 1
DW_LINE_set_address = 2
DW_LINE_define_file = 3
)
var standardopcodes = map[byte]opcodefn{
DW_LNS_copy: copyfn,
DW_LNS_advance_pc: advancepc,
DW_LNS_advance_line: advanceline,
DW_LNS_set_file: setfile,
DW_LNS_set_column: setcolumn,
DW_LNS_negate_stmt: negatestmt,
DW_LNS_set_basic_block: setbasicblock,
DW_LNS_const_add_pc: constaddpc,
DW_LNS_fixed_advance_pc: fixedadvancepc,
DW_LNS_prologue_end: prologueend,
DW_LNS_epilogue_begin: epiloguebegin,
}
var extendedopcodes = map[byte]opcodefn{
DW_LINE_end_sequence: endsequence,
DW_LINE_set_address: setaddress,
DW_LINE_define_file: definefile,
}
func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
opcodes := make([]opcodefn, len(standardopcodes)+1)
opcodes[0] = execExtendedOpcode
for op := range standardopcodes {
opcodes[op] = standardopcodes[op]
}
sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase}
return sm
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs.
func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) {
if lineInfo == nil {
return nil
}
var (
lastAddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
)
for {
if err := sm.next(); err != nil {
if lineInfo.Logf != nil {
lineInfo.Logf("AllPCsForFileLine error: %v", err)
}
break
}
if sm.line == l && sm.file == f && sm.address != lastAddr && sm.isStmt && sm.valid {
pcs = append(pcs, sm.address)
lastAddr = sm.address
}
}
return
}
var NoSourceError = errors.New("no source available")
// AllPCsBetween returns all PC addresses between begin and end (including both begin and end) that have the is_stmt flag set and do not belong to excludeFile:excludeLine
func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile string, excludeLine int) ([]uint64, error) {
if lineInfo == nil {
return nil, NoSourceError
}
var (
pcs []uint64
lastaddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
)
for {
if err := sm.next(); err != nil {
if lineInfo.Logf != nil {
lineInfo.Logf("AllPCsBetween error: %v", err)
}
break
}
if !sm.valid {
continue
}
if sm.address > end {
break
}
if (sm.address >= begin && sm.address > lastaddr) && sm.isStmt && ((sm.file != excludeFile) || (sm.line != excludeLine)) {
lastaddr = sm.address
pcs = append(pcs, sm.address)
}
}
return pcs, nil
}
// copy returns a copy of this state machine, running the returned state
// machine will not affect sm.
func (sm *StateMachine) copy() *StateMachine {
var r StateMachine
r = *sm
r.buf = bytes.NewBuffer(sm.buf.Bytes())
return &r
}
func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) {
sm = lineInfo.stateMachineCache[basePC]
if sm == nil {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm.PCToLine(basePC)
lineInfo.stateMachineCache[basePC] = sm
}
sm = sm.copy()
return
}
// PCToLine returns the filename and line number associated with pc.
// If pc isn't found inside lineInfo's table it will return the filename and
// line number associated with the closest PC address preceding pc.
// basePC will be used for caching, it's normally the entry point for the
// function containing pc.
func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
if lineInfo == nil {
return "", 0
}
if basePC > pc {
panic(fmt.Errorf("basePC after pc %#x %#x", basePC, pc))
}
var sm *StateMachine
if basePC == 0 {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
} else {
// Try to use the last state machine that we used for this function, if
// there isn't one or it's already past pc try to clone the cached state
// machine stopped at the entry point of the function.
// As a last resort start from the start of the debug_line section.
sm = lineInfo.lastMachineCache[basePC]
if sm == nil || sm.lastAddress > pc {
sm = lineInfo.stateMachineForEntry(basePC)
lineInfo.lastMachineCache[basePC] = sm
}
}
file, line, _ := sm.PCToLine(pc)
return file, line
}
func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
if !sm.started {
if err := sm.next(); err != nil {
if sm.dbl.Logf != nil {
sm.dbl.Logf("PCToLine error: %v", err)
}
return "", 0, false
}
}
if sm.lastAddress > pc {
return "", 0, false
}
for {
if sm.valid {
if sm.address > pc {
return sm.lastFile, sm.lastLine, true
}
if sm.address == pc {
return sm.file, sm.line, true
}
}
if err := sm.next(); err != nil {
if sm.dbl.Logf != nil {
sm.dbl.Logf("PCToLine error: %v", err)
}
break
}
}
if sm.valid {
return sm.file, sm.line, true
}
return "", 0, false
}
// LineToPC returns the first PC address associated with filename:lineno.
func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
if lineInfo == nil {
return 0
}
sm := newStateMachine(lineInfo, lineInfo.Instructions)
// if no instruction marked is_stmt is found fallback to the first
// instruction assigned to the filename:line.
var fallbackPC uint64
for {
if err := sm.next(); err != nil {
if lineInfo.Logf != nil && err != io.EOF {
lineInfo.Logf("LineToPC error: %v", err)
}
break
}
if sm.line == lineno && sm.file == filename && sm.valid {
if sm.isStmt {
return sm.address
} else if fallbackPC == 0 {
fallbackPC = sm.address
}
}
}
return fallbackPC
}
// PrologueEndPC returns the first PC address marked as prologue_end in the half open interval [start, end)
func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file string, line int, ok bool) {
sm := lineInfo.stateMachineForEntry(start)
for {
if sm.valid {
if sm.address >= end {
return 0, "", 0, false
}
if sm.prologueEnd {
return sm.address, sm.file, sm.line, true
}
}
if err := sm.next(); err != nil {
if lineInfo.Logf != nil {
lineInfo.Logf("PrologueEnd error: %v", err)
}
return 0, "", 0, false
}
}
}
func (sm *StateMachine) next() error {
sm.started = true
if sm.valid {
sm.lastAddress, sm.lastFile, sm.lastLine = sm.address, sm.file, sm.line
// valid is set by either a special opcode or a DW_LNS_copy, in both cases
// we need to reset basic_block, prologue_end and epilogue_begin
sm.basicBlock = false
sm.prologueEnd = false
sm.epilogueBegin = false
}
if sm.endSeq {
sm.endSeq = false
sm.file = sm.dbl.FileNames[0].Path
sm.line = 1
sm.column = 0
sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1)
sm.basicBlock = false
}
b, err := sm.buf.ReadByte()
if err != nil {
return err
}
if b < sm.dbl.Prologue.OpcodeBase {
if int(b) < len(sm.opcodes) {
sm.valid = false
sm.opcodes[b](sm, sm.buf)
} else {
// unimplemented standard opcode, read the number of arguments specified
// in the prologue and do nothing with them
opnum := sm.dbl.Prologue.StdOpLengths[b-1]
for i := 0; i < int(opnum); i++ {
util.DecodeSLEB128(sm.buf)
}
fmt.Printf("unknown opcode\n")
}
} else {
execSpecialOpcode(sm, b)
}
return nil
}
func execSpecialOpcode(sm *StateMachine, instr byte) {
var (
opcode = uint8(instr)
decoded = opcode - sm.dbl.Prologue.OpcodeBase
)
sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange))
sm.line += sm.lastDelta
sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength)
sm.valid = true
}
func execExtendedOpcode(sm *StateMachine, buf *bytes.Buffer) {
_, _ = util.DecodeULEB128(buf)
b, _ := buf.ReadByte()
if fn, ok := extendedopcodes[b]; ok {
fn(sm, buf)
}
}
func copyfn(sm *StateMachine, buf *bytes.Buffer) {
sm.valid = true
}
func advancepc(sm *StateMachine, buf *bytes.Buffer) {
addr, _ := util.DecodeULEB128(buf)
sm.address += addr * uint64(sm.dbl.Prologue.MinInstrLength)
}
func advanceline(sm *StateMachine, buf *bytes.Buffer) {
line, _ := util.DecodeSLEB128(buf)
sm.line += int(line)
sm.lastDelta = int(line)
}
func setfile(sm *StateMachine, buf *bytes.Buffer) {
i, _ := util.DecodeULEB128(buf)
if i-1 < uint64(len(sm.dbl.FileNames)) {
sm.file = sm.dbl.FileNames[i-1].Path
} else {
j := (i - 1) - uint64(len(sm.dbl.FileNames))
if j < uint64(len(sm.definedFiles)) {
sm.file = sm.definedFiles[j].Path
} else {
sm.file = ""
}
}
}
func setcolumn(sm *StateMachine, buf *bytes.Buffer) {
c, _ := util.DecodeULEB128(buf)
sm.column = uint(c)
}
func negatestmt(sm *StateMachine, buf *bytes.Buffer) {
sm.isStmt = !sm.isStmt
}
func setbasicblock(sm *StateMachine, buf *bytes.Buffer) {
sm.basicBlock = true
}
func constaddpc(sm *StateMachine, buf *bytes.Buffer) {
sm.address += uint64((255-sm.dbl.Prologue.OpcodeBase)/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength)
}
func fixedadvancepc(sm *StateMachine, buf *bytes.Buffer) {
var operand uint16
binary.Read(buf, binary.LittleEndian, &operand)
sm.address += uint64(operand)
}
func endsequence(sm *StateMachine, buf *bytes.Buffer) {
sm.endSeq = true
sm.valid = true
}
func setaddress(sm *StateMachine, buf *bytes.Buffer) {
var addr uint64
binary.Read(buf, binary.LittleEndian, &addr)
sm.address = addr + sm.dbl.staticBase
}
func definefile(sm *StateMachine, buf *bytes.Buffer) {
entry := readFileEntry(sm.dbl, sm.buf, false)
sm.definedFiles = append(sm.definedFiles, entry)
}
func prologueend(sm *StateMachine, buf *bytes.Buffer) {
sm.prologueEnd = true
}
func epiloguebegin(sm *StateMachine, buf *bytes.Buffer) {
sm.epilogueBegin = true
}

197
vendor/github.com/derekparker/delve/pkg/dwarf/op/op.go generated vendored Normal file
View File

@ -0,0 +1,197 @@
package op
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/derekparker/delve/pkg/dwarf/util"
)
type Opcode byte
//go:generate go run ../../../scripts/gen-opcodes.go opcodes.table opcodes.go
type stackfn func(Opcode, *context) error
type context struct {
buf *bytes.Buffer
stack []int64
pieces []Piece
reg bool
DwarfRegisters
}
// Piece is a piece of memory stored either at an address or in a register.
type Piece struct {
Size int
Addr int64
RegNum uint64
IsRegister bool
}
// ExecuteStackProgram executes a DWARF location expression and returns
// either an address (int64), or a slice of Pieces for location expressions
// that don't evaluate to an address (such as register and composite expressions).
func ExecuteStackProgram(regs DwarfRegisters, instructions []byte) (int64, []Piece, error) {
ctxt := &context{
buf: bytes.NewBuffer(instructions),
stack: make([]int64, 0, 3),
DwarfRegisters: regs,
}
for {
opcodeByte, err := ctxt.buf.ReadByte()
if err != nil {
break
}
opcode := Opcode(opcodeByte)
if ctxt.reg && opcode != DW_OP_piece {
break
}
fn, ok := oplut[opcode]
if !ok {
return 0, nil, fmt.Errorf("invalid instruction %#v", opcode)
}
err = fn(opcode, ctxt)
if err != nil {
return 0, nil, err
}
}
if ctxt.pieces != nil {
return 0, ctxt.pieces, nil
}
if len(ctxt.stack) == 0 {
return 0, nil, errors.New("empty OP stack")
}
return ctxt.stack[len(ctxt.stack)-1], nil, nil
}
// PrettyPrint prints instructions to out.
func PrettyPrint(out io.Writer, instructions []byte) {
in := bytes.NewBuffer(instructions)
for {
opcode, err := in.ReadByte()
if err != nil {
break
}
if name, hasname := opcodeName[Opcode(opcode)]; hasname {
io.WriteString(out, name)
out.Write([]byte{' '})
} else {
fmt.Fprintf(out, "%#x ", opcode)
}
for _, arg := range opcodeArgs[Opcode(opcode)] {
switch arg {
case 's':
n, _ := util.DecodeSLEB128(in)
fmt.Fprintf(out, "%#x ", n)
case 'u':
n, _ := util.DecodeULEB128(in)
fmt.Fprintf(out, "%#x ", n)
case '1':
var x uint8
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case '2':
var x uint16
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case '4':
var x uint32
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case '8':
var x uint64
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case 'B':
sz, _ := util.DecodeULEB128(in)
data := make([]byte, sz)
sz2, _ := in.Read(data)
data = data[:sz2]
fmt.Fprintf(out, "%d [%x] ", sz, data)
}
}
}
}
func callframecfa(opcode Opcode, ctxt *context) error {
if ctxt.CFA == 0 {
return fmt.Errorf("Could not retrieve CFA for current PC")
}
ctxt.stack = append(ctxt.stack, int64(ctxt.CFA))
return nil
}
func addr(opcode Opcode, ctxt *context) error {
ctxt.stack = append(ctxt.stack, int64(binary.LittleEndian.Uint64(ctxt.buf.Next(8))+ctxt.StaticBase))
return nil
}
func plus(opcode Opcode, ctxt *context) error {
var (
slen = len(ctxt.stack)
digits = ctxt.stack[slen-2 : slen]
st = ctxt.stack[:slen-2]
)
ctxt.stack = append(st, digits[0]+digits[1])
return nil
}
func plusuconsts(opcode Opcode, ctxt *context) error {
slen := len(ctxt.stack)
num, _ := util.DecodeULEB128(ctxt.buf)
ctxt.stack[slen-1] = ctxt.stack[slen-1] + int64(num)
return nil
}
func consts(opcode Opcode, ctxt *context) error {
num, _ := util.DecodeSLEB128(ctxt.buf)
ctxt.stack = append(ctxt.stack, num)
return nil
}
func framebase(opcode Opcode, ctxt *context) error {
num, _ := util.DecodeSLEB128(ctxt.buf)
ctxt.stack = append(ctxt.stack, ctxt.FrameBase+num)
return nil
}
func register(opcode Opcode, ctxt *context) error {
ctxt.reg = true
if opcode == DW_OP_regx {
n, _ := util.DecodeSLEB128(ctxt.buf)
ctxt.pieces = append(ctxt.pieces, Piece{IsRegister: true, RegNum: uint64(n)})
} else {
ctxt.pieces = append(ctxt.pieces, Piece{IsRegister: true, RegNum: uint64(opcode - DW_OP_reg0)})
}
return nil
}
func piece(opcode Opcode, ctxt *context) error {
sz, _ := util.DecodeULEB128(ctxt.buf)
if ctxt.reg {
ctxt.reg = false
ctxt.pieces[len(ctxt.pieces)-1].Size = int(sz)
return nil
}
if len(ctxt.stack) == 0 {
return errors.New("empty OP stack")
}
addr := ctxt.stack[len(ctxt.stack)-1]
ctxt.pieces = append(ctxt.pieces, Piece{Size: int(sz), Addr: addr})
ctxt.stack = ctxt.stack[:0]
return nil
}

View File

@ -0,0 +1,515 @@
// THIS FILE IS AUTOGENERATED, EDIT opcodes.table INSTEAD
package op
const (
DW_OP_addr Opcode = 0x03
DW_OP_deref Opcode = 0x06
DW_OP_const1u Opcode = 0x08
DW_OP_const1s Opcode = 0x09
DW_OP_const2u Opcode = 0x0a
DW_OP_const2s Opcode = 0x0b
DW_OP_const4u Opcode = 0x0c
DW_OP_const4s Opcode = 0x0d
DW_OP_const8u Opcode = 0x0e
DW_OP_const8s Opcode = 0x0f
DW_OP_constu Opcode = 0x10
DW_OP_consts Opcode = 0x11
DW_OP_dup Opcode = 0x12
DW_OP_drop Opcode = 0x13
DW_OP_over Opcode = 0x14
DW_OP_pick Opcode = 0x15
DW_OP_swap Opcode = 0x16
DW_OP_rot Opcode = 0x17
DW_OP_xderef Opcode = 0x18
DW_OP_abs Opcode = 0x19
DW_OP_and Opcode = 0x1a
DW_OP_div Opcode = 0x1b
DW_OP_minus Opcode = 0x1c
DW_OP_mod Opcode = 0x1d
DW_OP_mul Opcode = 0x1e
DW_OP_neg Opcode = 0x1f
DW_OP_not Opcode = 0x20
DW_OP_or Opcode = 0x21
DW_OP_plus Opcode = 0x22
DW_OP_plus_uconst Opcode = 0x23
DW_OP_shl Opcode = 0x24
DW_OP_shr Opcode = 0x25
DW_OP_shra Opcode = 0x26
DW_OP_xor Opcode = 0x27
DW_OP_bra Opcode = 0x28
DW_OP_eq Opcode = 0x29
DW_OP_ge Opcode = 0x2a
DW_OP_gt Opcode = 0x2b
DW_OP_le Opcode = 0x2c
DW_OP_lt Opcode = 0x2d
DW_OP_ne Opcode = 0x2e
DW_OP_skip Opcode = 0x2f
DW_OP_lit0 Opcode = 0x30
DW_OP_lit1 Opcode = 0x31
DW_OP_lit2 Opcode = 0x32
DW_OP_lit3 Opcode = 0x33
DW_OP_lit4 Opcode = 0x34
DW_OP_lit5 Opcode = 0x35
DW_OP_lit6 Opcode = 0x36
DW_OP_lit7 Opcode = 0x37
DW_OP_lit8 Opcode = 0x38
DW_OP_lit9 Opcode = 0x39
DW_OP_lit10 Opcode = 0x3a
DW_OP_lit11 Opcode = 0x3b
DW_OP_lit12 Opcode = 0x3c
DW_OP_lit13 Opcode = 0x3d
DW_OP_lit14 Opcode = 0x3e
DW_OP_lit15 Opcode = 0x3f
DW_OP_lit16 Opcode = 0x40
DW_OP_lit17 Opcode = 0x41
DW_OP_lit18 Opcode = 0x42
DW_OP_lit19 Opcode = 0x43
DW_OP_lit20 Opcode = 0x44
DW_OP_lit21 Opcode = 0x45
DW_OP_lit22 Opcode = 0x46
DW_OP_lit23 Opcode = 0x47
DW_OP_lit24 Opcode = 0x48
DW_OP_lit25 Opcode = 0x49
DW_OP_lit26 Opcode = 0x4a
DW_OP_lit27 Opcode = 0x4b
DW_OP_lit28 Opcode = 0x4c
DW_OP_lit29 Opcode = 0x4d
DW_OP_lit30 Opcode = 0x4e
DW_OP_lit31 Opcode = 0x4f
DW_OP_reg0 Opcode = 0x50
DW_OP_reg1 Opcode = 0x51
DW_OP_reg2 Opcode = 0x52
DW_OP_reg3 Opcode = 0x53
DW_OP_reg4 Opcode = 0x54
DW_OP_reg5 Opcode = 0x55
DW_OP_reg6 Opcode = 0x56
DW_OP_reg7 Opcode = 0x57
DW_OP_reg8 Opcode = 0x58
DW_OP_reg9 Opcode = 0x59
DW_OP_reg10 Opcode = 0x5a
DW_OP_reg11 Opcode = 0x5b
DW_OP_reg12 Opcode = 0x5c
DW_OP_reg13 Opcode = 0x5d
DW_OP_reg14 Opcode = 0x5e
DW_OP_reg15 Opcode = 0x5f
DW_OP_reg16 Opcode = 0x60
DW_OP_reg17 Opcode = 0x61
DW_OP_reg18 Opcode = 0x62
DW_OP_reg19 Opcode = 0x63
DW_OP_reg20 Opcode = 0x64
DW_OP_reg21 Opcode = 0x65
DW_OP_reg22 Opcode = 0x66
DW_OP_reg23 Opcode = 0x67
DW_OP_reg24 Opcode = 0x68
DW_OP_reg25 Opcode = 0x69
DW_OP_reg26 Opcode = 0x6a
DW_OP_reg27 Opcode = 0x6b
DW_OP_reg28 Opcode = 0x6c
DW_OP_reg29 Opcode = 0x6d
DW_OP_reg30 Opcode = 0x6e
DW_OP_reg31 Opcode = 0x6f
DW_OP_breg0 Opcode = 0x70
DW_OP_breg1 Opcode = 0x71
DW_OP_breg2 Opcode = 0x72
DW_OP_breg3 Opcode = 0x73
DW_OP_breg4 Opcode = 0x74
DW_OP_breg5 Opcode = 0x75
DW_OP_breg6 Opcode = 0x76
DW_OP_breg7 Opcode = 0x77
DW_OP_breg8 Opcode = 0x78
DW_OP_breg9 Opcode = 0x79
DW_OP_breg10 Opcode = 0x7a
DW_OP_breg11 Opcode = 0x7b
DW_OP_breg12 Opcode = 0x7c
DW_OP_breg13 Opcode = 0x7d
DW_OP_breg14 Opcode = 0x7e
DW_OP_breg15 Opcode = 0x7f
DW_OP_breg16 Opcode = 0x80
DW_OP_breg17 Opcode = 0x81
DW_OP_breg18 Opcode = 0x82
DW_OP_breg19 Opcode = 0x83
DW_OP_breg20 Opcode = 0x84
DW_OP_breg21 Opcode = 0x85
DW_OP_breg22 Opcode = 0x86
DW_OP_breg23 Opcode = 0x87
DW_OP_breg24 Opcode = 0x88
DW_OP_breg25 Opcode = 0x89
DW_OP_breg26 Opcode = 0x8a
DW_OP_breg27 Opcode = 0x8b
DW_OP_breg28 Opcode = 0x8c
DW_OP_breg29 Opcode = 0x8d
DW_OP_breg30 Opcode = 0x8e
DW_OP_breg31 Opcode = 0x8f
DW_OP_regx Opcode = 0x90
DW_OP_fbreg Opcode = 0x91
DW_OP_bregx Opcode = 0x92
DW_OP_piece Opcode = 0x93
DW_OP_deref_size Opcode = 0x94
DW_OP_xderef_size Opcode = 0x95
DW_OP_nop Opcode = 0x96
DW_OP_push_object_address Opcode = 0x97
DW_OP_call2 Opcode = 0x98
DW_OP_call4 Opcode = 0x99
DW_OP_call_ref Opcode = 0x9a
DW_OP_form_tls_address Opcode = 0x9b
DW_OP_call_frame_cfa Opcode = 0x9c
DW_OP_bit_piece Opcode = 0x9d
DW_OP_implicit_value Opcode = 0x9e
DW_OP_stack_value Opcode = 0x9f
)
var opcodeName = map[Opcode]string{
DW_OP_addr: "DW_OP_addr",
DW_OP_deref: "DW_OP_deref",
DW_OP_const1u: "DW_OP_const1u",
DW_OP_const1s: "DW_OP_const1s",
DW_OP_const2u: "DW_OP_const2u",
DW_OP_const2s: "DW_OP_const2s",
DW_OP_const4u: "DW_OP_const4u",
DW_OP_const4s: "DW_OP_const4s",
DW_OP_const8u: "DW_OP_const8u",
DW_OP_const8s: "DW_OP_const8s",
DW_OP_constu: "DW_OP_constu",
DW_OP_consts: "DW_OP_consts",
DW_OP_dup: "DW_OP_dup",
DW_OP_drop: "DW_OP_drop",
DW_OP_over: "DW_OP_over",
DW_OP_pick: "DW_OP_pick",
DW_OP_swap: "DW_OP_swap",
DW_OP_rot: "DW_OP_rot",
DW_OP_xderef: "DW_OP_xderef",
DW_OP_abs: "DW_OP_abs",
DW_OP_and: "DW_OP_and",
DW_OP_div: "DW_OP_div",
DW_OP_minus: "DW_OP_minus",
DW_OP_mod: "DW_OP_mod",
DW_OP_mul: "DW_OP_mul",
DW_OP_neg: "DW_OP_neg",
DW_OP_not: "DW_OP_not",
DW_OP_or: "DW_OP_or",
DW_OP_plus: "DW_OP_plus",
DW_OP_plus_uconst: "DW_OP_plus_uconst",
DW_OP_shl: "DW_OP_shl",
DW_OP_shr: "DW_OP_shr",
DW_OP_shra: "DW_OP_shra",
DW_OP_xor: "DW_OP_xor",
DW_OP_bra: "DW_OP_bra",
DW_OP_eq: "DW_OP_eq",
DW_OP_ge: "DW_OP_ge",
DW_OP_gt: "DW_OP_gt",
DW_OP_le: "DW_OP_le",
DW_OP_lt: "DW_OP_lt",
DW_OP_ne: "DW_OP_ne",
DW_OP_skip: "DW_OP_skip",
DW_OP_lit0: "DW_OP_lit0",
DW_OP_lit1: "DW_OP_lit1",
DW_OP_lit2: "DW_OP_lit2",
DW_OP_lit3: "DW_OP_lit3",
DW_OP_lit4: "DW_OP_lit4",
DW_OP_lit5: "DW_OP_lit5",
DW_OP_lit6: "DW_OP_lit6",
DW_OP_lit7: "DW_OP_lit7",
DW_OP_lit8: "DW_OP_lit8",
DW_OP_lit9: "DW_OP_lit9",
DW_OP_lit10: "DW_OP_lit10",
DW_OP_lit11: "DW_OP_lit11",
DW_OP_lit12: "DW_OP_lit12",
DW_OP_lit13: "DW_OP_lit13",
DW_OP_lit14: "DW_OP_lit14",
DW_OP_lit15: "DW_OP_lit15",
DW_OP_lit16: "DW_OP_lit16",
DW_OP_lit17: "DW_OP_lit17",
DW_OP_lit18: "DW_OP_lit18",
DW_OP_lit19: "DW_OP_lit19",
DW_OP_lit20: "DW_OP_lit20",
DW_OP_lit21: "DW_OP_lit21",
DW_OP_lit22: "DW_OP_lit22",
DW_OP_lit23: "DW_OP_lit23",
DW_OP_lit24: "DW_OP_lit24",
DW_OP_lit25: "DW_OP_lit25",
DW_OP_lit26: "DW_OP_lit26",
DW_OP_lit27: "DW_OP_lit27",
DW_OP_lit28: "DW_OP_lit28",
DW_OP_lit29: "DW_OP_lit29",
DW_OP_lit30: "DW_OP_lit30",
DW_OP_lit31: "DW_OP_lit31",
DW_OP_reg0: "DW_OP_reg0",
DW_OP_reg1: "DW_OP_reg1",
DW_OP_reg2: "DW_OP_reg2",
DW_OP_reg3: "DW_OP_reg3",
DW_OP_reg4: "DW_OP_reg4",
DW_OP_reg5: "DW_OP_reg5",
DW_OP_reg6: "DW_OP_reg6",
DW_OP_reg7: "DW_OP_reg7",
DW_OP_reg8: "DW_OP_reg8",
DW_OP_reg9: "DW_OP_reg9",
DW_OP_reg10: "DW_OP_reg10",
DW_OP_reg11: "DW_OP_reg11",
DW_OP_reg12: "DW_OP_reg12",
DW_OP_reg13: "DW_OP_reg13",
DW_OP_reg14: "DW_OP_reg14",
DW_OP_reg15: "DW_OP_reg15",
DW_OP_reg16: "DW_OP_reg16",
DW_OP_reg17: "DW_OP_reg17",
DW_OP_reg18: "DW_OP_reg18",
DW_OP_reg19: "DW_OP_reg19",
DW_OP_reg20: "DW_OP_reg20",
DW_OP_reg21: "DW_OP_reg21",
DW_OP_reg22: "DW_OP_reg22",
DW_OP_reg23: "DW_OP_reg23",
DW_OP_reg24: "DW_OP_reg24",
DW_OP_reg25: "DW_OP_reg25",
DW_OP_reg26: "DW_OP_reg26",
DW_OP_reg27: "DW_OP_reg27",
DW_OP_reg28: "DW_OP_reg28",
DW_OP_reg29: "DW_OP_reg29",
DW_OP_reg30: "DW_OP_reg30",
DW_OP_reg31: "DW_OP_reg31",
DW_OP_breg0: "DW_OP_breg0",
DW_OP_breg1: "DW_OP_breg1",
DW_OP_breg2: "DW_OP_breg2",
DW_OP_breg3: "DW_OP_breg3",
DW_OP_breg4: "DW_OP_breg4",
DW_OP_breg5: "DW_OP_breg5",
DW_OP_breg6: "DW_OP_breg6",
DW_OP_breg7: "DW_OP_breg7",
DW_OP_breg8: "DW_OP_breg8",
DW_OP_breg9: "DW_OP_breg9",
DW_OP_breg10: "DW_OP_breg10",
DW_OP_breg11: "DW_OP_breg11",
DW_OP_breg12: "DW_OP_breg12",
DW_OP_breg13: "DW_OP_breg13",
DW_OP_breg14: "DW_OP_breg14",
DW_OP_breg15: "DW_OP_breg15",
DW_OP_breg16: "DW_OP_breg16",
DW_OP_breg17: "DW_OP_breg17",
DW_OP_breg18: "DW_OP_breg18",
DW_OP_breg19: "DW_OP_breg19",
DW_OP_breg20: "DW_OP_breg20",
DW_OP_breg21: "DW_OP_breg21",
DW_OP_breg22: "DW_OP_breg22",
DW_OP_breg23: "DW_OP_breg23",
DW_OP_breg24: "DW_OP_breg24",
DW_OP_breg25: "DW_OP_breg25",
DW_OP_breg26: "DW_OP_breg26",
DW_OP_breg27: "DW_OP_breg27",
DW_OP_breg28: "DW_OP_breg28",
DW_OP_breg29: "DW_OP_breg29",
DW_OP_breg30: "DW_OP_breg30",
DW_OP_breg31: "DW_OP_breg31",
DW_OP_regx: "DW_OP_regx",
DW_OP_fbreg: "DW_OP_fbreg",
DW_OP_bregx: "DW_OP_bregx",
DW_OP_piece: "DW_OP_piece",
DW_OP_deref_size: "DW_OP_deref_size",
DW_OP_xderef_size: "DW_OP_xderef_size",
DW_OP_nop: "DW_OP_nop",
DW_OP_push_object_address: "DW_OP_push_object_address",
DW_OP_call2: "DW_OP_call2",
DW_OP_call4: "DW_OP_call4",
DW_OP_call_ref: "DW_OP_call_ref",
DW_OP_form_tls_address: "DW_OP_form_tls_address",
DW_OP_call_frame_cfa: "DW_OP_call_frame_cfa",
DW_OP_bit_piece: "DW_OP_bit_piece",
DW_OP_implicit_value: "DW_OP_implicit_value",
DW_OP_stack_value: "DW_OP_stack_value",
}
var opcodeArgs = map[Opcode]string{
DW_OP_addr: "8",
DW_OP_deref: "",
DW_OP_const1u: "1",
DW_OP_const1s: "1",
DW_OP_const2u: "2",
DW_OP_const2s: "2",
DW_OP_const4u: "4",
DW_OP_const4s: "4",
DW_OP_const8u: "8",
DW_OP_const8s: "8",
DW_OP_constu: "u",
DW_OP_consts: "s",
DW_OP_dup: "",
DW_OP_drop: "",
DW_OP_over: "",
DW_OP_pick: "",
DW_OP_swap: "",
DW_OP_rot: "",
DW_OP_xderef: "",
DW_OP_abs: "",
DW_OP_and: "",
DW_OP_div: "",
DW_OP_minus: "",
DW_OP_mod: "",
DW_OP_mul: "",
DW_OP_neg: "",
DW_OP_not: "",
DW_OP_or: "",
DW_OP_plus: "",
DW_OP_plus_uconst: "u",
DW_OP_shl: "",
DW_OP_shr: "",
DW_OP_shra: "",
DW_OP_xor: "",
DW_OP_bra: "2",
DW_OP_eq: "",
DW_OP_ge: "",
DW_OP_gt: "",
DW_OP_le: "",
DW_OP_lt: "",
DW_OP_ne: "",
DW_OP_skip: "2",
DW_OP_lit0: "",
DW_OP_lit1: "",
DW_OP_lit2: "",
DW_OP_lit3: "",
DW_OP_lit4: "",
DW_OP_lit5: "",
DW_OP_lit6: "",
DW_OP_lit7: "",
DW_OP_lit8: "",
DW_OP_lit9: "",
DW_OP_lit10: "",
DW_OP_lit11: "",
DW_OP_lit12: "",
DW_OP_lit13: "",
DW_OP_lit14: "",
DW_OP_lit15: "",
DW_OP_lit16: "",
DW_OP_lit17: "",
DW_OP_lit18: "",
DW_OP_lit19: "",
DW_OP_lit20: "",
DW_OP_lit21: "",
DW_OP_lit22: "",
DW_OP_lit23: "",
DW_OP_lit24: "",
DW_OP_lit25: "",
DW_OP_lit26: "",
DW_OP_lit27: "",
DW_OP_lit28: "",
DW_OP_lit29: "",
DW_OP_lit30: "",
DW_OP_lit31: "",
DW_OP_reg0: "",
DW_OP_reg1: "",
DW_OP_reg2: "",
DW_OP_reg3: "",
DW_OP_reg4: "",
DW_OP_reg5: "",
DW_OP_reg6: "",
DW_OP_reg7: "",
DW_OP_reg8: "",
DW_OP_reg9: "",
DW_OP_reg10: "",
DW_OP_reg11: "",
DW_OP_reg12: "",
DW_OP_reg13: "",
DW_OP_reg14: "",
DW_OP_reg15: "",
DW_OP_reg16: "",
DW_OP_reg17: "",
DW_OP_reg18: "",
DW_OP_reg19: "",
DW_OP_reg20: "",
DW_OP_reg21: "",
DW_OP_reg22: "",
DW_OP_reg23: "",
DW_OP_reg24: "",
DW_OP_reg25: "",
DW_OP_reg26: "",
DW_OP_reg27: "",
DW_OP_reg28: "",
DW_OP_reg29: "",
DW_OP_reg30: "",
DW_OP_reg31: "",
DW_OP_breg0: "s",
DW_OP_breg1: "s",
DW_OP_breg2: "s",
DW_OP_breg3: "s",
DW_OP_breg4: "s",
DW_OP_breg5: "s",
DW_OP_breg6: "s",
DW_OP_breg7: "s",
DW_OP_breg8: "s",
DW_OP_breg9: "s",
DW_OP_breg10: "s",
DW_OP_breg11: "s",
DW_OP_breg12: "s",
DW_OP_breg13: "s",
DW_OP_breg14: "s",
DW_OP_breg15: "s",
DW_OP_breg16: "s",
DW_OP_breg17: "s",
DW_OP_breg18: "s",
DW_OP_breg19: "s",
DW_OP_breg20: "s",
DW_OP_breg21: "s",
DW_OP_breg22: "s",
DW_OP_breg23: "s",
DW_OP_breg24: "s",
DW_OP_breg25: "s",
DW_OP_breg26: "s",
DW_OP_breg27: "s",
DW_OP_breg28: "s",
DW_OP_breg29: "s",
DW_OP_breg30: "s",
DW_OP_breg31: "s",
DW_OP_regx: "s",
DW_OP_fbreg: "s",
DW_OP_bregx: "us",
DW_OP_piece: "u",
DW_OP_deref_size: "1",
DW_OP_xderef_size: "1",
DW_OP_nop: "",
DW_OP_push_object_address: "",
DW_OP_call2: "2",
DW_OP_call4: "4",
DW_OP_call_ref: "4",
DW_OP_form_tls_address: "",
DW_OP_call_frame_cfa: "",
DW_OP_bit_piece: "uu",
DW_OP_implicit_value: "B",
DW_OP_stack_value: "",
}
var oplut = map[Opcode]stackfn{
DW_OP_addr: addr,
DW_OP_consts: consts,
DW_OP_plus: plus,
DW_OP_plus_uconst: plusuconsts,
DW_OP_reg0: register,
DW_OP_reg1: register,
DW_OP_reg2: register,
DW_OP_reg3: register,
DW_OP_reg4: register,
DW_OP_reg5: register,
DW_OP_reg6: register,
DW_OP_reg7: register,
DW_OP_reg8: register,
DW_OP_reg9: register,
DW_OP_reg10: register,
DW_OP_reg11: register,
DW_OP_reg12: register,
DW_OP_reg13: register,
DW_OP_reg14: register,
DW_OP_reg15: register,
DW_OP_reg16: register,
DW_OP_reg17: register,
DW_OP_reg18: register,
DW_OP_reg19: register,
DW_OP_reg20: register,
DW_OP_reg21: register,
DW_OP_reg22: register,
DW_OP_reg23: register,
DW_OP_reg24: register,
DW_OP_reg25: register,
DW_OP_reg26: register,
DW_OP_reg27: register,
DW_OP_reg28: register,
DW_OP_reg29: register,
DW_OP_reg30: register,
DW_OP_reg31: register,
DW_OP_regx: register,
DW_OP_fbreg: framebase,
DW_OP_piece: piece,
DW_OP_call_frame_cfa: callframecfa,
}

View File

@ -0,0 +1,175 @@
// This file is used by scripts/gen-opcodes.go to generate
// pkg/dwarf/op/opcodes.go
// Lines starting with // are comments and will be discarded.
// Non empty lines contain the following tab separated fields:
//
// <opcode name> <opcode code> <arguments> <function name>
//
// With the last column, <function name>, being optional.
//
// The arguments field should contain a string with one character for each
// argument of the opcode:
//
// s signed variable length integer
// u unsigned variable length integer
// 1 one byte unsigned integer
// 2 two bytes unsigned integer
// 4 four bytes unsigned integer
// 8 eight bytes unsigned integer
// B an unsigned variable length integer 'n' followed by n a block of n bytes
DW_OP_addr 0x03 "8" addr
DW_OP_deref 0x06 ""
DW_OP_const1u 0x08 "1"
DW_OP_const1s 0x09 "1"
DW_OP_const2u 0x0a "2"
DW_OP_const2s 0x0b "2"
DW_OP_const4u 0x0c "4"
DW_OP_const4s 0x0d "4"
DW_OP_const8u 0x0e "8"
DW_OP_const8s 0x0f "8"
DW_OP_constu 0x10 "u"
DW_OP_consts 0x11 "s" consts
DW_OP_dup 0x12 ""
DW_OP_drop 0x13 ""
DW_OP_over 0x14 ""
DW_OP_pick 0x15 ""
DW_OP_swap 0x16 ""
DW_OP_rot 0x17 ""
DW_OP_xderef 0x18 ""
DW_OP_abs 0x19 ""
DW_OP_and 0x1a ""
DW_OP_div 0x1b ""
DW_OP_minus 0x1c ""
DW_OP_mod 0x1d ""
DW_OP_mul 0x1e ""
DW_OP_neg 0x1f ""
DW_OP_not 0x20 ""
DW_OP_or 0x21 ""
DW_OP_plus 0x22 "" plus
DW_OP_plus_uconst 0x23 "u" plusuconsts
DW_OP_shl 0x24 ""
DW_OP_shr 0x25 ""
DW_OP_shra 0x26 ""
DW_OP_xor 0x27 ""
DW_OP_bra 0x28 "2"
DW_OP_eq 0x29 ""
DW_OP_ge 0x2a ""
DW_OP_gt 0x2b ""
DW_OP_le 0x2c ""
DW_OP_lt 0x2d ""
DW_OP_ne 0x2e ""
DW_OP_skip 0x2f "2"
DW_OP_lit0 0x30 ""
DW_OP_lit1 0x31 ""
DW_OP_lit2 0x32 ""
DW_OP_lit3 0x33 ""
DW_OP_lit4 0x34 ""
DW_OP_lit5 0x35 ""
DW_OP_lit6 0x36 ""
DW_OP_lit7 0x37 ""
DW_OP_lit8 0x38 ""
DW_OP_lit9 0x39 ""
DW_OP_lit10 0x3a ""
DW_OP_lit11 0x3b ""
DW_OP_lit12 0x3c ""
DW_OP_lit13 0x3d ""
DW_OP_lit14 0x3e ""
DW_OP_lit15 0x3f ""
DW_OP_lit16 0x40 ""
DW_OP_lit17 0x41 ""
DW_OP_lit18 0x42 ""
DW_OP_lit19 0x43 ""
DW_OP_lit20 0x44 ""
DW_OP_lit21 0x45 ""
DW_OP_lit22 0x46 ""
DW_OP_lit23 0x47 ""
DW_OP_lit24 0x48 ""
DW_OP_lit25 0x49 ""
DW_OP_lit26 0x4a ""
DW_OP_lit27 0x4b ""
DW_OP_lit28 0x4c ""
DW_OP_lit29 0x4d ""
DW_OP_lit30 0x4e ""
DW_OP_lit31 0x4f ""
DW_OP_reg0 0x50 "" register
DW_OP_reg1 0x51 "" register
DW_OP_reg2 0x52 "" register
DW_OP_reg3 0x53 "" register
DW_OP_reg4 0x54 "" register
DW_OP_reg5 0x55 "" register
DW_OP_reg6 0x56 "" register
DW_OP_reg7 0x57 "" register
DW_OP_reg8 0x58 "" register
DW_OP_reg9 0x59 "" register
DW_OP_reg10 0x5a "" register
DW_OP_reg11 0x5b "" register
DW_OP_reg12 0x5c "" register
DW_OP_reg13 0x5d "" register
DW_OP_reg14 0x5e "" register
DW_OP_reg15 0x5f "" register
DW_OP_reg16 0x60 "" register
DW_OP_reg17 0x61 "" register
DW_OP_reg18 0x62 "" register
DW_OP_reg19 0x63 "" register
DW_OP_reg20 0x64 "" register
DW_OP_reg21 0x65 "" register
DW_OP_reg22 0x66 "" register
DW_OP_reg23 0x67 "" register
DW_OP_reg24 0x68 "" register
DW_OP_reg25 0x69 "" register
DW_OP_reg26 0x6a "" register
DW_OP_reg27 0x6b "" register
DW_OP_reg28 0x6c "" register
DW_OP_reg29 0x6d "" register
DW_OP_reg30 0x6e "" register
DW_OP_reg31 0x6f "" register
DW_OP_breg0 0x70 "s"
DW_OP_breg1 0x71 "s"
DW_OP_breg2 0x72 "s"
DW_OP_breg3 0x73 "s"
DW_OP_breg4 0x74 "s"
DW_OP_breg5 0x75 "s"
DW_OP_breg6 0x76 "s"
DW_OP_breg7 0x77 "s"
DW_OP_breg8 0x78 "s"
DW_OP_breg9 0x79 "s"
DW_OP_breg10 0x7a "s"
DW_OP_breg11 0x7b "s"
DW_OP_breg12 0x7c "s"
DW_OP_breg13 0x7d "s"
DW_OP_breg14 0x7e "s"
DW_OP_breg15 0x7f "s"
DW_OP_breg16 0x80 "s"
DW_OP_breg17 0x81 "s"
DW_OP_breg18 0x82 "s"
DW_OP_breg19 0x83 "s"
DW_OP_breg20 0x84 "s"
DW_OP_breg21 0x85 "s"
DW_OP_breg22 0x86 "s"
DW_OP_breg23 0x87 "s"
DW_OP_breg24 0x88 "s"
DW_OP_breg25 0x89 "s"
DW_OP_breg26 0x8a "s"
DW_OP_breg27 0x8b "s"
DW_OP_breg28 0x8c "s"
DW_OP_breg29 0x8d "s"
DW_OP_breg30 0x8e "s"
DW_OP_breg31 0x8f "s"
DW_OP_regx 0x90 "s" register
DW_OP_fbreg 0x91 "s" framebase
DW_OP_bregx 0x92 "us"
DW_OP_piece 0x93 "u" piece
DW_OP_deref_size 0x94 "1"
DW_OP_xderef_size 0x95 "1"
DW_OP_nop 0x96 ""
DW_OP_push_object_address 0x97 ""
DW_OP_call2 0x98 "2"
DW_OP_call4 0x99 "4"
DW_OP_call_ref 0x9a "4"
DW_OP_form_tls_address 0x9b ""
DW_OP_call_frame_cfa 0x9c "" callframecfa
DW_OP_bit_piece 0x9d "uu"
DW_OP_implicit_value 0x9e "B"
DW_OP_stack_value 0x9f ""

View File

@ -0,0 +1,102 @@
package op
import (
"bytes"
"encoding/binary"
)
type DwarfRegisters struct {
StaticBase uint64
CFA int64
FrameBase int64
ObjBase int64
Regs []*DwarfRegister
ByteOrder binary.ByteOrder
PCRegNum uint64
SPRegNum uint64
BPRegNum uint64
}
type DwarfRegister struct {
Uint64Val uint64
Bytes []byte
}
// Uint64Val returns the uint64 value of register idx.
func (regs *DwarfRegisters) Uint64Val(idx uint64) uint64 {
reg := regs.Reg(idx)
if reg == nil {
return 0
}
return regs.Regs[idx].Uint64Val
}
// Bytes returns the bytes value of register idx, nil if the register is not
// defined.
func (regs *DwarfRegisters) Bytes(idx uint64) []byte {
reg := regs.Reg(idx)
if reg == nil {
return nil
}
if reg.Bytes == nil {
var buf bytes.Buffer
binary.Write(&buf, regs.ByteOrder, reg.Uint64Val)
reg.Bytes = buf.Bytes()
}
return reg.Bytes
}
// Reg returns register idx or nil if the register is not defined.
func (regs *DwarfRegisters) Reg(idx uint64) *DwarfRegister {
if idx >= uint64(len(regs.Regs)) {
return nil
}
return regs.Regs[idx]
}
func (regs *DwarfRegisters) PC() uint64 {
return regs.Uint64Val(regs.PCRegNum)
}
func (regs *DwarfRegisters) SP() uint64 {
return regs.Uint64Val(regs.SPRegNum)
}
func (regs *DwarfRegisters) BP() uint64 {
return regs.Uint64Val(regs.BPRegNum)
}
// AddReg adds register idx to regs.
func (regs *DwarfRegisters) AddReg(idx uint64, reg *DwarfRegister) {
if idx >= uint64(len(regs.Regs)) {
newRegs := make([]*DwarfRegister, idx+1)
copy(newRegs, regs.Regs)
regs.Regs = newRegs
}
regs.Regs[idx] = reg
}
func DwarfRegisterFromUint64(v uint64) *DwarfRegister {
return &DwarfRegister{Uint64Val: v}
}
func DwarfRegisterFromBytes(bytes []byte) *DwarfRegister {
var v uint64
switch len(bytes) {
case 1:
v = uint64(bytes[0])
case 2:
x := binary.LittleEndian.Uint16(bytes)
v = uint64(x)
case 4:
x := binary.LittleEndian.Uint32(bytes)
v = uint64(x)
default:
if len(bytes) >= 8 {
v = binary.LittleEndian.Uint64(bytes[:8])
}
}
return &DwarfRegister{Uint64Val: v, Bytes: bytes}
}

View File

@ -1,11 +1,11 @@
package reader package reader
import ( import (
"debug/dwarf"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/debug/dwarf" "github.com/derekparker/delve/pkg/dwarf/op"
"github.com/derekparker/delve/dwarf/op"
) )
type Reader struct { type Reader struct {
@ -34,7 +34,7 @@ func (reader *Reader) SeekToEntry(entry *dwarf.Entry) error {
// SeekToFunctionEntry moves the reader to the function that includes the // SeekToFunctionEntry moves the reader to the function that includes the
// specified program counter. // specified program counter.
func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) { func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) {
reader.Seek(0) reader.Seek(0)
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil { if err != nil {
@ -55,7 +55,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
continue continue
} }
if lowpc <= pc && highpc > pc { if lowpc <= uint64(pc) && highpc > uint64(pc) {
return entry, nil return entry, nil
} }
} }
@ -64,7 +64,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
} }
// Returns the address for the named entry. // Returns the address for the named entry.
func (reader *Reader) AddrFor(name string) (uint64, error) { func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
entry, err := reader.FindEntryNamed(name, false) entry, err := reader.FindEntryNamed(name, false)
if err != nil { if err != nil {
return 0, err return 0, err
@ -73,7 +73,7 @@ func (reader *Reader) AddrFor(name string) (uint64, error) {
if !ok { if !ok {
return 0, fmt.Errorf("type assertion failed") return 0, fmt.Errorf("type assertion failed")
} }
addr, err := op.ExecuteStackProgram(0, instructions) addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -99,7 +99,7 @@ func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (
if !ok { if !ok {
continue continue
} }
addr, err := op.ExecuteStackProgram(0, append(initialInstructions, instructions...)) addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...))
return uint64(addr), err return uint64(addr), err
} }
} }
@ -257,31 +257,7 @@ func (reader *Reader) InstructionsForEntry(entry *dwarf.Entry) ([]byte, error) {
return append([]byte{}, instructions...), nil return append([]byte{}, instructions...), nil
} }
// NextScopeVariable moves the reader to the next debug entry that describes a local variable and returns the entry. // NextMemberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
// All scope variables will be at the same depth
reader.SkipChildren()
// End of the current depth
if entry.Tag == 0 {
break
}
if entry.Tag == dwarf.TagVariable || entry.Tag == dwarf.TagFormalParameter {
return entry, nil
}
}
// No more items
return nil, nil
}
// NextMememberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) { func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil { if err != nil {
@ -343,3 +319,128 @@ func (reader *Reader) NextCompileUnit() (*dwarf.Entry, error) {
return nil, nil return nil, nil
} }
// Entry represents a debug_info entry.
// When calling Val, if the entry does not have the specified attribute, the
// entry specified by DW_AT_abstract_origin will be searched recursively.
type Entry interface {
Val(dwarf.Attr) interface{}
}
type compositeEntry []*dwarf.Entry
func (ce compositeEntry) Val(attr dwarf.Attr) interface{} {
for _, e := range ce {
if r := e.Val(attr); r != nil {
return r
}
}
return nil
}
// LoadAbstractOrigin loads the entry corresponding to the
// DW_AT_abstract_origin of entry and returns a combination of entry and its
// abstract origin.
func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.Offset) {
ao, ok := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
if !ok {
return entry, entry.Offset
}
r := []*dwarf.Entry{entry}
for {
aordr.Seek(ao)
e, _ := aordr.Next()
if e == nil {
break
}
r = append(r, e)
ao, ok = e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
if !ok {
break
}
}
return compositeEntry(r), entry.Offset
}
// InlineStackReader provides a way to read the stack of inlined calls at a
// specified PC address.
type InlineStackReader struct {
dwarf *dwarf.Data
reader *dwarf.Reader
entry *dwarf.Entry
depth int
pc uint64
err error
}
// InlineStack returns an InlineStackReader for the specified function and
// PC address.
// If pc is 0 then all inlined calls will be returned.
func InlineStack(dwarf *dwarf.Data, fnoff dwarf.Offset, pc RelAddr) *InlineStackReader {
reader := dwarf.Reader()
reader.Seek(fnoff)
return &InlineStackReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, pc: uint64(pc)}
}
// Next reads next inlined call in the stack, returns false if there aren't any.
func (irdr *InlineStackReader) Next() bool {
if irdr.err != nil {
return false
}
for {
irdr.entry, irdr.err = irdr.reader.Next()
if irdr.entry == nil || irdr.err != nil {
return false
}
switch irdr.entry.Tag {
case 0:
irdr.depth--
if irdr.depth == 0 {
return false
}
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
var recur bool
if irdr.pc != 0 {
recur, irdr.err = entryRangesContains(irdr.dwarf, irdr.entry, irdr.pc)
} else {
recur = true
}
if recur {
irdr.depth++
if irdr.entry.Tag == dwarf.TagInlinedSubroutine {
return true
}
} else {
if irdr.depth == 0 {
return false
}
irdr.reader.SkipChildren()
}
default:
irdr.reader.SkipChildren()
}
}
}
// Entry returns the DIE for the current inlined call.
func (irdr *InlineStackReader) Entry() *dwarf.Entry {
return irdr.entry
}
// Err returns an error, if any was encountered.
func (irdr *InlineStackReader) Err() error {
return irdr.err
}
// SkipChildren skips all children of the current inlined call.
func (irdr *InlineStackReader) SkipChildren() {
irdr.reader.SkipChildren()
}

View File

@ -0,0 +1,114 @@
package reader
import (
"errors"
"debug/dwarf"
)
// RelAddr is an address relative to the static base. For normal executables
// this is just a normal memory address, for PIE it's a relative address.
type RelAddr uint64
func ToRelAddr(addr uint64, staticBase uint64) RelAddr {
return RelAddr(addr - staticBase)
}
// VariableReader provides a way of reading the local variables and formal
// parameters of a function that are visible at the specified PC address.
type VariableReader struct {
dwarf *dwarf.Data
reader *dwarf.Reader
entry *dwarf.Entry
depth int
onlyVisible bool
pc uint64
line int
err error
}
// Variables returns a VariableReader for the function or lexical block at off.
// If onlyVisible is true only variables visible at pc will be returned by
// the VariableReader.
func Variables(dwarf *dwarf.Data, off dwarf.Offset, pc RelAddr, line int, onlyVisible bool) *VariableReader {
reader := dwarf.Reader()
reader.Seek(off)
return &VariableReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, onlyVisible: onlyVisible, pc: uint64(pc), line: line, err: nil}
}
// Next reads the next variable entry, returns false if there aren't any.
func (vrdr *VariableReader) Next() bool {
if vrdr.err != nil {
return false
}
for {
vrdr.entry, vrdr.err = vrdr.reader.Next()
if vrdr.entry == nil || vrdr.err != nil {
return false
}
switch vrdr.entry.Tag {
case 0:
vrdr.depth--
if vrdr.depth == 0 {
return false
}
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
recur := true
if vrdr.onlyVisible {
recur, vrdr.err = entryRangesContains(vrdr.dwarf, vrdr.entry, vrdr.pc)
if vrdr.err != nil {
return false
}
}
if recur && vrdr.entry.Children {
vrdr.depth++
} else {
if vrdr.depth == 0 {
return false
}
vrdr.reader.SkipChildren()
}
default:
if vrdr.depth == 0 {
vrdr.err = errors.New("offset was not lexical block or subprogram")
return false
}
if declLine, ok := vrdr.entry.Val(dwarf.AttrDeclLine).(int64); !ok || vrdr.line >= int(declLine) {
return true
}
}
}
}
func entryRangesContains(dwarf *dwarf.Data, entry *dwarf.Entry, pc uint64) (bool, error) {
rngs, err := dwarf.Ranges(entry)
if err != nil {
return false, err
}
for _, rng := range rngs {
if pc >= rng[0] && pc < rng[1] {
return true, nil
}
}
return false, nil
}
// Entry returns the current variable entry.
func (vrdr *VariableReader) Entry() *dwarf.Entry {
return vrdr.entry
}
// Depth returns the depth of the current scope
func (vrdr *VariableReader) Depth() int {
return vrdr.depth
}
// Err returns the error if there was one.
func (vrdr *VariableReader) Err() error {
return vrdr.err
}

View File

@ -4,23 +4,21 @@
// Buffered reading and decoding of DWARF data streams. // Buffered reading and decoding of DWARF data streams.
package dwarf package util
import ( import (
"encoding/binary" "debug/dwarf"
"fmt" "fmt"
"strconv"
) )
// Data buffer being decoded. // Data buffer being decoded.
type buf struct { type buf struct {
dwarf *Data dwarf *dwarf.Data
order binary.ByteOrder
format dataFormat format dataFormat
name string name string
off Offset off dwarf.Offset
data []byte data []byte
err error Err error
} }
// Data format, other than byte order. This affects the handling of // Data format, other than byte order. This affects the handling of
@ -37,22 +35,22 @@ type dataFormat interface {
} }
// Some parts of DWARF have no data format, e.g., abbrevs. // Some parts of DWARF have no data format, e.g., abbrevs.
type unknownFormat struct{} type UnknownFormat struct{}
func (u unknownFormat) version() int { func (u UnknownFormat) version() int {
return 0 return 0
} }
func (u unknownFormat) dwarf64() (bool, bool) { func (u UnknownFormat) dwarf64() (bool, bool) {
return false, false return false, false
} }
func (u unknownFormat) addrsize() int { func (u UnknownFormat) addrsize() int {
return 0 return 0
} }
func makeBuf(d *Data, format dataFormat, name string, off Offset, data []byte) buf { func MakeBuf(d *dwarf.Data, format dataFormat, name string, off dwarf.Offset, data []byte) buf {
return buf{d, d.order, format, name, off, data, nil} return buf{d, format, name, off, data, nil}
} }
func (b *buf) slice(length int) buf { func (b *buf) slice(length int) buf {
@ -63,7 +61,7 @@ func (b *buf) slice(length int) buf {
return n return n
} }
func (b *buf) uint8() uint8 { func (b *buf) Uint8() uint8 {
if len(b.data) < 1 { if len(b.data) < 1 {
b.error("underflow") b.error("underflow")
return 0 return 0
@ -81,7 +79,7 @@ func (b *buf) bytes(n int) []byte {
} }
data := b.data[0:n] data := b.data[0:n]
b.data = b.data[n:] b.data = b.data[n:]
b.off += Offset(n) b.off += dwarf.Offset(n)
return data return data
} }
@ -94,7 +92,7 @@ func (b *buf) string() string {
if b.data[i] == 0 { if b.data[i] == 0 {
s := string(b.data[0:i]) s := string(b.data[0:i])
b.data = b.data[i+1:] b.data = b.data[i+1:]
b.off += Offset(i + 1) b.off += dwarf.Offset(i + 1)
return s return s
} }
} }
@ -102,39 +100,15 @@ func (b *buf) string() string {
return "" return ""
} }
func (b *buf) uint16() uint16 {
a := b.bytes(2)
if a == nil {
return 0
}
return b.order.Uint16(a)
}
func (b *buf) uint32() uint32 {
a := b.bytes(4)
if a == nil {
return 0
}
return b.order.Uint32(a)
}
func (b *buf) uint64() uint64 {
a := b.bytes(8)
if a == nil {
return 0
}
return b.order.Uint64(a)
}
// Read a varint, which is 7 bits per byte, little endian. // Read a varint, which is 7 bits per byte, little endian.
// the 0x80 bit means read another byte. // the 0x80 bit means read another byte.
func (b *buf) varint() (c uint64, bits uint) { func (b *buf) Varint() (c uint64, bits uint) {
for i := 0; i < len(b.data); i++ { for i := 0; i < len(b.data); i++ {
byte := b.data[i] byte := b.data[i]
c |= uint64(byte&0x7F) << bits c |= uint64(byte&0x7F) << bits
bits += 7 bits += 7
if byte&0x80 == 0 { if byte&0x80 == 0 {
b.off += Offset(i + 1) b.off += dwarf.Offset(i + 1)
b.data = b.data[i+1:] b.data = b.data[i+1:]
return c, bits return c, bits
} }
@ -143,14 +117,14 @@ func (b *buf) varint() (c uint64, bits uint) {
} }
// Unsigned int is just a varint. // Unsigned int is just a varint.
func (b *buf) uint() uint64 { func (b *buf) Uint() uint64 {
x, _ := b.varint() x, _ := b.Varint()
return x return x
} }
// Signed int is a sign-extended varint. // Signed int is a sign-extended varint.
func (b *buf) int() int64 { func (b *buf) Int() int64 {
ux, bits := b.varint() ux, bits := b.Varint()
x := int64(ux) x := int64(ux)
if x&(1<<(bits-1)) != 0 { if x&(1<<(bits-1)) != 0 {
x |= -1 << bits x |= -1 << bits
@ -158,24 +132,8 @@ func (b *buf) int() int64 {
return x return x
} }
// Address-sized uint. // AssertEmpty checks that everything has been read from b.
func (b *buf) addr() uint64 { func (b *buf) AssertEmpty() {
switch b.format.addrsize() {
case 1:
return uint64(b.uint8())
case 2:
return uint64(b.uint16())
case 4:
return uint64(b.uint32())
case 8:
return uint64(b.uint64())
}
b.error("unknown address size")
return 0
}
// assertEmpty checks that everything has been read from b.
func (b *buf) assertEmpty() {
if len(b.data) == 0 { if len(b.data) == 0 {
return return
} }
@ -186,18 +144,8 @@ func (b *buf) assertEmpty() {
} }
func (b *buf) error(s string) { func (b *buf) error(s string) {
if b.err == nil { if b.Err == nil {
b.data = nil b.data = nil
b.err = DecodeError{b.name, b.off, s} b.Err = dwarf.DecodeError{b.name, b.off, s}
} }
} }
type DecodeError struct {
Name string
Offset Offset
Err string
}
func (e DecodeError) Error() string {
return "decoding dwarf section " + e.Name + " at offset 0x" + strconv.FormatInt(int64(e.Offset), 16) + ": " + e.Err
}

View File

@ -1,6 +1,12 @@
package util package util
import "bytes" import (
"bytes"
"io"
)
// The Little Endian Base 128 format is defined in the DWARF v4 standard,
// section 7.6, page 161 and following.
// DecodeULEB128 decodes an unsigned Little Endian Base 128 // DecodeULEB128 decodes an unsigned Little Endian Base 128
// represented number. // represented number.
@ -71,6 +77,45 @@ func DecodeSLEB128(buf *bytes.Buffer) (int64, uint32) {
return result, length return result, length
} }
// EncodeULEB128 encodes x to the unsigned Little Endian Base 128 format
// into out.
func EncodeULEB128(out io.ByteWriter, x uint64) {
for {
b := byte(x & 0x7f)
x = x >> 7
if x != 0 {
b = b | 0x80
}
out.WriteByte(b)
if x == 0 {
break
}
}
}
// EncodeSLEB128 encodes x to the signed Little Endian Base 128 format
// into out.
func EncodeSLEB128(out io.ByteWriter, x int64) {
for {
b := byte(x & 0x7f)
x >>= 7
signb := b & 0x40
last := false
if (x == 0 && signb == 0) || (x == -1 && signb != 0) {
last = true
} else {
b = b | 0x80
}
out.WriteByte(b)
if last {
break
}
}
}
func ParseString(data *bytes.Buffer) (string, uint32) { func ParseString(data *bytes.Buffer) (string, uint32) {
str, err := data.ReadString(0x0) str, err := data.ReadString(0x0)
if err != nil { if err != nil {

View File

@ -0,0 +1,178 @@
package goversion
import (
"os/exec"
"strconv"
"strings"
)
// GoVersion represents the Go version of
// the Go compiler version used to compile
// the target binary.
type GoVersion struct {
Major int
Minor int
Rev int
Beta int
RC int
Proposal string
}
var (
GoVer18Beta = GoVersion{1, 8, -1, 0, 0, ""}
)
// Parse parses a go version string
func Parse(ver string) (GoVersion, bool) {
var r GoVersion
var err1, err2, err3 error
if strings.HasPrefix(ver, "devel") {
return GoVersion{-1, 0, 0, 0, 0, ""}, true
}
if strings.HasPrefix(ver, "go") {
ver := strings.Split(ver, " ")[0]
v := strings.SplitN(ver[2:], ".", 4)
switch len(v) {
case 2:
r.Major, err1 = strconv.Atoi(v[0])
vr := strings.SplitN(v[1], "beta", 2)
if len(vr) == 2 {
r.Beta, err3 = strconv.Atoi(vr[1])
} else {
vr = strings.SplitN(v[1], "rc", 2)
if len(vr) == 2 {
r.RC, err3 = strconv.Atoi(vr[1])
} else {
r.Minor, err2 = strconv.Atoi(v[1])
if err2 != nil {
return GoVersion{}, false
}
return r, true
}
}
r.Minor, err2 = strconv.Atoi(vr[0])
r.Rev = -1
r.Proposal = ""
if err1 != nil || err2 != nil || err3 != nil {
return GoVersion{}, false
}
return r, true
case 3:
r.Major, err1 = strconv.Atoi(v[0])
r.Minor, err2 = strconv.Atoi(v[1])
r.Rev, err3 = strconv.Atoi(v[2])
r.Proposal = ""
if err1 != nil || err2 != nil || err3 != nil {
return GoVersion{}, false
}
return r, true
case 4:
r.Major, err1 = strconv.Atoi(v[0])
r.Minor, err2 = strconv.Atoi(v[1])
r.Rev, err3 = strconv.Atoi(v[2])
r.Proposal = v[3]
if err1 != nil || err2 != nil || err3 != nil || r.Proposal == "" {
return GoVersion{}, false
}
return r, true
default:
return GoVersion{}, false
}
}
return GoVersion{}, false
}
// AfterOrEqual returns whether one GoVersion is after or
// equal to the other.
func (v *GoVersion) AfterOrEqual(b GoVersion) bool {
if v.Major < b.Major {
return false
} else if v.Major > b.Major {
return true
}
if v.Minor < b.Minor {
return false
} else if v.Minor > b.Minor {
return true
}
if v.Rev < b.Rev {
return false
} else if v.Rev > b.Rev {
return true
}
if v.Beta < b.Beta {
return false
}
if v.RC < b.RC {
return false
}
return true
}
// IsDevel returns whether the GoVersion
// is a development version.
func (v *GoVersion) IsDevel() bool {
return v.Major < 0
}
const goVersionPrefix = "go version "
// Installed runs "go version" and parses the output
func Installed() (GoVersion, bool) {
out, err := exec.Command("go", "version").CombinedOutput()
if err != nil {
return GoVersion{}, false
}
s := string(out)
if !strings.HasPrefix(s, goVersionPrefix) {
return GoVersion{}, false
}
return Parse(s[len(goVersionPrefix):])
}
// VersionAfterOrEqual checks that version (as returned by runtime.Version()
// or go version) is major.minor or a later version, or a development
// version.
func VersionAfterOrEqual(version string, major, minor int) bool {
ver, _ := Parse(version)
if ver.IsDevel() {
return true
}
return ver.AfterOrEqual(GoVersion{major, minor, -1, 0, 0, ""})
}
const producerVersionPrefix = "Go cmd/compile "
// ProducerAfterOrEqual checks that the DW_AT_producer version is
// major.minor or a later version, or a development version.
func ProducerAfterOrEqual(producer string, major, minor int) bool {
if strings.HasPrefix(producer, producerVersionPrefix) {
producer = producer[len(producerVersionPrefix):]
}
ver, _ := Parse(producer)
if ver.IsDevel() {
return true
}
return ver.AfterOrEqual(GoVersion{major, minor, -1, 0, 0, ""})
}

View File

@ -0,0 +1,83 @@
package logflags
import (
"errors"
"io/ioutil"
"log"
"strings"
)
var debugger = false
var gdbWire = false
var lldbServerOutput = false
var debugLineErrors = false
var rpc = false
var fnCall = false
// GdbWire returns true if the gdbserial package should log all the packets
// exchanged with the stub.
func GdbWire() bool {
return gdbWire
}
// Debugger returns true if the debugger package should log.
func Debugger() bool {
return debugger
}
// LLDBServerOutput returns true if the output of the LLDB server should be
// redirected to standard output instead of suppressed.
func LLDBServerOutput() bool {
return lldbServerOutput
}
// DebugLineErrors returns true if pkg/dwarf/line should log its recoverable
// errors.
func DebugLineErrors() bool {
return debugLineErrors
}
// RPC returns true if rpc messages should be logged.
func RPC() bool {
return rpc
}
// FnCall returns true if the function call protocol should be logged.
func FnCall() bool {
return fnCall
}
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
// Setup sets debugger flags based on the contents of logstr.
func Setup(logFlag bool, logstr string) error {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
if !logFlag {
log.SetOutput(ioutil.Discard)
if logstr != "" {
return errLogstrWithoutLog
}
return nil
}
if logstr == "" {
logstr = "debugger"
}
v := strings.Split(logstr, ",")
for _, logcmd := range v {
switch logcmd {
case "debugger":
debugger = true
case "gdbwire":
gdbWire = true
case "lldbout":
lldbServerOutput = true
case "debuglineerr":
debugLineErrors = true
case "rpc":
rpc = true
case "fncall":
fnCall = true
}
}
return nil
}

306
vendor/github.com/derekparker/delve/pkg/proc/arch.go generated vendored Normal file
View File

@ -0,0 +1,306 @@
package proc
import (
"encoding/binary"
"github.com/derekparker/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/op"
"golang.org/x/arch/x86/x86asm"
)
// Arch defines an interface for representing a
// CPU architecture.
type Arch interface {
PtrSize() int
BreakpointInstruction() []byte
BreakpointSize() int
DerefTLS() bool
FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext
RegSize(uint64) int
RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters
GoroutineToDwarfRegisters(*G) op.DwarfRegisters
}
// AMD64 represents the AMD64 CPU architecture.
type AMD64 struct {
ptrSize int
breakInstruction []byte
breakInstructionLen int
gStructOffset uint64
hardwareBreakpointUsage []bool
goos string
// crosscall2fn is the DIE of crosscall2, a function used by the go runtime
// to call C functions. This function in go 1.9 (and previous versions) had
// a bad frame descriptor which needs to be fixed to generate good stack
// traces.
crosscall2fn *Function
// sigreturnfn is the DIE of runtime.sigreturn, the return trampoline for
// the signal handler. See comment in FixFrameUnwindContext for a
// description of why this is needed.
sigreturnfn *Function
}
const (
amd64DwarfIPRegNum uint64 = 16
amd64DwarfSPRegNum uint64 = 7
amd64DwarfBPRegNum uint64 = 6
)
// AMD64Arch returns an initialized AMD64
// struct.
func AMD64Arch(goos string) *AMD64 {
var breakInstr = []byte{0xCC}
return &AMD64{
ptrSize: 8,
breakInstruction: breakInstr,
breakInstructionLen: len(breakInstr),
hardwareBreakpointUsage: make([]bool, 4),
goos: goos,
}
}
// PtrSize returns the size of a pointer
// on this architecture.
func (a *AMD64) PtrSize() int {
return a.ptrSize
}
// BreakpointInstruction returns the Breakpoint
// instruction for this architecture.
func (a *AMD64) BreakpointInstruction() []byte {
return a.breakInstruction
}
// BreakpointSize returns the size of the
// breakpoint instruction on this architecture.
func (a *AMD64) BreakpointSize() int {
return a.breakInstructionLen
}
// DerefTLS returns true if the value of regs.TLS()+GStructOffset() is a
// pointer to the G struct
func (a *AMD64) DerefTLS() bool {
return a.goos == "windows"
}
const (
crosscall2SPOffsetBad = 0x8
crosscall2SPOffsetWindows = 0x118
crosscall2SPOffsetNonWindows = 0x58
)
// FixFrameUnwindContext adds default architecture rules to fctxt or returns
// the default frame unwind context if fctxt is nil.
func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext {
if a.sigreturnfn == nil {
a.sigreturnfn = bi.LookupFunc["runtime.sigreturn"]
}
if fctxt == nil || (a.sigreturnfn != nil && pc >= a.sigreturnfn.Entry && pc < a.sigreturnfn.End) {
//if true {
// When there's no frame descriptor entry use BP (the frame pointer) instead
// - return register is [bp + a.PtrSize()] (i.e. [cfa-a.PtrSize()])
// - cfa is bp + a.PtrSize()*2
// - bp is [bp] (i.e. [cfa-a.PtrSize()*2])
// - sp is cfa
// When the signal handler runs it will move the execution to the signal
// handling stack (installed using the sigaltstack system call).
// This isn't a proper stack switch: the pointer to g in TLS will still
// refer to whatever g was executing on that thread before the signal was
// received.
// Since go did not execute a stack switch the previous value of sp, pc
// and bp is not saved inside g.sched, as it normally would.
// The only way to recover is to either read sp/pc from the signal context
// parameter (the ucontext_t* parameter) or to unconditionally follow the
// frame pointer when we get to runtime.sigreturn (which is what we do
// here).
return &frame.FrameContext{
RetAddrReg: amd64DwarfIPRegNum,
Regs: map[uint64]frame.DWRule{
amd64DwarfIPRegNum: frame.DWRule{
Rule: frame.RuleOffset,
Offset: int64(-a.PtrSize()),
},
amd64DwarfBPRegNum: frame.DWRule{
Rule: frame.RuleOffset,
Offset: int64(-2 * a.PtrSize()),
},
amd64DwarfSPRegNum: frame.DWRule{
Rule: frame.RuleValOffset,
Offset: 0,
},
},
CFA: frame.DWRule{
Rule: frame.RuleCFA,
Reg: amd64DwarfBPRegNum,
Offset: int64(2 * a.PtrSize()),
},
}
}
if a.crosscall2fn == nil {
a.crosscall2fn = bi.LookupFunc["crosscall2"]
}
if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End {
rule := fctxt.CFA
if rule.Offset == crosscall2SPOffsetBad {
switch a.goos {
case "windows":
rule.Offset += crosscall2SPOffsetWindows
default:
rule.Offset += crosscall2SPOffsetNonWindows
}
}
fctxt.CFA = rule
}
// We assume that RBP is the frame pointer and we want to keep it updated,
// so that we can use it to unwind the stack even when we encounter frames
// without descriptor entries.
// If there isn't a rule already we emit one.
if fctxt.Regs[amd64DwarfBPRegNum].Rule == frame.RuleUndefined {
fctxt.Regs[amd64DwarfBPRegNum] = frame.DWRule{
Rule: frame.RuleFramePointer,
Reg: amd64DwarfBPRegNum,
Offset: 0,
}
}
return fctxt
}
// RegSize returns the size (in bytes) of register regnum.
// The mapping between hardware registers and DWARF registers is specified
// in the System V ABI AMD64 Architecture Processor Supplement page 57,
// figure 3.36
// https://www.uclibc.org/docs/psABI-x86_64.pdf
func (a *AMD64) RegSize(regnum uint64) int {
// XMM registers
if regnum > amd64DwarfIPRegNum && regnum <= 32 {
return 16
}
// x87 registers
if regnum >= 33 && regnum <= 40 {
return 10
}
return 8
}
// The mapping between hardware registers and DWARF registers is specified
// in the System V ABI AMD64 Architecture Processor Supplement page 57,
// figure 3.36
// https://www.uclibc.org/docs/psABI-x86_64.pdf
var asm64DwarfToHardware = map[int]x86asm.Reg{
0: x86asm.RAX,
1: x86asm.RDX,
2: x86asm.RCX,
3: x86asm.RBX,
4: x86asm.RSI,
5: x86asm.RDI,
8: x86asm.R8,
9: x86asm.R9,
10: x86asm.R10,
11: x86asm.R11,
12: x86asm.R12,
13: x86asm.R13,
14: x86asm.R14,
15: x86asm.R15,
}
var amd64DwarfToName = map[int]string{
17: "XMM0",
18: "XMM1",
19: "XMM2",
20: "XMM3",
21: "XMM4",
22: "XMM5",
23: "XMM6",
24: "XMM7",
25: "XMM8",
26: "XMM9",
27: "XMM10",
28: "XMM11",
29: "XMM12",
30: "XMM13",
31: "XMM14",
32: "XMM15",
33: "ST(0)",
34: "ST(1)",
35: "ST(2)",
36: "ST(3)",
37: "ST(4)",
38: "ST(5)",
39: "ST(6)",
40: "ST(7)",
49: "Eflags",
50: "Es",
51: "Cs",
52: "Ss",
53: "Ds",
54: "Fs",
55: "Gs",
58: "Fs_base",
59: "Gs_base",
64: "MXCSR",
65: "CW",
66: "SW",
}
func maxAmd64DwarfRegister() int {
max := int(amd64DwarfIPRegNum)
for i := range asm64DwarfToHardware {
if i > max {
max = i
}
}
for i := range amd64DwarfToName {
if i > max {
max = i
}
}
return max
}
// RegistersToDwarfRegisters converts hardware registers to the format used
// by the DWARF expression interpreter.
func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, maxAmd64DwarfRegister()+1)
dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC())
dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(regs.SP())
dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(regs.BP())
for dwarfReg, asmReg := range asm64DwarfToHardware {
v, err := regs.Get(int(asmReg))
if err == nil {
dregs[dwarfReg] = op.DwarfRegisterFromUint64(v)
}
}
for _, reg := range regs.Slice() {
for dwarfReg, regName := range amd64DwarfToName {
if regName == reg.Name {
dregs[dwarfReg] = op.DwarfRegisterFromBytes(reg.Bytes)
}
}
}
return op.DwarfRegisters{StaticBase: staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
}
// GoroutineToDwarfRegisters extract the saved DWARF registers from a parked
// goroutine in the format used by the DWARF expression interpreter.
func (a *AMD64) GoroutineToDwarfRegisters(g *G) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, amd64DwarfIPRegNum+1)
dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(g.PC)
dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(g.SP)
dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(g.BP)
return op.DwarfRegisters{StaticBase: g.variable.bi.staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
}

878
vendor/github.com/derekparker/delve/pkg/proc/bininfo.go generated vendored Normal file
View File

@ -0,0 +1,878 @@
package proc
import (
"bytes"
"debug/dwarf"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"sort"
"strings"
"sync"
"time"
"github.com/derekparker/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/line"
"github.com/derekparker/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/goversion"
)
// BinaryInfo holds information on the binary being executed.
type BinaryInfo struct {
lastModified time.Time // Time the executable of this process was last modified
GOOS string
closer io.Closer
sepDebugCloser io.Closer
staticBase uint64
// Maps package names to package paths, needed to lookup types inside DWARF info
packageMap map[string]string
Arch Arch
dwarf *dwarf.Data
frameEntries frame.FrameDescriptionEntries
loclist loclistReader
compileUnits []*compileUnit
types map[string]dwarf.Offset
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
gStructOffset uint64
// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
Functions []Function
// Sources is a list of all source files found in debug_line.
Sources []string
// LookupFunc maps function names to a description of the function.
LookupFunc map[string]*Function
typeCache map[dwarf.Offset]godwarf.Type
loadModuleDataOnce sync.Once
moduleData []moduleData
nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry
// runtimeTypeToDIE maps between the offset of a runtime._type in
// runtime.moduledata.types and the offset of the DIE in debug_info. This
// map is filled by using the extended attribute godwarf.AttrGoRuntimeType
// which was added in go 1.11.
runtimeTypeToDIE map[uint64]runtimeTypeDIE
// consts[off] lists all the constants with the type defined at offset off.
consts constantsMap
loadErrMu sync.Mutex
loadErr error
dwarfReader *dwarf.Reader
}
// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedLinuxArch = errors.New("unsupported architecture - only linux/amd64 is supported")
// ErrUnsupportedWindowsArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported")
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
type compileUnit struct {
Name string // univocal name for non-go compile units
LowPC uint64
Ranges [][2]uint64
entry *dwarf.Entry // debug_info entry describing this compile unit
isgo bool // true if this is the go compile unit
lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit
concreteInlinedFns []inlinedFn // list of concrete inlined functions within this compile unit
optimized bool // this compile unit is optimized
producer string // producer attribute
startOffset, endOffset dwarf.Offset // interval of offsets contained in this compile unit
}
type partialUnitConstant struct {
name string
typ dwarf.Offset
value int64
}
type partialUnit struct {
entry *dwarf.Entry
types map[string]dwarf.Offset
variables []packageVar
constants []partialUnitConstant
functions []Function
}
// inlinedFn represents a concrete inlined function, e.g.
// an entry for the generated code of an inlined function.
type inlinedFn struct {
Name string // Name of the function that was inlined
LowPC, HighPC uint64 // Address range of the generated inlined instructions
CallFile string // File of the call site of the inlined function
CallLine int64 // Line of the call site of the inlined function
Parent *Function // The function that contains this inlined function
}
// Function describes a function in the target program.
type Function struct {
Name string
Entry, End uint64 // same as DW_AT_lowpc and DW_AT_highpc
offset dwarf.Offset
cu *compileUnit
}
// PackageName returns the package part of the symbol name,
// or the empty string if there is none.
// Borrowed from $GOROOT/debug/gosym/symtab.go
func (fn *Function) PackageName() string {
return packageName(fn.Name)
}
func packageName(name string) string {
pathend := strings.LastIndex(name, "/")
if pathend < 0 {
pathend = 0
}
if i := strings.Index(name[pathend:], "."); i != -1 {
return name[:pathend+i]
}
return ""
}
// ReceiverName returns the receiver type name of this symbol,
// or the empty string if there is none.
// Borrowed from $GOROOT/debug/gosym/symtab.go
func (fn *Function) ReceiverName() string {
pathend := strings.LastIndex(fn.Name, "/")
if pathend < 0 {
pathend = 0
}
l := strings.Index(fn.Name[pathend:], ".")
r := strings.LastIndex(fn.Name[pathend:], ".")
if l == -1 || r == -1 || l == r {
return ""
}
return fn.Name[pathend+l+1 : pathend+r]
}
// BaseName returns the symbol name without the package or receiver name.
// Borrowed from $GOROOT/debug/gosym/symtab.go
func (fn *Function) BaseName() string {
if i := strings.LastIndex(fn.Name, "."); i != -1 {
return fn.Name[i+1:]
}
return fn.Name
}
// Optimized returns true if the function was optimized by the compiler.
func (fn *Function) Optimized() bool {
return fn.cu.optimized
}
type constantsMap map[dwarf.Offset]*constantType
type constantType struct {
initialized bool
values []constantValue
}
type constantValue struct {
name string
fullName string
value int64
singleBit bool
}
// packageVar represents a package-level variable (or a C global variable).
// If a global variable does not have an address (for example it's stored in
// a register, or non-contiguously) addr will be 0.
type packageVar struct {
name string
offset dwarf.Offset
addr uint64
}
type loclistReader struct {
data []byte
cur int
ptrSz int
}
func (rdr *loclistReader) Seek(off int) {
rdr.cur = off
}
func (rdr *loclistReader) read(sz int) []byte {
r := rdr.data[rdr.cur : rdr.cur+sz]
rdr.cur += sz
return r
}
func (rdr *loclistReader) oneAddr() uint64 {
switch rdr.ptrSz {
case 4:
addr := binary.LittleEndian.Uint32(rdr.read(rdr.ptrSz))
if addr == ^uint32(0) {
return ^uint64(0)
}
return uint64(addr)
case 8:
addr := uint64(binary.LittleEndian.Uint64(rdr.read(rdr.ptrSz)))
return addr
default:
panic("bad address size")
}
}
func (rdr *loclistReader) Next(e *loclistEntry) bool {
e.lowpc = rdr.oneAddr()
e.highpc = rdr.oneAddr()
if e.lowpc == 0 && e.highpc == 0 {
return false
}
if e.BaseAddressSelection() {
e.instr = nil
return true
}
instrlen := binary.LittleEndian.Uint16(rdr.read(2))
e.instr = rdr.read(int(instrlen))
return true
}
type loclistEntry struct {
lowpc, highpc uint64
instr []byte
}
type runtimeTypeDIE struct {
offset dwarf.Offset
kind int64
}
func (e *loclistEntry) BaseAddressSelection() bool {
return e.lowpc == ^uint64(0)
}
type buildIDHeader struct {
Namesz uint32
Descsz uint32
Type uint32
}
// NewBinaryInfo returns an initialized but unloaded BinaryInfo struct.
func NewBinaryInfo(goos, goarch string) *BinaryInfo {
r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)}
// TODO: find better way to determine proc arch (perhaps use executable file info).
switch goarch {
case "amd64":
r.Arch = AMD64Arch(goos)
}
return r
}
// LoadBinaryInfo will load and store the information from the binary at 'path'.
// It is expected this will be called in parallel with other initialization steps
// so a sync.WaitGroup must be provided.
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, wg *sync.WaitGroup) error {
fi, err := os.Stat(path)
if err == nil {
bi.lastModified = fi.ModTime()
}
switch bi.GOOS {
case "linux":
return bi.LoadBinaryInfoElf(path, entryPoint, wg)
case "windows":
return bi.LoadBinaryInfoPE(path, entryPoint, wg)
case "darwin":
return bi.LoadBinaryInfoMacho(path, entryPoint, wg)
}
return errors.New("unsupported operating system")
return nil
}
// GStructOffset returns the offset of the G
// struct in thread local storage.
func (bi *BinaryInfo) GStructOffset() uint64 {
return bi.gStructOffset
}
// LastModified returns the last modified time of the binary.
func (bi *BinaryInfo) LastModified() time.Time {
return bi.lastModified
}
// DwarfReader returns a reader for the dwarf data
func (bi *BinaryInfo) DwarfReader() *reader.Reader {
return reader.New(bi.dwarf)
}
// Types returns list of types present in the debugged program.
func (bi *BinaryInfo) Types() ([]string, error) {
types := make([]string, 0, len(bi.types))
for k := range bi.types {
types = append(types, k)
}
return types, nil
}
// PCToLine converts an instruction address to a file/line/function.
func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *Function) {
fn := bi.PCToFunc(pc)
if fn == nil {
return "", 0, nil
}
f, ln := fn.cu.lineInfo.PCToLine(fn.Entry, pc)
return f, ln, fn
}
// LineToPC converts a file:line into a memory address.
func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *Function, err error) {
for _, cu := range bi.compileUnits {
if cu.lineInfo.Lookup[filename] != nil {
pc = cu.lineInfo.LineToPC(filename, lineno)
if pc == 0 {
// Check to see if this file:line belongs to the call site
// of an inlined function.
for _, ifn := range cu.concreteInlinedFns {
if strings.Contains(ifn.CallFile, filename) && ifn.CallLine == int64(lineno) {
pc = ifn.LowPC
fn = ifn.Parent
return
}
}
}
fn = bi.PCToFunc(pc)
if fn != nil {
return
}
}
}
err = fmt.Errorf("could not find %s:%d", filename, lineno)
return
}
// AllPCsForFileLine returns all PC addresses for the given filename:lineno.
func (bi *BinaryInfo) AllPCsForFileLine(filename string, lineno int) []uint64 {
r := make([]uint64, 0, 1)
for _, cu := range bi.compileUnits {
if cu.lineInfo.Lookup[filename] != nil {
r = append(r, cu.lineInfo.AllPCsForFileLine(filename, lineno)...)
}
}
return r
}
// PCToFunc returns the function containing the given PC address
func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
i := sort.Search(len(bi.Functions), func(i int) bool {
fn := bi.Functions[i]
return pc <= fn.Entry || (fn.Entry <= pc && pc < fn.End)
})
if i != len(bi.Functions) {
fn := &bi.Functions[i]
if fn.Entry <= pc && pc < fn.End {
return fn
}
}
return nil
}
// Close closes all internal readers.
func (bi *BinaryInfo) Close() error {
if bi.sepDebugCloser != nil {
bi.sepDebugCloser.Close()
}
return bi.closer.Close()
}
func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) {
bi.loadErrMu.Lock()
bi.loadErr = fmt.Errorf(fmtstr, args...)
bi.loadErrMu.Unlock()
}
// LoadError returns any internal load error.
func (bi *BinaryInfo) LoadError() error {
return bi.loadErr
}
type nilCloser struct{}
func (c *nilCloser) Close() error { return nil }
// LoadFromData creates a new BinaryInfo object using the specified data.
// This is used for debugging BinaryInfo, you should use LoadBinary instead.
func (bi *BinaryInfo) LoadFromData(dwdata *dwarf.Data, debugFrameBytes, debugLineBytes, debugLocBytes []byte) {
bi.closer = (*nilCloser)(nil)
bi.sepDebugCloser = (*nilCloser)(nil)
bi.dwarf = dwdata
if debugFrameBytes != nil {
bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), bi.staticBase)
}
bi.loclistInit(debugLocBytes)
bi.loadDebugInfoMaps(debugLineBytes, nil, nil)
}
func (bi *BinaryInfo) loclistInit(data []byte) {
bi.loclist.data = data
bi.loclist.ptrSz = bi.Arch.PtrSize()
}
func (bi *BinaryInfo) locationExpr(entry reader.Entry, attr dwarf.Attr, pc uint64) ([]byte, string, error) {
a := entry.Val(attr)
if a == nil {
return nil, "", fmt.Errorf("no location attribute %s", attr)
}
if instr, ok := a.([]byte); ok {
var descr bytes.Buffer
fmt.Fprintf(&descr, "[block] ")
op.PrettyPrint(&descr, instr)
return instr, descr.String(), nil
}
off, ok := a.(int64)
if !ok {
return nil, "", fmt.Errorf("could not interpret location attribute %s", attr)
}
if bi.loclist.data == nil {
return nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x (no debug_loc section found)", off, pc)
}
instr := bi.loclistEntry(off, pc)
if instr == nil {
return nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x", off, pc)
}
var descr bytes.Buffer
fmt.Fprintf(&descr, "[%#x:%#x] ", off, pc)
op.PrettyPrint(&descr, instr)
return instr, descr.String(), nil
}
// Location returns the location described by attribute attr of entry.
// This will either be an int64 address or a slice of Pieces for locations
// that don't correspond to a single memory address (registers, composite
// locations).
func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, regs op.DwarfRegisters) (int64, []op.Piece, string, error) {
instr, descr, err := bi.locationExpr(entry, attr, pc)
if err != nil {
return 0, nil, "", err
}
addr, pieces, err := op.ExecuteStackProgram(regs, instr)
return addr, pieces, descr, err
}
// loclistEntry returns the loclist entry in the loclist starting at off,
// for address pc.
func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
var base uint64
if cu := bi.findCompileUnit(pc); cu != nil {
base = cu.LowPC
}
bi.loclist.Seek(int(off))
var e loclistEntry
for bi.loclist.Next(&e) {
if e.BaseAddressSelection() {
base = e.highpc
continue
}
if pc >= e.lowpc+base && pc < e.highpc+base {
return e.instr
}
}
return nil
}
// findCompileUnit returns the compile unit containing address pc.
func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit {
for _, cu := range bi.compileUnits {
for _, rng := range cu.Ranges {
if pc >= rng[0] && pc < rng[1] {
return cu
}
}
}
return nil
}
func (bi *BinaryInfo) findCompileUnitForOffset(off dwarf.Offset) *compileUnit {
for _, cu := range bi.compileUnits {
if off >= cu.startOffset && off < cu.endOffset {
return cu
}
}
return nil
}
// Producer returns the value of DW_AT_producer.
func (bi *BinaryInfo) Producer() string {
for _, cu := range bi.compileUnits {
if cu.isgo && cu.producer != "" {
return cu.producer
}
}
return ""
}
// Type returns the Dwarf type entry at `offset`.
func (bi *BinaryInfo) Type(offset dwarf.Offset) (godwarf.Type, error) {
return godwarf.ReadType(bi.dwarf, offset, bi.typeCache)
}
// ELF ///////////////////////////////////////////////////////////////
// ErrNoBuildIDNote is used in openSeparateDebugInfo to signal there's no
// build-id note on the binary, so LoadBinaryInfoElf will return
// the error message coming from elfFile.DWARF() instead.
type ErrNoBuildIDNote struct{}
func (e *ErrNoBuildIDNote) Error() string {
return "can't find build-id note on binary"
}
// openSeparateDebugInfo searches for a file containing the separate
// debug info for the binary using the "build ID" method as described
// in GDB's documentation [1], and if found returns two handles, one
// for the bare file, and another for its corresponding elf.File.
// [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, error) {
buildid := exe.Section(".note.gnu.build-id")
if buildid == nil {
return nil, nil, &ErrNoBuildIDNote{}
}
br := buildid.Open()
bh := new(buildIDHeader)
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
return nil, nil, errors.New("can't read build-id header: " + err.Error())
}
name := make([]byte, bh.Namesz)
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
return nil, nil, errors.New("can't read build-id name: " + err.Error())
}
if strings.TrimSpace(string(name)) != "GNU\x00" {
return nil, nil, errors.New("invalid build-id signature")
}
descBinary := make([]byte, bh.Descsz)
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
return nil, nil, errors.New("can't read build-id desc: " + err.Error())
}
desc := hex.EncodeToString(descBinary)
debugPath := fmt.Sprintf("/usr/lib/debug/.build-id/%s/%s.debug", desc[:2], desc[2:])
sepFile, err := os.OpenFile(debugPath, 0, os.ModePerm)
if err != nil {
return nil, nil, errors.New("can't open separate debug file: " + err.Error())
}
elfFile, err := elf.NewFile(sepFile)
if err != nil {
sepFile.Close()
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, err.Error())
}
if elfFile.Machine != elf.EM_X86_64 {
sepFile.Close()
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, ErrUnsupportedLinuxArch.Error())
}
return sepFile, elfFile, nil
}
// LoadBinaryInfoElf specifically loads information from an ELF binary.
func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync.WaitGroup) error {
exe, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return err
}
bi.closer = exe
elfFile, err := elf.NewFile(exe)
if err != nil {
return err
}
if elfFile.Machine != elf.EM_X86_64 {
return ErrUnsupportedLinuxArch
}
if entryPoint != 0 {
bi.staticBase = entryPoint - elfFile.Entry
} else {
if elfFile.Type == elf.ET_DYN {
return ErrCouldNotDetermineRelocation
}
}
dwarfFile := elfFile
bi.dwarf, err = elfFile.DWARF()
if err != nil {
var sepFile *os.File
var serr error
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile)
if serr != nil {
if _, ok := serr.(*ErrNoBuildIDNote); ok {
return err
}
return serr
}
bi.sepDebugCloser = sepFile
bi.dwarf, err = dwarfFile.DWARF()
if err != nil {
return err
}
}
bi.dwarfReader = bi.dwarf.Reader()
debugLineBytes, err := godwarf.GetDebugSectionElf(dwarfFile, "line")
if err != nil {
return err
}
debugLocBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "loc")
bi.loclistInit(debugLocBytes)
wg.Add(3)
go bi.parseDebugFrameElf(dwarfFile, wg)
go bi.loadDebugInfoMaps(debugLineBytes, wg, nil)
go bi.setGStructOffsetElf(dwarfFile, wg)
return nil
}
func (bi *BinaryInfo) parseDebugFrameElf(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameData, err := godwarf.GetDebugSectionElf(exe, "frame")
if err != nil {
bi.setLoadError("could not get .debug_frame section: %v", err)
return
}
debugInfoData, err := godwarf.GetDebugSectionElf(exe, "info")
if err != nil {
bi.setLoadError("could not get .debug_info section: %v", err)
return
}
bi.frameEntries = frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), bi.staticBase)
}
func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
// This is a bit arcane. Essentially:
// - If the program is pure Go, it can do whatever it wants, and puts the G
// pointer at %fs-8.
// - Otherwise, Go asks the external linker to place the G pointer by
// emitting runtime.tlsg, a TLS symbol, which is relocated to the chosen
// offset in libc's TLS block.
symbols, err := exe.Symbols()
if err != nil {
bi.setLoadError("could not parse ELF symbols: %v", err)
return
}
var tlsg *elf.Symbol
for _, symbol := range symbols {
if symbol.Name == "runtime.tlsg" {
s := symbol
tlsg = &s
break
}
}
if tlsg == nil {
bi.gStructOffset = ^uint64(8) + 1 // -8
return
}
var tls *elf.Prog
for _, prog := range exe.Progs {
if prog.Type == elf.PT_TLS {
tls = prog
break
}
}
// The TLS register points to the end of the TLS block, which is
// tls.Memsz long. runtime.tlsg is an offset from the beginning of that block.
bi.gStructOffset = ^(tls.Memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value
}
// PE ////////////////////////////////////////////////////////////////
const _IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
// LoadBinaryInfoPE specifically loads information from a PE binary.
func (bi *BinaryInfo) LoadBinaryInfoPE(path string, entryPoint uint64, wg *sync.WaitGroup) error {
peFile, closer, err := openExecutablePathPE(path)
if err != nil {
return err
}
bi.closer = closer
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
return ErrUnsupportedWindowsArch
}
bi.dwarf, err = peFile.DWARF()
if err != nil {
return err
}
//TODO(aarzilli): actually test this when Go supports PIE buildmode on Windows.
opth := peFile.OptionalHeader.(*pe.OptionalHeader64)
if entryPoint != 0 {
bi.staticBase = entryPoint - opth.ImageBase
} else {
if opth.DllCharacteristics&_IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE != 0 {
return ErrCouldNotDetermineRelocation
}
}
bi.dwarfReader = bi.dwarf.Reader()
debugLineBytes, err := godwarf.GetDebugSectionPE(peFile, "line")
if err != nil {
return err
}
debugLocBytes, _ := godwarf.GetDebugSectionPE(peFile, "loc")
bi.loclistInit(debugLocBytes)
wg.Add(2)
go bi.parseDebugFramePE(peFile, wg)
go bi.loadDebugInfoMaps(debugLineBytes, wg, nil)
// Use ArbitraryUserPointer (0x28) as pointer to pointer
// to G struct per:
// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
bi.gStructOffset = 0x28
return nil
}
func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return nil, nil, err
}
peFile, err := pe.NewFile(f)
if err != nil {
f.Close()
return nil, nil, err
}
return peFile, f, nil
}
func (bi *BinaryInfo) parseDebugFramePE(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameBytes, err := godwarf.GetDebugSectionPE(exe, "frame")
if err != nil {
bi.setLoadError("could not get .debug_frame section: %v", err)
return
}
debugInfoBytes, err := godwarf.GetDebugSectionPE(exe, "info")
if err != nil {
bi.setLoadError("could not get .debug_info section: %v", err)
return
}
bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), bi.staticBase)
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
for _, s := range f.Symbols {
if s.Name != name {
continue
}
if s.SectionNumber <= 0 {
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
}
if len(f.Sections) < int(s.SectionNumber) {
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
}
return s, nil
}
return nil, fmt.Errorf("no %s symbol found", name)
}
// MACH-O ////////////////////////////////////////////////////////////
// LoadBinaryInfoMacho specifically loads information from a Mach-O binary.
func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, entryPoint uint64, wg *sync.WaitGroup) error {
exe, err := macho.Open(path)
if err != nil {
return err
}
bi.closer = exe
if exe.Cpu != macho.CpuAmd64 {
return ErrUnsupportedDarwinArch
}
bi.dwarf, err = exe.DWARF()
if err != nil {
return err
}
bi.dwarfReader = bi.dwarf.Reader()
debugLineBytes, err := godwarf.GetDebugSectionMacho(exe, "line")
if err != nil {
return err
}
debugLocBytes, _ := godwarf.GetDebugSectionMacho(exe, "loc")
bi.loclistInit(debugLocBytes)
wg.Add(2)
go bi.parseDebugFrameMacho(exe, wg)
go bi.loadDebugInfoMaps(debugLineBytes, wg, bi.setGStructOffsetMacho)
return nil
}
func (bi *BinaryInfo) setGStructOffsetMacho() {
// In go1.11 it's 0x30, before 0x8a0, see:
// https://github.com/golang/go/issues/23617
// and go commit b3a854c733257c5249c3435ffcee194f8439676a
producer := bi.Producer()
if producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 11) {
bi.gStructOffset = 0x30
return
}
bi.gStructOffset = 0x8a0
}
func (bi *BinaryInfo) parseDebugFrameMacho(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameBytes, err := godwarf.GetDebugSectionMacho(exe, "frame")
if err != nil {
bi.setLoadError("could not get __debug_frame section: %v", err)
return
}
debugInfoBytes, err := godwarf.GetDebugSectionMacho(exe, "info")
if err != nil {
bi.setLoadError("could not get .debug_info section: %v", err)
return
}
bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), bi.staticBase)
}

View File

@ -0,0 +1,437 @@
package proc
import (
"errors"
"fmt"
"go/ast"
"go/constant"
"reflect"
)
// Breakpoint represents a breakpoint. Stores information on the break
// point including the byte of data that originally was stored at that
// address.
type Breakpoint struct {
// File & line information for printing.
FunctionName string
File string
Line int
Addr uint64 // Address breakpoint is set for.
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
Name string // User defined name of the breakpoint
ID int // Monotonically increasing ID.
// Kind describes whether this is an internal breakpoint (for next'ing or
// stepping).
// A single breakpoint can be both a UserBreakpoint and some kind of
// internal breakpoint, but it can not be two different kinds of internal
// breakpoint.
Kind BreakpointKind
// Breakpoint information
Tracepoint bool // Tracepoint flag
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached
// DeferReturns: when kind == NextDeferBreakpoint this breakpoint
// will also check if the caller is runtime.gopanic or if the return
// address is in the DeferReturns array.
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
// deferred function, DeferReturns is populated with the
// addresses of calls to runtime.deferreturn in the current
// function. This insures that the breakpoint on the deferred
// function only triggers on panic or on the defer call to
// the function, not when the function is called directly
DeferReturns []uint64
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
// internalCond is the same as Cond but used for the condition of internal breakpoints
internalCond ast.Expr
// ReturnInfo describes how to collect return variables when this
// breakpoint is hit as a return breakpoint.
returnInfo *returnBreakpointInfo
}
// BreakpointKind determines the behavior of delve when the
// breakpoint is reached.
type BreakpointKind uint16
const (
// UserBreakpoint is a user set breakpoint
UserBreakpoint BreakpointKind = (1 << iota)
// NextBreakpoint is a breakpoint set by Next, Continue
// will stop on it and delete it
NextBreakpoint
// NextDeferBreakpoint is a breakpoint set by Next on the
// first deferred function. In addition to checking their condition
// breakpoints of this kind will also check that the function has been
// called by runtime.gopanic or through runtime.deferreturn.
NextDeferBreakpoint
// StepBreakpoint is a breakpoint set by Step on a CALL instruction,
// Continue will set a new breakpoint (of NextBreakpoint kind) on the
// destination of CALL, delete this breakpoint and then continue again
StepBreakpoint
)
func (bp *Breakpoint) String() string {
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.ID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
}
// BreakpointExistsError is returned when trying to set a breakpoint at
// an address that already has a breakpoint set for it.
type BreakpointExistsError struct {
File string
Line int
Addr uint64
}
func (bpe BreakpointExistsError) Error() string {
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.File, bpe.Line, bpe.Addr)
}
// InvalidAddressError represents the result of
// attempting to set a breakpoint at an invalid address.
type InvalidAddressError struct {
Address uint64
}
func (iae InvalidAddressError) Error() string {
return fmt.Sprintf("Invalid address %#v\n", iae.Address)
}
type returnBreakpointInfo struct {
retFrameCond ast.Expr
fn *Function
frameOffset int64
spOffset int64
}
// CheckCondition evaluates bp's condition on thread.
func (bp *Breakpoint) CheckCondition(thread Thread) BreakpointState {
bpstate := BreakpointState{Breakpoint: bp, Active: false, Internal: false, CondError: nil}
if bp.Cond == nil && bp.internalCond == nil {
bpstate.Active = true
bpstate.Internal = bp.IsInternal()
return bpstate
}
nextDeferOk := true
if bp.Kind&NextDeferBreakpoint != 0 {
frames, err := ThreadStacktrace(thread, 2)
if err == nil {
ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
isdeferreturn := false
if len(frames) >= 1 {
for _, pc := range bp.DeferReturns {
if frames[0].Ret == pc {
isdeferreturn = true
break
}
}
}
nextDeferOk = ispanic || isdeferreturn
}
}
if bp.IsInternal() {
// Check internalCondition if this is also an internal breakpoint
bpstate.Active, bpstate.CondError = evalBreakpointCondition(thread, bp.internalCond)
bpstate.Active = bpstate.Active && nextDeferOk
if bpstate.Active || bpstate.CondError != nil {
bpstate.Internal = true
return bpstate
}
}
if bp.IsUser() {
// Check normal condition if this is also a user breakpoint
bpstate.Active, bpstate.CondError = evalBreakpointCondition(thread, bp.Cond)
}
return bpstate
}
// IsInternal returns true if bp is an internal breakpoint.
// User-set breakpoints can overlap with internal breakpoints, in that case
// both IsUser and IsInternal will be true.
func (bp *Breakpoint) IsInternal() bool {
return bp.Kind != UserBreakpoint
}
// IsUser returns true if bp is a user-set breakpoint.
// User-set breakpoints can overlap with internal breakpoints, in that case
// both IsUser and IsInternal will be true.
func (bp *Breakpoint) IsUser() bool {
return bp.Kind&UserBreakpoint != 0
}
func evalBreakpointCondition(thread Thread, cond ast.Expr) (bool, error) {
if cond == nil {
return true, nil
}
scope, err := GoroutineScope(thread)
if err != nil {
return true, err
}
v, err := scope.evalAST(cond)
if err != nil {
return true, fmt.Errorf("error evaluating expression: %v", err)
}
if v.Kind != reflect.Bool {
return true, errors.New("condition expression not boolean")
}
v.loadValue(loadFullValue)
if v.Unreadable != nil {
return true, fmt.Errorf("condition expression unreadable: %v", v.Unreadable)
}
return constant.BoolVal(v.Value), nil
}
// NoBreakpointError is returned when trying to
// clear a breakpoint that does not exist.
type NoBreakpointError struct {
Addr uint64
}
func (nbp NoBreakpointError) Error() string {
return fmt.Sprintf("no breakpoint at %#v", nbp.Addr)
}
// BreakpointMap represents an (address, breakpoint) map.
type BreakpointMap struct {
M map[uint64]*Breakpoint
breakpointIDCounter int
internalBreakpointIDCounter int
}
// NewBreakpointMap creates a new BreakpointMap.
func NewBreakpointMap() BreakpointMap {
return BreakpointMap{
M: make(map[uint64]*Breakpoint),
}
}
// ResetBreakpointIDCounter resets the breakpoint ID counter of bpmap.
func (bpmap *BreakpointMap) ResetBreakpointIDCounter() {
bpmap.breakpointIDCounter = 0
}
type writeBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error)
type clearBreakpointFn func(*Breakpoint) error
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
// function, call proc.Process.SetBreakpoint instead, this function exists
// to implement proc.Process.SetBreakpoint.
func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) {
if bp, ok := bpmap.M[addr]; ok {
// We can overlap one internal breakpoint with one user breakpoint, we
// need to support this otherwise a conditional breakpoint can mask a
// breakpoint set by next or step.
if (kind != UserBreakpoint && bp.Kind != UserBreakpoint) || (kind == UserBreakpoint && bp.IsUser()) {
return bp, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
}
bp.Kind |= kind
if kind != UserBreakpoint {
bp.internalCond = cond
} else {
bp.Cond = cond
}
return bp, nil
}
f, l, fn, originalData, err := writeBreakpoint(addr)
if err != nil {
return nil, err
}
newBreakpoint := &Breakpoint{
FunctionName: fn.Name,
File: f,
Line: l,
Addr: addr,
Kind: kind,
OriginalData: originalData,
HitCount: map[int]uint64{},
}
if kind != UserBreakpoint {
bpmap.internalBreakpointIDCounter++
newBreakpoint.ID = bpmap.internalBreakpointIDCounter
newBreakpoint.internalCond = cond
} else {
bpmap.breakpointIDCounter++
newBreakpoint.ID = bpmap.breakpointIDCounter
newBreakpoint.Cond = cond
}
bpmap.M[addr] = newBreakpoint
return newBreakpoint, nil
}
// SetWithID creates a breakpoint at addr, with the specified ID.
func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) {
bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint)
if err == nil {
bp.ID = id
bpmap.breakpointIDCounter--
}
return bp, err
}
// Clear clears the breakpoint at addr.
// Do not call this function call proc.Process.ClearBreakpoint instead.
func (bpmap *BreakpointMap) Clear(addr uint64, clearBreakpoint clearBreakpointFn) (*Breakpoint, error) {
bp, ok := bpmap.M[addr]
if !ok {
return nil, NoBreakpointError{Addr: addr}
}
bp.Kind &= ^UserBreakpoint
bp.Cond = nil
if bp.Kind != 0 {
return bp, nil
}
if err := clearBreakpoint(bp); err != nil {
return nil, err
}
delete(bpmap.M, addr)
return bp, nil
}
// ClearInternalBreakpoints removes all internal breakpoints from the map,
// calling clearBreakpoint on each one.
// Do not call this function, call proc.Process.ClearInternalBreakpoints
// instead, this function is used to implement that.
func (bpmap *BreakpointMap) ClearInternalBreakpoints(clearBreakpoint clearBreakpointFn) error {
for addr, bp := range bpmap.M {
bp.Kind = bp.Kind & UserBreakpoint
bp.internalCond = nil
bp.returnInfo = nil
if bp.Kind != 0 {
continue
}
if err := clearBreakpoint(bp); err != nil {
return err
}
delete(bpmap.M, addr)
}
return nil
}
// HasInternalBreakpoints returns true if bpmap has at least one internal
// breakpoint set.
func (bpmap *BreakpointMap) HasInternalBreakpoints() bool {
for _, bp := range bpmap.M {
if bp.IsInternal() {
return true
}
}
return false
}
// BreakpointState describes the state of a breakpoint in a thread.
type BreakpointState struct {
*Breakpoint
// Active is true if the breakpoint condition was met.
Active bool
// Internal is true if the breakpoint was matched as an internal
// breakpoint.
Internal bool
// CondError contains any error encountered while evaluating the
// breakpoint's condition.
CondError error
}
// Clear zeros the struct.
func (bpstate *BreakpointState) Clear() {
bpstate.Breakpoint = nil
bpstate.Active = false
bpstate.Internal = false
bpstate.CondError = nil
}
func (bpstate *BreakpointState) String() string {
s := bpstate.Breakpoint.String()
if bpstate.Active {
s += " active"
}
if bpstate.Internal {
s += " internal"
}
return s
}
func configureReturnBreakpoint(bi *BinaryInfo, bp *Breakpoint, topframe *Stackframe, retFrameCond ast.Expr) {
if topframe.Current.Fn == nil {
return
}
bp.returnInfo = &returnBreakpointInfo{
retFrameCond: retFrameCond,
fn: topframe.Current.Fn,
frameOffset: topframe.FrameOffset(),
spOffset: topframe.FrameOffset() - int64(bi.Arch.PtrSize()), // must be the value that SP had at the entry point of the function
}
}
func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
if rbpi == nil {
return nil
}
g, err := GetG(thread)
if err != nil {
return returnInfoError("could not get g", err, thread)
}
scope, err := GoroutineScope(thread)
if err != nil {
return returnInfoError("could not get scope", err, thread)
}
v, err := scope.evalAST(rbpi.retFrameCond)
if err != nil || v.Unreadable != nil || v.Kind != reflect.Bool {
// This condition was evaluated as part of the breakpoint condition
// evaluation, if the errors happen they will be reported as part of the
// condition errors.
return nil
}
if !constant.BoolVal(v.Value) {
// Breakpoint not hit as a return breakpoint.
return nil
}
oldFrameOffset := rbpi.frameOffset + int64(g.stackhi)
oldSP := uint64(rbpi.spOffset + int64(g.stackhi))
err = fakeFunctionEntryScope(scope, rbpi.fn, oldFrameOffset, oldSP)
if err != nil {
return returnInfoError("could not read function entry", err, thread)
}
vars, err := scope.Locals()
if err != nil {
return returnInfoError("could not evaluate return variables", err, thread)
}
vars = filterVariables(vars, func(v *Variable) bool {
return (v.Flags & VariableReturnArgument) != 0
})
// Go saves the return variables in the opposite order that the user
// specifies them so here we reverse the slice to make it easier to
// understand.
for i := 0; i < len(vars)/2; i++ {
vars[i], vars[len(vars)-i-1] = vars[len(vars)-i-1], vars[i]
}
return vars
}
func returnInfoError(descr string, err error, mem MemoryReadWriter) []*Variable {
v := newConstant(constant.MakeString(fmt.Sprintf("%s: %v", descr, err.Error())), mem)
v.Name = "return value read error"
return []*Variable{v}
}

View File

@ -0,0 +1,526 @@
package core
import (
"errors"
"fmt"
"go/ast"
"io"
"sync"
"github.com/derekparker/delve/pkg/proc"
)
// A SplicedMemory represents a memory space formed from multiple regions,
// each of which may override previously regions. For example, in the following
// core, the program text was loaded at 0x400000:
// Start End Page Offset
// 0x0000000000400000 0x000000000044f000 0x0000000000000000
// but then it's partially overwritten with an RW mapping whose data is stored
// in the core file:
// Type Offset VirtAddr PhysAddr
// FileSiz MemSiz Flags Align
// LOAD 0x0000000000004000 0x000000000049a000 0x0000000000000000
// 0x0000000000002000 0x0000000000002000 RW 1000
// This can be represented in a SplicedMemory by adding the original region,
// then putting the RW mapping on top of it.
type SplicedMemory struct {
readers []readerEntry
}
type readerEntry struct {
offset uintptr
length uintptr
reader proc.MemoryReader
}
// Add adds a new region to the SplicedMemory, which may override existing regions.
func (r *SplicedMemory) Add(reader proc.MemoryReader, off, length uintptr) {
if length == 0 {
return
}
end := off + length - 1
newReaders := make([]readerEntry, 0, len(r.readers))
add := func(e readerEntry) {
if e.length == 0 {
return
}
newReaders = append(newReaders, e)
}
inserted := false
// Walk through the list of regions, fixing up any that overlap and inserting the new one.
for _, entry := range r.readers {
entryEnd := entry.offset + entry.length - 1
switch {
case entryEnd < off:
// Entry is completely before the new region.
add(entry)
case end < entry.offset:
// Entry is completely after the new region.
if !inserted {
add(readerEntry{off, length, reader})
inserted = true
}
add(entry)
case off <= entry.offset && entryEnd <= end:
// Entry is completely overwritten by the new region. Drop.
case entry.offset < off && entryEnd <= end:
// New region overwrites the end of the entry.
entry.length = off - entry.offset
add(entry)
case off <= entry.offset && end < entryEnd:
// New reader overwrites the beginning of the entry.
if !inserted {
add(readerEntry{off, length, reader})
inserted = true
}
overlap := entry.offset - off
entry.offset += overlap
entry.length -= overlap
add(entry)
case entry.offset < off && end < entryEnd:
// New region punches a hole in the entry. Split it in two and put the new region in the middle.
add(readerEntry{entry.offset, off - entry.offset, entry.reader})
add(readerEntry{off, length, reader})
add(readerEntry{end + 1, entryEnd - end, entry.reader})
inserted = true
default:
panic(fmt.Sprintf("Unhandled case: existing entry is %v len %v, new is %v len %v", entry.offset, entry.length, off, length))
}
}
if !inserted {
newReaders = append(newReaders, readerEntry{off, length, reader})
}
r.readers = newReaders
}
// ReadMemory implements MemoryReader.ReadMemory.
func (r *SplicedMemory) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
started := false
for _, entry := range r.readers {
if entry.offset+entry.length < addr {
if !started {
continue
}
return n, fmt.Errorf("hit unmapped area at %v after %v bytes", addr, n)
}
// Don't go past the region.
pb := buf
if addr+uintptr(len(buf)) > entry.offset+entry.length {
pb = pb[:entry.offset+entry.length-addr]
}
pn, err := entry.reader.ReadMemory(pb, addr)
n += pn
if err != nil || pn != len(pb) {
return n, err
}
buf = buf[pn:]
addr += uintptr(pn)
if len(buf) == 0 {
// Done, don't bother scanning the rest.
return n, nil
}
}
if n == 0 {
return 0, fmt.Errorf("offset %v did not match any regions", addr)
}
return n, nil
}
// OffsetReaderAt wraps a ReaderAt into a MemoryReader, subtracting a fixed
// offset from the address. This is useful to represent a mapping in an address
// space. For example, if program text is mapped in at 0x400000, an
// OffsetReaderAt with offset 0x400000 can be wrapped around file.Open(program)
// to return the results of a read in that part of the address space.
type OffsetReaderAt struct {
reader io.ReaderAt
offset uintptr
}
// ReadMemory will read the memory at addr-offset.
func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
return r.reader.ReadAt(buf, int64(addr-r.offset))
}
// Process represents a core file.
type Process struct {
bi *proc.BinaryInfo
core *Core
breakpoints proc.BreakpointMap
currentThread *Thread
selectedGoroutine *proc.G
common proc.CommonProcess
}
// Thread represents a thread in the core file being debugged.
type Thread struct {
th *LinuxPrStatus
fpregs []proc.Register
p *Process
common proc.CommonThread
}
var (
// ErrWriteCore is returned when attempting to write to the core
// process memory.
ErrWriteCore = errors.New("can not write to core process")
// ErrShortRead is returned on a short read.
ErrShortRead = errors.New("short read")
// ErrContinueCore is returned when trying to continue execution of a core process.
ErrContinueCore = errors.New("can not continue execution of core process")
// ErrChangeRegisterCore is returned when trying to change register values for core files.
ErrChangeRegisterCore = errors.New("can not change register values of core process")
)
// OpenCore will open the core file and return a Process struct.
func OpenCore(corePath, exePath string) (*Process, error) {
core, err := readCore(corePath, exePath)
if err != nil {
return nil, err
}
p := &Process{
core: core,
breakpoints: proc.NewBreakpointMap(),
bi: proc.NewBinaryInfo("linux", "amd64"),
}
for _, thread := range core.Threads {
thread.p = p
}
var wg sync.WaitGroup
err = p.bi.LoadBinaryInfo(exePath, core.entryPoint, &wg)
wg.Wait()
if err == nil {
err = p.bi.LoadError()
}
if err != nil {
return nil, err
}
for _, th := range p.core.Threads {
p.currentThread = th
break
}
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return p, nil
}
// BinInfo will return the binary info.
func (p *Process) BinInfo() *proc.BinaryInfo {
return p.bi
}
// Recorded returns whether this is a live or recorded process. Always returns true for core files.
func (p *Process) Recorded() (bool, string) { return true, "" }
// Restart will only return an error for core files, as they are not executing.
func (p *Process) Restart(string) error { return ErrContinueCore }
// Direction will only return an error as you cannot continue a core process.
func (p *Process) Direction(proc.Direction) error { return ErrContinueCore }
// When does not apply to core files, it is to support the Mozilla 'rr' backend.
func (p *Process) When() (string, error) { return "", nil }
// Checkpoint for core files returns an error, there is no execution of a core file.
func (p *Process) Checkpoint(string) (int, error) { return -1, ErrContinueCore }
// Checkpoints returns nil on core files, you cannot set checkpoints when debugging core files.
func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, nil }
// ClearCheckpoint clears a checkpoint, but will only return an error for core files.
func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not found") }
// ReadMemory will return memory from the core file at the specified location and put the
// read memory into `data`, returning the length read, and returning an error if
// the length read is shorter than the length of the `data` buffer.
func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
n, err = t.p.core.ReadMemory(data, addr)
if err == nil && n != len(data) {
err = ErrShortRead
}
return n, err
}
// WriteMemory will only return an error for core files, you cannot write
// to the memory of a core process.
func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
return 0, ErrWriteCore
}
// Location returns the location of this thread based on
// the value of the instruction pointer register.
func (t *Thread) Location() (*proc.Location, error) {
f, l, fn := t.p.bi.PCToLine(t.th.Reg.Rip)
return &proc.Location{PC: t.th.Reg.Rip, File: f, Line: l, Fn: fn}, nil
}
// Breakpoint returns the current breakpoint this thread is stopped at.
// For core files this always returns an empty BreakpointState struct, as
// there are no breakpoints when debugging core files.
func (t *Thread) Breakpoint() proc.BreakpointState {
return proc.BreakpointState{}
}
// ThreadID returns the ID for this thread.
func (t *Thread) ThreadID() int {
return int(t.th.Pid)
}
// Registers returns the current value of the registers for this thread.
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
r := &Registers{&t.th.Reg, nil}
if floatingPoint {
r.fpregs = t.fpregs
}
return r, nil
}
// RestoreRegisters will only return an error for core files,
// you cannot change register values for core files.
func (t *Thread) RestoreRegisters(proc.Registers) error {
return ErrChangeRegisterCore
}
// Arch returns the architecture the target is built for and executing on.
func (t *Thread) Arch() proc.Arch {
return t.p.bi.Arch
}
// BinInfo returns information about the binary.
func (t *Thread) BinInfo() *proc.BinaryInfo {
return t.p.bi
}
// StepInstruction will only return an error for core files,
// you cannot execute a core file.
func (t *Thread) StepInstruction() error {
return ErrContinueCore
}
// Blocked will return false always for core files as there is
// no execution.
func (t *Thread) Blocked() bool {
return false
}
// SetCurrentBreakpoint will always just return nil
// for core files, as there are no breakpoints in core files.
func (t *Thread) SetCurrentBreakpoint() error {
return nil
}
// Common returns a struct containing common information
// across thread implementations.
func (t *Thread) Common() *proc.CommonThread {
return &t.common
}
// SetPC will always return an error, you cannot
// change register values when debugging core files.
func (t *Thread) SetPC(uint64) error {
return ErrChangeRegisterCore
}
// SetSP will always return an error, you cannot
// change register values when debugging core files.
func (t *Thread) SetSP(uint64) error {
return ErrChangeRegisterCore
}
// SetDX will always return an error, you cannot
// change register values when debugging core files.
func (t *Thread) SetDX(uint64) error {
return ErrChangeRegisterCore
}
// Breakpoints will return all breakpoints for the process.
func (p *Process) Breakpoints() *proc.BreakpointMap {
return &p.breakpoints
}
// ClearBreakpoint will always return an error as you cannot set or clear
// breakpoints on core files.
func (p *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
return nil, proc.NoBreakpointError{Addr: addr}
}
// ClearInternalBreakpoints will always return nil and have no
// effect since you cannot set breakpoints on core files.
func (p *Process) ClearInternalBreakpoints() error {
return nil
}
// ContinueOnce will always return an error because you
// cannot control execution of a core file.
func (p *Process) ContinueOnce() (proc.Thread, error) {
return nil, ErrContinueCore
}
// StepInstruction will always return an error
// as you cannot control execution of a core file.
func (p *Process) StepInstruction() error {
return ErrContinueCore
}
// RequestManualStop will return nil and have no effect
// as you cannot control execution of a core file.
func (p *Process) RequestManualStop() error {
return nil
}
// CheckAndClearManualStopRequest will always return false and
// have no effect since there are no manual stop requests as
// there is no controlling execution of a core file.
func (p *Process) CheckAndClearManualStopRequest() bool {
return false
}
// CurrentThread returns the current active thread.
func (p *Process) CurrentThread() proc.Thread {
return p.currentThread
}
// Detach will always return nil and have no
// effect as you cannot detach from a core file
// and have it continue execution or exit.
func (p *Process) Detach(bool) error {
return nil
}
// Valid returns whether the process is active. Always returns true
// for core files as it cannot exit or be otherwise detached from.
func (p *Process) Valid() (bool, error) {
return true, nil
}
// Common returns common information across Process
// implementations.
func (p *Process) Common() *proc.CommonProcess {
return &p.common
}
// Pid returns the process ID of this process.
func (p *Process) Pid() int {
return p.core.Pid
}
// ResumeNotify is a no-op on core files as we cannot
// control execution.
func (p *Process) ResumeNotify(chan<- struct{}) {
}
// SelectedGoroutine returns the current active and selected
// goroutine.
func (p *Process) SelectedGoroutine() *proc.G {
return p.selectedGoroutine
}
// SetBreakpoint will always return an error for core files as you cannot write memory or control execution.
func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
return nil, ErrWriteCore
}
// SwitchGoroutine will change the selected and active goroutine.
func (p *Process) SwitchGoroutine(gid int) error {
g, err := proc.FindGoroutine(p, gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil
}
if g.Thread != nil {
return p.SwitchThread(g.Thread.ThreadID())
}
p.selectedGoroutine = g
return nil
}
// SwitchThread will change the selected and active thread.
func (p *Process) SwitchThread(tid int) error {
if th, ok := p.core.Threads[tid]; ok {
p.currentThread = th
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// ThreadList will return a list of all threads currently in the process.
func (p *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(p.core.Threads))
for _, v := range p.core.Threads {
r = append(r, v)
}
return r
}
// FindThread will return the thread with the corresponding thread ID.
func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
t, ok := p.core.Threads[threadID]
return t, ok
}
// Registers represents the CPU registers.
type Registers struct {
*LinuxCoreRegisters
fpregs []proc.Register
}
// Slice will return a slice containing all registers and their values.
func (r *Registers) Slice() []proc.Register {
var regs = []struct {
k string
v uint64
}{
{"Rip", r.Rip},
{"Rsp", r.Rsp},
{"Rax", r.Rax},
{"Rbx", r.Rbx},
{"Rcx", r.Rcx},
{"Rdx", r.Rdx},
{"Rdi", r.Rdi},
{"Rsi", r.Rsi},
{"Rbp", r.Rbp},
{"R8", r.R8},
{"R9", r.R9},
{"R10", r.R10},
{"R11", r.R11},
{"R12", r.R12},
{"R13", r.R13},
{"R14", r.R14},
{"R15", r.R15},
{"Orig_rax", r.Orig_rax},
{"Cs", r.Cs},
{"Eflags", r.Eflags},
{"Ss", r.Ss},
{"Fs_base", r.Fs_base},
{"Gs_base", r.Gs_base},
{"Ds", r.Ds},
{"Es", r.Es},
{"Fs", r.Fs},
{"Gs", r.Gs},
}
out := make([]proc.Register, 0, len(regs))
for _, reg := range regs {
if reg.k == "Eflags" {
out = proc.AppendEflagReg(out, reg.k, reg.v)
} else {
out = proc.AppendQwordReg(out, reg.k, reg.v)
}
}
out = append(out, r.fpregs...)
return out
}
// Copy will return a copy of the registers that is guarenteed
// not to change.
func (r *Registers) Copy() proc.Registers {
return r
}

View File

@ -0,0 +1,556 @@
package core
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"io"
"os"
"golang.org/x/arch/x86/x86asm"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil"
)
// Copied from golang.org/x/sys/unix.PtraceRegs since it's not available on
// all systems.
type LinuxCoreRegisters struct {
R15 uint64
R14 uint64
R13 uint64
R12 uint64
Rbp uint64
Rbx uint64
R11 uint64
R10 uint64
R9 uint64
R8 uint64
Rax uint64
Rcx uint64
Rdx uint64
Rsi uint64
Rdi uint64
Orig_rax uint64
Rip uint64
Cs uint64
Eflags uint64
Rsp uint64
Ss uint64
Fs_base uint64
Gs_base uint64
Ds uint64
Es uint64
Fs uint64
Gs uint64
}
// Copied from golang.org/x/sys/unix.Timeval since it's not available on all
// systems.
type LinuxCoreTimeval struct {
Sec int64
Usec int64
}
// NT_FILE is file mapping information, e.g. program text mappings. Desc is a LinuxNTFile.
const NT_FILE elf.NType = 0x46494c45 // "FILE".
// NT_X86_XSTATE is other registers, including AVX and such.
const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAVE area.
// NT_AUXV is the note type for notes containing a copy of the Auxv array
const NT_AUXV elf.NType = 0x6
// PC returns the value of RIP.
func (r *LinuxCoreRegisters) PC() uint64 {
return r.Rip
}
// SP returns the value of RSP.
func (r *LinuxCoreRegisters) SP() uint64 {
return r.Rsp
}
// BP returns the value of RBP.
func (r *LinuxCoreRegisters) BP() uint64 {
return r.Rbp
}
// CX returns the value of RCX.
func (r *LinuxCoreRegisters) CX() uint64 {
return r.Rcx
}
// TLS returns the location of the thread local storate,
// which will be the value of Fs_base.
func (r *LinuxCoreRegisters) TLS() uint64 {
return r.Fs_base
}
// GAddr returns the address of the G struct. Always returns 0
// and false for core files.
func (r *LinuxCoreRegisters) GAddr() (uint64, bool) {
return 0, false
}
// Get returns the value of the register requested via the
// register number, returning an error if that register
// could not be found.
func (r *LinuxCoreRegisters) Get(n int) (uint64, error) {
reg := x86asm.Reg(n)
const (
mask8 = 0x000f
mask16 = 0x00ff
mask32 = 0xffff
)
switch reg {
// 8-bit
case x86asm.AL:
return r.Rax & mask8, nil
case x86asm.CL:
return r.Rcx & mask8, nil
case x86asm.DL:
return r.Rdx & mask8, nil
case x86asm.BL:
return r.Rbx & mask8, nil
case x86asm.AH:
return (r.Rax >> 8) & mask8, nil
case x86asm.CH:
return (r.Rcx >> 8) & mask8, nil
case x86asm.DH:
return (r.Rdx >> 8) & mask8, nil
case x86asm.BH:
return (r.Rbx >> 8) & mask8, nil
case x86asm.SPB:
return r.Rsp & mask8, nil
case x86asm.BPB:
return r.Rbp & mask8, nil
case x86asm.SIB:
return r.Rsi & mask8, nil
case x86asm.DIB:
return r.Rdi & mask8, nil
case x86asm.R8B:
return r.R8 & mask8, nil
case x86asm.R9B:
return r.R9 & mask8, nil
case x86asm.R10B:
return r.R10 & mask8, nil
case x86asm.R11B:
return r.R11 & mask8, nil
case x86asm.R12B:
return r.R12 & mask8, nil
case x86asm.R13B:
return r.R13 & mask8, nil
case x86asm.R14B:
return r.R14 & mask8, nil
case x86asm.R15B:
return r.R15 & mask8, nil
// 16-bit
case x86asm.AX:
return r.Rax & mask16, nil
case x86asm.CX:
return r.Rcx & mask16, nil
case x86asm.DX:
return r.Rdx & mask16, nil
case x86asm.BX:
return r.Rbx & mask16, nil
case x86asm.SP:
return r.Rsp & mask16, nil
case x86asm.BP:
return r.Rbp & mask16, nil
case x86asm.SI:
return r.Rsi & mask16, nil
case x86asm.DI:
return r.Rdi & mask16, nil
case x86asm.R8W:
return r.R8 & mask16, nil
case x86asm.R9W:
return r.R9 & mask16, nil
case x86asm.R10W:
return r.R10 & mask16, nil
case x86asm.R11W:
return r.R11 & mask16, nil
case x86asm.R12W:
return r.R12 & mask16, nil
case x86asm.R13W:
return r.R13 & mask16, nil
case x86asm.R14W:
return r.R14 & mask16, nil
case x86asm.R15W:
return r.R15 & mask16, nil
// 32-bit
case x86asm.EAX:
return r.Rax & mask32, nil
case x86asm.ECX:
return r.Rcx & mask32, nil
case x86asm.EDX:
return r.Rdx & mask32, nil
case x86asm.EBX:
return r.Rbx & mask32, nil
case x86asm.ESP:
return r.Rsp & mask32, nil
case x86asm.EBP:
return r.Rbp & mask32, nil
case x86asm.ESI:
return r.Rsi & mask32, nil
case x86asm.EDI:
return r.Rdi & mask32, nil
case x86asm.R8L:
return r.R8 & mask32, nil
case x86asm.R9L:
return r.R9 & mask32, nil
case x86asm.R10L:
return r.R10 & mask32, nil
case x86asm.R11L:
return r.R11 & mask32, nil
case x86asm.R12L:
return r.R12 & mask32, nil
case x86asm.R13L:
return r.R13 & mask32, nil
case x86asm.R14L:
return r.R14 & mask32, nil
case x86asm.R15L:
return r.R15 & mask32, nil
// 64-bit
case x86asm.RAX:
return r.Rax, nil
case x86asm.RCX:
return r.Rcx, nil
case x86asm.RDX:
return r.Rdx, nil
case x86asm.RBX:
return r.Rbx, nil
case x86asm.RSP:
return r.Rsp, nil
case x86asm.RBP:
return r.Rbp, nil
case x86asm.RSI:
return r.Rsi, nil
case x86asm.RDI:
return r.Rdi, nil
case x86asm.R8:
return r.R8, nil
case x86asm.R9:
return r.R9, nil
case x86asm.R10:
return r.R10, nil
case x86asm.R11:
return r.R11, nil
case x86asm.R12:
return r.R12, nil
case x86asm.R13:
return r.R13, nil
case x86asm.R14:
return r.R14, nil
case x86asm.R15:
return r.R15, nil
}
return 0, proc.ErrUnknownRegister
}
// readCore reads a core file from corePath corresponding to the executable at
// exePath. For details on the Linux ELF core format, see:
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
// and, if absolutely desperate, readelf.c from the binutils source.
func readCore(corePath, exePath string) (*Core, error) {
coreFile, err := elf.Open(corePath)
if err != nil {
return nil, err
}
exe, err := os.Open(exePath)
if err != nil {
return nil, err
}
exeELF, err := elf.NewFile(exe)
if err != nil {
return nil, err
}
if coreFile.Type != elf.ET_CORE {
return nil, fmt.Errorf("%v is not a core file", coreFile)
}
if exeELF.Type != elf.ET_EXEC && exeELF.Type != elf.ET_DYN {
return nil, fmt.Errorf("%v is not an exe file", exeELF)
}
notes, err := readNotes(coreFile)
if err != nil {
return nil, err
}
memory := buildMemory(coreFile, exeELF, exe, notes)
entryPoint := findEntryPoint(notes)
core := &Core{
MemoryReader: memory,
Threads: map[int]*Thread{},
entryPoint: entryPoint,
}
var lastThread *Thread
for _, note := range notes {
switch note.Type {
case elf.NT_PRSTATUS:
t := note.Desc.(*LinuxPrStatus)
lastThread = &Thread{t, nil, nil, proc.CommonThread{}}
core.Threads[int(t.Pid)] = lastThread
case NT_X86_XSTATE:
if lastThread != nil {
lastThread.fpregs = note.Desc.(*proc.LinuxX86Xstate).Decode()
}
case elf.NT_PRPSINFO:
core.Pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
}
}
return core, nil
}
// Core represents a core file.
type Core struct {
proc.MemoryReader
Threads map[int]*Thread
Pid int
entryPoint uint64
}
// Note is a note from the PT_NOTE prog.
// Relevant types:
// - NT_FILE: File mapping information, e.g. program text mappings. Desc is a LinuxNTFile.
// - NT_PRPSINFO: Information about a process, including PID and signal. Desc is a LinuxPrPsInfo.
// - NT_PRSTATUS: Information about a thread, including base registers, state, etc. Desc is a LinuxPrStatus.
// - NT_FPREGSET (Not implemented): x87 floating point registers.
// - NT_X86_XSTATE: Other registers, including AVX and such.
type Note struct {
Type elf.NType
Name string
Desc interface{} // Decoded Desc from the
}
// readNotes reads all the notes from the notes prog in core.
func readNotes(core *elf.File) ([]*Note, error) {
var notesProg *elf.Prog
for _, prog := range core.Progs {
if prog.Type == elf.PT_NOTE {
notesProg = prog
break
}
}
r := notesProg.Open()
notes := []*Note{}
for {
note, err := readNote(r)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
notes = append(notes, note)
}
return notes, nil
}
// readNote reads a single note from r, decoding the descriptor if possible.
func readNote(r io.ReadSeeker) (*Note, error) {
// Notes are laid out as described in the SysV ABI:
// http://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section
note := &Note{}
hdr := &ELFNotesHdr{}
err := binary.Read(r, binary.LittleEndian, hdr)
if err != nil {
return nil, err // don't wrap so readNotes sees EOF.
}
note.Type = elf.NType(hdr.Type)
name := make([]byte, hdr.Namesz)
if _, err := r.Read(name); err != nil {
return nil, fmt.Errorf("reading name: %v", err)
}
note.Name = string(name)
if err := skipPadding(r, 4); err != nil {
return nil, fmt.Errorf("aligning after name: %v", err)
}
desc := make([]byte, hdr.Descsz)
if _, err := r.Read(desc); err != nil {
return nil, fmt.Errorf("reading desc: %v", err)
}
descReader := bytes.NewReader(desc)
switch note.Type {
case elf.NT_PRSTATUS:
note.Desc = &LinuxPrStatus{}
if err := binary.Read(descReader, binary.LittleEndian, note.Desc); err != nil {
return nil, fmt.Errorf("reading NT_PRSTATUS: %v", err)
}
case elf.NT_PRPSINFO:
note.Desc = &LinuxPrPsInfo{}
if err := binary.Read(descReader, binary.LittleEndian, note.Desc); err != nil {
return nil, fmt.Errorf("reading NT_PRPSINFO: %v", err)
}
case NT_FILE:
// No good documentation reference, but the structure is
// simply a header, including entry count, followed by that
// many entries, and then the file name of each entry,
// null-delimited. Not reading the names here.
data := &LinuxNTFile{}
if err := binary.Read(descReader, binary.LittleEndian, &data.LinuxNTFileHdr); err != nil {
return nil, fmt.Errorf("reading NT_FILE header: %v", err)
}
for i := 0; i < int(data.Count); i++ {
entry := &LinuxNTFileEntry{}
if err := binary.Read(descReader, binary.LittleEndian, entry); err != nil {
return nil, fmt.Errorf("reading NT_FILE entry %v: %v", i, err)
}
data.entries = append(data.entries, entry)
}
note.Desc = data
case NT_X86_XSTATE:
var fpregs proc.LinuxX86Xstate
if err := proc.LinuxX86XstateRead(desc, true, &fpregs); err != nil {
return nil, err
}
note.Desc = &fpregs
case NT_AUXV:
note.Desc = desc
}
if err := skipPadding(r, 4); err != nil {
return nil, fmt.Errorf("aligning after desc: %v", err)
}
return note, nil
}
// skipPadding moves r to the next multiple of pad.
func skipPadding(r io.ReadSeeker, pad int64) error {
pos, err := r.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
if pos%pad == 0 {
return nil
}
if _, err := r.Seek(pad-(pos%pad), os.SEEK_CUR); err != nil {
return err
}
return nil
}
func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*Note) proc.MemoryReader {
memory := &SplicedMemory{}
// For now, assume all file mappings are to the exe.
for _, note := range notes {
if note.Type == NT_FILE {
fileNote := note.Desc.(*LinuxNTFile)
for _, entry := range fileNote.entries {
r := &OffsetReaderAt{
reader: exe,
offset: uintptr(entry.Start - (entry.FileOfs * fileNote.PageSize)),
}
memory.Add(r, uintptr(entry.Start), uintptr(entry.End-entry.Start))
}
}
}
// Load memory segments from exe and then from the core file,
// allowing the corefile to overwrite previously loaded segments
for _, elfFile := range []*elf.File{exeELF, core} {
for _, prog := range elfFile.Progs {
if prog.Type == elf.PT_LOAD {
if prog.Filesz == 0 {
continue
}
r := &OffsetReaderAt{
reader: prog.ReaderAt,
offset: uintptr(prog.Vaddr),
}
memory.Add(r, uintptr(prog.Vaddr), uintptr(prog.Filesz))
}
}
}
return memory
}
func findEntryPoint(notes []*Note) uint64 {
for _, note := range notes {
if note.Type == NT_AUXV {
return linutil.EntryPointFromAuxvAMD64(note.Desc.([]byte))
}
}
return 0
}
// LinuxPrPsInfo has various structures from the ELF spec and the Linux kernel.
// AMD64 specific primarily because of unix.PtraceRegs, but also
// because some of the fields are word sized.
// See http://lxr.free-electrons.com/source/include/uapi/linux/elfcore.h
type LinuxPrPsInfo struct {
State uint8
Sname int8
Zomb uint8
Nice int8
_ [4]uint8
Flag uint64
Uid, Gid uint32
Pid, Ppid, Pgrp, Sid int32
Fname [16]uint8
Args [80]uint8
}
// LinuxPrStatus is a copy of the prstatus kernel struct.
type LinuxPrStatus struct {
Siginfo LinuxSiginfo
Cursig uint16
_ [2]uint8
Sigpend uint64
Sighold uint64
Pid, Ppid, Pgrp, Sid int32
Utime, Stime, CUtime, CStime LinuxCoreTimeval
Reg LinuxCoreRegisters
Fpvalid int32
}
// LinuxSiginfo is a copy of the
// siginfo kernel struct.
type LinuxSiginfo struct {
Signo int32
Code int32
Errno int32
}
// LinuxNTFile contains information on mapped files.
type LinuxNTFile struct {
LinuxNTFileHdr
entries []*LinuxNTFileEntry
}
// LinuxNTFileHdr is a header struct for NTFile.
type LinuxNTFileHdr struct {
Count uint64
PageSize uint64
}
// LinuxNTFileEntry is an entry of an NT_FILE note.
type LinuxNTFileEntry struct {
Start uint64
End uint64
FileOfs uint64
}
// ELFNotesHdr is the ELF Notes header.
// Same size on 64 and 32-bit machines.
type ELFNotesHdr struct {
Namesz uint32
Descsz uint32
Type uint32
}

121
vendor/github.com/derekparker/delve/pkg/proc/disasm.go generated vendored Normal file
View File

@ -0,0 +1,121 @@
package proc
import "sort"
// AsmInstruction represents one assembly instruction.
type AsmInstruction struct {
Loc Location
DestLoc *Location
Bytes []byte
Breakpoint bool
AtPC bool
Inst *archInst
}
// AssemblyFlavour is the assembly syntax to display.
type AssemblyFlavour int
const (
// GNUFlavour will display GNU assembly syntax.
GNUFlavour = AssemblyFlavour(iota)
// IntelFlavour will display Intel assembly syntax.
IntelFlavour
// GoFlavour will display Go assembly syntax.
GoFlavour
)
// Disassemble disassembles target memory between startPC and endPC, marking
// the current instruction being executed in goroutine g.
// If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble will evaluate the argument of the CALL instruction using the thread's registers
// Be aware that the Bytes field of each returned instruction is a slice of a larger array of size endPC - startPC
func Disassemble(dbp Process, g *G, startPC, endPC uint64) ([]AsmInstruction, error) {
if _, err := dbp.Valid(); err != nil {
return nil, err
}
if g == nil {
ct := dbp.CurrentThread()
regs, _ := ct.Registers(false)
return disassemble(ct, regs, dbp.Breakpoints(), dbp.BinInfo(), startPC, endPC, false)
}
var regs Registers
var mem MemoryReadWriter = dbp.CurrentThread()
if g.Thread != nil {
mem = g.Thread
regs, _ = g.Thread.Registers(false)
}
return disassemble(mem, regs, dbp.Breakpoints(), dbp.BinInfo(), startPC, endPC, false)
}
func disassemble(memrw MemoryReadWriter, regs Registers, breakpoints *BreakpointMap, bi *BinaryInfo, startPC, endPC uint64, singleInstr bool) ([]AsmInstruction, error) {
mem := make([]byte, int(endPC-startPC))
_, err := memrw.ReadMemory(mem, uintptr(startPC))
if err != nil {
return nil, err
}
r := make([]AsmInstruction, 0, len(mem)/15)
pc := startPC
var curpc uint64
if regs != nil {
curpc = regs.PC()
}
for len(mem) > 0 {
bp, atbp := breakpoints.M[pc]
if atbp {
for i := range bp.OriginalData {
mem[i] = bp.OriginalData[i]
}
}
file, line, fn := bi.PCToLine(pc)
loc := Location{PC: pc, File: file, Line: line, Fn: fn}
inst, err := asmDecode(mem, pc)
if err == nil {
atpc := (regs != nil) && (curpc == pc)
destloc := resolveCallArg(inst, atpc, regs, memrw, bi)
r = append(r, AsmInstruction{Loc: loc, DestLoc: destloc, Bytes: mem[:inst.Len], Breakpoint: atbp, AtPC: atpc, Inst: inst})
pc += uint64(inst.Size())
mem = mem[inst.Size():]
} else {
r = append(r, AsmInstruction{Loc: loc, Bytes: mem[:1], Breakpoint: atbp, Inst: nil})
pc++
mem = mem[1:]
}
if singleInstr {
break
}
}
return r, nil
}
// Looks up symbol (either functions or global variables) at address addr.
// Used by disassembly formatter.
func (bi *BinaryInfo) symLookup(addr uint64) (string, uint64) {
fn := bi.PCToFunc(addr)
if fn != nil {
if fn.Entry == addr {
// only report the function name if it's the exact address because it's
// easier to read the absolute address than function_name+offset.
return fn.Name, fn.Entry
}
return "", 0
}
i := sort.Search(len(bi.packageVars), func(i int) bool {
return bi.packageVars[i].addr >= addr
})
if i >= len(bi.packageVars) {
return "", 0
}
if bi.packageVars[i].addr > addr {
// report previous variable + offset if i-th variable starts after addr
i--
}
if i > 0 {
return bi.packageVars[i].name, bi.packageVars[i].addr
}
return "", 0
}

View File

@ -1,26 +1,26 @@
package proc package proc
import ( import (
"debug/gosym"
"encoding/binary" "encoding/binary"
"rsc.io/x86/x86asm"
"golang.org/x/arch/x86/x86asm"
) )
var maxInstructionLength uint64 = 15 var maxInstructionLength uint64 = 15
type ArchInst x86asm.Inst type archInst x86asm.Inst
func asmDecode(mem []byte, pc uint64) (*ArchInst, error) { func asmDecode(mem []byte, pc uint64) (*archInst, error) {
inst, err := x86asm.Decode(mem, 64) inst, err := x86asm.Decode(mem, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
patchPCRel(pc, &inst) patchPCRel(pc, &inst)
r := ArchInst(inst) r := archInst(inst)
return &r, nil return &r, nil
} }
func (inst *ArchInst) Size() int { func (inst *archInst) Size() int {
return inst.Len return inst.Len
} }
@ -32,10 +32,11 @@ func patchPCRel(pc uint64, inst *x86asm.Inst) {
inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len)) inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len))
} }
} }
return
} }
func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string { // Text will return the assembly instructions in human readable format according to
// the flavour specified.
func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
if inst.Inst == nil { if inst.Inst == nil {
return "?" return "?"
} }
@ -44,25 +45,27 @@ func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string {
switch flavour { switch flavour {
case GNUFlavour: case GNUFlavour:
text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst)) text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
case GoFlavour:
text = x86asm.GoSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
case IntelFlavour: case IntelFlavour:
fallthrough fallthrough
default: default:
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst)) text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
}
if inst.IsCall() && inst.DestLoc != nil && inst.DestLoc.Fn != nil {
text += " " + inst.DestLoc.Fn.Name
} }
return text return text
} }
// IsCall returns true if the instruction is a CALL or LCALL instruction.
func (inst *AsmInstruction) IsCall() bool { func (inst *AsmInstruction) IsCall() bool {
if inst.Inst == nil {
return false
}
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
} }
func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs Registers) *Location { func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL { if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
return nil return nil
} }
@ -88,10 +91,6 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
if arg.Segment != 0 { if arg.Segment != 0 {
return nil return nil
} }
regs, err := thread.Registers(false)
if err != nil {
return nil
}
base, err1 := regs.Get(int(arg.Base)) base, err1 := regs.Get(int(arg.Base))
index, err2 := regs.Get(int(arg.Index)) index, err2 := regs.Get(int(arg.Index))
if err1 != nil || err2 != nil { if err1 != nil || err2 != nil {
@ -99,7 +98,8 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
} }
addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp) addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
//TODO: should this always be 64 bits instead of inst.MemBytes? //TODO: should this always be 64 bits instead of inst.MemBytes?
pcbytes, err := thread.readMemory(addr, inst.MemBytes) pcbytes := make([]byte, inst.MemBytes)
_, err := mem.ReadMemory(pcbytes, addr)
if err != nil { if err != nil {
return nil return nil
} }
@ -108,7 +108,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
return nil return nil
} }
file, line, fn := thread.dbp.PCToLine(pc) file, line, fn := bininfo.PCToLine(pc)
if fn == nil { if fn == nil {
return nil return nil
} }
@ -142,10 +142,16 @@ func init() {
} }
} }
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn // firstPCAfterPrologueDisassembly returns the address of the first
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry // instruction after the prologue for function fn by disassembling fn and
func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) { // matching the instructions against known split-stack prologue patterns.
text, err := dbp.CurrentThread.Disassemble(fn.Entry, fn.End, false) // If sameline is set firstPCAfterPrologueDisassembly will always return an
// address associated with the same line as fn.Entry
func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (uint64, error) {
var mem MemoryReadWriter = p.CurrentThread()
breakpoints := p.Breakpoints()
bi := p.BinInfo()
text, err := disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End, false)
if err != nil { if err != nil {
return fn.Entry, err return fn.Entry, err
} }

View File

@ -3,6 +3,7 @@ package proc
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/constant" "go/constant"
@ -10,11 +11,16 @@ import (
"go/printer" "go/printer"
"go/token" "go/token"
"reflect" "reflect"
"strconv"
"strings"
"github.com/derekparker/delve/dwarf/reader" "github.com/derekparker/delve/pkg/dwarf/godwarf"
"golang.org/x/debug/dwarf" "github.com/derekparker/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/goversion"
) )
var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
// EvalExpression returns the value of the given expression. // EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) { func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
t, err := parser.ParseExpr(expr) t, err := parser.ParseExpr(expr)
@ -22,7 +28,10 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable,
return nil, err return nil, err
} }
ev, err := scope.evalAST(t) ev, err := scope.evalToplevelTypeCast(t, cfg)
if ev == nil && err == nil {
ev, err = scope.evalAST(t)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -33,6 +42,123 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable,
return ev, nil return ev, nil
} }
// evalToplevelTypeCast implements certain type casts that we only support
// at the outermost levels of an expression.
func (scope *EvalScope) evalToplevelTypeCast(t ast.Expr, cfg LoadConfig) (*Variable, error) {
call, _ := t.(*ast.CallExpr)
if call == nil || len(call.Args) != 1 {
return nil, nil
}
targetTypeStr := exprToString(removeParen(call.Fun))
var targetType godwarf.Type
switch targetTypeStr {
case "[]byte", "[]uint8":
targetType = fakeSliceType(&godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "uint8"}, BitSize: 8, BitOffset: 0}})
case "[]int32", "[]rune":
targetType = fakeSliceType(&godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "int32"}, BitSize: 32, BitOffset: 0}})
case "string":
var err error
targetType, err = scope.BinInfo.findType("string")
if err != nil {
return nil, err
}
default:
return nil, nil
}
argv, err := scope.evalToplevelTypeCast(call.Args[0], cfg)
if argv == nil && err == nil {
argv, err = scope.evalAST(call.Args[0])
}
if err != nil {
return nil, err
}
argv.loadValue(cfg)
if argv.Unreadable != nil {
return nil, argv.Unreadable
}
v := newVariable("", 0, targetType, scope.BinInfo, scope.Mem)
v.loaded = true
converr := fmt.Errorf("can not convert %q to %s", exprToString(call.Args[0]), targetTypeStr)
switch targetTypeStr {
case "[]byte", "[]uint8":
if argv.Kind != reflect.String {
return nil, converr
}
for i, ch := range []byte(constant.StringVal(argv.Value)) {
e := scope.newVariable("", argv.Addr+uintptr(i), targetType.(*godwarf.SliceType).ElemType, argv.mem)
e.loaded = true
e.Value = constant.MakeInt64(int64(ch))
v.Children = append(v.Children, *e)
}
v.Len = int64(len(v.Children))
v.Cap = v.Len
return v, nil
case "[]int32", "[]rune":
if argv.Kind != reflect.String {
return nil, converr
}
for i, ch := range constant.StringVal(argv.Value) {
e := scope.newVariable("", argv.Addr+uintptr(i), targetType.(*godwarf.SliceType).ElemType, argv.mem)
e.loaded = true
e.Value = constant.MakeInt64(int64(ch))
v.Children = append(v.Children, *e)
}
v.Len = int64(len(v.Children))
v.Cap = v.Len
return v, nil
case "string":
switch argv.Kind {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
b, _ := constant.Int64Val(argv.Value)
s := string(b)
v.Value = constant.MakeString(s)
v.Len = int64(len(s))
return v, nil
case reflect.Slice:
switch elemType := argv.RealType.(*godwarf.SliceType).ElemType.(type) {
case *godwarf.UintType:
if elemType.Name != "uint8" && elemType.Name != "byte" {
return nil, nil
}
bytes := make([]byte, len(argv.Children))
for i := range argv.Children {
n, _ := constant.Int64Val(argv.Children[i].Value)
bytes[i] = byte(n)
}
v.Value = constant.MakeString(string(bytes))
case *godwarf.IntType:
if elemType.Name != "int32" && elemType.Name != "rune" {
return nil, nil
}
runes := make([]rune, len(argv.Children))
for i := range argv.Children {
n, _ := constant.Int64Val(argv.Children[i].Value)
runes[i] = rune(n)
}
v.Value = constant.MakeString(string(runes))
default:
return nil, nil
}
v.Len = int64(len(constant.StringVal(v.Value)))
return v, nil
default:
return nil, nil
}
}
return nil, nil
}
func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
switch node := t.(type) { switch node := t.(type) {
case *ast.CallExpr: case *ast.CallExpr:
@ -64,11 +190,25 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
// try to interpret the selector as a package variable // try to interpret the selector as a package variable
if maybePkg, ok := node.X.(*ast.Ident); ok { if maybePkg, ok := node.X.(*ast.Ident); ok {
if maybePkg.Name == "runtime" && node.Sel.Name == "curg" { if maybePkg.Name == "runtime" && node.Sel.Name == "curg" {
return scope.Thread.getGVariable() if scope.Gvar == nil {
} else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { return nilVariable, nil
}
return scope.Gvar.clone(), nil
} else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" {
return newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem), nil
} else if v, err := scope.findGlobal(maybePkg.Name + "." + node.Sel.Name); err == nil {
return v, nil return v, nil
} }
} }
// try to accept "package/path".varname syntax for package variables
if maybePkg, ok := node.X.(*ast.BasicLit); ok && maybePkg.Kind == token.STRING {
pkgpath, err := strconv.Unquote(maybePkg.Value)
if err == nil {
if v, err := scope.findGlobal(pkgpath + "." + node.Sel.Name); err == nil {
return v, nil
}
}
}
// if it's not a package variable then it must be a struct member access // if it's not a package variable then it must be a struct member access
return scope.evalStructSelector(node) return scope.evalStructSelector(node)
@ -102,7 +242,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
return scope.evalBinary(node) return scope.evalBinary(node)
case *ast.BasicLit: case *ast.BasicLit:
return newConstant(constant.MakeFromLiteral(node.Value, node.Kind, 0), scope.Thread), nil return newConstant(constant.MakeFromLiteral(node.Value, node.Kind, 0), scope.Mem), nil
default: default:
return nil, fmt.Errorf("expression %T not implemented", t) return nil, fmt.Errorf("expression %T not implemented", t)
@ -116,6 +256,17 @@ func exprToString(t ast.Expr) string {
return buf.String() return buf.String()
} }
func removeParen(n ast.Expr) ast.Expr {
for {
p, ok := n.(*ast.ParenExpr)
if !ok {
break
}
n = p.X
}
return n
}
// Eval type cast expressions // Eval type cast expressions
func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
argv, err := scope.evalAST(node.Args[0]) argv, err := scope.evalAST(node.Args[0])
@ -130,15 +281,9 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
fnnode := node.Fun fnnode := node.Fun
// remove all enclosing parenthesis from the type name // remove all enclosing parenthesis from the type name
for { fnnode = removeParen(fnnode)
p, ok := fnnode.(*ast.ParenExpr)
if !ok {
break
}
fnnode = p.X
}
styp, err := scope.Thread.dbp.findTypeExpr(fnnode) styp, err := scope.BinInfo.findTypeExpr(fnnode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -146,11 +291,11 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
converr := fmt.Errorf("can not convert %q to %s", exprToString(node.Args[0]), typ.String()) converr := fmt.Errorf("can not convert %q to %s", exprToString(node.Args[0]), typ.String())
v := newVariable("", 0, styp, scope.Thread.dbp, scope.Thread) v := newVariable("", 0, styp, scope.BinInfo, scope.Mem)
v.loaded = true v.loaded = true
switch ttyp := typ.(type) { switch ttyp := typ.(type) {
case *dwarf.PtrType: case *godwarf.PtrType:
switch argv.Kind { switch argv.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
// ok // ok
@ -162,10 +307,10 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
n, _ := constant.Int64Val(argv.Value) n, _ := constant.Int64Val(argv.Value)
v.Children = []Variable{*(scope.newVariable("", uintptr(n), ttyp.Type))} v.Children = []Variable{*(scope.newVariable("", uintptr(n), ttyp.Type, scope.Mem))}
return v, nil return v, nil
case *dwarf.UintType: case *godwarf.UintType:
switch argv.Kind { switch argv.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, _ := constant.Int64Val(argv.Value) n, _ := constant.Int64Val(argv.Value)
@ -179,8 +324,11 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
x, _ := constant.Float64Val(argv.Value) x, _ := constant.Float64Val(argv.Value)
v.Value = constant.MakeUint64(uint64(x)) v.Value = constant.MakeUint64(uint64(x))
return v, nil return v, nil
case reflect.Ptr:
v.Value = constant.MakeUint64(uint64(argv.Children[0].Addr))
return v, nil
} }
case *dwarf.IntType: case *godwarf.IntType:
switch argv.Kind { switch argv.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, _ := constant.Int64Val(argv.Value) n, _ := constant.Int64Val(argv.Value)
@ -195,7 +343,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
v.Value = constant.MakeInt64(int64(x)) v.Value = constant.MakeInt64(int64(x))
return v, nil return v, nil
} }
case *dwarf.FloatType: case *godwarf.FloatType:
switch argv.Kind { switch argv.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough fallthrough
@ -205,7 +353,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
v.Value = argv.Value v.Value = argv.Value
return v, nil return v, nil
} }
case *dwarf.ComplexType: case *godwarf.ComplexType:
switch argv.Kind { switch argv.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough fallthrough
@ -370,10 +518,10 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
sz := int64(0) sz := int64(0)
if realev.RealType != nil { if realev.RealType != nil {
sz = realev.RealType.(*dwarf.FloatType).Size() sz = realev.RealType.(*godwarf.FloatType).Size()
} }
if imagev.RealType != nil { if imagev.RealType != nil {
isz := imagev.RealType.(*dwarf.FloatType).Size() isz := imagev.RealType.(*godwarf.FloatType).Size()
if isz > sz { if isz > sz {
sz = isz sz = isz
} }
@ -383,9 +531,9 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
sz = 128 sz = 128
} }
typ := &dwarf.ComplexType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: int64(sz / 8), Name: fmt.Sprintf("complex%d", sz)}, BitSize: sz, BitOffset: 0}} typ := &godwarf.ComplexType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: int64(sz / 8), Name: fmt.Sprintf("complex%d", sz)}, BitSize: sz, BitOffset: 0}}
r := realev.newVariable("", 0, typ) r := realev.newVariable("", 0, typ, nil)
r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value)) r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value))
return r, nil return r, nil
} }
@ -432,33 +580,29 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
switch node.Name { switch node.Name {
case "true", "false": case "true", "false":
return newConstant(constant.MakeBool(node.Name == "true"), scope.Thread), nil return newConstant(constant.MakeBool(node.Name == "true"), scope.Mem), nil
case "nil": case "nil":
return nilVariable, nil return nilVariable, nil
} }
// try to interpret this as a local variable vars, err := scope.Locals()
v, err := scope.extractVarInfo(node.Name) if err != nil {
if err == nil { return nil, err
return v, nil
} }
origErr := err for i := range vars {
// workaround: sometimes go inserts an entry for '&varname' instead of varname if vars[i].Name == node.Name && vars[i].Flags&VariableShadowed == 0 {
v, err = scope.extractVarInfo("&" + node.Name) return vars[i], nil
if err == nil { }
v = v.maybeDereference()
v.Name = node.Name
return v, nil
} }
// if it's not a local variable then it could be a package variable w/o explicit package name // if it's not a local variable then it could be a package variable w/o explicit package name
_, _, fn := scope.Thread.dbp.PCToLine(scope.PC) if scope.Fn != nil {
if fn != nil { if v, err := scope.findGlobal(scope.Fn.PackageName() + "." + node.Name); err == nil {
if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil {
v.Name = node.Name v.Name = node.Name
return v, nil return v, nil
} }
} }
return nil, origErr return nil, fmt.Errorf("could not find symbol value for %s", node.Name)
} }
// Evaluates expressions <subexpr>.<field name> where subexpr is not a package name // Evaluates expressions <subexpr>.<field name> where subexpr is not a package name
@ -467,6 +611,13 @@ func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
rv, err := xv.findMethod(node.Sel.Name)
if err != nil {
return nil, err
}
if rv != nil {
return rv, nil
}
return xv.structMember(node.Sel.Name) return xv.structMember(node.Sel.Name)
} }
@ -489,13 +640,23 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err
if xv.Children[0].Addr == 0 { if xv.Children[0].Addr == 0 {
return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type)) return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type))
} }
typ, err := scope.Thread.dbp.findTypeExpr(node.Type) // Accept .(data) as a type assertion that always succeeds, so that users
if err != nil { // can access the data field of an interface without actually having to
return nil, err // type the concrete type.
} if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" {
if xv.Children[0].DwarfType.Common().Name != typ.Common().Name { typ, err := scope.BinInfo.findTypeExpr(node.Type)
return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name) if err != nil {
return nil, err
}
if xv.Children[0].DwarfType.Common().Name != typ.Common().Name {
return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name)
}
} }
// loadInterface will set OnlyAddr for the data member since here we are
// passing false to loadData, however returning the variable with OnlyAddr
// set here would be wrong since, once the expression evaluation
// terminates, the value of this variable will be loaded.
xv.Children[0].OnlyAddr = false
return &xv.Children[0], nil return &xv.Children[0], nil
} }
@ -509,12 +670,27 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
return nil, xev.Unreadable return nil, xev.Unreadable
} }
xev = xev.maybeDereference()
idxev, err := scope.evalAST(node.Index) idxev, err := scope.evalAST(node.Index)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cantindex := fmt.Errorf("expression \"%s\" (%s) does not support indexing", exprToString(node.X), xev.TypeString())
switch xev.Kind { switch xev.Kind {
case reflect.Ptr:
if xev == nilVariable {
return nil, cantindex
}
_, isarrptr := xev.RealType.(*godwarf.PtrType).Type.(*godwarf.ArrayType)
if !isarrptr {
return nil, cantindex
}
xev = xev.maybeDereference()
fallthrough
case reflect.Slice, reflect.Array, reflect.String: case reflect.Slice, reflect.Array, reflect.String:
if xev.Base == 0 { if xev.Base == 0 {
return nil, fmt.Errorf("can not index \"%s\"", exprToString(node.X)) return nil, fmt.Errorf("can not index \"%s\"", exprToString(node.X))
@ -532,8 +708,7 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
} }
return xev.mapAccess(idxev) return xev.mapAccess(idxev)
default: default:
return nil, fmt.Errorf("expression \"%s\" (%s) does not support indexing", exprToString(node.X), xev.TypeString()) return nil, cantindex
} }
} }
@ -585,9 +760,9 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) {
return nil, fmt.Errorf("second slice argument must be empty for maps") return nil, fmt.Errorf("second slice argument must be empty for maps")
} }
xev.mapSkip += int(low) xev.mapSkip += int(low)
xev.loadValue(loadFullValue) xev.mapIterator() // reads map length
if xev.Unreadable != nil { if int64(xev.mapSkip) >= xev.Len {
return nil, xev.Unreadable return nil, fmt.Errorf("map index out of bounds")
} }
return xev, nil return xev, nil
default: default:
@ -631,14 +806,18 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) {
return nil, fmt.Errorf("can not take address of \"%s\"", exprToString(node.X)) return nil, fmt.Errorf("can not take address of \"%s\"", exprToString(node.X))
} }
xev.OnlyAddr = true return xev.pointerToVariable(), nil
}
typename := "*" + xev.DwarfType.Common().Name func (v *Variable) pointerToVariable() *Variable {
rv := scope.newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, Type: xev.DwarfType}) v.OnlyAddr = true
rv.Children = []Variable{*xev}
typename := "*" + v.DwarfType.Common().Name
rv := v.newVariable("", 0, &godwarf.PtrType{CommonType: godwarf.CommonType{ByteSize: int64(v.bi.Arch.PtrSize()), Name: typename}, Type: v.DwarfType}, v.mem)
rv.Children = []Variable{*v}
rv.loaded = true rv.loaded = true
return rv, nil return rv
} }
func constantUnaryOp(op token.Token, y constant.Value) (r constant.Value, err error) { func constantUnaryOp(op token.Token, y constant.Value) (r constant.Value, err error) {
@ -688,6 +867,9 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) {
if xv.Unreadable != nil { if xv.Unreadable != nil {
return nil, xv.Unreadable return nil, xv.Unreadable
} }
if xv.FloatSpecial != 0 {
return nil, errOperationOnSpecialFloat
}
if xv.Value == nil { if xv.Value == nil {
return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X)) return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X))
} }
@ -696,14 +878,14 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) {
return nil, err return nil, err
} }
if xv.DwarfType != nil { if xv.DwarfType != nil {
r := xv.newVariable("", 0, xv.DwarfType) r := xv.newVariable("", 0, xv.DwarfType, scope.Mem)
r.Value = rc r.Value = rc
return r, nil return r, nil
} }
return newConstant(rc, xv.mem), nil return newConstant(rc, xv.mem), nil
} }
func negotiateType(op token.Token, xv, yv *Variable) (dwarf.Type, error) { func negotiateType(op token.Token, xv, yv *Variable) (godwarf.Type, error) {
if xv == nilVariable { if xv == nilVariable {
return nil, negotiateTypeNil(op, yv) return nil, negotiateTypeNil(op, yv)
} }
@ -777,23 +959,36 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
xv.loadValue(loadFullValue)
if xv.Unreadable != nil {
return nil, xv.Unreadable
}
// short circuits logical operators
switch node.Op {
case token.LAND:
if !constant.BoolVal(xv.Value) {
return newConstant(xv.Value, xv.mem), nil
}
case token.LOR:
if constant.BoolVal(xv.Value) {
return newConstant(xv.Value, xv.mem), nil
}
}
yv, err := scope.evalAST(node.Y) yv, err := scope.evalAST(node.Y)
if err != nil { if err != nil {
return nil, err return nil, err
} }
xv.loadValue(loadFullValue)
yv.loadValue(loadFullValue) yv.loadValue(loadFullValue)
if xv.Unreadable != nil {
return nil, xv.Unreadable
}
if yv.Unreadable != nil { if yv.Unreadable != nil {
return nil, yv.Unreadable return nil, yv.Unreadable
} }
if xv.FloatSpecial != 0 || yv.FloatSpecial != 0 {
return nil, errOperationOnSpecialFloat
}
typ, err := negotiateType(node.Op, xv, yv) typ, err := negotiateType(node.Op, xv, yv)
if err != nil { if err != nil {
return nil, err return nil, err
@ -801,8 +996,8 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
op := node.Op op := node.Op
if typ != nil && (op == token.QUO) { if typ != nil && (op == token.QUO) {
_, isint := typ.(*dwarf.IntType) _, isint := typ.(*godwarf.IntType)
_, isuint := typ.(*dwarf.UintType) _, isuint := typ.(*godwarf.UintType)
if isint || isuint { if isint || isuint {
// forces integer division if the result type is integer // forces integer division if the result type is integer
op = token.QUO_ASSIGN op = token.QUO_ASSIGN
@ -835,13 +1030,16 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
return newConstant(rc, xv.mem), nil return newConstant(rc, xv.mem), nil
} }
r := xv.newVariable("", 0, typ) r := xv.newVariable("", 0, typ, scope.Mem)
r.Value = rc r.Value = rc
if r.Kind == reflect.String {
r.Len = xv.Len + yv.Len
}
return r, nil return r, nil
} }
} }
// Comapres xv to yv using operator op // Compares xv to yv using operator op
// Both xv and yv must be loaded and have a compatible type (as determined by negotiateType) // Both xv and yv must be loaded and have a compatible type (as determined by negotiateType)
func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) { func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) {
switch xv.Kind { switch xv.Kind {
@ -854,6 +1052,14 @@ func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) {
case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
return constantCompare(op, xv.Value, yv.Value) return constantCompare(op, xv.Value, yv.Value)
case reflect.String: case reflect.String:
if xv.Len != yv.Len {
switch op {
case token.EQL:
return false, nil
case token.NEQ:
return true, nil
}
}
if int64(len(constant.StringVal(xv.Value))) != xv.Len || int64(len(constant.StringVal(yv.Value))) != yv.Len { if int64(len(constant.StringVal(xv.Value))) != xv.Len || int64(len(constant.StringVal(yv.Value))) != yv.Len {
return false, fmt.Errorf("string too long for comparison") return false, fmt.Errorf("string too long for comparison")
} }
@ -898,7 +1104,7 @@ func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) {
return false, nil return false, nil
} }
if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len { if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len {
return false, fmt.Errorf("sturcture too deep for comparison") return false, fmt.Errorf("structure too deep for comparison")
} }
eql, err = equalChildren(xv, yv, false) eql, err = equalChildren(xv, yv, false)
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
@ -924,7 +1130,7 @@ func (v *Variable) isNil() bool {
case reflect.Ptr: case reflect.Ptr:
return v.Children[0].Addr == 0 return v.Children[0].Addr == 0
case reflect.Interface: case reflect.Interface:
return false return v.Children[0].Addr == 0 && v.Children[0].Kind == reflect.Invalid
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
return v.Base == 0 return v.Base == 0
} }
@ -956,7 +1162,7 @@ func (v *Variable) asInt() (int64, error) {
if v.Unreadable != nil { if v.Unreadable != nil {
return 0, v.Unreadable return 0, v.Unreadable
} }
if _, ok := v.DwarfType.(*dwarf.IntType); !ok { if _, ok := v.DwarfType.(*godwarf.IntType); !ok {
return 0, fmt.Errorf("can not convert value of type %s to int", v.DwarfType.String()) return 0, fmt.Errorf("can not convert value of type %s to int", v.DwarfType.String())
} }
} }
@ -974,7 +1180,7 @@ func (v *Variable) asUint() (uint64, error) {
if v.Unreadable != nil { if v.Unreadable != nil {
return 0, v.Unreadable return 0, v.Unreadable
} }
if _, ok := v.DwarfType.(*dwarf.UintType); !ok { if _, ok := v.DwarfType.(*godwarf.UintType); !ok {
return 0, fmt.Errorf("can not convert value of type %s to uint", v.DwarfType.String()) return 0, fmt.Errorf("can not convert value of type %s to uint", v.DwarfType.String())
} }
} }
@ -982,10 +1188,18 @@ func (v *Variable) asUint() (uint64, error) {
return n, nil return n, nil
} }
func (v *Variable) isType(typ dwarf.Type, kind reflect.Kind) error { type typeConvErr struct {
srcType, dstType godwarf.Type
}
func (err *typeConvErr) Error() string {
return fmt.Sprintf("can not convert value of type %s to %s", err.srcType.String(), err.dstType.String())
}
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
if v.DwarfType != nil { if v.DwarfType != nil {
if typ != nil && typ.String() != v.RealType.String() { if typ != nil && typ.String() != v.RealType.String() {
return fmt.Errorf("can not convert value of type %s to %s", v.DwarfType.String(), typ.String()) return &typeConvErr{v.DwarfType, typ}
} }
return nil return nil
} }
@ -1010,27 +1224,27 @@ func (v *Variable) isType(typ dwarf.Type, kind reflect.Kind) error {
} }
switch typ.(type) { switch typ.(type) {
case *dwarf.IntType: case *godwarf.IntType:
if v.Value.Kind() != constant.Int { if v.Value.Kind() != constant.Int {
return converr return converr
} }
case *dwarf.UintType: case *godwarf.UintType:
if v.Value.Kind() != constant.Int { if v.Value.Kind() != constant.Int {
return converr return converr
} }
case *dwarf.FloatType: case *godwarf.FloatType:
if (v.Value.Kind() != constant.Int) && (v.Value.Kind() != constant.Float) { if (v.Value.Kind() != constant.Int) && (v.Value.Kind() != constant.Float) {
return converr return converr
} }
case *dwarf.BoolType: case *godwarf.BoolType:
if v.Value.Kind() != constant.Bool { if v.Value.Kind() != constant.Bool {
return converr return converr
} }
case *dwarf.StringType: case *godwarf.StringType:
if v.Value.Kind() != constant.String { if v.Value.Kind() != constant.String {
return converr return converr
} }
case *dwarf.ComplexType: case *godwarf.ComplexType:
if v.Value.Kind() != constant.Complex && v.Value.Kind() != constant.Float && v.Value.Kind() != constant.Int { if v.Value.Kind() != constant.Complex && v.Value.Kind() != constant.Float && v.Value.Kind() != constant.Int {
return converr return converr
} }
@ -1045,7 +1259,11 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
if idx < 0 || int64(idx) >= v.Len { if idx < 0 || int64(idx) >= v.Len {
return nil, fmt.Errorf("index out of bounds") return nil, fmt.Errorf("index out of bounds")
} }
return v.newVariable("", v.Base+uintptr(int64(idx)*v.stride), v.fieldType), nil mem := v.mem
if v.Kind != reflect.Array {
mem = DereferenceMemory(mem)
}
return v.newVariable("", v.Base+uintptr(int64(idx)*v.stride), v.fieldType, mem), nil
} }
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) { func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
@ -1063,7 +1281,7 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
} }
if first { if first {
first = false first = false
if err := idx.isType(key.DwarfType, key.Kind); err != nil { if err := idx.isType(key.RealType, key.Kind); err != nil {
return nil, err return nil, err
} }
} }
@ -1095,22 +1313,16 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
} }
typ := v.DwarfType typ := v.DwarfType
if _, isarr := v.DwarfType.(*dwarf.ArrayType); isarr { if _, isarr := v.DwarfType.(*godwarf.ArrayType); isarr {
typ = &dwarf.SliceType{ typ = fakeSliceType(v.fieldType)
StructType: dwarf.StructType{
CommonType: dwarf.CommonType{
ByteSize: 24,
Name: "",
},
StructName: fmt.Sprintf("[]%s", v.fieldType.Common().Name),
Kind: "struct",
Field: nil,
},
ElemType: v.fieldType,
}
} }
r := v.newVariable("", 0, typ) mem := v.mem
if v.Kind != reflect.Array {
mem = DereferenceMemory(mem)
}
r := v.newVariable("", 0, typ, mem)
r.Cap = len r.Cap = len
r.Len = len r.Len = len
r.Base = base r.Base = base
@ -1119,3 +1331,147 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
return r, nil return r, nil
} }
// findMethod finds method mname in the type of variable v
func (v *Variable) findMethod(mname string) (*Variable, error) {
if _, isiface := v.RealType.(*godwarf.InterfaceType); isiface {
v.loadInterface(0, false, loadFullValue)
if v.Unreadable != nil {
return nil, v.Unreadable
}
return v.Children[0].findMethod(mname)
}
typ := v.DwarfType
ptyp, isptr := typ.(*godwarf.PtrType)
if isptr {
typ = ptyp.Type
}
if _, istypedef := typ.(*godwarf.TypedefType); !istypedef {
return nil, nil
}
typePath := typ.Common().Name
dot := strings.LastIndex(typePath, ".")
if dot < 0 {
// probably just a C type
return nil, nil
}
pkg := typePath[:dot]
receiver := typePath[dot+1:]
if fn, ok := v.bi.LookupFunc[fmt.Sprintf("%s.%s.%s", pkg, receiver, mname)]; ok {
r, err := functionToVariable(fn, v.bi, v.mem)
if err != nil {
return nil, err
}
if isptr {
r.Children = append(r.Children, *(v.maybeDereference()))
} else {
r.Children = append(r.Children, *v)
}
return r, nil
}
if fn, ok := v.bi.LookupFunc[fmt.Sprintf("%s.(*%s).%s", pkg, receiver, mname)]; ok {
r, err := functionToVariable(fn, v.bi, v.mem)
if err != nil {
return nil, err
}
if isptr {
r.Children = append(r.Children, *v)
} else {
r.Children = append(r.Children, *(v.pointerToVariable()))
}
return r, nil
}
return nil, nil
}
func functionToVariable(fn *Function, bi *BinaryInfo, mem MemoryReadWriter) (*Variable, error) {
typ, err := fn.fakeType(bi, true)
if err != nil {
return nil, err
}
v := newVariable(fn.Name, 0, typ, bi, mem)
v.Value = constant.MakeString(fn.Name)
v.loaded = true
v.Base = uintptr(fn.Entry)
return v, nil
}
func fakeSliceType(fieldType godwarf.Type) godwarf.Type {
return &godwarf.SliceType{
StructType: godwarf.StructType{
CommonType: godwarf.CommonType{
ByteSize: 24,
Name: "",
},
StructName: fmt.Sprintf("[]%s", fieldType.Common().Name),
Kind: "struct",
Field: nil,
},
ElemType: fieldType,
}
}
var errMethodEvalUnsupported = errors.New("evaluating methods not supported on this version of Go")
func (fn *Function) fakeType(bi *BinaryInfo, removeReceiver bool) (*godwarf.FuncType, error) {
if producer := bi.Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 10) {
// versions of Go prior to 1.10 do not distinguish between parameters and
// return values, therefore we can't use a subprogram DIE to derive a
// function type.
return nil, errMethodEvalUnsupported
}
_, formalArgs, err := funcCallArgs(fn, bi, true)
if err != nil {
return nil, err
}
if removeReceiver {
formalArgs = formalArgs[1:]
}
args := make([]string, 0, len(formalArgs))
rets := make([]string, 0, len(formalArgs))
for _, formalArg := range formalArgs {
var s string
if strings.HasPrefix(formalArg.name, "~") {
s = formalArg.typ.String()
} else {
s = fmt.Sprintf("%s %s", formalArg.name, formalArg.typ.String())
}
if formalArg.isret {
rets = append(rets, s)
} else {
args = append(args, s)
}
}
argstr := strings.Join(args, ", ")
var retstr string
switch len(rets) {
case 0:
retstr = ""
case 1:
retstr = " " + rets[0]
default:
retstr = " (" + strings.Join(rets, ", ") + ")"
}
return &godwarf.FuncType{
CommonType: godwarf.CommonType{
Name: "func(" + argstr + ")" + retstr,
ReflectKind: reflect.Func,
},
//TODO(aarzilli): at the moment we aren't using the ParamType and
// ReturnType fields of FuncType anywhere (when this is returned to the
// client it's first converted to a string and the function calling code
// reads the subroutine entry because it needs to know the stack offsets).
// If we start using them they should be filled here.
}, nil
}

561
vendor/github.com/derekparker/delve/pkg/proc/fncall.go generated vendored Normal file
View File

@ -0,0 +1,561 @@
package proc
import (
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/parser"
"reflect"
"sort"
"github.com/derekparker/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/logflags"
"github.com/sirupsen/logrus"
"golang.org/x/arch/x86/x86asm"
)
// This file implements the function call injection introduced in go1.11.
//
// The protocol is described in $GOROOT/src/runtime/asm_amd64.s in the
// comments for function runtime·debugCallV1.
//
// There are two main entry points here. The first one is CallFunction which
// evaluates a function call expression, sets up the function call on the
// selected goroutine and resumes execution of the process.
//
// The second one is (*FunctionCallState).step() which is called every time
// the process stops at a breakpoint inside one of the debug injcetion
// functions.
const (
debugCallFunctionNamePrefix1 = "debugCall"
debugCallFunctionNamePrefix2 = "runtime.debugCall"
debugCallFunctionName = "runtime.debugCallV1"
)
var (
errFuncCallUnsupported = errors.New("function calls not supported by this version of Go")
errFuncCallUnsupportedBackend = errors.New("backend does not support function calls")
errFuncCallInProgress = errors.New("cannot call function while another function call is already in progress")
errNotACallExpr = errors.New("not a function call")
errNoGoroutine = errors.New("no goroutine selected")
errGoroutineNotRunning = errors.New("selected goroutine not running")
errNotEnoughStack = errors.New("not enough stack space")
errTooManyArguments = errors.New("too many arguments")
errNotEnoughArguments = errors.New("not enough arguments")
errNoAddrUnsupported = errors.New("arguments to a function call must have an address")
errNotAGoFunction = errors.New("not a Go function")
)
type functionCallState struct {
// inProgress is true if a function call is in progress
inProgress bool
// finished is true if the function call terminated
finished bool
// savedRegs contains the saved registers
savedRegs Registers
// expr contains an expression describing the current function call
expr string
// err contains a saved error
err error
// fn is the function that is being called
fn *Function
// closureAddr is the address of the closure being called
closureAddr uint64
// argmem contains the argument frame of this function call
argmem []byte
// retvars contains the return variables after the function call terminates without panic'ing
retvars []*Variable
// retLoadCfg is the load configuration used to load return values
retLoadCfg *LoadConfig
// panicvar is a variable used to store the value of the panic, if the
// called function panics.
panicvar *Variable
}
// CallFunction starts a debugger injected function call on the current thread of p.
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
// description of the protocol.
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
bi := p.BinInfo()
if !p.Common().fncallEnabled {
return errFuncCallUnsupportedBackend
}
fncall := &p.Common().fncallState
if fncall.inProgress {
return errFuncCallInProgress
}
*fncall = functionCallState{}
dbgcallfn := bi.LookupFunc[debugCallFunctionName]
if dbgcallfn == nil {
return errFuncCallUnsupported
}
// check that the selected goroutine is running
g := p.SelectedGoroutine()
if g == nil {
return errNoGoroutine
}
if g.Status != Grunning || g.Thread == nil {
return errGoroutineNotRunning
}
// check that there are at least 256 bytes free on the stack
regs, err := g.Thread.Registers(true)
if err != nil {
return err
}
regs = regs.Copy()
if regs.SP()-256 <= g.stacklo {
return errNotEnoughStack
}
_, err = regs.Get(int(x86asm.RAX))
if err != nil {
return errFuncCallUnsupportedBackend
}
fn, closureAddr, argvars, err := funcCallEvalExpr(p, expr)
if err != nil {
return err
}
argmem, err := funcCallArgFrame(fn, argvars, g, bi)
if err != nil {
return err
}
if err := callOP(bi, g.Thread, regs, dbgcallfn.Entry); err != nil {
return err
}
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
if err := writePointer(bi, g.Thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(len(argmem))); err != nil {
return err
}
fncall.inProgress = true
fncall.savedRegs = regs
fncall.expr = expr
fncall.fn = fn
fncall.closureAddr = closureAddr
fncall.argmem = argmem
fncall.retLoadCfg = retLoadCfg
fncallLog("function call initiated %v frame size %d\n", fn, len(argmem))
return Continue(p)
}
func fncallLog(fmtstr string, args ...interface{}) {
if !logflags.FnCall() {
return
}
logrus.WithFields(logrus.Fields{"layer": "proc", "kind": "fncall"}).Infof(fmtstr, args...)
}
// writePointer writes val as an architecture pointer at addr in mem.
func writePointer(bi *BinaryInfo, mem MemoryReadWriter, addr, val uint64) error {
ptrbuf := make([]byte, bi.Arch.PtrSize())
// TODO: use target architecture endianness instead of LittleEndian
switch len(ptrbuf) {
case 4:
binary.LittleEndian.PutUint32(ptrbuf, uint32(val))
case 8:
binary.LittleEndian.PutUint64(ptrbuf, val)
default:
panic(fmt.Errorf("unsupported pointer size %d", len(ptrbuf)))
}
_, err := mem.WriteMemory(uintptr(addr), ptrbuf)
return err
}
// callOP simulates a call instruction on the given thread:
// * pushes the current value of PC on the stack (adjusting SP)
// * changes the value of PC to callAddr
// Note: regs are NOT updated!
func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) error {
sp := regs.SP()
// push PC on the stack
sp -= uint64(bi.Arch.PtrSize())
if err := thread.SetSP(sp); err != nil {
return err
}
if err := writePointer(bi, thread, sp, regs.PC()); err != nil {
return err
}
return thread.SetPC(callAddr)
}
// funcCallEvalExpr evaluates expr, which must be a function call, returns
// the function being called and its arguments.
func funcCallEvalExpr(p Process, expr string) (fn *Function, closureAddr uint64, argvars []*Variable, err error) {
bi := p.BinInfo()
scope, err := GoroutineScope(p.CurrentThread())
if err != nil {
return nil, 0, nil, err
}
t, err := parser.ParseExpr(expr)
if err != nil {
return nil, 0, nil, err
}
callexpr, iscall := t.(*ast.CallExpr)
if !iscall {
return nil, 0, nil, errNotACallExpr
}
fnvar, err := scope.evalAST(callexpr.Fun)
if err != nil {
return nil, 0, nil, err
}
if fnvar.Kind != reflect.Func {
return nil, 0, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun))
}
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0})
if fnvar.Unreadable != nil {
return nil, 0, nil, fnvar.Unreadable
}
if fnvar.Base == 0 {
return nil, 0, nil, errors.New("nil pointer dereference")
}
fn = bi.PCToFunc(uint64(fnvar.Base))
if fn == nil {
return nil, 0, nil, fmt.Errorf("could not find DIE for function %q", exprToString(callexpr.Fun))
}
if !fn.cu.isgo {
return nil, 0, nil, errNotAGoFunction
}
argvars = make([]*Variable, 0, len(callexpr.Args)+1)
if len(fnvar.Children) > 0 {
// receiver argument
argvars = append(argvars, &fnvar.Children[0])
}
for i := range callexpr.Args {
argvar, err := scope.evalAST(callexpr.Args[i])
if err != nil {
return nil, 0, nil, err
}
argvar.Name = exprToString(callexpr.Args[i])
argvars = append(argvars, argvar)
}
return fn, fnvar.funcvalAddr(), argvars, nil
}
type funcCallArg struct {
name string
typ godwarf.Type
off int64
isret bool
}
// funcCallArgFrame checks type and pointer escaping for the arguments and
// returns the argument frame.
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo) (argmem []byte, err error) {
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
if err != nil {
return nil, err
}
if len(actualArgs) > len(formalArgs) {
return nil, errTooManyArguments
}
if len(actualArgs) < len(formalArgs) {
return nil, errNotEnoughArguments
}
// constructs arguments frame
argmem = make([]byte, argFrameSize)
argmemWriter := &bufferMemoryReadWriter{argmem}
for i := range formalArgs {
formalArg := &formalArgs[i]
actualArg := actualArgs[i]
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
return nil, fmt.Errorf("can not pass %s to %s: %v", actualArg.Name, formalArg.name, err)
}
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
// by convertToEface.
formalArgVar := newVariable(formalArg.name, uintptr(formalArg.off+fakeAddress), formalArg.typ, bi, argmemWriter)
if err := formalArgVar.setValue(actualArg, actualArg.Name); err != nil {
return nil, err
}
}
return argmem, nil
}
func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) {
const CFA = 0x1000
vrdr := reader.Variables(bi.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, bi.staticBase), int(^uint(0)>>1), false)
// typechecks arguments, calculates argument frame size
for vrdr.Next() {
e := vrdr.Entry()
if e.Tag != dwarf.TagFormalParameter {
continue
}
entry, argname, typ, err := readVarEntry(e, bi)
if err != nil {
return 0, nil, err
}
typ = resolveTypedef(typ)
locprog, _, err := bi.locationExpr(entry, dwarf.AttrLocation, fn.Entry)
if err != nil {
return 0, nil, fmt.Errorf("could not get argument location of %s: %v", argname, err)
}
off, _, err := op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog)
if err != nil {
return 0, nil, fmt.Errorf("unsupported location expression for argument %s: %v", argname, err)
}
off -= CFA
if e := off + typ.Size(); e > argFrameSize {
argFrameSize = e
}
if isret, _ := entry.Val(dwarf.AttrVarParam).(bool); !isret || includeRet {
formalArgs = append(formalArgs, funcCallArg{name: argname, typ: typ, off: off, isret: isret})
}
}
if err := vrdr.Err(); err != nil {
return 0, nil, fmt.Errorf("DWARF read error: %v", err)
}
sort.Slice(formalArgs, func(i, j int) bool {
return formalArgs[i].off < formalArgs[j].off
})
return argFrameSize, formalArgs, nil
}
func escapeCheck(v *Variable, name string, g *G) error {
switch v.Kind {
case reflect.Ptr:
w := v.maybeDereference()
return escapeCheckPointer(w.Addr, name, g)
case reflect.Chan, reflect.String, reflect.Slice:
return escapeCheckPointer(v.Base, name, g)
case reflect.Map:
sv := v.clone()
sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
sv = sv.maybeDereference()
return escapeCheckPointer(sv.Addr, name, g)
case reflect.Struct:
t := v.RealType.(*godwarf.StructType)
for _, field := range t.Field {
fv, _ := v.toField(field)
if err := escapeCheck(fv, fmt.Sprintf("%s.%s", name, field.Name), g); err != nil {
return err
}
}
case reflect.Array:
for i := int64(0); i < v.Len; i++ {
sv, _ := v.sliceAccess(int(i))
if err := escapeCheck(sv, fmt.Sprintf("%s[%d]", name, i), g); err != nil {
return err
}
}
case reflect.Func:
if err := escapeCheckPointer(uintptr(v.funcvalAddr()), name, g); err != nil {
return err
}
}
return nil
}
func escapeCheckPointer(addr uintptr, name string, g *G) error {
if uint64(addr) >= g.stacklo && uint64(addr) < g.stackhi {
return fmt.Errorf("stack object passed to escaping pointer: %s", name)
}
return nil
}
const (
debugCallAXPrecheckFailed = 8
debugCallAXCompleteCall = 0
debugCallAXReadReturn = 1
debugCallAXReadPanic = 2
debugCallAXRestoreRegisters = 16
)
func (fncall *functionCallState) step(p Process) {
bi := p.BinInfo()
thread := p.CurrentThread()
regs, err := thread.Registers(false)
if err != nil {
fncall.err = err
fncall.finished = true
fncall.inProgress = false
return
}
regs = regs.Copy()
rax, _ := regs.Get(int(x86asm.RAX))
if logflags.FnCall() {
loc, _ := thread.Location()
var pc uint64
var fnname string
if loc != nil {
pc = loc.PC
if loc.Fn != nil {
fnname = loc.Fn.Name
}
}
fncallLog("function call interrupt rax=%#x (PC=%#x in %s)\n", rax, pc, fnname)
}
switch rax {
case debugCallAXPrecheckFailed:
// get error from top of the stack and return it to user
errvar, err := readTopstackVariable(thread, regs, "string", loadFullValue)
if err != nil {
fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
break
}
errvar.Name = "err"
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
case debugCallAXCompleteCall:
// write arguments to the stack, call final function
n, err := thread.WriteMemory(uintptr(regs.SP()), fncall.argmem)
if err != nil {
fncall.err = fmt.Errorf("could not write arguments: %v", err)
}
if n != len(fncall.argmem) {
fncall.err = fmt.Errorf("short argument write: %d %d", n, len(fncall.argmem))
}
if fncall.closureAddr != 0 {
// When calling a function pointer we must set the DX register to the
// address of the function pointer itself.
thread.SetDX(fncall.closureAddr)
}
callOP(bi, thread, regs, fncall.fn.Entry)
case debugCallAXRestoreRegisters:
// runtime requests that we restore the registers (all except pc and sp),
// this is also the last step of the function call protocol.
fncall.finished = true
pc, sp := regs.PC(), regs.SP()
if err := thread.RestoreRegisters(fncall.savedRegs); err != nil {
fncall.err = fmt.Errorf("could not restore registers: %v", err)
}
if err := thread.SetPC(pc); err != nil {
fncall.err = fmt.Errorf("could not restore PC: %v", err)
}
if err := thread.SetSP(sp); err != nil {
fncall.err = fmt.Errorf("could not restore SP: %v", err)
}
if err := stepInstructionOut(p, thread, debugCallFunctionName, debugCallFunctionName); err != nil {
fncall.err = fmt.Errorf("could not step out of %s: %v", debugCallFunctionName, err)
}
case debugCallAXReadReturn:
// read return arguments from stack
if fncall.retLoadCfg == nil || fncall.panicvar != nil {
break
}
scope, err := ThreadScope(thread)
if err != nil {
fncall.err = fmt.Errorf("could not get return values: %v", err)
break
}
// pretend we are still inside the function we called
fakeFunctionEntryScope(scope, fncall.fn, int64(regs.SP()), regs.SP()-uint64(bi.Arch.PtrSize()))
fncall.retvars, err = scope.Locals()
if err != nil {
fncall.err = fmt.Errorf("could not get return values: %v", err)
break
}
fncall.retvars = filterVariables(fncall.retvars, func(v *Variable) bool {
return (v.Flags & VariableReturnArgument) != 0
})
loadValues(fncall.retvars, *fncall.retLoadCfg)
case debugCallAXReadPanic:
// read panic value from stack
if fncall.retLoadCfg == nil {
return
}
fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", *fncall.retLoadCfg)
if err != nil {
fncall.err = fmt.Errorf("could not get panic: %v", err)
break
}
fncall.panicvar.Name = "~panic"
fncall.panicvar.loadValue(*fncall.retLoadCfg)
if fncall.panicvar.Unreadable != nil {
fncall.err = fmt.Errorf("could not get panic: %v", fncall.panicvar.Unreadable)
break
}
default:
// Got an unknown AX value, this is probably bad but the safest thing
// possible is to ignore it and hope it didn't matter.
fncallLog("unknown value of AX %#x", rax)
}
}
func readTopstackVariable(thread Thread, regs Registers, typename string, loadCfg LoadConfig) (*Variable, error) {
bi := thread.BinInfo()
scope, err := ThreadScope(thread)
if err != nil {
return nil, err
}
typ, err := bi.findType(typename)
if err != nil {
return nil, err
}
v := scope.newVariable("", uintptr(regs.SP()), typ, scope.Mem)
v.loadValue(loadCfg)
if v.Unreadable != nil {
return nil, v.Unreadable
}
return v, nil
}
// fakeEntryScope alters scope to pretend that we are at the entry point of
// fn and CFA and SP are the ones passed as argument.
// This function is used to create a scope for a call frame that doesn't
// exist anymore, to read the return variables of an injected function call,
// or after a stepout command.
func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64) error {
scope.PC = fn.Entry
scope.Fn = fn
scope.File, scope.Line, _ = scope.BinInfo.PCToLine(fn.Entry)
scope.Regs.CFA = cfa
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = sp
scope.BinInfo.dwarfReader.Seek(fn.offset)
e, err := scope.BinInfo.dwarfReader.Next()
if err != nil {
return err
}
scope.Regs.FrameBase, _, _, _ = scope.BinInfo.Location(e, dwarf.AttrFrameBase, scope.PC, scope.Regs)
return nil
}
func (fncall *functionCallState) returnValues() []*Variable {
if fncall.panicvar != nil {
return []*Variable{fncall.panicvar}
}
return fncall.retvars
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
// +build linux darwin
package gdbserial
import (
"os/signal"
"syscall"
)
func sysProcAttr(foreground bool) *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setpgid: true, Pgid: 0, Foreground: foreground}
}
func foregroundSignalsIgnore() {
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
}

View File

@ -0,0 +1,10 @@
package gdbserial
import "syscall"
func sysProcAttr(foreground bool) *syscall.SysProcAttr {
return nil
}
func foregroundSignalsIgnore() {
}

View File

@ -0,0 +1,267 @@
package gdbserial
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strconv"
"strings"
"unicode"
)
// Record uses rr to record the execution of the specified program and
// returns the trace directory's path.
func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
if err := checkRRAvailabe(); err != nil {
return "", err
}
rfd, wfd, err := os.Pipe()
if err != nil {
return "", err
}
args := make([]string, 0, len(cmd)+2)
args = append(args, "record", "--print-trace-dir=3")
args = append(args, cmd...)
rrcmd := exec.Command("rr", args...)
rrcmd.Stdin = os.Stdin
if !quiet {
rrcmd.Stdout = os.Stdout
rrcmd.Stderr = os.Stderr
}
rrcmd.ExtraFiles = []*os.File{wfd}
rrcmd.Dir = wd
done := make(chan struct{})
go func() {
bs, _ := ioutil.ReadAll(rfd)
tracedir = strings.TrimSpace(string(bs))
close(done)
}()
err = rrcmd.Run()
// ignore run errors, it could be the program crashing
wfd.Close()
<-done
return
}
// Replay starts an instance of rr in replay mode, with the specified trace
// directory, and connects to it.
func Replay(tracedir string, quiet bool) (*Process, error) {
if err := checkRRAvailabe(); err != nil {
return nil, err
}
rrcmd := exec.Command("rr", "replay", "--dbgport=0", tracedir)
rrcmd.Stdout = os.Stdout
stderr, err := rrcmd.StderrPipe()
if err != nil {
return nil, err
}
rrcmd.SysProcAttr = sysProcAttr(false)
initch := make(chan rrInit)
go rrStderrParser(stderr, initch, quiet)
err = rrcmd.Start()
if err != nil {
return nil, err
}
init := <-initch
if init.err != nil {
rrcmd.Process.Kill()
return nil, init.err
}
p := New(rrcmd.Process)
p.tracedir = tracedir
err = p.Dial(init.port, init.exe, 0)
if err != nil {
rrcmd.Process.Kill()
return nil, err
}
return p, nil
}
// ErrPerfEventParanoid is the error returned by Reply and Record if
// /proc/sys/kernel/perf_event_paranoid is greater than 1.
type ErrPerfEventParanoid struct {
actual int
}
func (err ErrPerfEventParanoid) Error() string {
return fmt.Sprintf("rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is %d", err.actual)
}
func checkRRAvailabe() error {
if _, err := exec.LookPath("rr"); err != nil {
return &ErrBackendUnavailable{}
}
// Check that /proc/sys/kernel/perf_event_paranoid doesn't exist or is <= 1.
buf, err := ioutil.ReadFile("/proc/sys/kernel/perf_event_paranoid")
if err == nil {
perfEventParanoid, _ := strconv.Atoi(strings.TrimSpace(string(buf)))
if perfEventParanoid > 1 {
return ErrPerfEventParanoid{perfEventParanoid}
}
}
return nil
}
type rrInit struct {
port string
exe string
err error
}
const (
rrGdbCommandPrefix = " gdb "
rrGdbLaunchPrefix = "Launch gdb with"
targetCmd = "target extended-remote "
)
func rrStderrParser(stderr io.Reader, initch chan<- rrInit, quiet bool) {
rd := bufio.NewReader(stderr)
for {
line, err := rd.ReadString('\n')
if err != nil {
initch <- rrInit{"", "", err}
close(initch)
return
}
if strings.HasPrefix(line, rrGdbCommandPrefix) {
initch <- rrParseGdbCommand(line[len(rrGdbCommandPrefix):])
close(initch)
break
}
if strings.HasPrefix(line, rrGdbLaunchPrefix) {
continue
}
if !quiet {
os.Stderr.Write([]byte(line))
}
}
io.Copy(os.Stderr, rd)
}
type ErrMalformedRRGdbCommand struct {
line, reason string
}
func (err *ErrMalformedRRGdbCommand) Error() string {
return fmt.Sprintf("malformed gdb command %q: %s", err.line, err.reason)
}
func rrParseGdbCommand(line string) rrInit {
port := ""
fields := splitQuotedFields(line)
for i := 0; i < len(fields); i++ {
switch fields[i] {
case "-ex":
if i+1 >= len(fields) {
return rrInit{err: &ErrMalformedRRGdbCommand{line, "-ex not followed by an argument"}}
}
arg := fields[i+1]
if !strings.HasPrefix(arg, targetCmd) {
continue
}
port = arg[len(targetCmd):]
i++
case "-l":
// skip argument
i++
}
}
if port == "" {
return rrInit{err: &ErrMalformedRRGdbCommand{line, "could not find -ex argument"}}
}
exe := fields[len(fields)-1]
return rrInit{port: port, exe: exe}
}
// Like strings.Fields but ignores spaces inside areas surrounded
// by single quotes.
// To specify a single quote use backslash to escape it: '\''
func splitQuotedFields(in string) []string {
type stateEnum int
const (
inSpace stateEnum = iota
inField
inQuote
inQuoteEscaped
)
state := inSpace
r := []string{}
var buf bytes.Buffer
for _, ch := range in {
switch state {
case inSpace:
if ch == '\'' {
state = inQuote
} else if !unicode.IsSpace(ch) {
buf.WriteRune(ch)
state = inField
}
case inField:
if ch == '\'' {
state = inQuote
} else if unicode.IsSpace(ch) {
r = append(r, buf.String())
buf.Reset()
} else {
buf.WriteRune(ch)
}
case inQuote:
if ch == '\'' {
state = inField
} else if ch == '\\' {
state = inQuoteEscaped
} else {
buf.WriteRune(ch)
}
case inQuoteEscaped:
buf.WriteRune(ch)
state = inQuote
}
}
if buf.Len() != 0 {
r = append(r, buf.String())
}
return r
}
// RecordAndReplay acts like calling Record and then Replay.
func RecordAndReplay(cmd []string, wd string, quiet bool) (p *Process, tracedir string, err error) {
tracedir, err = Record(cmd, wd, quiet)
if tracedir == "" {
return nil, "", err
}
p, err = Replay(tracedir, quiet)
return p, tracedir, err
}

View File

@ -0,0 +1,129 @@
package proc
import (
"go/ast"
)
// Process represents the target of the debugger. This
// target could be a system process, core file, etc.
//
// Implementations of Process are not required to be thread safe and users
// of Process should not assume they are.
// There is one exception to this rule: it is safe to call RequestManualStop
// concurrently with ContinueOnce.
type Process interface {
Info
ProcessManipulation
BreakpointManipulation
RecordingManipulation
}
// RecordingManipulation is an interface for manipulating process recordings.
type RecordingManipulation interface {
// Recorded returns true if the current process is a recording and the path
// to the trace directory.
Recorded() (recorded bool, tracedir string)
// Restart restarts the recording from the specified position, or from the
// last checkpoint if pos == "".
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
// number.
Restart(pos string) error
// Direction changes execution direction.
Direction(Direction) error
// When returns current recording position.
When() (string, error)
// Checkpoint sets a checkpoint at the current position.
Checkpoint(where string) (id int, err error)
// Checkpoints returns the list of currently set checkpoint.
Checkpoints() ([]Checkpoint, error)
// ClearCheckpoint removes a checkpoint.
ClearCheckpoint(id int) error
}
// Direction is the direction of execution for the target process.
type Direction int8
const (
// Forward direction executes the target normally.
Forward Direction = 0
// Backward direction executes the target in reverse.
Backward Direction = 1
)
// Checkpoint is a checkpoint
type Checkpoint struct {
ID int
When string
Where string
}
// Info is an interface that provides general information on the target.
type Info interface {
Pid() int
// ResumeNotify specifies a channel that will be closed the next time
// ContinueOnce finishes resuming the target.
ResumeNotify(chan<- struct{})
// Valid returns true if this Process can be used. When it returns false it
// also returns an error describing why the Process is invalid (either
// ErrProcessExited or ProcessDetachedError).
Valid() (bool, error)
BinInfo() *BinaryInfo
// Common returns a struct with fields common to all backends
Common() *CommonProcess
ThreadInfo
GoroutineInfo
}
// ThreadInfo is an interface for getting information on active threads
// in the process.
type ThreadInfo interface {
FindThread(threadID int) (Thread, bool)
ThreadList() []Thread
CurrentThread() Thread
}
// GoroutineInfo is an interface for getting information on running goroutines.
type GoroutineInfo interface {
SelectedGoroutine() *G
}
// ProcessManipulation is an interface for changing the execution state of a process.
type ProcessManipulation interface {
ContinueOnce() (trapthread Thread, err error)
StepInstruction() error
SwitchThread(int) error
SwitchGoroutine(int) error
RequestManualStop() error
// CheckAndClearManualStopRequest returns true the first time it's called
// after a call to RequestManualStop.
CheckAndClearManualStopRequest() bool
Detach(bool) error
}
// BreakpointManipulation is an interface for managing breakpoints.
type BreakpointManipulation interface {
Breakpoints() *BreakpointMap
SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error)
ClearBreakpoint(addr uint64) (*Breakpoint, error)
ClearInternalBreakpoints() error
}
// CommonProcess contains fields used by this package, common to all
// implementations of the Process interface.
type CommonProcess struct {
allGCache []*G
fncallState functionCallState
fncallEnabled bool
}
// NewCommonProcess returns a struct with fields common across
// all process implementations.
func NewCommonProcess(fncallEnabled bool) CommonProcess {
return CommonProcess{fncallEnabled: fncallEnabled}
}
// ClearAllGCache clears the cached contents of the cache for runtime.allgs.
func (p *CommonProcess) ClearAllGCache() {
p.allGCache = nil
}

View File

@ -0,0 +1,39 @@
package linutil
import (
"bytes"
"encoding/binary"
)
const (
_AT_NULL_AMD64 = 0
_AT_ENTRY_AMD64 = 9
)
// EntryPointFromAuxv searches the elf auxiliary vector for the entry point
// address.
// For a description of the auxiliary vector (auxv) format see:
// System V Application Binary Interface, AMD64 Architecture Processor
// Supplement, section 3.4.3
func EntryPointFromAuxvAMD64(auxv []byte) uint64 {
rd := bytes.NewBuffer(auxv)
for {
var tag, val uint64
err := binary.Read(rd, binary.LittleEndian, &tag)
if err != nil {
return 0
}
err = binary.Read(rd, binary.LittleEndian, &val)
if err != nil {
return 0
}
switch tag {
case _AT_NULL_AMD64:
return 0
case _AT_ENTRY_AMD64:
return val
}
}
}

View File

@ -0,0 +1,4 @@
// This package contains functions and data structures used by both the
// linux implementation of the native backend and the core backend to deal
// with structures used by the linux kernel.
package linutil

156
vendor/github.com/derekparker/delve/pkg/proc/mem.go generated vendored Normal file
View File

@ -0,0 +1,156 @@
package proc
import (
"errors"
"github.com/derekparker/delve/pkg/dwarf/op"
)
const cacheEnabled = true
// MemoryReader is like io.ReaderAt, but the offset is a uintptr so that it
// can address all of 64-bit memory.
// Redundant with memoryReadWriter but more easily suited to working with
// the standard io package.
type MemoryReader interface {
// ReadMemory is just like io.ReaderAt.ReadAt.
ReadMemory(buf []byte, addr uintptr) (n int, err error)
}
// MemoryReadWriter is an interface for reading or writing to
// the targets memory. This allows us to read from the actual
// target memory or possibly a cache.
type MemoryReadWriter interface {
MemoryReader
WriteMemory(addr uintptr, data []byte) (written int, err error)
}
type memCache struct {
loaded bool
cacheAddr uintptr
cache []byte
mem MemoryReadWriter
}
func (m *memCache) contains(addr uintptr, size int) bool {
return addr >= m.cacheAddr && addr <= (m.cacheAddr+uintptr(len(m.cache)-size))
}
func (m *memCache) ReadMemory(data []byte, addr uintptr) (n int, err error) {
if m.contains(addr, len(data)) {
if !m.loaded {
_, err := m.mem.ReadMemory(m.cache, m.cacheAddr)
if err != nil {
return 0, err
}
m.loaded = true
}
copy(data, m.cache[addr-m.cacheAddr:])
return len(data), nil
}
return m.mem.ReadMemory(data, addr)
}
func (m *memCache) WriteMemory(addr uintptr, data []byte) (written int, err error) {
return m.mem.WriteMemory(addr, data)
}
func cacheMemory(mem MemoryReadWriter, addr uintptr, size int) MemoryReadWriter {
if !cacheEnabled {
return mem
}
if size <= 0 {
return mem
}
switch cacheMem := mem.(type) {
case *memCache:
if cacheMem.contains(addr, size) {
return mem
}
case *compositeMemory:
return mem
}
return &memCache{false, addr, make([]byte, size), mem}
}
// fakeAddress used by extractVarInfoFromEntry for variables that do not
// have a memory address, we can't use 0 because a lot of code (likely
// including client code) assumes that addr == 0 is nil
const fakeAddress = 0xbeef0000
// compositeMemory represents a chunk of memory that is stored in CPU
// registers or non-contiguously.
//
// When optimizations are enabled the compiler will store some variables
// into registers and sometimes it will also store structs non-contiguously
// with some fields stored into CPU registers and other fields stored in
// memory.
type compositeMemory struct {
realmem MemoryReadWriter
regs op.DwarfRegisters
pieces []op.Piece
data []byte
}
func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece) *compositeMemory {
cmem := &compositeMemory{realmem: mem, regs: regs, pieces: pieces, data: []byte{}}
for _, piece := range pieces {
if piece.IsRegister {
reg := regs.Bytes(piece.RegNum)
sz := piece.Size
if sz == 0 && len(pieces) == 1 {
sz = len(reg)
}
cmem.data = append(cmem.data, reg[:sz]...)
} else {
buf := make([]byte, piece.Size)
mem.ReadMemory(buf, uintptr(piece.Addr))
cmem.data = append(cmem.data, buf...)
}
}
return cmem
}
func (mem *compositeMemory) ReadMemory(data []byte, addr uintptr) (int, error) {
addr -= fakeAddress
if addr >= uintptr(len(mem.data)) || addr+uintptr(len(data)) > uintptr(len(mem.data)) {
return 0, errors.New("read out of bounds")
}
copy(data, mem.data[addr:addr+uintptr(len(data))])
return len(data), nil
}
func (mem *compositeMemory) WriteMemory(addr uintptr, data []byte) (int, error) {
//TODO(aarzilli): implement
return 0, errors.New("can't write composite memory")
}
// DereferenceMemory returns a MemoryReadWriter that can read and write the
// memory pointed to by pointers in this memory.
// Normally mem and mem.Dereference are the same object, they are different
// only if this MemoryReadWriter is used to access memory outside of the
// normal address space of the inferior process (such as data contained in
// registers, or composite memory).
func DereferenceMemory(mem MemoryReadWriter) MemoryReadWriter {
switch mem := mem.(type) {
case *compositeMemory:
return mem.realmem
}
return mem
}
// bufferMemoryReadWriter is dummy a MemoryReadWriter backed by a []byte.
type bufferMemoryReadWriter struct {
buf []byte
}
func (mem *bufferMemoryReadWriter) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
copy(buf, mem.buf[addr-fakeAddress:][:len(buf)])
return len(buf), nil
}
func (mem *bufferMemoryReadWriter) WriteMemory(addr uintptr, data []byte) (written int, err error) {
copy(mem.buf[addr-fakeAddress:], data)
return len(data), nil
}

View File

@ -11,11 +11,11 @@ type moduleData struct {
typemapVar *Variable typemapVar *Variable
} }
func (dbp *Process) loadModuleData() (err error) { func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
dbp.loadModuleDataOnce.Do(func() { bi.loadModuleDataOnce.Do(func() {
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0} scope := globalScope(bi, mem)
var md *Variable var md *Variable
md, err = scope.packageVarAddr("runtime.firstmoduledata") md, err = scope.findGlobal("runtime.firstmoduledata")
if err != nil { if err != nil {
return return
} }
@ -43,7 +43,7 @@ func (dbp *Process) loadModuleData() (err error) {
return return
} }
dbp.moduleData = append(dbp.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar}) bi.moduleData = append(bi.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar})
md = nextVar.maybeDereference() md = nextVar.maybeDereference()
if md.Unreadable != nil { if md.Unreadable != nil {
@ -56,56 +56,65 @@ func (dbp *Process) loadModuleData() (err error) {
return return
} }
func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, error) { func findModuleDataForType(bi *BinaryInfo, typeAddr uintptr, mem MemoryReadWriter) (*moduleData, error) {
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go if err := loadModuleData(bi, mem); err != nil {
if err := dbp.loadModuleData(); err != nil {
return nil, err return nil, err
} }
var md *moduleData var md *moduleData
for i := range dbp.moduleData { for i := range bi.moduleData {
if typeAddr >= dbp.moduleData[i].types && typeAddr < dbp.moduleData[i].etypes { if typeAddr >= bi.moduleData[i].types && typeAddr < bi.moduleData[i].etypes {
md = &dbp.moduleData[i] md = &bi.moduleData[i]
} }
} }
rtyp, err := dbp.findType("runtime._type") return md, nil
}
func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (*Variable, error) {
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
md, err := findModuleDataForType(bi, typeAddr, mem)
if err != nil {
return nil, err
}
rtyp, err := bi.findType("runtime._type")
if err != nil { if err != nil {
return nil, err return nil, err
} }
if md == nil { if md == nil {
v, err := dbp.reflectOffsMapAccess(off) v, err := reflectOffsMapAccess(bi, off, mem)
if err != nil { if err != nil {
return nil, err return nil, err
} }
v.loadValue(LoadConfig{false, 1, 0, 0, -1}) v.loadValue(LoadConfig{false, 1, 0, 0, -1})
addr, _ := constant.Int64Val(v.Value) addr, _ := constant.Int64Val(v.Value)
return v.newVariable(v.Name, uintptr(addr), rtyp), nil return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil
} }
if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread)); t != nil { if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem)); t != nil {
return t, nil return t, nil
} }
res := md.types + uintptr(off) res := md.types + uintptr(off)
return dbp.CurrentThread.newVariable("", res, rtyp), nil return newVariable("", res, rtyp, bi, mem), nil
} }
func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag string, pkgpathoff int32, err error) { func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go // See runtime.resolveNameOff in $GOROOT/src/runtime/type.go
if err = dbp.loadModuleData(); err != nil { if err = loadModuleData(bi, mem); err != nil {
return "", "", 0, err return "", "", 0, err
} }
for _, md := range dbp.moduleData { for _, md := range bi.moduleData {
if typeAddr >= md.types && typeAddr < md.etypes { if typeAddr >= md.types && typeAddr < md.etypes {
return dbp.loadName(md.types + off) return loadName(bi, md.types+off, mem)
} }
} }
v, err := dbp.reflectOffsMapAccess(off) v, err := reflectOffsMapAccess(bi, off, mem)
if err != nil { if err != nil {
return "", "", 0, err return "", "", 0, err
} }
@ -115,12 +124,12 @@ func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag str
return "", "", 0, resv.Unreadable return "", "", 0, resv.Unreadable
} }
return dbp.loadName(resv.Addr) return loadName(bi, resv.Addr, mem)
} }
func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) { func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0} scope := globalScope(bi, mem)
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") reflectOffs, err := scope.findGlobal("runtime.reflectOffs")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -130,7 +139,7 @@ func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) {
return nil, err return nil, err
} }
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread)) return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem))
} }
const ( const (
@ -140,17 +149,19 @@ const (
nameflagHasPkg = 1 << 2 nameflagHasPkg = 1 << 2
) )
func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, err error) { func loadName(bi *BinaryInfo, addr uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
off := addr off := addr
namedata, err := dbp.CurrentThread.readMemory(off, 3) namedata := make([]byte, 3)
_, err = mem.ReadMemory(namedata, off)
off += 3 off += 3
if err != nil { if err != nil {
return "", "", 0, err return "", "", 0, err
} }
namelen := uint16(namedata[1]<<8) | uint16(namedata[2]) namelen := uint16(namedata[1])<<8 | uint16(namedata[2])
rawstr, err := dbp.CurrentThread.readMemory(off, int(namelen)) rawstr := make([]byte, int(namelen))
_, err = mem.ReadMemory(rawstr, off)
off += uintptr(namelen) off += uintptr(namelen)
if err != nil { if err != nil {
return "", "", 0, err return "", "", 0, err
@ -159,14 +170,16 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32,
name = string(rawstr) name = string(rawstr)
if namedata[0]&nameflagHasTag != 0 { if namedata[0]&nameflagHasTag != 0 {
taglendata, err := dbp.CurrentThread.readMemory(off, 2) taglendata := make([]byte, 2)
_, err = mem.ReadMemory(taglendata, off)
off += 2 off += 2
if err != nil { if err != nil {
return "", "", 0, err return "", "", 0, err
} }
taglen := uint16(taglendata[0]<<8) | uint16(taglendata[1]) taglen := uint16(taglendata[0])<<8 | uint16(taglendata[1])
rawstr, err := dbp.CurrentThread.readMemory(off, int(taglen)) rawstr := make([]byte, int(taglen))
_, err = mem.ReadMemory(rawstr, off)
off += uintptr(taglen) off += uintptr(taglen)
if err != nil { if err != nil {
return "", "", 0, err return "", "", 0, err
@ -176,7 +189,8 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32,
} }
if namedata[0]&nameflagHasPkg != 0 { if namedata[0]&nameflagHasPkg != 0 {
pkgdata, err := dbp.CurrentThread.readMemory(off, 4) pkgdata := make([]byte, 4)
_, err = mem.ReadMemory(pkgdata, off)
if err != nil { if err != nil {
return "", "", 0, err return "", "", 0, err
} }

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
/* /*
* IDENTIFICATION: * IDENTIFICATION:
* stub generated Sun Feb 22 20:54:31 2015 * stub generated Sun Feb 22 20:54:31 2015

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
#include "exec_darwin.h" #include "exec_darwin.h"
#include "stdio.h" #include "stdio.h"

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
#include "proc_darwin.h" #include "proc_darwin.h"
#include <unistd.h> #include <unistd.h>

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
/* /*
* IDENTIFICATION: * IDENTIFICATION:
* stub generated Sat Feb 21 18:10:52 2015 * stub generated Sat Feb 21 18:10:52 2015

View File

@ -0,0 +1,128 @@
//+build darwin,!macnative
package native
import (
"errors"
"sync"
"github.com/derekparker/delve/pkg/proc"
)
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
// Launch returns ErrNativeBackendDisabled.
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
return nil, ErrNativeBackendDisabled
}
// Attach returns ErrNativeBackendDisabled.
func Attach(pid int) (*Process, error) {
return nil, ErrNativeBackendDisabled
}
// WaitStatus is a synonym for the platform-specific WaitStatus
type WaitStatus struct{}
// OSSpecificDetails holds information specific to the OSX/Darwin
// operating system / kernel.
type OSSpecificDetails struct{}
// OSProcessDetails holds Darwin specific information.
type OSProcessDetails struct{}
func findExecutable(path string, pid int) string {
panic(ErrNativeBackendDisabled)
}
func killProcess(pid int) error {
panic(ErrNativeBackendDisabled)
}
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) requestManualStop() (err error) {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) resume() error {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) trapWait(pid int) (*Thread, error) {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) stop(trapthread *Thread) (err error) {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) updateThreadList() error {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) kill() (err error) {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) detach(kill bool) error {
panic(ErrNativeBackendDisabled)
}
func (dbp *Process) entryPoint() (uint64, error) {
panic(ErrNativeBackendDisabled)
}
// Blocked returns true if the thread is blocked
func (t *Thread) Blocked() bool {
panic(ErrNativeBackendDisabled)
}
// SetPC sets the value of the PC register.
func (thread *Thread) SetPC(pc uint64) error {
panic(ErrNativeBackendDisabled)
}
// SetSP sets the value of the SP register.
func (thread *Thread) SetSP(sp uint64) error {
panic(ErrNativeBackendDisabled)
}
// SetDX sets the value of the DX register.
func (thread *Thread) SetDX(dx uint64) error {
panic(ErrNativeBackendDisabled)
}
// ReadMemory reads len(buf) bytes at addr into buf.
func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
panic(ErrNativeBackendDisabled)
}
// WriteMemory writes the contents of data at addr.
func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
panic(ErrNativeBackendDisabled)
}
func (t *Thread) resume() error {
panic(ErrNativeBackendDisabled)
}
func (t *Thread) singleStep() error {
panic(ErrNativeBackendDisabled)
}
func (t *Thread) restoreRegisters(sr proc.Registers) error {
panic(ErrNativeBackendDisabled)
}
// Stopped returns whether the thread is stopped at
// the operating system level.
func (t *Thread) Stopped() bool {
panic(ErrNativeBackendDisabled)
}

View File

@ -0,0 +1,453 @@
package native
import (
"fmt"
"go/ast"
"runtime"
"sync"
"github.com/derekparker/delve/pkg/proc"
)
// Process represents all of the information the debugger
// is holding onto regarding the process we are debugging.
type Process struct {
bi *proc.BinaryInfo
pid int // Process Pid
// Breakpoint table, holds information on breakpoints.
// Maps instruction address to Breakpoint struct.
breakpoints proc.BreakpointMap
// List of threads mapped as such: pid -> *Thread
threads map[int]*Thread
// Active thread
currentThread *Thread
// Goroutine that will be used by default to set breakpoint, eval variables, etc...
// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
selectedGoroutine *proc.G
common proc.CommonProcess
os *OSProcessDetails
firstStart bool
stopMu sync.Mutex
resumeChan chan<- struct{}
ptraceChan chan func()
ptraceDoneChan chan interface{}
childProcess bool // this process was launched, not attached to
manualStopRequested bool
exited, detached bool
}
// New returns an initialized Process struct. Before returning,
// it will also launch a goroutine in order to handle ptrace(2)
// functions. For more information, see the documentation on
// `handlePtraceFuncs`.
func New(pid int) *Process {
dbp := &Process{
pid: pid,
threads: make(map[int]*Thread),
breakpoints: proc.NewBreakpointMap(),
firstStart: true,
os: new(OSProcessDetails),
ptraceChan: make(chan func()),
ptraceDoneChan: make(chan interface{}),
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
}
go dbp.handlePtraceFuncs()
return dbp
}
// BinInfo will return the binary info struct associated with this process.
func (dbp *Process) BinInfo() *proc.BinaryInfo {
return dbp.bi
}
// Recorded always returns false for the native proc backend.
func (dbp *Process) Recorded() (bool, string) { return false, "" }
// Restart will always return an error in the native proc backend, only for
// recorded traces.
func (dbp *Process) Restart(string) error { return proc.ErrNotRecorded }
// Direction will always return an error in the native proc backend, only for
// recorded traces.
func (dbp *Process) Direction(proc.Direction) error { return proc.ErrNotRecorded }
// When will always return an empty string and nil, not supported on native proc backend.
func (dbp *Process) When() (string, error) { return "", nil }
// Checkpoint will always return an error on the native proc backend,
// only supported for recorded traces.
func (dbp *Process) Checkpoint(string) (int, error) { return -1, proc.ErrNotRecorded }
// Checkpoints will always return an error on the native proc backend,
// only supported for recorded traces.
func (dbp *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.ErrNotRecorded }
// ClearCheckpoint will always return an error on the native proc backend,
// only supported in recorded traces.
func (dbp *Process) ClearCheckpoint(int) error { return proc.ErrNotRecorded }
// Detach from the process being debugged, optionally killing it.
func (dbp *Process) Detach(kill bool) (err error) {
if dbp.exited {
return nil
}
if kill && dbp.childProcess {
err := dbp.kill()
if err != nil {
return err
}
dbp.bi.Close()
return nil
}
if !kill {
// Clean up any breakpoints we've set.
for _, bp := range dbp.breakpoints.M {
if bp != nil {
_, err := dbp.ClearBreakpoint(bp.Addr)
if err != nil {
return err
}
}
}
}
dbp.execPtraceFunc(func() {
err = dbp.detach(kill)
if err != nil {
return
}
if kill {
err = killProcess(dbp.pid)
}
})
dbp.detached = true
dbp.postExit()
return
}
// Valid returns whether the process is still attached to and
// has not exited.
func (dbp *Process) Valid() (bool, error) {
if dbp.detached {
return false, &proc.ProcessDetachedError{}
}
if dbp.exited {
return false, &proc.ErrProcessExited{Pid: dbp.Pid()}
}
return true, nil
}
// ResumeNotify specifies a channel that will be closed the next time
// ContinueOnce finishes resuming the target.
func (dbp *Process) ResumeNotify(ch chan<- struct{}) {
dbp.resumeChan = ch
}
// Pid returns the process ID.
func (dbp *Process) Pid() int {
return dbp.pid
}
// SelectedGoroutine returns the current selected,
// active goroutine.
func (dbp *Process) SelectedGoroutine() *proc.G {
return dbp.selectedGoroutine
}
// ThreadList returns a list of threads in the process.
func (dbp *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(dbp.threads))
for _, v := range dbp.threads {
r = append(r, v)
}
return r
}
// FindThread attempts to find the thread with the specified ID.
func (dbp *Process) FindThread(threadID int) (proc.Thread, bool) {
th, ok := dbp.threads[threadID]
return th, ok
}
// CurrentThread returns the current selected, active thread.
func (dbp *Process) CurrentThread() proc.Thread {
return dbp.currentThread
}
// Breakpoints returns a list of breakpoints currently set.
func (dbp *Process) Breakpoints() *proc.BreakpointMap {
return &dbp.breakpoints
}
// LoadInformation finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *Process) LoadInformation(path string) error {
var wg sync.WaitGroup
path = findExecutable(path, dbp.pid)
entryPoint, err := dbp.entryPoint()
if err != nil {
return err
}
wg.Add(1)
go dbp.loadProcessInformation(&wg)
err = dbp.bi.LoadBinaryInfo(path, entryPoint, &wg)
wg.Wait()
if err == nil {
err = dbp.bi.LoadError()
}
return err
}
// RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
dbp.stopMu.Lock()
defer dbp.stopMu.Unlock()
dbp.manualStopRequested = true
return dbp.requestManualStop()
}
// CheckAndClearManualStopRequest checks if a manual stop has
// been requested, and then clears that state.
func (dbp *Process) CheckAndClearManualStopRequest() bool {
dbp.stopMu.Lock()
defer dbp.stopMu.Unlock()
msr := dbp.manualStopRequested
dbp.manualStopRequested = false
return msr
}
func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
f, l, fn := dbp.bi.PCToLine(uint64(addr))
if fn == nil {
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
}
originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
if err != nil {
return "", 0, nil, nil, err
}
if err := dbp.writeSoftwareBreakpoint(dbp.currentThread, addr); err != nil {
return "", 0, nil, nil, err
}
return f, l, fn, originalData, nil
}
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
// break point table.
func (dbp *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
return dbp.breakpoints.Set(addr, kind, cond, dbp.writeBreakpoint)
}
// ClearBreakpoint clears the breakpoint at addr.
func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
if dbp.exited {
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
}
return dbp.breakpoints.Clear(addr, dbp.currentThread.ClearBreakpoint)
}
// ContinueOnce will continue the target until it stops.
// This could be the result of a breakpoint or signal.
func (dbp *Process) ContinueOnce() (proc.Thread, error) {
if dbp.exited {
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
}
if err := dbp.resume(); err != nil {
return nil, err
}
dbp.common.ClearAllGCache()
for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear()
}
if dbp.resumeChan != nil {
close(dbp.resumeChan)
dbp.resumeChan = nil
}
trapthread, err := dbp.trapWait(-1)
if err != nil {
return nil, err
}
if err := dbp.stop(trapthread); err != nil {
return nil, err
}
return trapthread, err
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// associated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Process) StepInstruction() (err error) {
thread := dbp.currentThread
if dbp.selectedGoroutine != nil {
if dbp.selectedGoroutine.Thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(dbp.selectedGoroutine.PC, proc.NextBreakpoint, proc.SameGoroutineCondition(dbp.selectedGoroutine)); err != nil {
return err
}
return proc.Continue(dbp)
}
thread = dbp.selectedGoroutine.Thread.(*Thread)
}
dbp.common.ClearAllGCache()
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
thread.CurrentBreakpoint.Clear()
err = thread.StepInstruction()
if err != nil {
return err
}
err = thread.SetCurrentBreakpoint()
if err != nil {
return err
}
if g, _ := proc.GetG(thread); g != nil {
dbp.selectedGoroutine = g
}
return nil
}
// SwitchThread changes from current thread to the thread specified by `tid`.
func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
if th, ok := dbp.threads[tid]; ok {
dbp.currentThread = th
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// SwitchGoroutine changes from current thread to the thread
// running the specified goroutine.
func (dbp *Process) SwitchGoroutine(gid int) error {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
g, err := proc.FindGoroutine(dbp, gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil
}
if g.Thread != nil {
return dbp.SwitchThread(g.Thread.ThreadID())
}
dbp.selectedGoroutine = g
return nil
}
// FindBreakpoint finds the breakpoint for the given pc.
func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
if bp, ok := dbp.breakpoints.M[pc-uint64(dbp.bi.Arch.BreakpointSize())]; ok {
return bp, true
}
// Directly use addr to lookup breakpoint.
if bp, ok := dbp.breakpoints.M[pc]; ok {
return bp, true
}
return nil, false
}
// Returns a new Process struct.
func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
err := dbp.LoadInformation(path)
if err != nil {
return dbp, err
}
if err := dbp.updateThreadList(); err != nil {
return dbp, err
}
// selectedGoroutine can not be set correctly by the call to updateThreadList
// because without calling SetGStructOffset we can not read the G struct of currentThread
// but without calling updateThreadList we can not examine memory to determine
// the offset of g struct inside TLS
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
return dbp, nil
}
// ClearInternalBreakpoints will clear all non-user set breakpoints. These
// breakpoints are set for internal operations such as 'next'.
func (dbp *Process) ClearInternalBreakpoints() error {
return dbp.breakpoints.ClearInternalBreakpoints(func(bp *proc.Breakpoint) error {
if err := dbp.currentThread.ClearBreakpoint(bp); err != nil {
return err
}
for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint == bp {
thread.CurrentBreakpoint.Clear()
}
}
return nil
})
}
func (dbp *Process) handlePtraceFuncs() {
// We must ensure here that we are running on the same thread during
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
for fn := range dbp.ptraceChan {
fn()
dbp.ptraceDoneChan <- nil
}
}
func (dbp *Process) execPtraceFunc(fn func()) {
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}
func (dbp *Process) postExit() {
dbp.exited = true
close(dbp.ptraceChan)
close(dbp.ptraceDoneChan)
dbp.bi.Close()
}
func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
_, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction())
return err
}
// Common returns common information across Process
// implementations
func (dbp *Process) Common() *proc.CommonProcess {
return &dbp.common
}

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
#include "proc_darwin.h" #include "proc_darwin.h"
static const unsigned char info_plist[] static const unsigned char info_plist[]

View File

@ -1,4 +1,6 @@
package proc //+build darwin,macnative
package native
// #include "proc_darwin.h" // #include "proc_darwin.h"
// #include "threads_darwin.h" // #include "threads_darwin.h"
@ -6,7 +8,6 @@ package proc
// #include <stdlib.h> // #include <stdlib.h>
import "C" import "C"
import ( import (
"debug/gosym"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -15,11 +16,9 @@ import (
"sync" "sync"
"unsafe" "unsafe"
"golang.org/x/debug/macho"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc"
) )
// OSProcessDetails holds Darwin specific information. // OSProcessDetails holds Darwin specific information.
@ -28,6 +27,7 @@ type OSProcessDetails struct {
exceptionPort C.mach_port_t // mach port for receiving mach exceptions. exceptionPort C.mach_port_t // mach port for receiving mach exceptions.
notificationPort C.mach_port_t // mach port for dead name notification (process exit). notificationPort C.mach_port_t // mach port for dead name notification (process exit).
initialized bool initialized bool
halt bool
// the main port we use, will return messages from both the // the main port we use, will return messages from both the
// exception and notification ports. // exception and notification ports.
@ -38,10 +38,10 @@ type OSProcessDetails struct {
// custom fork/exec process in order to take advantage of // custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin which will turn Unix signals into // PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions. // Mach exceptions.
func Launch(cmd []string, wd string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
// check that the argument to Launch is an executable file // check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, NotExecutableErr return nil, proc.ErrNotExecutable
} }
argv0Go, err := filepath.Abs(cmd[0]) argv0Go, err := filepath.Abs(cmd[0])
if err != nil { if err != nil {
@ -77,7 +77,8 @@ func Launch(cmd []string, wd string) (*Process, error) {
if pid <= 0 { if pid <= 0 {
return nil, fmt.Errorf("could not fork/exec") return nil, fmt.Errorf("could not fork/exec")
} }
dbp.Pid = pid dbp.pid = pid
dbp.childProcess = true
for i := range argvSlice { for i := range argvSlice {
C.free(unsafe.Pointer(argvSlice[i])) C.free(unsafe.Pointer(argvSlice[i]))
} }
@ -86,12 +87,17 @@ func Launch(cmd []string, wd string) (*Process, error) {
// trapWait to wait until the child process calls execve. // trapWait to wait until the child process calls execve.
for { for {
err = dbp.updateThreadListForTask(C.get_task_for_pid(C.int(dbp.Pid))) task := C.get_task_for_pid(C.int(dbp.pid))
if err == nil { // The task_for_pid call races with the fork call. This can
break // result in the parent task being returned instead of the child.
} if task != dbp.os.task {
if err != couldNotGetThreadCount && err != couldNotGetThreadList { err = dbp.updateThreadListForTask(task)
return nil, err if err == nil {
break
}
if err != couldNotGetThreadCount && err != couldNotGetThreadList {
return nil, err
}
} }
} }
@ -99,26 +105,21 @@ func Launch(cmd []string, wd string) (*Process, error) {
return nil, err return nil, err
} }
dbp.allGCache = nil dbp.common.ClearAllGCache()
for _, th := range dbp.Threads { for _, th := range dbp.threads {
th.clearBreakpointState() th.CurrentBreakpoint.Clear()
} }
trapthread, err := dbp.trapWait(-1) trapthread, err := dbp.trapWait(-1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := dbp.Halt(); err != nil { if err := dbp.stop(nil); err != nil {
return nil, dbp.exitGuard(err)
}
_, err = dbp.waitForStop()
if err != nil {
return nil, err return nil, err
} }
dbp.os.initialized = true dbp.os.initialized = true
dbp, err = initializeDebugProcess(dbp, argv0Go, false) dbp, err = initializeDebugProcess(dbp, argv0Go)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -144,19 +145,34 @@ func Attach(pid int) (*Process, error) {
dbp.os.initialized = true dbp.os.initialized = true
return initializeDebugProcess(dbp, "", true) var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
if err != nil {
return nil, err
}
_, _, err = dbp.wait(dbp.pid, 0)
if err != nil {
return nil, err
}
dbp, err = initializeDebugProcess(dbp, "")
if err != nil {
dbp.Detach(false)
return nil, err
}
return dbp, nil
} }
// Kill kills the process. // Kill kills the process.
func (dbp *Process) Kill() (err error) { func (dbp *Process) kill() (err error) {
if dbp.exited { if dbp.exited {
return nil return nil
} }
err = sys.Kill(-dbp.Pid, sys.SIGKILL) err = sys.Kill(-dbp.pid, sys.SIGKILL)
if err != nil { if err != nil {
return errors.New("could not deliver signal: " + err.Error()) return errors.New("could not deliver signal: " + err.Error())
} }
for port := range dbp.Threads { for port := range dbp.threads {
if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS { if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
return errors.New("could not resume task") return errors.New("could not resume task")
} }
@ -175,9 +191,10 @@ func (dbp *Process) Kill() (err error) {
func (dbp *Process) requestManualStop() (err error) { func (dbp *Process) requestManualStop() (err error) {
var ( var (
task = C.mach_port_t(dbp.os.task) task = C.mach_port_t(dbp.os.task)
thread = C.mach_port_t(dbp.CurrentThread.os.threadAct) thread = C.mach_port_t(dbp.currentThread.os.threadAct)
exceptionPort = C.mach_port_t(dbp.os.exceptionPort) exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
) )
dbp.os.halt = true
kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT) kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not raise mach exception") return fmt.Errorf("could not raise mach exception")
@ -218,12 +235,12 @@ func (dbp *Process) updateThreadListForTask(task C.task_t) error {
return couldNotGetThreadList return couldNotGetThreadList
} }
for _, thread := range dbp.Threads { for _, thread := range dbp.threads {
thread.os.exists = false thread.os.exists = false
} }
for _, port := range list { for _, port := range list {
thread, ok := dbp.Threads[int(port)] thread, ok := dbp.threads[int(port)]
if !ok { if !ok {
thread, err = dbp.addThread(int(port), false) thread, err = dbp.addThread(int(port), false)
if err != nil { if err != nil {
@ -233,9 +250,9 @@ func (dbp *Process) updateThreadListForTask(task C.task_t) error {
thread.os.exists = true thread.os.exists = true
} }
for threadID, thread := range dbp.Threads { for threadID, thread := range dbp.threads {
if !thread.os.exists { if !thread.os.exists {
delete(dbp.Threads, threadID) delete(dbp.threads, threadID)
} }
} }
@ -243,7 +260,7 @@ func (dbp *Process) updateThreadListForTask(task C.task_t) error {
} }
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) { func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
if thread, ok := dbp.Threads[port]; ok { if thread, ok := dbp.threads[port]; ok {
return thread, nil return thread, nil
} }
thread := &Thread{ thread := &Thread{
@ -251,107 +268,19 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
dbp: dbp, dbp: dbp,
os: new(OSSpecificDetails), os: new(OSSpecificDetails),
} }
dbp.Threads[port] = thread dbp.threads[port] = thread
thread.os.threadAct = C.thread_act_t(port) thread.os.threadAct = C.thread_act_t(port)
if dbp.CurrentThread == nil { if dbp.currentThread == nil {
dbp.SwitchThread(thread.ID) dbp.SwitchThread(thread.ID)
} }
return thread, nil return thread, nil
} }
func (dbp *Process) parseDebugFrame(exe *macho.File, wg *sync.WaitGroup) { func findExecutable(path string, pid int) string {
defer wg.Done()
debugFrameSec := exe.Section("__debug_frame")
debugInfoSec := exe.Section("__debug_info")
if debugFrameSec != nil && debugInfoSec != nil {
debugFrame, err := exe.Section("__debug_frame").Data()
if err != nil {
fmt.Println("could not get __debug_frame section", err)
os.Exit(1)
}
dat, err := debugInfoSec.Data()
if err != nil {
fmt.Println("could not get .debug_info section", err)
os.Exit(1)
}
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
} else {
fmt.Println("could not find __debug_frame section in binary")
os.Exit(1)
}
}
func (dbp *Process) obtainGoSymbols(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := exe.Section("__gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gosymtab section", err)
os.Exit(1)
}
}
if sec := exe.Section("__gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gopclntab section", err)
os.Exit(1)
}
}
pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.goSymTable = tab
}
func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
if sec := exe.Section("__debug_line"); sec != nil {
debugLine, err := exe.Section("__debug_line").Data()
if err != nil {
fmt.Println("could not get __debug_line section", err)
os.Exit(1)
}
dbp.lineInfo = line.Parse(debugLine)
} else {
fmt.Println("could not find __debug_line section in binary")
os.Exit(1)
}
}
var UnsupportedArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported")
func (dbp *Process) findExecutable(path string) (*macho.File, string, error) {
if path == "" { if path == "" {
path = C.GoString(C.find_executable(C.int(dbp.Pid))) path = C.GoString(C.find_executable(C.int(pid)))
} }
exe, err := macho.Open(path) return path
if err != nil {
return nil, path, err
}
if exe.Cpu != macho.CpuAmd64 {
return nil, path, UnsupportedArchErr
}
dbp.dwarf, err = exe.DWARF()
if err != nil {
return nil, path, err
}
return exe, path, nil
} }
func (dbp *Process) trapWait(pid int) (*Thread, error) { func (dbp *Process) trapWait(pid int) (*Thread, error) {
@ -369,19 +298,22 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
continue continue
} }
if !dbp.os.initialized { if !dbp.os.initialized {
if pidtask := C.get_task_for_pid(C.int(dbp.Pid)); pidtask != 0 && dbp.os.task != pidtask { if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask {
continue continue
} }
} }
_, status, err := dbp.wait(dbp.Pid, 0) _, status, err := dbp.wait(dbp.pid, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbp.postExit() dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()} return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
case C.MACH_RCV_INTERRUPTED: case C.MACH_RCV_INTERRUPTED:
if !dbp.halt { dbp.stopMu.Lock()
halt := dbp.os.halt
dbp.stopMu.Unlock()
if !halt {
// Call trapWait again, it seems // Call trapWait again, it seems
// MACH_RCV_INTERRUPTED is emitted before // MACH_RCV_INTERRUPTED is emitted before
// process natural death _sometimes_. // process natural death _sometimes_.
@ -407,10 +339,13 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
// Since we cannot be notified of new threads on OS X // Since we cannot be notified of new threads on OS X
// this is as good a time as any to check for them. // this is as good a time as any to check for them.
dbp.updateThreadList() dbp.updateThreadList()
th, ok := dbp.Threads[int(port)] th, ok := dbp.threads[int(port)]
if !ok { if !ok {
if dbp.halt { dbp.stopMu.Lock()
dbp.halt = false halt := dbp.os.halt
dbp.stopMu.Unlock()
if halt {
dbp.os.halt = false
return th, nil return th, nil
} }
if dbp.firstStart || th.singleStepping { if dbp.firstStart || th.singleStepping {
@ -427,7 +362,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
} }
func (dbp *Process) waitForStop() ([]int, error) { func (dbp *Process) waitForStop() ([]int, error) {
ports := make([]int, 0, len(dbp.Threads)) ports := make([]int, 0, len(dbp.threads))
count := 0 count := 0
for { for {
var task C.task_t var task C.task_t
@ -448,23 +383,6 @@ func (dbp *Process) waitForStop() ([]int, error) {
} }
} }
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
ports, err := dbp.waitForStop()
if err != nil {
return err
}
trapthread.SetCurrentBreakpoint()
for _, port := range ports {
if th, ok := dbp.Threads[port]; ok {
err := th.SetCurrentBreakpoint()
if err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) { func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done() wg.Done()
} }
@ -483,29 +401,70 @@ func (dbp *Process) exitGuard(err error) error {
if err != ErrContinueThread { if err != ErrContinueThread {
return err return err
} }
_, status, werr := dbp.wait(dbp.Pid, sys.WNOHANG) _, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
if werr == nil && status.Exited() { if werr == nil && status.Exited() {
dbp.postExit() dbp.postExit()
return ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()} return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
} }
return err return err
} }
func (dbp *Process) resume() error { func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it // all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.Threads { for _, thread := range dbp.threads {
if thread.CurrentBreakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := thread.StepInstruction(); err != nil {
return err return err
} }
thread.CurrentBreakpoint = nil thread.CurrentBreakpoint.Clear()
} }
} }
// everything is resumed // everything is resumed
for _, thread := range dbp.Threads { for _, thread := range dbp.threads {
if err := thread.resume(); err != nil { if err := thread.resume(); err != nil {
return dbp.exitGuard(err) return dbp.exitGuard(err)
} }
} }
return nil return nil
} }
// stop stops all running threads and sets breakpoints
func (dbp *Process) stop(trapthread *Thread) (err error) {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
for _, th := range dbp.threads {
if !th.Stopped() {
if err := th.stop(); err != nil {
return dbp.exitGuard(err)
}
}
}
ports, err := dbp.waitForStop()
if err != nil {
return err
}
if !dbp.os.initialized {
return nil
}
trapthread.SetCurrentBreakpoint()
for _, port := range ports {
if th, ok := dbp.threads[port]; ok {
err := th.SetCurrentBreakpoint()
if err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) detach(kill bool) error {
return PtraceDetach(dbp.pid, 0)
}
func (dbp *Process) entryPoint() (uint64, error) {
//TODO(aarzilli): implement this
return 0, nil
}

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
#include <sys/types.h> #include <sys/types.h>
#include <libproc.h> #include <libproc.h>
#include <mach/mach.h> #include <mach/mach.h>

View File

@ -1,13 +1,13 @@
package proc package native
import ( import (
"bytes" "bytes"
"debug/gosym"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
@ -18,9 +18,9 @@ import (
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/dwarf/line" "github.com/derekparker/delve/pkg/proc/linutil"
"golang.org/x/debug/elf" "github.com/mattn/go-isatty"
) )
// Process statuses // Process statuses
@ -46,55 +46,86 @@ type OSProcessDetails struct {
// Launch creates and begins debugging a new process. First entry in // Launch creates and begins debugging a new process. First entry in
// `cmd` is the program to run, and then rest are the arguments // `cmd` is the program to run, and then rest are the arguments
// to be supplied to that process. `wd` is working directory of the program. // to be supplied to that process. `wd` is working directory of the program.
func Launch(cmd []string, wd string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
var ( var (
proc *exec.Cmd process *exec.Cmd
err error err error
) )
// check that the argument to Launch is an executable file // check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, NotExecutableErr return nil, proc.ErrNotExecutable
} }
if !isatty.IsTerminal(os.Stdin.Fd()) {
// exec.(*Process).Start will fail if we try to send a process to
// foreground but we are not attached to a terminal.
foreground = false
}
dbp := New(0) dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
proc = exec.Command(cmd[0]) process = exec.Command(cmd[0])
proc.Args = cmd process.Args = cmd
proc.Stdout = os.Stdout process.Stdout = os.Stdout
proc.Stderr = os.Stderr process.Stderr = os.Stderr
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true} process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground}
if wd != "" { if foreground {
proc.Dir = wd signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
process.Stdin = os.Stdin
} }
err = proc.Start() if wd != "" {
process.Dir = wd
}
err = process.Start()
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbp.Pid = proc.Process.Pid dbp.pid = process.Process.Pid
_, _, err = dbp.wait(proc.Process.Pid, 0) dbp.childProcess = true
_, _, err = dbp.wait(process.Process.Pid, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err) return nil, fmt.Errorf("waiting for target execve failed: %s", err)
} }
return initializeDebugProcess(dbp, proc.Path, false) return initializeDebugProcess(dbp, process.Path)
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) { func Attach(pid int) (*Process, error) {
return initializeDebugProcess(New(pid), "", true) dbp := New(pid)
dbp.common = proc.NewCommonProcess(true)
var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
if err != nil {
return nil, err
}
_, _, err = dbp.wait(dbp.pid, 0)
if err != nil {
return nil, err
}
dbp, err = initializeDebugProcess(dbp, "")
if err != nil {
dbp.Detach(false)
return nil, err
}
return dbp, nil
} }
// Kill kills the target process. // kill kills the target process.
func (dbp *Process) Kill() (err error) { func (dbp *Process) kill() (err error) {
if dbp.exited { if dbp.exited {
return nil return nil
} }
if !dbp.Threads[dbp.Pid].Stopped() { if !dbp.threads[dbp.pid].Stopped() {
return errors.New("process must be stopped in order to kill it") return errors.New("process must be stopped in order to kill it")
} }
if err = sys.Kill(-dbp.Pid, sys.SIGKILL); err != nil { if err = sys.Kill(-dbp.pid, sys.SIGKILL); err != nil {
return errors.New("could not deliver signal " + err.Error()) return errors.New("could not deliver signal " + err.Error())
} }
if _, _, err = dbp.wait(dbp.Pid, 0); err != nil { if _, _, err = dbp.wait(dbp.pid, 0); err != nil {
return return
} }
dbp.postExit() dbp.postExit()
@ -102,13 +133,13 @@ func (dbp *Process) Kill() (err error) {
} }
func (dbp *Process) requestManualStop() (err error) { func (dbp *Process) requestManualStop() (err error) {
return sys.Kill(dbp.Pid, sys.SIGTRAP) return sys.Kill(dbp.pid, sys.SIGTRAP)
} }
// Attach to a newly created thread, and store that thread in our list of // Attach to a newly created thread, and store that thread in our list of
// known threads. // known threads.
func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) { func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
if thread, ok := dbp.Threads[tid]; ok { if thread, ok := dbp.threads[tid]; ok {
return thread, nil return thread, nil
} }
@ -122,7 +153,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
// if we truly don't have permissions. // if we truly don't have permissions.
return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err) return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
} }
pid, status, err := dbp.wait(tid, 0) pid, status, err := dbp.waitFast(tid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -133,7 +164,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
if err == syscall.ESRCH { if err == syscall.ESRCH {
if _, _, err = dbp.wait(tid, 0); err != nil { if _, _, err = dbp.waitFast(tid); err != nil {
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
} }
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
@ -145,132 +176,44 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
} }
} }
dbp.Threads[tid] = &Thread{ dbp.threads[tid] = &Thread{
ID: tid, ID: tid,
dbp: dbp, dbp: dbp,
os: new(OSSpecificDetails), os: new(OSSpecificDetails),
} }
if dbp.CurrentThread == nil { if dbp.currentThread == nil {
dbp.SwitchThread(tid) dbp.SwitchThread(tid)
} }
return dbp.Threads[tid], nil return dbp.threads[tid], nil
} }
func (dbp *Process) updateThreadList() error { func (dbp *Process) updateThreadList() error {
tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.Pid)) tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.pid))
for _, tidpath := range tids { for _, tidpath := range tids {
tidstr := filepath.Base(tidpath) tidstr := filepath.Base(tidpath)
tid, err := strconv.Atoi(tidstr) tid, err := strconv.Atoi(tidstr)
if err != nil { if err != nil {
return err return err
} }
if _, err := dbp.addThread(tid, tid != dbp.Pid); err != nil { if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {
return err return err
} }
} }
return nil return nil
} }
var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") func findExecutable(path string, pid int) string {
func (dbp *Process) findExecutable(path string) (*elf.File, string, error) {
if path == "" { if path == "" {
path = fmt.Sprintf("/proc/%d/exe", dbp.Pid) path = fmt.Sprintf("/proc/%d/exe", pid)
}
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return nil, path, err
}
elfFile, err := elf.NewFile(f)
if err != nil {
return nil, path, err
}
if elfFile.Machine != elf.EM_X86_64 {
return nil, path, UnsupportedArchErr
}
dbp.dwarf, err = elfFile.DWARF()
if err != nil {
return nil, path, err
}
return elfFile, path, nil
}
func (dbp *Process) parseDebugFrame(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameSec := exe.Section(".debug_frame")
debugInfoSec := exe.Section(".debug_info")
if debugFrameSec != nil && debugInfoSec != nil {
debugFrame, err := exe.Section(".debug_frame").Data()
if err != nil {
fmt.Println("could not get .debug_frame section", err)
os.Exit(1)
}
dat, err := debugInfoSec.Data()
if err != nil {
fmt.Println("could not get .debug_info section", err)
os.Exit(1)
}
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
} else {
fmt.Println("could not find .debug_frame section in binary")
os.Exit(1)
}
}
func (dbp *Process) obtainGoSymbols(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := exe.Section(".gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gosymtab section", err)
os.Exit(1)
}
}
if sec := exe.Section(".gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gopclntab section", err)
os.Exit(1)
}
}
pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.goSymTable = tab
}
func (dbp *Process) parseDebugLineInfo(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
if sec := exe.Section(".debug_line"); sec != nil {
debugLine, err := exe.Section(".debug_line").Data()
if err != nil {
fmt.Println("could not get .debug_line section", err)
os.Exit(1)
}
dbp.lineInfo = line.Parse(debugLine)
} else {
fmt.Println("could not find .debug_line section in binary")
os.Exit(1)
} }
return path
} }
func (dbp *Process) trapWait(pid int) (*Thread, error) { func (dbp *Process) trapWait(pid int) (*Thread, error) {
return dbp.trapWaitInternal(pid, false)
}
func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) {
for { for {
wpid, status, err := dbp.wait(pid, 0) wpid, status, err := dbp.wait(pid, 0)
if err != nil { if err != nil {
@ -279,16 +222,16 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
if wpid == 0 { if wpid == 0 {
continue continue
} }
th, ok := dbp.Threads[wpid] th, ok := dbp.threads[wpid]
if ok { if ok {
th.Status = (*WaitStatus)(status) th.Status = (*WaitStatus)(status)
} }
if status.Exited() { if status.Exited() {
if wpid == dbp.Pid { if wpid == dbp.pid {
dbp.postExit() dbp.postExit()
return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()} return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
} }
delete(dbp.Threads, wpid) delete(dbp.threads, wpid)
continue continue
} }
if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
@ -307,19 +250,25 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
if err != nil { if err != nil {
if err == sys.ESRCH { if err == sys.ESRCH {
// thread died while we were adding it // thread died while we were adding it
delete(dbp.threads, int(cloned))
continue continue
} }
return nil, err return nil, err
} }
if halt {
th.os.running = false
dbp.threads[int(wpid)].os.running = false
return nil, nil
}
if err = th.Continue(); err != nil { if err = th.Continue(); err != nil {
if err == sys.ESRCH { if err == sys.ESRCH {
// thread died while we were adding it // thread died while we were adding it
delete(dbp.Threads, th.ID) delete(dbp.threads, th.ID)
continue continue
} }
return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err) return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
} }
if err = dbp.Threads[int(wpid)].Continue(); err != nil { if err = dbp.threads[int(wpid)].Continue(); err != nil {
if err != sys.ESRCH { if err != sys.ESRCH {
return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err) return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err)
} }
@ -330,20 +279,15 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
// Sometimes we get an unknown thread, ignore it? // Sometimes we get an unknown thread, ignore it?
continue continue
} }
if status.StopSignal() == sys.SIGTRAP && dbp.halt { if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) {
th.running = false th.os.running = false
dbp.halt = false
return th, nil
}
if status.StopSignal() == sys.SIGTRAP {
th.running = false
return th, nil return th, nil
} }
if th != nil { if th != nil {
// TODO(dp) alert user about unexpected signals here. // TODO(dp) alert user about unexpected signals here.
if err := th.resumeWithSig(int(status.StopSignal())); err != nil { if err := th.resumeWithSig(int(status.StopSignal())); err != nil {
if err == sys.ESRCH { if err == sys.ESRCH {
return nil, ProcessExitedError{Pid: dbp.Pid} return nil, proc.ErrProcessExited{Pid: dbp.pid}
} }
return nil, err return nil, err
} }
@ -354,19 +298,19 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) { func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.Pid)) comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
if err == nil { if err == nil {
// removes newline character // removes newline character
comm = bytes.TrimSuffix(comm, []byte("\n")) comm = bytes.TrimSuffix(comm, []byte("\n"))
} }
if comm == nil || len(comm) <= 0 { if comm == nil || len(comm) <= 0 {
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.Pid)) stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
if err != nil { if err != nil {
fmt.Printf("Could not read proc stat: %v\n", err) fmt.Printf("Could not read proc stat: %v\n", err)
os.Exit(1) os.Exit(1)
} }
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.Pid) expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
rexp, err := regexp.Compile(expr) rexp, err := regexp.Compile(expr)
if err != nil { if err != nil {
fmt.Printf("Regexp compile error: %v\n", err) fmt.Printf("Regexp compile error: %v\n", err)
@ -374,7 +318,7 @@ func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
} }
match := rexp.FindSubmatch(stat) match := rexp.FindSubmatch(stat)
if match == nil { if match == nil {
fmt.Printf("No match found using regexp '%s' in /proc/%d/stat\n", expr, dbp.Pid) fmt.Printf("No match found using regexp '%s' in /proc/%d/stat\n", expr, dbp.pid)
os.Exit(1) os.Exit(1)
} }
comm = match[1] comm = match[1]
@ -402,9 +346,16 @@ func status(pid int, comm string) rune {
return state return state
} }
// waitFast is like wait but does not handle process-exit correctly
func (dbp *Process) waitFast(pid int) (int, *sys.WaitStatus, error) {
var s sys.WaitStatus
wpid, err := sys.Wait4(pid, &s, sys.WALL, nil)
return wpid, &s, err
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
var s sys.WaitStatus var s sys.WaitStatus
if (pid != dbp.Pid) || (options != 0) { if (pid != dbp.pid) || (options != 0) {
wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil) wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
return wpid, &s, err return wpid, &s, err
} }
@ -434,24 +385,12 @@ func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
} }
} }
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
for _, th := range dbp.Threads {
if th.CurrentBreakpoint == nil {
err := th.SetCurrentBreakpoint()
if err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) exitGuard(err error) error { func (dbp *Process) exitGuard(err error) error {
if err != sys.ESRCH { if err != sys.ESRCH {
return err return err
} }
if status(dbp.Pid, dbp.os.comm) == StatusZombie { if status(dbp.pid, dbp.os.comm) == StatusZombie {
_, err := dbp.trapWait(-1) _, err := dbp.trapWaitInternal(-1, false)
return err return err
} }
@ -460,16 +399,16 @@ func (dbp *Process) exitGuard(err error) error {
func (dbp *Process) resume() error { func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it // all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.Threads { for _, thread := range dbp.threads {
if thread.CurrentBreakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := thread.StepInstruction(); err != nil {
return err return err
} }
thread.CurrentBreakpoint = nil thread.CurrentBreakpoint.Clear()
} }
} }
// everything is resumed // everything is resumed
for _, thread := range dbp.Threads { for _, thread := range dbp.threads {
if err := thread.resume(); err != nil && err != sys.ESRCH { if err := thread.resume(); err != nil && err != sys.ESRCH {
return err return err
} }
@ -477,6 +416,78 @@ func (dbp *Process) resume() error {
return nil return nil
} }
// stop stops all running threads threads and sets breakpoints
func (dbp *Process) stop(trapthread *Thread) (err error) {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
for _, th := range dbp.threads {
if !th.Stopped() {
if err := th.stop(); err != nil {
return dbp.exitGuard(err)
}
}
}
// wait for all threads to stop
for {
allstopped := true
for _, th := range dbp.threads {
if th.os.running {
allstopped = false
break
}
}
if allstopped {
break
}
_, err := dbp.trapWaitInternal(-1, true)
if err != nil {
return err
}
}
// set breakpoints on all threads
for _, th := range dbp.threads {
if th.CurrentBreakpoint.Breakpoint == nil {
if err := th.SetCurrentBreakpoint(); err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) detach(kill bool) error {
for threadID := range dbp.threads {
err := PtraceDetach(threadID, 0)
if err != nil {
return err
}
}
if kill {
return nil
}
// For some reason the process will sometimes enter stopped state after a
// detach, this doesn't happen immediately either.
// We have to wait a bit here, then check if the main thread is stopped and
// SIGCONT it if it is.
time.Sleep(50 * time.Millisecond)
if s := status(dbp.pid, dbp.os.comm); s == 'T' {
sys.Kill(dbp.pid, sys.SIGCONT)
}
return nil
}
func (dbp *Process) entryPoint() (uint64, error) {
auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
if err != nil {
return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
}
return linutil.EntryPointFromAuxvAMD64(auxvbuf), nil
}
func killProcess(pid int) error { func killProcess(pid int) error {
return sys.Kill(pid, sys.SIGINT) return sys.Kill(pid, sys.SIGINT)
} }

View File

@ -0,0 +1,497 @@
package native
import (
"debug/pe"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"unsafe"
sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/pkg/proc"
)
// OSProcessDetails holds Windows specific information.
type OSProcessDetails struct {
hProcess syscall.Handle
breakThread int
entryPoint uint64
}
func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return nil, nil, err
}
peFile, err := pe.NewFile(f)
if err != nil {
f.Close()
return nil, nil, err
}
return peFile, f, nil
}
// Launch creates and begins debugging a new process.
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
argv0Go, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
}
// Make sure the binary exists and is an executable file
if filepath.Base(cmd[0]) == cmd[0] {
if _, err := exec.LookPath(cmd[0]); err != nil {
return nil, err
}
}
_, closer, err := openExecutablePathPE(argv0Go)
if err != nil {
return nil, proc.ErrNotExecutable
}
closer.Close()
var p *os.Process
dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() {
attr := &os.ProcAttr{
Dir: wd,
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
Sys: &syscall.SysProcAttr{
CreationFlags: _DEBUG_ONLY_THIS_PROCESS,
},
}
p, err = os.StartProcess(argv0Go, cmd, attr)
})
if err != nil {
return nil, err
}
defer p.Release()
dbp.pid = p.Pid
dbp.childProcess = true
return newDebugProcess(dbp, argv0Go)
}
// newDebugProcess prepares process pid for debugging.
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
// It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
// immediately after launching under DEBUG_ONLY_THIS_PROCESS.
// Attaching with DebugActiveProcess has similar effect.
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
}
if tid == 0 {
dbp.postExit()
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
}
// Suspend all threads so that the call to _ContinueDebugEvent will
// not resume the target.
for _, thread := range dbp.threads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
})
if err != nil {
return nil, err
}
return initializeDebugProcess(dbp, exepath)
}
// findExePath searches for process pid, and returns its executable path.
func findExePath(pid int) (string, error) {
// Original code suggested different approach (see below).
// Maybe it could be useful in the future.
//
// Find executable path from PID/handle on Windows:
// https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
p, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid))
if err != nil {
return "", err
}
defer syscall.CloseHandle(p)
n := uint32(128)
for {
buf := make([]uint16, int(n))
err = _QueryFullProcessImageName(p, 0, &buf[0], &n)
switch err {
case syscall.ERROR_INSUFFICIENT_BUFFER:
// try bigger buffer
n *= 2
// but stop if it gets too big
if n > 10000 {
return "", err
}
case nil:
return syscall.UTF16ToString(buf[:n]), nil
default:
return "", err
}
}
}
// Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) {
// TODO: Probably should have SeDebugPrivilege before starting here.
err := _DebugActiveProcess(uint32(pid))
if err != nil {
return nil, err
}
exepath, err := findExePath(pid)
if err != nil {
return nil, err
}
dbp, err := newDebugProcess(New(pid), exepath)
if err != nil {
if dbp != nil {
dbp.Detach(false)
}
return nil, err
}
return dbp, nil
}
// kill kills the process.
func (dbp *Process) kill() error {
if dbp.exited {
return nil
}
p, err := os.FindProcess(dbp.pid)
if err != nil {
return err
}
defer p.Release()
// TODO: Should not have to ignore failures here,
// but some tests appear to Kill twice causing
// this to fail on second attempt.
_ = syscall.TerminateProcess(dbp.os.hProcess, 1)
dbp.execPtraceFunc(func() {
dbp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions)
})
p.Wait()
dbp.postExit()
return nil
}
func (dbp *Process) requestManualStop() error {
return _DebugBreakProcess(dbp.os.hProcess)
}
func (dbp *Process) updateThreadList() error {
// We ignore this request since threads are being
// tracked as they are created/killed in waitForDebugEvent.
return nil
}
func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, suspendNewThreads bool) (*Thread, error) {
if thread, ok := dbp.threads[threadID]; ok {
return thread, nil
}
thread := &Thread{
ID: threadID,
dbp: dbp,
os: new(OSSpecificDetails),
}
thread.os.hThread = hThread
dbp.threads[threadID] = thread
if dbp.currentThread == nil {
dbp.SwitchThread(thread.ID)
}
if suspendNewThreads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
return thread, nil
}
func findExecutable(path string, pid int) string {
return path
}
type waitForDebugEventFlags int
const (
waitBlocking waitForDebugEventFlags = 1 << iota
waitSuspendNewThreads
waitDontHandleExceptions
)
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
var debugEvent _DEBUG_EVENT
shouldExit := false
for {
continueStatus := uint32(_DBG_CONTINUE)
var milliseconds uint32 = 0
if flags&waitBlocking != 0 {
milliseconds = syscall.INFINITE
}
// Wait for a debug event...
err := _WaitForDebugEvent(&debugEvent, milliseconds)
if err != nil {
return 0, 0, err
}
// ... handle each event kind ...
unionPtr := unsafe.Pointer(&debugEvent.U[0])
switch debugEvent.DebugEventCode {
case _CREATE_PROCESS_DEBUG_EVENT:
debugInfo := (*_CREATE_PROCESS_DEBUG_INFO)(unionPtr)
hFile := debugInfo.File
if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile)
if err != nil {
return 0, 0, err
}
}
dbp.os.entryPoint = uint64(debugInfo.BaseOfImage)
dbp.os.hProcess = debugInfo.Process
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _CREATE_THREAD_DEBUG_EVENT:
debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr)
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _EXIT_THREAD_DEBUG_EVENT:
delete(dbp.threads, int(debugEvent.ThreadId))
break
case _OUTPUT_DEBUG_STRING_EVENT:
//TODO: Handle debug output strings
break
case _LOAD_DLL_DEBUG_EVENT:
debugInfo := (*_LOAD_DLL_DEBUG_INFO)(unionPtr)
hFile := debugInfo.File
if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile)
if err != nil {
return 0, 0, err
}
}
break
case _UNLOAD_DLL_DEBUG_EVENT:
break
case _RIP_EVENT:
break
case _EXCEPTION_DEBUG_EVENT:
if flags&waitDontHandleExceptions != 0 {
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
break
}
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
tid := int(debugEvent.ThreadId)
switch code := exception.ExceptionRecord.ExceptionCode; code {
case _EXCEPTION_BREAKPOINT:
// check if the exception address really is a breakpoint instruction, if
// it isn't we already removed that breakpoint and we can't deal with
// this exception anymore.
atbp := true
if thread, found := dbp.threads[tid]; found {
data := make([]byte, dbp.bi.Arch.BreakpointSize())
if _, err := thread.ReadMemory(data, exception.ExceptionRecord.ExceptionAddress); err == nil {
instr := dbp.bi.Arch.BreakpointInstruction()
for i := range instr {
if data[i] != instr[i] {
atbp = false
break
}
}
}
if !atbp {
thread.SetPC(uint64(exception.ExceptionRecord.ExceptionAddress))
}
}
if atbp {
dbp.os.breakThread = tid
return tid, 0, nil
} else {
continueStatus = _DBG_CONTINUE
}
case _EXCEPTION_SINGLE_STEP:
dbp.os.breakThread = tid
return tid, 0, nil
default:
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
}
case _EXIT_PROCESS_DEBUG_EVENT:
debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr)
exitCode = int(debugInfo.ExitCode)
shouldExit = true
default:
return 0, 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode)
}
// .. and then continue unless we received an event that indicated we should break into debugger.
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
if err != nil {
return 0, 0, err
}
if shouldExit {
return 0, exitCode, nil
}
}
}
func (dbp *Process) trapWait(pid int) (*Thread, error) {
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
}
if tid == 0 {
dbp.postExit()
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
}
th := dbp.threads[tid]
return th, nil
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
return 0, nil, fmt.Errorf("not implemented: wait")
}
func (dbp *Process) exitGuard(err error) error {
return err
}
func (dbp *Process) resume() error {
for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint.Clear()
}
}
for _, thread := range dbp.threads {
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}
return nil
}
// stop stops all running threads threads and sets breakpoints
func (dbp *Process) stop(trapthread *Thread) (err error) {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
// While the debug event that stopped the target was being propagated
// other target threads could generate other debug events.
// After this function we need to know about all the threads
// stopped on a breakpoint. To do that we first suspend all target
// threads and then repeatedly call _ContinueDebugEvent followed by
// waitForDebugEvent in non-blocking mode.
// We need to explicitly call SuspendThread because otherwise the
// call to _ContinueDebugEvent will resume execution of some of the
// target threads.
err = trapthread.SetCurrentBreakpoint()
if err != nil {
return err
}
for _, thread := range dbp.threads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return err
}
}
for {
var err error
var tid int
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
if err == nil {
tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads)
}
})
if err != nil {
return err
}
if tid == 0 {
break
}
err = dbp.threads[tid].SetCurrentBreakpoint()
if err != nil {
return err
}
}
return nil
}
func (dbp *Process) detach(kill bool) error {
if !kill {
for _, thread := range dbp.threads {
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}
}
return _DebugActiveProcessStop(uint32(dbp.pid))
}
func (dbp *Process) entryPoint() (uint64, error) {
return dbp.os.entryPoint, nil
}
func killProcess(pid int) error {
p, err := os.FindProcess(pid)
if err != nil {
return err
}
defer p.Release()
return p.Kill()
}

View File

@ -1,4 +1,6 @@
package proc //+build darwin,macnative
package native
import sys "golang.org/x/sys/unix" import sys "golang.org/x/sys/unix"

View File

@ -1,11 +1,12 @@
package proc package native
import ( import (
"encoding/binary"
"syscall" "syscall"
"unsafe" "unsafe"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc"
) )
// PtraceAttach executes the sys.PtraceAttach call. // PtraceAttach executes the sys.PtraceAttach call.
@ -56,9 +57,10 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html // See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
// and amd64_supply_xsave in gdb/amd64-tdep.c.html // and amd64_supply_xsave in gdb/amd64-tdep.c.html
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developers Manual, Volume 1: Basic Architecture // and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developers Manual, Volume 1: Basic Architecture
func PtraceGetRegset(tid int) (regset PtraceXsave, err error) { func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.PtraceFpRegs)), 0, 0) _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.PtraceFpRegs)), 0, 0)
if err == syscall.Errno(0) { if err == syscall.Errno(0) || err == syscall.ENODEV {
// ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
err = nil err = nil
} }
@ -66,31 +68,16 @@ func PtraceGetRegset(tid int) (regset PtraceXsave, err error) {
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE} iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0) _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) { if err != syscall.Errno(0) {
if err == syscall.ENODEV {
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/derekparker/delve/issues/1022
err = nil
}
return return
} else { } else {
err = nil err = nil
} }
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= iov.Len { regset.Xsave = xstateargs[:iov.Len]
return err = proc.LinuxX86XstateRead(regset.Xsave, false, &regset)
} return regset, err
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:iov.Len]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return
} }

View File

@ -1,12 +1,18 @@
package proc //+build darwin,macnative
package native
// #include "threads_darwin.h" // #include "threads_darwin.h"
import "C" import "C"
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"rsc.io/x86/x86asm"
"unsafe" "unsafe"
"golang.org/x/arch/x86/x86asm"
"github.com/derekparker/delve/pkg/proc"
) )
// Regs represents CPU registers on an AMD64 processor. // Regs represents CPU registers on an AMD64 processor.
@ -33,10 +39,10 @@ type Regs struct {
fs uint64 fs uint64
gs uint64 gs uint64
gsBase uint64 gsBase uint64
fpregs []Register fpregs []proc.Register
} }
func (r *Regs) Slice() []Register { func (r *Regs) Slice() []proc.Register {
var regs = []struct { var regs = []struct {
k string k string
v uint64 v uint64
@ -64,12 +70,12 @@ func (r *Regs) Slice() []Register {
{"Gs", r.gs}, {"Gs", r.gs},
{"Gs_base", r.gsBase}, {"Gs_base", r.gsBase},
} }
out := make([]Register, 0, len(regs)+len(r.fpregs)) out := make([]proc.Register, 0, len(regs)+len(r.fpregs))
for _, reg := range regs { for _, reg := range regs {
if reg.k == "Rflags" { if reg.k == "Rflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64) out = proc.AppendEflagReg(out, reg.k, reg.v)
} else { } else {
out = appendQwordReg(out, reg.k, reg.v) out = proc.AppendQwordReg(out, reg.k, reg.v)
} }
} }
out = append(out, r.fpregs...) out = append(out, r.fpregs...)
@ -88,6 +94,10 @@ func (r *Regs) SP() uint64 {
return r.rsp return r.rsp
} }
func (r *Regs) BP() uint64 {
return r.rbp
}
// CX returns the value of the RCX register. // CX returns the value of the RCX register.
func (r *Regs) CX() uint64 { func (r *Regs) CX() uint64 {
return r.rcx return r.rcx
@ -100,8 +110,12 @@ func (r *Regs) TLS() uint64 {
return r.gsBase return r.gsBase
} }
func (r *Regs) GAddr() (uint64, bool) {
return 0, false
}
// SetPC sets the RIP register to the value specified by `pc`. // SetPC sets the RIP register to the value specified by `pc`.
func (r *Regs) SetPC(thread *Thread, pc uint64) error { func (thread *Thread) SetPC(pc uint64) error {
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc)) kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not set pc") return fmt.Errorf("could not set pc")
@ -109,6 +123,15 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
return nil return nil
} }
// SetSP sets the RSP register to the value specified by `pc`.
func (thread *Thread) SetSP(sp uint64) error {
return errors.New("not implemented")
}
func (thread *Thread) SetDX(dx uint64) error {
return errors.New("not implemented")
}
func (r *Regs) Get(n int) (uint64, error) { func (r *Regs) Get(n int) (uint64, error) {
reg := x86asm.Reg(n) reg := x86asm.Reg(n)
const ( const (
@ -130,7 +153,7 @@ func (r *Regs) Get(n int) (uint64, error) {
case x86asm.AH: case x86asm.AH:
return (r.rax >> 8) & mask8, nil return (r.rax >> 8) & mask8, nil
case x86asm.CH: case x86asm.CH:
return (r.rax >> 8) & mask8, nil return (r.rcx >> 8) & mask8, nil
case x86asm.DH: case x86asm.DH:
return (r.rdx >> 8) & mask8, nil return (r.rdx >> 8) & mask8, nil
case x86asm.BH: case x86asm.BH:
@ -263,10 +286,10 @@ func (r *Regs) Get(n int) (uint64, error) {
return r.r15, nil return r.r15, nil
} }
return 0, UnknownRegisterError return 0, proc.ErrUnknownRegister
} }
func registers(thread *Thread, floatingPoint bool) (Registers, error) { func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
var state C.x86_thread_state64_t var state C.x86_thread_state64_t
var identity C.thread_identifier_info_data_t var identity C.thread_identifier_info_data_t
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state) kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state)
@ -322,42 +345,31 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
return nil, fmt.Errorf("could not get floating point registers") return nil, fmt.Errorf("could not get floating point registers")
} }
regs.fpregs = appendWordReg(regs.fpregs, "CW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fcw)))) regs.fpregs = proc.AppendWordReg(regs.fpregs, "CW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fcw))))
regs.fpregs = appendWordReg(regs.fpregs, "SW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fsw)))) regs.fpregs = proc.AppendWordReg(regs.fpregs, "SW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fsw))))
regs.fpregs = appendWordReg(regs.fpregs, "TW", uint16(fpstate.__fpu_ftw)) regs.fpregs = proc.AppendWordReg(regs.fpregs, "TW", uint16(fpstate.__fpu_ftw))
regs.fpregs = appendWordReg(regs.fpregs, "FOP", uint16(fpstate.__fpu_fop)) regs.fpregs = proc.AppendWordReg(regs.fpregs, "FOP", uint16(fpstate.__fpu_fop))
regs.fpregs = appendQwordReg(regs.fpregs, "FIP", uint64(fpstate.__fpu_cs)<<32|uint64(fpstate.__fpu_ip)) regs.fpregs = proc.AppendQwordReg(regs.fpregs, "FIP", uint64(fpstate.__fpu_cs)<<32|uint64(fpstate.__fpu_ip))
regs.fpregs = appendQwordReg(regs.fpregs, "FDP", uint64(fpstate.__fpu_ds)<<32|uint64(fpstate.__fpu_dp)) regs.fpregs = proc.AppendQwordReg(regs.fpregs, "FDP", uint64(fpstate.__fpu_ds)<<32|uint64(fpstate.__fpu_dp))
for i, st := range []*C.char{&fpstate.__fpu_stmm0.__mmst_reg[0], &fpstate.__fpu_stmm1.__mmst_reg[0], &fpstate.__fpu_stmm2.__mmst_reg[0], &fpstate.__fpu_stmm3.__mmst_reg[0], &fpstate.__fpu_stmm4.__mmst_reg[0], &fpstate.__fpu_stmm5.__mmst_reg[0], &fpstate.__fpu_stmm6.__mmst_reg[0], &fpstate.__fpu_stmm7.__mmst_reg[0]} { for i, st := range []*C.char{&fpstate.__fpu_stmm0.__mmst_reg[0], &fpstate.__fpu_stmm1.__mmst_reg[0], &fpstate.__fpu_stmm2.__mmst_reg[0], &fpstate.__fpu_stmm3.__mmst_reg[0], &fpstate.__fpu_stmm4.__mmst_reg[0], &fpstate.__fpu_stmm5.__mmst_reg[0], &fpstate.__fpu_stmm6.__mmst_reg[0], &fpstate.__fpu_stmm7.__mmst_reg[0]} {
stb := C.GoBytes(unsafe.Pointer(st), 10) stb := C.GoBytes(unsafe.Pointer(st), 10)
mantissa := binary.LittleEndian.Uint64(stb[:8]) mantissa := binary.LittleEndian.Uint64(stb[:8])
exponent := binary.LittleEndian.Uint16(stb[8:]) exponent := binary.LittleEndian.Uint16(stb[8:])
regs.fpregs = appendX87Reg(regs.fpregs, i, exponent, mantissa) regs.fpregs = proc.AppendX87Reg(regs.fpregs, i, exponent, mantissa)
} }
regs.fpregs = appendFlagReg(regs.fpregs, "MXCSR", uint64(fpstate.__fpu_mxcsr), mxcsrDescription, 32) regs.fpregs = proc.AppendMxcsrReg(regs.fpregs, "MXCSR", uint64(fpstate.__fpu_mxcsr))
regs.fpregs = appendDwordReg(regs.fpregs, "MXCSR_MASK", uint32(fpstate.__fpu_mxcsrmask)) regs.fpregs = proc.AppendDwordReg(regs.fpregs, "MXCSR_MASK", uint32(fpstate.__fpu_mxcsrmask))
for i, xmm := range []*C.char{&fpstate.__fpu_xmm0.__xmm_reg[0], &fpstate.__fpu_xmm1.__xmm_reg[0], &fpstate.__fpu_xmm2.__xmm_reg[0], &fpstate.__fpu_xmm3.__xmm_reg[0], &fpstate.__fpu_xmm4.__xmm_reg[0], &fpstate.__fpu_xmm5.__xmm_reg[0], &fpstate.__fpu_xmm6.__xmm_reg[0], &fpstate.__fpu_xmm7.__xmm_reg[0], &fpstate.__fpu_xmm8.__xmm_reg[0], &fpstate.__fpu_xmm9.__xmm_reg[0], &fpstate.__fpu_xmm10.__xmm_reg[0], &fpstate.__fpu_xmm11.__xmm_reg[0], &fpstate.__fpu_xmm12.__xmm_reg[0], &fpstate.__fpu_xmm13.__xmm_reg[0], &fpstate.__fpu_xmm14.__xmm_reg[0], &fpstate.__fpu_xmm15.__xmm_reg[0]} { for i, xmm := range []*C.char{&fpstate.__fpu_xmm0.__xmm_reg[0], &fpstate.__fpu_xmm1.__xmm_reg[0], &fpstate.__fpu_xmm2.__xmm_reg[0], &fpstate.__fpu_xmm3.__xmm_reg[0], &fpstate.__fpu_xmm4.__xmm_reg[0], &fpstate.__fpu_xmm5.__xmm_reg[0], &fpstate.__fpu_xmm6.__xmm_reg[0], &fpstate.__fpu_xmm7.__xmm_reg[0], &fpstate.__fpu_xmm8.__xmm_reg[0], &fpstate.__fpu_xmm9.__xmm_reg[0], &fpstate.__fpu_xmm10.__xmm_reg[0], &fpstate.__fpu_xmm11.__xmm_reg[0], &fpstate.__fpu_xmm12.__xmm_reg[0], &fpstate.__fpu_xmm13.__xmm_reg[0], &fpstate.__fpu_xmm14.__xmm_reg[0], &fpstate.__fpu_xmm15.__xmm_reg[0]} {
regs.fpregs = appendSSEReg(regs.fpregs, fmt.Sprintf("XMM%d", i), C.GoBytes(unsafe.Pointer(xmm), 16)) regs.fpregs = proc.AppendSSEReg(regs.fpregs, fmt.Sprintf("XMM%d", i), C.GoBytes(unsafe.Pointer(xmm), 16))
} }
} }
return regs, nil return regs, nil
} }
func (thread *Thread) saveRegisters() (Registers, error) { func (r *Regs) Copy() proc.Registers {
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers) //TODO(aarzilli): implement this to support function calls
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not save register contents")
}
return &Regs{rip: uint64(thread.os.registers.__rip), rsp: uint64(thread.os.registers.__rsp)}, nil
}
func (thread *Thread) restoreRegisters() error {
kret := C.set_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers)
if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not save register contents")
}
return nil return nil
} }

View File

@ -1,20 +1,22 @@
package proc package native
import ( import (
"fmt" "fmt"
"rsc.io/x86/x86asm" "golang.org/x/arch/x86/x86asm"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc"
) )
// Regs is a wrapper for sys.PtraceRegs. // Regs is a wrapper for sys.PtraceRegs.
type Regs struct { type Regs struct {
regs *sys.PtraceRegs regs *sys.PtraceRegs
fpregs []Register fpregs []proc.Register
fpregset *proc.LinuxX86Xstate
} }
func (r *Regs) Slice() []Register { func (r *Regs) Slice() []proc.Register {
var regs = []struct { var regs = []struct {
k string k string
v uint64 v uint64
@ -47,12 +49,12 @@ func (r *Regs) Slice() []Register {
{"Fs", r.regs.Fs}, {"Fs", r.regs.Fs},
{"Gs", r.regs.Gs}, {"Gs", r.regs.Gs},
} }
out := make([]Register, 0, len(regs)+len(r.fpregs)) out := make([]proc.Register, 0, len(regs)+len(r.fpregs))
for _, reg := range regs { for _, reg := range regs {
if reg.k == "Eflags" { if reg.k == "Eflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64) out = proc.AppendEflagReg(out, reg.k, reg.v)
} else { } else {
out = appendQwordReg(out, reg.k, reg.v) out = proc.AppendQwordReg(out, reg.k, reg.v)
} }
} }
out = append(out, r.fpregs...) out = append(out, r.fpregs...)
@ -69,6 +71,10 @@ func (r *Regs) SP() uint64 {
return r.regs.Rsp return r.regs.Rsp
} }
func (r *Regs) BP() uint64 {
return r.regs.Rbp
}
// CX returns the value of RCX register. // CX returns the value of RCX register.
func (r *Regs) CX() uint64 { func (r *Regs) CX() uint64 {
return r.regs.Rcx return r.regs.Rcx
@ -80,10 +86,44 @@ func (r *Regs) TLS() uint64 {
return r.regs.Fs_base return r.regs.Fs_base
} }
func (r *Regs) GAddr() (uint64, bool) {
return 0, false
}
// SetPC sets RIP to the value specified by 'pc'. // SetPC sets RIP to the value specified by 'pc'.
func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) { func (thread *Thread) SetPC(pc uint64) error {
ir, err := registers(thread, false)
if err != nil {
return err
}
r := ir.(*Regs)
r.regs.SetPC(pc) r.regs.SetPC(pc)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) }) thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
return err
}
// SetSP sets RSP to the value specified by 'sp'
func (thread *Thread) SetSP(sp uint64) (err error) {
var ir proc.Registers
ir, err = registers(thread, false)
if err != nil {
return err
}
r := ir.(*Regs)
r.regs.Rsp = sp
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
return
}
func (thread *Thread) SetDX(dx uint64) (err error) {
var ir proc.Registers
ir, err = registers(thread, false)
if err != nil {
return err
}
r := ir.(*Regs)
r.regs.Rdx = dx
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
return return
} }
@ -108,7 +148,7 @@ func (r *Regs) Get(n int) (uint64, error) {
case x86asm.AH: case x86asm.AH:
return (r.regs.Rax >> 8) & mask8, nil return (r.regs.Rax >> 8) & mask8, nil
case x86asm.CH: case x86asm.CH:
return (r.regs.Rax >> 8) & mask8, nil return (r.regs.Rcx >> 8) & mask8, nil
case x86asm.DH: case x86asm.DH:
return (r.regs.Rdx >> 8) & mask8, nil return (r.regs.Rdx >> 8) & mask8, nil
case x86asm.BH: case x86asm.BH:
@ -241,10 +281,10 @@ func (r *Regs) Get(n int) (uint64, error) {
return r.regs.R15, nil return r.regs.R15, nil
} }
return 0, UnknownRegisterError return 0, proc.ErrUnknownRegister
} }
func registers(thread *Thread, floatingPoint bool) (Registers, error) { func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
var ( var (
regs sys.PtraceRegs regs sys.PtraceRegs
err error err error
@ -253,9 +293,11 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
r := &Regs{&regs, nil} r := &Regs{&regs, nil, nil}
if floatingPoint { if floatingPoint {
r.fpregs, err = thread.fpRegisters() var fpregset proc.LinuxX86Xstate
r.fpregs, fpregset, err = thread.fpRegisters()
r.fpregset = &fpregset
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -263,27 +305,6 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
return r, nil return r, nil
} }
// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
padding [24]uint32
}
type PtraceXsave struct {
PtraceFpRegs
AvxState bool // contains AVX state
YmmSpace [256]byte
}
const ( const (
_X86_XSTATE_MAX_SIZE = 2688 _X86_XSTATE_MAX_SIZE = 2688
_NT_X86_XSTATE = 0x202 _NT_X86_XSTATE = 0x202
@ -294,32 +315,28 @@ const (
_XSAVE_SSE_REGION_LEN = 416 _XSAVE_SSE_REGION_LEN = 416
) )
func (thread *Thread) fpRegisters() (regs []Register, err error) { func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs proc.LinuxX86Xstate, err error) {
var fpregs PtraceXsave
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) }) thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
regs = fpregs.Decode()
// x87 registers if err != nil {
regs = appendWordReg(regs, "CW", fpregs.Cwd) err = fmt.Errorf("could not get floating point registers: %v", err.Error())
regs = appendWordReg(regs, "SW", fpregs.Swd)
regs = appendWordReg(regs, "TW", fpregs.Ftw)
regs = appendWordReg(regs, "FOP", fpregs.Fop)
regs = appendQwordReg(regs, "FIP", fpregs.Rip)
regs = appendQwordReg(regs, "FDP", fpregs.Rdp)
for i := 0; i < len(fpregs.StSpace); i += 4 {
regs = appendX87Reg(regs, i/4, uint16(fpregs.StSpace[i+2]), uint64(fpregs.StSpace[i+1])<<32|uint64(fpregs.StSpace[i]))
} }
// SSE registers
regs = appendFlagReg(regs, "MXCSR", uint64(fpregs.Mxcsr), mxcsrDescription, 32)
regs = appendDwordReg(regs, "MXCSR_MASK", fpregs.MxcrMask)
for i := 0; i < len(fpregs.XmmSpace); i += 16 {
regs = appendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), fpregs.XmmSpace[i:i+16])
if fpregs.AvxState {
regs = appendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), fpregs.YmmSpace[i:i+16])
}
}
return return
} }
// Copy returns a copy of these registers that is
// guarenteed not to change.
func (r *Regs) Copy() proc.Registers {
var rr Regs
rr.regs = &sys.PtraceRegs{}
rr.fpregset = &proc.LinuxX86Xstate{}
*(rr.regs) = *(r.regs)
if r.fpregset != nil {
*(rr.fpregset) = *(r.fpregset)
}
if r.fpregs != nil {
rr.fpregs = make([]proc.Register, len(r.fpregs))
copy(rr.fpregs, r.fpregs)
}
return &rr
}

View File

@ -1,9 +1,12 @@
package proc package native
import ( import (
"fmt" "fmt"
"rsc.io/x86/x86asm"
"unsafe" "unsafe"
"golang.org/x/arch/x86/x86asm"
"github.com/derekparker/delve/pkg/proc"
) )
// Regs represents CPU registers on an AMD64 processor. // Regs represents CPU registers on an AMD64 processor.
@ -30,10 +33,11 @@ type Regs struct {
fs uint64 fs uint64
gs uint64 gs uint64
tls uint64 tls uint64
context *_CONTEXT
fltSave *_XMM_SAVE_AREA32 fltSave *_XMM_SAVE_AREA32
} }
func (r *Regs) Slice() []Register { func (r *Regs) Slice() []proc.Register {
var regs = []struct { var regs = []struct {
k string k string
v uint64 v uint64
@ -65,31 +69,31 @@ func (r *Regs) Slice() []Register {
if r.fltSave != nil { if r.fltSave != nil {
outlen += 6 + 8 + 2 + 16 outlen += 6 + 8 + 2 + 16
} }
out := make([]Register, 0, outlen) out := make([]proc.Register, 0, outlen)
for _, reg := range regs { for _, reg := range regs {
if reg.k == "Eflags" { if reg.k == "Eflags" {
out = append(out, Register{reg.k, eflagsDescription.Describe(reg.v, 64)}) out = proc.AppendEflagReg(out, reg.k, reg.v)
} else { } else {
out = appendQwordReg(out, reg.k, reg.v) out = proc.AppendQwordReg(out, reg.k, reg.v)
} }
} }
if r.fltSave != nil { if r.fltSave != nil {
out = appendWordReg(out, "CW", r.fltSave.ControlWord) out = proc.AppendWordReg(out, "CW", r.fltSave.ControlWord)
out = appendWordReg(out, "SW", r.fltSave.StatusWord) out = proc.AppendWordReg(out, "SW", r.fltSave.StatusWord)
out = appendWordReg(out, "TW", uint16(r.fltSave.TagWord)) out = proc.AppendWordReg(out, "TW", uint16(r.fltSave.TagWord))
out = appendWordReg(out, "FOP", r.fltSave.ErrorOpcode) out = proc.AppendWordReg(out, "FOP", r.fltSave.ErrorOpcode)
out = appendQwordReg(out, "FIP", uint64(r.fltSave.ErrorSelector)<<32|uint64(r.fltSave.ErrorOffset)) out = proc.AppendQwordReg(out, "FIP", uint64(r.fltSave.ErrorSelector)<<32|uint64(r.fltSave.ErrorOffset))
out = appendQwordReg(out, "FDP", uint64(r.fltSave.DataSelector)<<32|uint64(r.fltSave.DataOffset)) out = proc.AppendQwordReg(out, "FDP", uint64(r.fltSave.DataSelector)<<32|uint64(r.fltSave.DataOffset))
for i := range r.fltSave.FloatRegisters { for i := range r.fltSave.FloatRegisters {
out = appendX87Reg(out, i, uint16(r.fltSave.FloatRegisters[i].High), r.fltSave.FloatRegisters[i].Low) out = proc.AppendX87Reg(out, i, uint16(r.fltSave.FloatRegisters[i].High), r.fltSave.FloatRegisters[i].Low)
} }
out = appendFlagReg(out, "MXCSR", uint64(r.fltSave.MxCsr), mxcsrDescription, 32) out = proc.AppendMxcsrReg(out, "MXCSR", uint64(r.fltSave.MxCsr))
out = appendDwordReg(out, "MXCSR_MASK", r.fltSave.MxCsr_Mask) out = proc.AppendDwordReg(out, "MXCSR_MASK", r.fltSave.MxCsr_Mask)
for i := 0; i < len(r.fltSave.XmmRegisters); i += 16 { for i := 0; i < len(r.fltSave.XmmRegisters); i += 16 {
out = appendSSEReg(out, fmt.Sprintf("XMM%d", i/16), r.fltSave.XmmRegisters[i:i+16]) out = proc.AppendSSEReg(out, fmt.Sprintf("XMM%d", i/16), r.fltSave.XmmRegisters[i:i+16])
} }
} }
return out return out
@ -107,6 +111,10 @@ func (r *Regs) SP() uint64 {
return r.rsp return r.rsp
} }
func (r *Regs) BP() uint64 {
return r.rbp
}
// CX returns the value of the RCX register. // CX returns the value of the RCX register.
func (r *Regs) CX() uint64 { func (r *Regs) CX() uint64 {
return r.rcx return r.rcx
@ -119,8 +127,12 @@ func (r *Regs) TLS() uint64 {
return r.tls return r.tls
} }
func (r *Regs) GAddr() (uint64, bool) {
return 0, false
}
// SetPC sets the RIP register to the value specified by `pc`. // SetPC sets the RIP register to the value specified by `pc`.
func (r *Regs) SetPC(thread *Thread, pc uint64) error { func (thread *Thread) SetPC(pc uint64) error {
context := newCONTEXT() context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL context.ContextFlags = _CONTEXT_ALL
@ -134,6 +146,35 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
return _SetThreadContext(thread.os.hThread, context) return _SetThreadContext(thread.os.hThread, context)
} }
// SetSP sets the RSP register to the value specified by `sp`.
func (thread *Thread) SetSP(sp uint64) error {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
err := _GetThreadContext(thread.os.hThread, context)
if err != nil {
return err
}
context.Rsp = sp
return _SetThreadContext(thread.os.hThread, context)
}
func (thread *Thread) SetDX(dx uint64) error {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
err := _GetThreadContext(thread.os.hThread, context)
if err != nil {
return err
}
context.Rdx = dx
return _SetThreadContext(thread.os.hThread, context)
}
func (r *Regs) Get(n int) (uint64, error) { func (r *Regs) Get(n int) (uint64, error) {
reg := x86asm.Reg(n) reg := x86asm.Reg(n)
const ( const (
@ -155,7 +196,7 @@ func (r *Regs) Get(n int) (uint64, error) {
case x86asm.AH: case x86asm.AH:
return (r.rax >> 8) & mask8, nil return (r.rax >> 8) & mask8, nil
case x86asm.CH: case x86asm.CH:
return (r.rax >> 8) & mask8, nil return (r.rcx >> 8) & mask8, nil
case x86asm.DH: case x86asm.DH:
return (r.rdx >> 8) & mask8, nil return (r.rdx >> 8) & mask8, nil
case x86asm.BH: case x86asm.BH:
@ -288,10 +329,10 @@ func (r *Regs) Get(n int) (uint64, error) {
return r.r15, nil return r.r15, nil
} }
return 0, UnknownRegisterError return 0, proc.ErrUnknownRegister
} }
func registers(thread *Thread, floatingPoint bool) (Registers, error) { func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
context := newCONTEXT() context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL context.ContextFlags = _CONTEXT_ALL
@ -334,14 +375,16 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
if floatingPoint { if floatingPoint {
regs.fltSave = &context.FltSave regs.fltSave = &context.FltSave
} }
regs.context = context
return regs, nil return regs, nil
} }
func (thread *Thread) saveRegisters() (Registers, error) { func (r *Regs) Copy() proc.Registers {
return nil, fmt.Errorf("not implemented: saveRegisters") var rr Regs
} rr = *r
rr.context = newCONTEXT()
func (thread *Thread) restoreRegisters() error { *(rr.context) = *(r.context)
return fmt.Errorf("not implemented: restoreRegisters") rr.fltSave = &rr.context.FltSave
return &rr
} }

View File

@ -1,6 +1,6 @@
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go //go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
package proc package native
import ( import (
"syscall" "syscall"

View File

@ -1,4 +1,4 @@
package proc package native
import "unsafe" import "unsafe"

View File

@ -0,0 +1,174 @@
package native
import (
"fmt"
"github.com/derekparker/delve/pkg/proc"
)
// Thread represents a single thread in the traced process
// ID represents the thread id or port, Process holds a reference to the
// Process struct that contains info on the process as
// a whole, and Status represents the last result of a `wait` call
// on this thread.
type Thread struct {
ID int // Thread ID or mach port
Status *WaitStatus // Status returned from last wait call
CurrentBreakpoint proc.BreakpointState // Breakpoint thread is currently stopped at
dbp *Process
singleStepping bool
os *OSSpecificDetails
common proc.CommonThread
}
// Continue the execution of this thread.
//
// If we are currently at a breakpoint, we'll clear it
// first and then resume execution. Thread will continue until
// it hits a breakpoint or is signaled.
func (t *Thread) Continue() error {
pc, err := t.PC()
if err != nil {
return err
}
// Check whether we are stopped at a breakpoint, and
// if so, single step over it before continuing.
if _, ok := t.dbp.FindBreakpoint(pc); ok {
if err := t.StepInstruction(); err != nil {
return err
}
}
return t.resume()
}
// StepInstruction steps a single instruction.
//
// Executes exactly one instruction and then returns.
// If the thread is at a breakpoint, we first clear it,
// execute the instruction, and then replace the breakpoint.
// Otherwise we simply execute the next instruction.
func (t *Thread) StepInstruction() (err error) {
t.singleStepping = true
defer func() {
t.singleStepping = false
}()
pc, err := t.PC()
if err != nil {
return err
}
bp, ok := t.dbp.FindBreakpoint(pc)
if ok {
// Clear the breakpoint so that we can continue execution.
err = t.ClearBreakpoint(bp)
if err != nil {
return err
}
// Restore breakpoint now that we have passed it.
defer func() {
err = t.dbp.writeSoftwareBreakpoint(t, bp.Addr)
}()
}
err = t.singleStep()
if err != nil {
if _, exited := err.(proc.ErrProcessExited); exited {
return err
}
return fmt.Errorf("step failed: %s", err.Error())
}
return nil
}
// Location returns the threads location, including the file:line
// of the corresponding source code, the function we're in
// and the current instruction address.
func (t *Thread) Location() (*proc.Location, error) {
pc, err := t.PC()
if err != nil {
return nil, err
}
f, l, fn := t.dbp.bi.PCToLine(pc)
return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
}
// Arch returns the architecture the binary is
// compiled for and executing on.
func (t *Thread) Arch() proc.Arch {
return t.dbp.bi.Arch
}
// BinInfo returns information on the binary.
func (t *Thread) BinInfo() *proc.BinaryInfo {
return t.dbp.bi
}
// Common returns information common across Process
// implementations.
func (t *Thread) Common() *proc.CommonThread {
return &t.common
}
// SetCurrentBreakpoint sets the current breakpoint that this
// thread is stopped at as CurrentBreakpoint on the thread struct.
func (t *Thread) SetCurrentBreakpoint() error {
t.CurrentBreakpoint.Clear()
pc, err := t.PC()
if err != nil {
return err
}
if bp, ok := t.dbp.FindBreakpoint(pc); ok {
if err = t.SetPC(bp.Addr); err != nil {
return err
}
t.CurrentBreakpoint = bp.CheckCondition(t)
if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
if g, err := proc.GetG(t); err == nil {
t.CurrentBreakpoint.HitCount[g.ID]++
}
t.CurrentBreakpoint.TotalHitCount++
}
}
return nil
}
// Breakpoint returns the current breakpoint that is active
// on this thread.
func (t *Thread) Breakpoint() proc.BreakpointState {
return t.CurrentBreakpoint
}
// ThreadID returns the ID of this thread.
func (t *Thread) ThreadID() int {
return t.ID
}
// ClearBreakpoint clears the specified breakpoint.
func (t *Thread) ClearBreakpoint(bp *proc.Breakpoint) error {
if _, err := t.WriteMemory(uintptr(bp.Addr), bp.OriginalData); err != nil {
return fmt.Errorf("could not clear breakpoint %s", err)
}
return nil
}
// Registers obtains register values from the debugged process.
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
return registers(t, floatingPoint)
}
// RestoreRegisters will set the value of the CPU registers to those
// passed in via 'savedRegs'.
func (t *Thread) RestoreRegisters(savedRegs proc.Registers) error {
return t.restoreRegisters(savedRegs)
}
// PC returns the current program counter value for this thread.
func (t *Thread) PC() (uint64, error) {
regs, err := t.Registers(false)
if err != nil {
return 0, err
}
return regs.PC(), nil
}

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
#include "threads_darwin.h" #include "threads_darwin.h"
int int

View File

@ -1,12 +1,18 @@
package proc //+build darwin,macnative
package native
// #include "threads_darwin.h" // #include "threads_darwin.h"
// #include "proc_darwin.h" // #include "proc_darwin.h"
import "C" import "C"
import ( import (
"errors"
"fmt" "fmt"
sys "golang.org/x/sys/unix"
"unsafe" "unsafe"
sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc"
) )
// WaitStatus is a synonym for the platform-specific WaitStatus // WaitStatus is a synonym for the platform-specific WaitStatus
@ -24,7 +30,7 @@ type OSSpecificDetails struct {
// be continued. // be continued.
var ErrContinueThread = fmt.Errorf("could not continue thread") var ErrContinueThread = fmt.Errorf("could not continue thread")
func (t *Thread) halt() (err error) { func (t *Thread) stop() (err error) {
kret := C.thread_suspend(t.os.threadAct) kret := C.thread_suspend(t.os.threadAct)
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
errStr := C.GoString(C.mach_error_string(C.mach_error_t(kret))) errStr := C.GoString(C.mach_error_string(C.mach_error_t(kret)))
@ -35,7 +41,7 @@ func (t *Thread) halt() (err error) {
return return
} }
if _, ok := t.dbp.Threads[t.ID]; ok { if _, ok := t.dbp.threads[t.ID]; ok {
err = fmt.Errorf("could not suspend thread %d %s", t.ID, errStr) err = fmt.Errorf("could not suspend thread %d %s", t.ID, errStr)
return return
} }
@ -49,7 +55,7 @@ func (t *Thread) singleStep() error {
return fmt.Errorf("could not single step") return fmt.Errorf("could not single step")
} }
for { for {
twthread, err := t.dbp.trapWait(t.dbp.Pid) twthread, err := t.dbp.trapWait(t.dbp.pid)
if err != nil { if err != nil {
return err return err
} }
@ -66,10 +72,9 @@ func (t *Thread) singleStep() error {
} }
func (t *Thread) resume() error { func (t *Thread) resume() error {
t.running = true
// TODO(dp) set flag for ptrace stops // TODO(dp) set flag for ptrace stops
var err error var err error
t.dbp.execPtraceFunc(func() { err = PtraceCont(t.dbp.Pid, 0) }) t.dbp.execPtraceFunc(func() { err = PtraceCont(t.dbp.pid, 0) })
if err == nil { if err == nil {
return nil return nil
} }
@ -80,29 +85,35 @@ func (t *Thread) resume() error {
return nil return nil
} }
func (t *Thread) blocked() bool { func (t *Thread) Blocked() bool {
// TODO(dp) cache the func pc to remove this lookup // TODO(dp) cache the func pc to remove this lookup
pc, err := t.PC() regs, err := t.Registers(false)
if err != nil { if err != nil {
return false return false
} }
fn := t.dbp.goSymTable.PCToFunc(pc) pc := regs.PC()
fn := t.BinInfo().PCToFunc(pc)
if fn == nil { if fn == nil {
return false return false
} }
switch fn.Name { switch fn.Name {
case "runtime.kevent", "runtime.mach_semaphore_wait", "runtime.usleep": case "runtime.kevent", "runtime.mach_semaphore_wait", "runtime.usleep", "runtime.mach_semaphore_timedwait":
return true return true
default: default:
return false return false
} }
} }
func (t *Thread) stopped() bool { // Stopped returns whether the thread is stopped at
// the operating system level.
func (t *Thread) Stopped() bool {
return C.thread_blocked(t.os.threadAct) > C.int(0) return C.thread_blocked(t.os.threadAct) > C.int(0)
} }
func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) { func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
if t.dbp.exited {
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
}
if len(data) == 0 { if len(data) == 0 {
return 0, nil return 0, nil
} }
@ -117,20 +128,26 @@ func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) {
return len(data), nil return len(data), nil
} }
func (t *Thread) readMemory(addr uintptr, size int) ([]byte, error) { func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
if size == 0 { if t.dbp.exited {
return nil, nil return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
}
if len(buf) == 0 {
return 0, nil
} }
var ( var (
buf = make([]byte, size)
vmData = unsafe.Pointer(&buf[0]) vmData = unsafe.Pointer(&buf[0])
vmAddr = C.mach_vm_address_t(addr) vmAddr = C.mach_vm_address_t(addr)
length = C.mach_msg_type_number_t(size) length = C.mach_msg_type_number_t(len(buf))
) )
ret := C.read_memory(t.dbp.os.task, vmAddr, vmData, length) ret := C.read_memory(t.dbp.os.task, vmAddr, vmData, length)
if ret < 0 { if ret < 0 {
return nil, fmt.Errorf("could not read memory") return 0, fmt.Errorf("could not read memory")
} }
return buf, nil return len(buf), nil
}
func (t *Thread) restoreRegisters(sr proc.Registers) error {
return errors.New("not implemented")
} }

View File

@ -1,3 +1,5 @@
//+build darwin,macnative
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
#include <mach/mach.h> #include <mach/mach.h>

View File

@ -0,0 +1,132 @@
package native
import (
"fmt"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc"
)
type WaitStatus sys.WaitStatus
// OSSpecificDetails hold Linux specific
// process details.
type OSSpecificDetails struct {
registers sys.PtraceRegs
running bool
}
func (t *Thread) stop() (err error) {
err = sys.Tgkill(t.dbp.pid, t.ID, sys.SIGSTOP)
if err != nil {
err = fmt.Errorf("stop err %s on thread %d", err, t.ID)
return
}
return
}
// Stopped returns whether the thread is stopped at
// the operating system level.
func (t *Thread) Stopped() bool {
state := status(t.ID, t.dbp.os.comm)
return state == StatusTraceStop || state == StatusTraceStopT
}
func (t *Thread) resume() error {
return t.resumeWithSig(0)
}
func (t *Thread) resumeWithSig(sig int) (err error) {
t.os.running = true
t.dbp.execPtraceFunc(func() { err = PtraceCont(t.ID, sig) })
return
}
func (t *Thread) singleStep() (err error) {
for {
t.dbp.execPtraceFunc(func() { err = sys.PtraceSingleStep(t.ID) })
if err != nil {
return err
}
wpid, status, err := t.dbp.waitFast(t.ID)
if err != nil {
return err
}
if (status == nil || status.Exited()) && wpid == t.dbp.pid {
t.dbp.postExit()
rs := 0
if status != nil {
rs = status.ExitStatus()
}
return proc.ErrProcessExited{Pid: t.dbp.pid, Status: rs}
}
if wpid == t.ID && status.StopSignal() == sys.SIGTRAP {
return nil
}
}
}
func (t *Thread) Blocked() bool {
regs, err := t.Registers(false)
if err != nil {
return false
}
pc := regs.PC()
fn := t.BinInfo().PCToFunc(pc)
if fn != nil && ((fn.Name == "runtime.futex") || (fn.Name == "runtime.usleep") || (fn.Name == "runtime.clone")) {
return true
}
return false
}
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
sr := savedRegs.(*Regs)
var restoreRegistersErr error
t.dbp.execPtraceFunc(func() {
restoreRegistersErr = sys.PtraceSetRegs(t.ID, sr.regs)
if restoreRegistersErr != nil {
return
}
if sr.fpregset.Xsave != nil {
iov := sys.Iovec{Base: &sr.fpregset.Xsave[0], Len: uint64(len(sr.fpregset.Xsave))}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
return
}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(0), uintptr(unsafe.Pointer(&sr.fpregset.PtraceFpRegs)), 0, 0)
return
})
if restoreRegistersErr == syscall.Errno(0) {
restoreRegistersErr = nil
}
return restoreRegistersErr
}
func (t *Thread) WriteMemory(addr uintptr, data []byte) (written int, err error) {
if t.dbp.exited {
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
}
if len(data) == 0 {
return
}
t.dbp.execPtraceFunc(func() { written, err = sys.PtracePokeData(t.ID, addr, data) })
return
}
func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
if t.dbp.exited {
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
}
if len(data) == 0 {
return
}
t.dbp.execPtraceFunc(func() { _, err = sys.PtracePeekData(t.ID, addr, data) })
if err == nil {
n = len(data)
}
return
}

View File

@ -1,9 +1,12 @@
package proc package native
import ( import (
"errors"
"syscall" "syscall"
sys "golang.org/x/sys/windows" sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/pkg/proc"
) )
// WaitStatus is a synonym for the platform-specific WaitStatus // WaitStatus is a synonym for the platform-specific WaitStatus
@ -15,16 +18,6 @@ type OSSpecificDetails struct {
hThread syscall.Handle hThread syscall.Handle
} }
func (t *Thread) halt() (err error) {
// Ignore the request to halt. On Windows, all threads are halted
// on return from WaitForDebugEvent.
return nil
// TODO - This may not be correct in all usages of dbp.Halt. There
// are some callers who use dbp.Halt() to stop the process when it is not
// already broken on a debug event.
}
func (t *Thread) singleStep() error { func (t *Thread) singleStep() error {
context := newCONTEXT() context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL context.ContextFlags = _CONTEXT_ALL
@ -57,7 +50,7 @@ func (t *Thread) singleStep() error {
} }
if tid == 0 { if tid == 0 {
t.dbp.postExit() t.dbp.postExit()
return ProcessExitedError{Pid: t.dbp.Pid, Status: exitCode} return proc.ErrProcessExited{Pid: t.dbp.pid, Status: exitCode}
} }
if t.dbp.os.breakThread == t.ID { if t.dbp.os.breakThread == t.ID {
@ -65,7 +58,7 @@ func (t *Thread) singleStep() error {
} }
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(t.dbp.Pid), uint32(t.dbp.os.breakThread), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.dbp.os.breakThread), _DBG_CONTINUE)
}) })
} }
@ -75,7 +68,7 @@ func (t *Thread) singleStep() error {
} }
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(t.dbp.Pid), uint32(t.ID), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE)
}) })
if err != nil { if err != nil {
return err return err
@ -93,24 +86,24 @@ func (t *Thread) singleStep() error {
} }
func (t *Thread) resume() error { func (t *Thread) resume() error {
t.running = true
var err error var err error
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {
//TODO: Note that we are ignoring the thread we were asked to continue and are continuing the //TODO: Note that we are ignoring the thread we were asked to continue and are continuing the
//thread that we last broke on. //thread that we last broke on.
err = _ContinueDebugEvent(uint32(t.dbp.Pid), uint32(t.ID), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE)
}) })
return err return err
} }
func (t *Thread) blocked() bool { func (t *Thread) Blocked() bool {
// TODO: Probably incorrect - what are the runtime functions that // TODO: Probably incorrect - what are the runtime functions that
// indicate blocking on Windows? // indicate blocking on Windows?
pc, err := t.PC() regs, err := t.Registers(false)
if err != nil { if err != nil {
return false return false
} }
fn := t.dbp.goSymTable.PCToFunc(pc) pc := regs.PC()
fn := t.BinInfo().PCToFunc(pc)
if fn == nil { if fn == nil {
return false return false
} }
@ -122,13 +115,19 @@ func (t *Thread) blocked() bool {
} }
} }
func (t *Thread) stopped() bool { // Stopped returns whether the thread is stopped at the operating system
// TODO: We are assuming that threads are always stopped // level. On windows this always returns true.
// during command execution. func (t *Thread) Stopped() bool {
return true return true
} }
func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) { func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
if t.dbp.exited {
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
}
if len(data) == 0 {
return 0, nil
}
var count uintptr var count uintptr
err := _WriteProcessMemory(t.dbp.os.hProcess, addr, &data[0], uintptr(len(data)), &count) err := _WriteProcessMemory(t.dbp.os.hProcess, addr, &data[0], uintptr(len(data)), &count)
if err != nil { if err != nil {
@ -137,15 +136,23 @@ func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) {
return int(count), nil return int(count), nil
} }
func (t *Thread) readMemory(addr uintptr, size int) ([]byte, error) { var ErrShortRead = errors.New("short read")
if size == 0 {
return nil, nil func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
if t.dbp.exited {
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
}
if len(buf) == 0 {
return 0, nil
} }
var count uintptr var count uintptr
buf := make([]byte, size) err := _ReadProcessMemory(t.dbp.os.hProcess, addr, &buf[0], uintptr(len(buf)), &count)
err := _ReadProcessMemory(t.dbp.os.hProcess, addr, &buf[0], uintptr(size), &count) if err == nil && count != uintptr(len(buf)) {
if err != nil { err = ErrShortRead
return nil, err
} }
return buf[:count], nil return int(count), err
}
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
return _SetThreadContext(t.os.hThread, savedRegs.(*Regs).context)
} }

View File

@ -1,6 +1,6 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT // MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package proc package native
import ( import (
"syscall" "syscall"

658
vendor/github.com/derekparker/delve/pkg/proc/proc.go generated vendored Normal file
View File

@ -0,0 +1,658 @@
package proc
import (
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/token"
"path/filepath"
"strconv"
"strings"
)
// ErrNotExecutable is returned after attempting to execute a non-executable file
// to begin a debug session.
var ErrNotExecutable = errors.New("not an executable file")
// ErrNotRecorded is returned when an action is requested that is
// only possible on recorded (traced) programs.
var ErrNotRecorded = errors.New("not a recording")
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
const UnrecoveredPanic = "unrecovered-panic"
// ErrProcessExited indicates that the process has exited and contains both
// process id and exit status.
type ErrProcessExited struct {
Pid int
Status int
}
func (pe ErrProcessExited) Error() string {
return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status)
}
// ProcessDetachedError indicates that we detached from the target process.
type ProcessDetachedError struct {
}
func (pe ProcessDetachedError) Error() string {
return "detached from the process"
}
// FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normalized to lower case and '/' on Windows.
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
pc, fn, err := p.BinInfo().LineToPC(fileName, lineno)
if err != nil {
return 0, err
}
if fn.Entry == pc {
pc, _ = FirstPCAfterPrologue(p, fn, true)
}
return pc, nil
}
// ErrFunctionNotFound is returned when failing to find the
// function named 'FuncName' within the binary.
type ErrFunctionNotFound struct {
FuncName string
}
func (err *ErrFunctionNotFound) Error() string {
return fmt.Sprintf("Could not find function %s\n", err.FuncName)
}
// FindFunctionLocation finds address of a function's line
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
// If lineOffset is passed FindFunctionLocation will return the address of that line
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
// Note that setting breakpoints at that address will cause surprising behavior:
// https://github.com/derekparker/delve/issues/170
func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) {
bi := p.BinInfo()
origfn := bi.LookupFunc[funcName]
if origfn == nil {
return 0, &ErrFunctionNotFound{funcName}
}
if firstLine {
return FirstPCAfterPrologue(p, origfn, false)
} else if lineOffset > 0 {
filename, lineno := origfn.cu.lineInfo.PCToLine(origfn.Entry, origfn.Entry)
breakAddr, _, err := bi.LineToPC(filename, lineno+lineOffset)
return breakAddr, err
}
return origfn.Entry, nil
}
// Next continues execution until the next source line.
func Next(dbp Process) (err error) {
if _, err := dbp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasInternalBreakpoints() {
return fmt.Errorf("next while nexting")
}
if err = next(dbp, false, false); err != nil {
dbp.ClearInternalBreakpoints()
return
}
return Continue(dbp)
}
// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// or is otherwise stopped.
func Continue(dbp Process) error {
if _, err := dbp.Valid(); err != nil {
return err
}
for _, thread := range dbp.ThreadList() {
thread.Common().returnValues = nil
}
dbp.CheckAndClearManualStopRequest()
defer func() {
// Make sure we clear internal breakpoints if we simultaneously receive a
// manual stop request and hit a breakpoint.
if dbp.CheckAndClearManualStopRequest() {
dbp.ClearInternalBreakpoints()
}
}()
for {
if dbp.CheckAndClearManualStopRequest() {
dbp.ClearInternalBreakpoints()
return nil
}
trapthread, err := dbp.ContinueOnce()
if err != nil {
return err
}
threads := dbp.ThreadList()
if err := pickCurrentThread(dbp, trapthread, threads); err != nil {
return err
}
curthread := dbp.CurrentThread()
curbp := curthread.Breakpoint()
switch {
case curbp.Breakpoint == nil:
// runtime.Breakpoint, manual stop or debugCallV1-related stop
recorded, _ := dbp.Recorded()
if recorded {
return conditionErrors(threads)
}
loc, err := curthread.Location()
if err != nil || loc.Fn == nil {
return conditionErrors(threads)
}
switch {
case loc.Fn.Name == "runtime.breakpoint":
// Single-step current thread until we exit runtime.breakpoint and
// runtime.Breakpoint.
// On go < 1.8 it was sufficient to single-step twice on go1.8 a change
// to the compiler requires 4 steps.
if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
return err
}
return conditionErrors(threads)
case strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) || strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2):
fncall := &dbp.Common().fncallState
if !fncall.inProgress {
return conditionErrors(threads)
}
fncall.step(dbp)
// only stop execution if the function call finished
if fncall.finished {
fncall.inProgress = false
if fncall.err != nil {
return fncall.err
}
curthread.Common().returnValues = fncall.returnValues()
return conditionErrors(threads)
}
default:
return conditionErrors(threads)
}
case curbp.Active && curbp.Internal:
if curbp.Kind == StepBreakpoint {
// See description of proc.(*Process).next for the meaning of StepBreakpoints
if err := conditionErrors(threads); err != nil {
return err
}
regs, err := curthread.Registers(false)
if err != nil {
return err
}
pc := regs.PC()
text, err := disassemble(curthread, regs, dbp.Breakpoints(), dbp.BinInfo(), pc, pc+maxInstructionLength, true)
if err != nil {
return err
}
// here we either set a breakpoint into the destination of the CALL
// instruction or we determined that the called function is hidden,
// either way we need to resume execution
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err
}
} else {
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
if err := dbp.ClearInternalBreakpoints(); err != nil {
return err
}
return conditionErrors(threads)
}
case curbp.Active:
onNextGoroutine, err := onNextGoroutine(curthread, dbp.Breakpoints())
if err != nil {
return err
}
if onNextGoroutine {
err := dbp.ClearInternalBreakpoints()
if err != nil {
return err
}
}
if curbp.Name == UnrecoveredPanic {
dbp.ClearInternalBreakpoints()
}
return conditionErrors(threads)
default:
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
}
}
}
func conditionErrors(threads []Thread) error {
var condErr error
for _, th := range threads {
if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil {
if condErr == nil {
condErr = bp.CondError
} else {
return fmt.Errorf("multiple errors evaluating conditions")
}
}
}
return condErr
}
// pick a new dbp.currentThread, with the following priority:
// - a thread with onTriggeredInternalBreakpoint() == true
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
// - trapthread
func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
for _, th := range threads {
if bp := th.Breakpoint(); bp.Active && bp.Internal {
return dbp.SwitchThread(th.ThreadID())
}
}
if bp := trapthread.Breakpoint(); bp.Active {
return dbp.SwitchThread(trapthread.ThreadID())
}
for _, th := range threads {
if bp := th.Breakpoint(); bp.Active {
return dbp.SwitchThread(th.ThreadID())
}
}
return dbp.SwitchThread(trapthread.ThreadID())
}
// stepInstructionOut repeatedly calls StepInstruction until the current
// function is neither fnname1 or fnname2.
// This function is used to step out of runtime.Breakpoint as well as
// runtime.debugCallV1.
func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string) error {
for {
if err := curthread.StepInstruction(); err != nil {
return err
}
loc, err := curthread.Location()
if err != nil || loc.Fn == nil || (loc.Fn.Name != fnname1 && loc.Fn.Name != fnname2) {
if g := dbp.SelectedGoroutine(); g != nil {
g.CurrentLoc = *loc
}
return nil
}
}
}
// Step will continue until another source line is reached.
// Will step into functions.
func Step(dbp Process) (err error) {
if _, err := dbp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasInternalBreakpoints() {
return fmt.Errorf("next while nexting")
}
if err = next(dbp, true, false); err != nil {
switch err.(type) {
case ErrThreadBlocked: // Noop
default:
dbp.ClearInternalBreakpoints()
return
}
}
return Continue(dbp)
}
// SameGoroutineCondition returns an expression that evaluates to true when
// the current goroutine is g.
func SameGoroutineCondition(g *G) ast.Expr {
if g == nil {
return nil
}
return &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{
X: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "curg"},
},
Sel: &ast.Ident{Name: "goid"},
},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(g.ID)},
}
}
func frameoffCondition(frameoff int64) ast.Expr {
return &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "frameoff"},
},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.FormatInt(frameoff, 10)},
}
}
func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
if cond == nil {
return nil
}
return &ast.BinaryExpr{
Op: token.LAND,
X: cond,
Y: frameoffCondition(frameoff),
}
}
// StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed
func StepOut(dbp Process) error {
if _, err := dbp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasInternalBreakpoints() {
return fmt.Errorf("next while nexting")
}
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread)
if err != nil {
return err
}
success := false
defer func() {
if !success {
dbp.ClearInternalBreakpoints()
}
}()
if topframe.Inlined {
if err := next(dbp, false, true); err != nil {
return err
}
success = true
return Continue(dbp)
}
sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
var deferpc uint64
if filepath.Ext(topframe.Current.File) == ".go" {
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil {
return err
}
}
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
if bp != nil {
// For StepOut we do not want to step into the deferred function
// when it's called by runtime.deferreturn so we do not populate
// DeferReturns.
bp.DeferReturns = []uint64{}
}
}
if topframe.Ret == 0 && deferpc == 0 {
return errors.New("nothing to stepout to")
}
if topframe.Ret != 0 {
bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
if err != nil {
if _, isexists := err.(BreakpointExistsError); !isexists {
return err
}
}
if bp != nil {
configureReturnBreakpoint(dbp.BinInfo(), bp, &topframe, retFrameCond)
}
}
if bp := curthread.Breakpoint(); bp.Breakpoint == nil {
curthread.SetCurrentBreakpoint()
}
success = true
return Continue(dbp)
}
// GoroutinesInfo returns an array of G structures representing the information
// Delve cares about from the internal runtime G structure.
func GoroutinesInfo(dbp Process) ([]*G, error) {
if _, err := dbp.Valid(); err != nil {
return nil, err
}
if dbp.Common().allGCache != nil {
return dbp.Common().allGCache, nil
}
var (
threadg = map[int]*G{}
allg []*G
rdr = dbp.BinInfo().DwarfReader()
)
threads := dbp.ThreadList()
for _, th := range threads {
if th.Blocked() {
continue
}
g, _ := GetG(th)
if g != nil {
threadg[g.ID] = g
}
}
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
if err != nil {
return nil, err
}
allglenBytes := make([]byte, 8)
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
if err != nil {
return nil, err
}
allglen := binary.LittleEndian.Uint64(allglenBytes)
rdr.Seek(0)
allgentryaddr, err := rdr.AddrFor("runtime.allgs", dbp.BinInfo().staticBase)
if err != nil {
// try old name (pre Go 1.6)
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
if err != nil {
return nil, err
}
}
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
if err != nil {
return nil, err
}
allgptr := binary.LittleEndian.Uint64(faddr)
for i := uint64(0); i < allglen; i++ {
gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
if err != nil {
allg = append(allg, &G{Unreadable: err})
continue
}
g, err := gvar.parseG()
if err != nil {
allg = append(allg, &G{Unreadable: err})
continue
}
if thg, allocated := threadg[g.ID]; allocated {
loc, err := thg.Thread.Location()
if err != nil {
return nil, err
}
g.Thread = thg.Thread
// Prefer actual thread location information.
g.CurrentLoc = *loc
g.SystemStack = thg.SystemStack
}
if g.Status != Gdead {
allg = append(allg, g)
}
}
dbp.Common().allGCache = allg
return allg, nil
}
// FindGoroutine returns a G struct representing the goroutine
// specified by `gid`.
func FindGoroutine(dbp Process, gid int) (*G, error) {
if gid == -1 {
return dbp.SelectedGoroutine(), nil
}
gs, err := GoroutinesInfo(dbp)
if err != nil {
return nil, err
}
for i := range gs {
if gs[i].ID == gid {
if gs[i].Unreadable != nil {
return nil, gs[i].Unreadable
}
return gs[i], nil
}
}
return nil, fmt.Errorf("Unknown goroutine %d", gid)
}
// ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame.
func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
if _, err := dbp.Valid(); err != nil {
return nil, err
}
ct := dbp.CurrentThread()
g, err := FindGoroutine(dbp, gid)
if err != nil {
return nil, err
}
if g == nil {
return ThreadScope(ct)
}
var thread MemoryReadWriter
if g.Thread == nil {
thread = ct
} else {
thread = g.Thread
}
locs, err := g.Stacktrace(frame+1, false)
if err != nil {
return nil, err
}
if frame >= len(locs) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
}
return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
}
// FrameToScope returns a new EvalScope for frames[0].
// If frames has at least two elements all memory between
// frames[0].Regs.SP() and frames[1].Regs.CFA will be cached.
// Otherwise all memory between frames[0].Regs.SP() and frames[0].Regs.CFA
// will be cached.
func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope {
var gvar *Variable
if g != nil {
gvar = g.variable
}
// Creates a cacheMem that will preload the entire stack frame the first
// time any local variable is read.
// Remember that the stack grows downward in memory.
minaddr := frames[0].Regs.SP()
var maxaddr uint64
if len(frames) > 1 && frames[0].SystemStack == frames[1].SystemStack {
maxaddr = uint64(frames[1].Regs.CFA)
} else {
maxaddr = uint64(frames[0].Regs.CFA)
}
if maxaddr > minaddr && maxaddr-minaddr < maxFramePrefetchSize {
thread = cacheMemory(thread, uintptr(minaddr), int(maxaddr-minaddr))
}
s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, Gvar: gvar, BinInfo: bi, frameOffset: frames[0].FrameOffset()}
s.PC = frames[0].lastpc
return s
}
// CreateUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
// This function is meant to be called by implementations of the Process interface.
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) {
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
}
if err == nil {
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint)
if err == nil {
bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
}
}
}
// FirstPCAfterPrologue returns the address of the first
// instruction after the prologue for function fn.
// If sameline is set FirstPCAfterPrologue will always return an
// address associated with the same line as fn.Entry.
func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
pc, _, line, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End)
if ok {
if !sameline {
return pc, nil
}
_, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
if entryLine == line {
return pc, nil
}
}
pc, err := firstPCAfterPrologueDisassembly(p, fn, sameline)
if err != nil {
return fn.Entry, err
}
if pc == fn.Entry {
// Look for the first instruction with the stmt flag set, so that setting a
// breakpoint with file:line and with the function name always result on
// the same instruction being selected.
entryFile, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
if pc, _, err := p.BinInfo().LineToPC(entryFile, entryLine); err == nil && pc >= fn.Entry && pc < fn.End {
return pc, nil
}
}
return pc, nil
}

View File

@ -0,0 +1,348 @@
package proc
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"strings"
)
// Registers is an interface for a generic register type. The
// interface encapsulates the generic values / actions
// we need independent of arch. The concrete register types
// will be different depending on OS/Arch.
type Registers interface {
PC() uint64
SP() uint64
BP() uint64
CX() uint64
TLS() uint64
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
GAddr() (uint64, bool)
Get(int) (uint64, error)
Slice() []Register
// Copy returns a copy of the registers that is guaranteed not to change
// when the registers of the associated thread change.
Copy() Registers
}
// Register represents a CPU register.
type Register struct {
Name string
Bytes []byte
Value string
}
// AppendWordReg appends a word (16 bit) register to regs.
func AppendWordReg(regs []Register, name string, value uint16) []Register {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, value)
return append(regs, Register{name, buf.Bytes(), fmt.Sprintf("%#04x", value)})
}
// AppendDwordReg appends a double word (32 bit) register to regs.
func AppendDwordReg(regs []Register, name string, value uint32) []Register {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, value)
return append(regs, Register{name, buf.Bytes(), fmt.Sprintf("%#08x", value)})
}
// AppendQwordReg appends a quad word (64 bit) register to regs.
func AppendQwordReg(regs []Register, name string, value uint64) []Register {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, value)
return append(regs, Register{name, buf.Bytes(), fmt.Sprintf("%#016x", value)})
}
func appendFlagReg(regs []Register, name string, value uint64, descr flagRegisterDescr, size int) []Register {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, value)
return append(regs, Register{name, buf.Bytes()[:size], descr.Describe(value, size)})
}
// AppendEflagReg appends EFLAG register to regs.
func AppendEflagReg(regs []Register, name string, value uint64) []Register {
return appendFlagReg(regs, name, value, eflagsDescription, 64)
}
// AppendMxcsrReg appends MXCSR register to regs.
func AppendMxcsrReg(regs []Register, name string, value uint64) []Register {
return appendFlagReg(regs, name, value, mxcsrDescription, 32)
}
// AppendX87Reg appends a 80 bit float register to regs.
func AppendX87Reg(regs []Register, index int, exponent uint16, mantissa uint64) []Register {
var f float64
fset := false
const (
_SIGNBIT = 1 << 15
_EXP_BIAS = (1 << 14) - 1 // 2^(n-1) - 1 = 16383
_SPECIALEXP = (1 << 15) - 1 // all bits set
_HIGHBIT = 1 << 63
_QUIETBIT = 1 << 62
)
sign := 1.0
if exponent&_SIGNBIT != 0 {
sign = -1.0
}
exponent &= ^uint16(_SIGNBIT)
NaN := math.NaN()
Inf := math.Inf(+1)
switch exponent {
case 0:
switch {
case mantissa == 0:
f = sign * 0.0
fset = true
case mantissa&_HIGHBIT != 0:
f = NaN
fset = true
}
case _SPECIALEXP:
switch {
case mantissa&_HIGHBIT == 0:
f = sign * Inf
fset = true
default:
f = NaN // signaling NaN
fset = true
}
default:
if mantissa&_HIGHBIT == 0 {
f = NaN
fset = true
}
}
if !fset {
significand := float64(mantissa) / (1 << 63)
f = sign * math.Ldexp(significand, int(exponent-_EXP_BIAS))
}
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, exponent)
binary.Write(&buf, binary.LittleEndian, mantissa)
return append(regs, Register{fmt.Sprintf("ST(%d)", index), buf.Bytes(), fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)})
}
// AppendSSEReg appends a 256 bit SSE register to regs.
func AppendSSEReg(regs []Register, name string, xmm []byte) []Register {
buf := bytes.NewReader(xmm)
var out bytes.Buffer
var vi [16]uint8
for i := range vi {
binary.Read(buf, binary.LittleEndian, &vi[i])
}
fmt.Fprintf(&out, "0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8], vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0])
fmt.Fprintf(&out, "\tv2_int={ %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x }", vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0], vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8])
fmt.Fprintf(&out, "\tv4_int={ %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x }", vi[3], vi[2], vi[1], vi[0], vi[7], vi[6], vi[5], vi[4], vi[11], vi[10], vi[9], vi[8], vi[15], vi[14], vi[13], vi[12])
fmt.Fprintf(&out, "\tv8_int={ %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x }", vi[1], vi[0], vi[3], vi[2], vi[5], vi[4], vi[7], vi[6], vi[9], vi[8], vi[11], vi[10], vi[13], vi[12], vi[15], vi[14])
fmt.Fprintf(&out, "\tv16_int={ %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x }", vi[0], vi[1], vi[2], vi[3], vi[4], vi[5], vi[6], vi[7], vi[8], vi[9], vi[10], vi[11], vi[12], vi[13], vi[14], vi[15])
buf.Seek(0, os.SEEK_SET)
var v2 [2]float64
for i := range v2 {
binary.Read(buf, binary.LittleEndian, &v2[i])
}
fmt.Fprintf(&out, "\tv2_float={ %g %g }", v2[0], v2[1])
buf.Seek(0, os.SEEK_SET)
var v4 [4]float32
for i := range v4 {
binary.Read(buf, binary.LittleEndian, &v4[i])
}
fmt.Fprintf(&out, "\tv4_float={ %g %g %g %g }", v4[0], v4[1], v4[2], v4[3])
return append(regs, Register{name, xmm, out.String()})
}
// ErrUnknownRegister is returned when the value of an unknown
// register is requested.
var ErrUnknownRegister = errors.New("unknown register")
type flagRegisterDescr []flagDescr
type flagDescr struct {
name string
mask uint64
}
var mxcsrDescription flagRegisterDescr = []flagDescr{
{"FZ", 1 << 15},
{"RZ/RN", 1<<14 | 1<<13},
{"PM", 1 << 12},
{"UM", 1 << 11},
{"OM", 1 << 10},
{"ZM", 1 << 9},
{"DM", 1 << 8},
{"IM", 1 << 7},
{"DAZ", 1 << 6},
{"PE", 1 << 5},
{"UE", 1 << 4},
{"OE", 1 << 3},
{"ZE", 1 << 2},
{"DE", 1 << 1},
{"IE", 1 << 0},
}
var eflagsDescription flagRegisterDescr = []flagDescr{
{"CF", 1 << 0},
{"", 1 << 1},
{"PF", 1 << 2},
{"AF", 1 << 4},
{"ZF", 1 << 6},
{"SF", 1 << 7},
{"TF", 1 << 8},
{"IF", 1 << 9},
{"DF", 1 << 10},
{"OF", 1 << 11},
{"IOPL", 1<<12 | 1<<13},
{"NT", 1 << 14},
{"RF", 1 << 16},
{"VM", 1 << 17},
{"AC", 1 << 18},
{"VIF", 1 << 19},
{"VIP", 1 << 20},
{"ID", 1 << 21},
}
func (descr flagRegisterDescr) Mask() uint64 {
var r uint64
for _, f := range descr {
r = r | f.mask
}
return r
}
func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
var r []string
for _, f := range descr {
if f.name == "" {
continue
}
// rbm is f.mask with only the right-most bit set:
// 0001 1100 -> 0000 0100
rbm := f.mask & -f.mask
if rbm == f.mask {
if reg&f.mask != 0 {
r = append(r, f.name)
}
} else {
x := (reg & f.mask) >> uint64(math.Log2(float64(rbm)))
r = append(r, fmt.Sprintf("%s=%x", f.name, x))
}
}
if reg & ^descr.Mask() != 0 {
r = append(r, fmt.Sprintf("unknown_flags=%x", reg&^descr.Mask()))
}
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
}
// PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
Padding [24]uint32
}
// LinuxX86Xstate represents amd64 XSAVE area. See Section 13.1 (and
// following) of Intel® 64 and IA-32 Architectures Software Developers
// Manual, Volume 1: Basic Architecture.
type LinuxX86Xstate struct {
PtraceFpRegs
Xsave []byte // raw xsave area
AvxState bool // contains AVX state
YmmSpace [256]byte
}
// Decode decodes an XSAVE area to a list of name/value pairs of registers.
func (xsave *LinuxX86Xstate) Decode() (regs []Register) {
// x87 registers
regs = AppendWordReg(regs, "CW", xsave.Cwd)
regs = AppendWordReg(regs, "SW", xsave.Swd)
regs = AppendWordReg(regs, "TW", xsave.Ftw)
regs = AppendWordReg(regs, "FOP", xsave.Fop)
regs = AppendQwordReg(regs, "FIP", xsave.Rip)
regs = AppendQwordReg(regs, "FDP", xsave.Rdp)
for i := 0; i < len(xsave.StSpace); i += 4 {
regs = AppendX87Reg(regs, i/4, uint16(xsave.StSpace[i+2]), uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i]))
}
// SSE registers
regs = AppendMxcsrReg(regs, "MXCSR", uint64(xsave.Mxcsr))
regs = AppendDwordReg(regs, "MXCSR_MASK", xsave.MxcrMask)
for i := 0; i < len(xsave.XmmSpace); i += 16 {
regs = AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16])
if xsave.AvxState {
regs = AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16])
}
}
return
}
const (
_XSAVE_HEADER_START = 512
_XSAVE_HEADER_LEN = 64
_XSAVE_EXTENDED_REGION_START = 576
_XSAVE_SSE_REGION_LEN = 416
)
// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset.
// If readLegacy is true regset.PtraceFpRegs will be filled with the
// contents of the legacy region of the XSAVE area.
// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures
// Software Developers Manual, Volume 1: Basic Architecture.
func LinuxX86XstateRead(xstateargs []byte, readLegacy bool, regset *LinuxX86Xstate) error {
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) {
return nil
}
if readLegacy {
rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START])
if err := binary.Read(rdr, binary.LittleEndian, &regset.PtraceFpRegs); err != nil {
return err
}
}
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return nil
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return nil
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return nil
}

View File

@ -0,0 +1,90 @@
package proc
import (
"bytes"
"encoding/binary"
"golang.org/x/arch/x86/x86asm"
)
var dwarfToAsm = map[int]x86asm.Reg{
0: x86asm.RAX,
1: x86asm.RDX,
2: x86asm.RCX,
3: x86asm.RBX,
4: x86asm.RSI,
5: x86asm.RDI,
6: x86asm.RBP,
7: x86asm.RSP,
8: x86asm.R8,
9: x86asm.R9,
10: x86asm.R10,
11: x86asm.R11,
12: x86asm.R12,
13: x86asm.R13,
14: x86asm.R14,
15: x86asm.R15,
16: x86asm.RIP,
}
var dwarfToName = map[int]string{
17: "XMM0",
18: "XMM1",
19: "XMM2",
20: "XMM3",
21: "XMM4",
22: "XMM5",
23: "XMM6",
24: "XMM7",
25: "XMM8",
26: "XMM9",
27: "XMM10",
28: "XMM11",
29: "XMM12",
30: "XMM13",
31: "XMM14",
32: "XMM15",
33: "ST(0)",
34: "ST(1)",
35: "ST(2)",
36: "ST(3)",
37: "ST(4)",
38: "ST(5)",
39: "ST(6)",
40: "ST(7)",
49: "Eflags",
50: "Es",
51: "Cs",
52: "Ss",
53: "Ds",
54: "Fs",
55: "Gs",
58: "Fs_base",
59: "Gs_base",
64: "MXCSR",
65: "CW",
66: "SW",
}
// GetDwarfRegister maps between DWARF register numbers and architecture
// registers.
// The mapping is specified in the System V ABI AMD64 Architecture Processor
// Supplement page 57, figure 3.36
// https://www.uclibc.org/docs/psABI-x86_64.pdf
func GetDwarfRegister(regs Registers, i int) []byte {
if asmreg, ok := dwarfToAsm[i]; ok {
x, _ := regs.Get(int(asmreg))
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, x)
return buf.Bytes()
}
if regname, ok := dwarfToName[i]; ok {
regslice := regs.Slice()
for _, reg := range regslice {
if reg.Name == regname {
return reg.Bytes
}
}
}
return []byte{}
}

687
vendor/github.com/derekparker/delve/pkg/proc/stack.go generated vendored Normal file
View File

@ -0,0 +1,687 @@
package proc
import (
"debug/dwarf"
"errors"
"fmt"
"go/constant"
"strings"
"github.com/derekparker/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader"
)
// This code is partly adapted from runtime.gentraceback in
// $GOROOT/src/runtime/traceback.go
// Stackframe represents a frame in a system stack.
//
// Each stack frame has two locations Current and Call.
//
// For the topmost stackframe Current and Call are the same location.
//
// For stackframes after the first Current is the location corresponding to
// the return address and Call is the location of the CALL instruction that
// was last executed on the frame. Note however that Call.PC is always equal
// to Current.PC, because finding the correct value for Call.PC would
// require disassembling each function in the stacktrace.
//
// For synthetic stackframes generated for inlined function calls Current.Fn
// is the function containing the inlining and Call.Fn in the inlined
// function.
type Stackframe struct {
Current, Call Location
// Frame registers.
Regs op.DwarfRegisters
// High address of the stack.
stackHi uint64
// Return address for this stack frame (as read from the stack frame itself).
Ret uint64
// Address to the memory location containing the return address
addrret uint64
// Err is set if an error occurred during stacktrace
Err error
// SystemStack is true if this frame belongs to a system stack.
SystemStack bool
// Inlined is true if this frame is actually an inlined call.
Inlined bool
// Bottom is true if this is the bottom of the stack
Bottom bool
// lastpc is a memory address guaranteed to belong to the last instruction
// executed in this stack frame.
// For the topmost stack frame this will be the same as Current.PC and
// Call.PC, for other stack frames it will usually be Current.PC-1, but
// could be different when inlined calls are involved in the stacktrace.
// Note that this address isn't guaranteed to belong to the start of an
// instruction and, for this reason, should not be propagated outside of
// pkg/proc.
// Use this value to determine active lexical scopes for the stackframe.
lastpc uint64
// TopmostDefer is the defer that would be at the top of the stack when a
// panic unwind would get to this call frame, in other words it's the first
// deferred function that will be called if the runtime unwinds past this
// call frame.
TopmostDefer *Defer
// Defers is the list of functions deferred by this stack frame (so far).
Defers []*Defer
}
// FrameOffset returns the address of the stack frame, absolute for system
// stack frames or as an offset from stackhi for goroutine stacks (a
// negative value).
func (frame *Stackframe) FrameOffset() int64 {
if frame.SystemStack {
return frame.Regs.CFA
}
return frame.Regs.CFA - int64(frame.stackHi)
}
// FramePointerOffset returns the value of the frame pointer, absolute for
// system stack frames or as an offset from stackhi for goroutine stacks (a
// negative value).
func (frame *Stackframe) FramePointerOffset() int64 {
if frame.SystemStack {
return int64(frame.Regs.BP())
}
return int64(frame.Regs.BP()) - int64(frame.stackHi)
}
// ThreadStacktrace returns the stack trace for thread.
// Note the locations in the array are return addresses not call addresses.
func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
g, _ := GetG(thread)
if g == nil {
regs, err := thread.Registers(true)
if err != nil {
return nil, err
}
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs, thread.BinInfo().staticBase), 0, nil, -1, nil)
return it.stacktrace(depth)
}
return g.Stacktrace(depth, false)
}
func (g *G) stackIterator() (*stackIterator, error) {
stkbar, err := g.stkbar()
if err != nil {
return nil, err
}
if g.Thread != nil {
regs, err := g.Thread.Registers(true)
if err != nil {
return nil, err
}
return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs, g.variable.bi.staticBase), g.stackhi, stkbar, g.stkbarPos, g), nil
}
return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos, g), nil
}
// Stacktrace returns the stack trace for a goroutine.
// Note the locations in the array are return addresses not call addresses.
func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) {
it, err := g.stackIterator()
if err != nil {
return nil, err
}
frames, err := it.stacktrace(depth)
if err != nil {
return nil, err
}
if readDefers {
g.readDefers(frames)
}
return frames, nil
}
// NullAddrError is an error for a null address.
type NullAddrError struct{}
func (n NullAddrError) Error() string {
return "NULL address"
}
// stackIterator holds information
// required to iterate and walk the program
// stack.
type stackIterator struct {
pc uint64
top bool
atend bool
frame Stackframe
bi *BinaryInfo
mem MemoryReadWriter
err error
stackhi uint64
systemstack bool
stackBarrierPC uint64
stkbar []savedLR
// regs is the register set for the current frame
regs op.DwarfRegisters
g *G // the goroutine being stacktraced, nil if we are stacktracing a goroutine-less thread
g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use)
dwarfReader *dwarf.Reader
}
type savedLR struct {
ptr uint64
val uint64
}
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int, g *G) *stackIterator {
stackBarrierFunc := bi.LookupFunc["runtime.stackBarrier"] // stack barriers were removed in Go 1.9
var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil {
stackBarrierPC = stackBarrierFunc.Entry
fn := bi.PCToFunc(regs.PC())
if fn != nil && fn.Name == "runtime.stackBarrier" {
// We caught the goroutine as it's executing the stack barrier, we must
// determine whether or not g.stackPos has already been incremented or not.
if len(stkbar) > 0 && stkbar[stkbarPos].ptr < regs.SP() {
// runtime.stackBarrier has not incremented stkbarPos.
} else if stkbarPos > 0 && stkbar[stkbarPos-1].ptr < regs.SP() {
// runtime.stackBarrier has incremented stkbarPos.
stkbarPos--
} else {
return &stackIterator{err: fmt.Errorf("failed to unwind through stackBarrier at SP %x", regs.SP())}
}
}
stkbar = stkbar[stkbarPos:]
}
var g0_sched_sp uint64
systemstack := true
if g != nil {
systemstack = g.SystemStack
g0var, _ := g.variable.fieldVariable("m").structMember("g0")
if g0var != nil {
g0, _ := g0var.parseG()
if g0 != nil {
g0_sched_sp = g0.SP
}
}
}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp, dwarfReader: bi.dwarf.Reader()}
}
// Next points the iterator to the next stack frame.
func (it *stackIterator) Next() bool {
if it.err != nil || it.atend {
return false
}
callFrameRegs, ret, retaddr := it.advanceRegs()
it.frame = it.newStackframe(ret, retaddr)
if it.stkbar != nil && it.frame.Ret == it.stackBarrierPC && it.frame.addrret == it.stkbar[0].ptr {
// Skip stack barrier frames
it.frame.Ret = it.stkbar[0].val
it.stkbar = it.stkbar[1:]
}
if it.switchStack() {
return true
}
if it.frame.Ret <= 0 {
it.atend = true
return true
}
it.top = false
it.pc = it.frame.Ret
it.regs = callFrameRegs
return true
}
// asmcgocallSPOffsetSaveSlot is the offset from systemstack.SP where
// (goroutine.SP - StackHi) is saved in runtime.asmcgocall after the stack
// switch happens.
const asmcgocallSPOffsetSaveSlot = 0x28
// switchStack will use the current frame to determine if it's time to
// switch between the system stack and the goroutine stack or vice versa.
// Sets it.atend when the top of the stack is reached.
func (it *stackIterator) switchStack() bool {
if it.frame.Current.Fn == nil {
return false
}
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall":
if it.top || !it.systemstack {
return false
}
// This function is called by a goroutine to execute a C function and
// switches from the goroutine stack to the system stack.
// Since we are unwinding the stack from callee to caller we have switch
// from the system stack to the goroutine stack.
off, _ := readIntRaw(it.mem, uintptr(it.regs.SP()+asmcgocallSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize())) // reads "offset of SP from StackHi" from where runtime.asmcgocall saved it
oldsp := it.regs.SP()
it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(int64(it.stackhi) - off)
// runtime.asmcgocall can also be called from inside the system stack,
// in that case no stack switch actually happens
if it.regs.SP() == oldsp {
return false
}
it.systemstack = false
// advances to the next frame in the call stack
it.frame.addrret = uint64(int64(it.regs.SP()) + int64(it.bi.Arch.PtrSize()))
it.frame.Ret, _ = readUintRaw(it.mem, uintptr(it.frame.addrret), int64(it.bi.Arch.PtrSize()))
it.pc = it.frame.Ret
it.top = false
return true
case "runtime.cgocallback_gofunc":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_amd64.s
//
// When a C functions calls back into go it will eventually call into
// runtime.cgocallback_gofunc which is the function that does the stack
// switch from the system stack back into the goroutine stack
// Since we are going backwards on the stack here we see the transition
// as goroutine stack -> system stack.
if it.top || it.systemstack {
return false
}
if it.g0_sched_sp <= 0 {
return false
}
// entering the system stack
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g0_sched_sp
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
it.g0_sched_sp, _ = readUintRaw(it.mem, uintptr(it.regs.SP()), int64(it.bi.Arch.PtrSize()))
it.top = false
callFrameRegs, ret, retaddr := it.advanceRegs()
frameOnSystemStack := it.newStackframe(ret, retaddr)
it.pc = frameOnSystemStack.Ret
it.regs = callFrameRegs
it.systemstack = true
return true
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
// Look for "top of stack" functions.
it.atend = true
return true
default:
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") {
// The runtime switches to the system stack in multiple places.
// This usually happens through a call to runtime.systemstack but there
// are functions that switch to the system stack manually (for example
// runtime.morestack).
// Since we are only interested in printing the system stack for cgo
// calls we switch directly to the goroutine stack if we detect that the
// function at the top of the stack is a runtime function.
it.systemstack = false
it.top = false
it.pc = it.g.PC
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP
it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP
return true
}
return false
}
}
// Frame returns the frame the iterator is pointing at.
func (it *stackIterator) Frame() Stackframe {
it.frame.Bottom = it.atend
return it.frame
}
// Err returns the error encountered during stack iteration.
func (it *stackIterator) Err() error {
return it.err
}
// frameBase calculates the frame base pseudo-register for DWARF for fn and
// the current frame.
func (it *stackIterator) frameBase(fn *Function) int64 {
it.dwarfReader.Seek(fn.offset)
e, err := it.dwarfReader.Next()
if err != nil {
return 0
}
fb, _, _, _ := it.bi.Location(e, dwarf.AttrFrameBase, it.pc, it.regs)
return fb
}
func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe {
if retaddr == 0 {
it.err = NullAddrError{}
return Stackframe{}
}
f, l, fn := it.bi.PCToLine(it.pc)
if fn == nil {
f = "?"
l = -1
} else {
it.regs.FrameBase = it.frameBase(fn)
}
r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack, lastpc: it.pc}
if !it.top {
fnname := ""
if r.Current.Fn != nil {
fnname = r.Current.Fn.Name
}
switch fnname {
case "runtime.mstart", "runtime.systemstack_switch":
// these frames are inserted by runtime.systemstack and there is no CALL
// instruction to look for at pc - 1
r.Call = r.Current
default:
if r.Current.Fn != nil && it.pc == r.Current.Fn.Entry {
// if the return address is the entry point of the function that
// contains it then this is some kind of fake return frame (for example
// runtime.sigreturn) that didn't actually call the current frame,
// attempting to get the location of the CALL instruction would just
// obfuscate what's going on, since there is no CALL instruction.
r.Call = r.Current
} else {
r.lastpc = it.pc - 1
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1)
if r.Call.Fn == nil {
r.Call.File = "?"
r.Call.Line = -1
}
r.Call.PC = r.Current.PC
}
}
} else {
r.Call = r.Current
}
return r
}
func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) {
if depth < 0 {
return nil, errors.New("negative maximum stack depth")
}
frames := make([]Stackframe, 0, depth+1)
for it.Next() {
frames = it.appendInlineCalls(frames, it.Frame())
if len(frames) >= depth+1 {
break
}
}
if err := it.Err(); err != nil {
if len(frames) == 0 {
return nil, err
}
frames = append(frames, Stackframe{Err: err})
}
return frames, nil
}
func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe) []Stackframe {
if frame.Call.Fn == nil {
return append(frames, frame)
}
if frame.Call.Fn.cu.lineInfo == nil {
return append(frames, frame)
}
callpc := frame.Call.PC
if len(frames) > 0 {
callpc--
}
irdr := reader.InlineStack(it.bi.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, it.bi.staticBase))
for irdr.Next() {
entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), it.dwarfReader)
fnname, okname := entry.Val(dwarf.AttrName).(string)
fileidx, okfileidx := entry.Val(dwarf.AttrCallFile).(int64)
line, okline := entry.Val(dwarf.AttrCallLine).(int64)
if !okname || !okfileidx || !okline {
break
}
if fileidx-1 < 0 || fileidx-1 >= int64(len(frame.Current.Fn.cu.lineInfo.FileNames)) {
break
}
inlfn := &Function{Name: fnname, Entry: frame.Call.Fn.Entry, End: frame.Call.Fn.End, offset: offset, cu: frame.Call.Fn.cu}
frames = append(frames, Stackframe{
Current: frame.Current,
Call: Location{
frame.Call.PC,
frame.Call.File,
frame.Call.Line,
inlfn,
},
Regs: frame.Regs,
stackHi: frame.stackHi,
Ret: frame.Ret,
addrret: frame.addrret,
Err: frame.Err,
SystemStack: frame.SystemStack,
Inlined: true,
lastpc: frame.lastpc,
})
frame.Call.File = frame.Current.Fn.cu.lineInfo.FileNames[fileidx-1].Path
frame.Call.Line = int(line)
}
return append(frames, frame)
}
// advanceRegs calculates it.callFrameRegs using it.regs and the frame
// descriptor entry for the current stack frame.
// it.regs.CallFrameCFA is updated.
func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uint64, retaddr uint64) {
fde, err := it.bi.frameEntries.FDEForPC(it.pc)
var framectx *frame.FrameContext
if _, nofde := err.(*frame.ErrNoFDEForPC); nofde {
framectx = it.bi.Arch.FixFrameUnwindContext(nil, it.pc, it.bi)
} else {
framectx = it.bi.Arch.FixFrameUnwindContext(fde.EstablishFrame(it.pc), it.pc, it.bi)
}
cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0)
if cfareg == nil {
it.err = fmt.Errorf("CFA becomes undefined at PC %#x", it.pc)
return op.DwarfRegisters{StaticBase: it.bi.staticBase}, 0, 0
}
it.regs.CFA = int64(cfareg.Uint64Val)
callFrameRegs = op.DwarfRegisters{StaticBase: it.bi.staticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum}
// According to the standard the compiler should be responsible for emitting
// rules for the RSP register so that it can then be used to calculate CFA,
// however neither Go nor GCC do this.
// In the following line we copy GDB's behaviour by assuming this is
// implicit.
// See also the comment in dwarf2_frame_default_init in
// $GDB_SOURCE/dwarf2-frame.c
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
for i, regRule := range framectx.Regs {
reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
callFrameRegs.AddReg(i, reg)
if i == framectx.RetAddrReg {
if reg == nil {
if err == nil {
err = fmt.Errorf("Undefined return address at %#x", it.pc)
}
it.err = err
} else {
ret = reg.Uint64Val
}
retaddr = uint64(it.regs.CFA + regRule.Offset)
}
}
return callFrameRegs, ret, retaddr
}
func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, cfa int64) (*op.DwarfRegister, error) {
switch rule.Rule {
default:
fallthrough
case frame.RuleUndefined:
return nil, nil
case frame.RuleSameVal:
reg := *it.regs.Reg(regnum)
return &reg, nil
case frame.RuleOffset:
return it.readRegisterAt(regnum, uint64(cfa+rule.Offset))
case frame.RuleValOffset:
return op.DwarfRegisterFromUint64(uint64(cfa + rule.Offset)), nil
case frame.RuleRegister:
return it.regs.Reg(rule.Reg), nil
case frame.RuleExpression:
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
if err != nil {
return nil, err
}
return it.readRegisterAt(regnum, uint64(v))
case frame.RuleValExpression:
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
if err != nil {
return nil, err
}
return op.DwarfRegisterFromUint64(uint64(v)), nil
case frame.RuleArchitectural:
return nil, errors.New("architectural frame rules are unsupported")
case frame.RuleCFA:
if it.regs.Reg(rule.Reg) == nil {
return nil, nil
}
return op.DwarfRegisterFromUint64(uint64(int64(it.regs.Uint64Val(rule.Reg)) + rule.Offset)), nil
case frame.RuleFramePointer:
curReg := it.regs.Reg(rule.Reg)
if curReg == nil {
return nil, nil
}
if curReg.Uint64Val <= uint64(cfa) {
return it.readRegisterAt(regnum, curReg.Uint64Val)
}
newReg := *curReg
return &newReg, nil
}
}
func (it *stackIterator) readRegisterAt(regnum uint64, addr uint64) (*op.DwarfRegister, error) {
buf := make([]byte, it.bi.Arch.RegSize(regnum))
_, err := it.mem.ReadMemory(buf, uintptr(addr))
if err != nil {
return nil, err
}
return op.DwarfRegisterFromBytes(buf), nil
}
// Defer represents one deferred call
type Defer struct {
DeferredPC uint64 // Value of field _defer.fn.fn, the deferred function
DeferPC uint64 // PC address of instruction that added this defer
SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space)
link *Defer // Next deferred function
variable *Variable
Unreadable error
}
// readDefers decorates the frames with the function deferred at each stack frame.
func (g *G) readDefers(frames []Stackframe) {
curdefer := g.Defer()
i := 0
// scan simultaneously frames and the curdefer linked list, assigning
// defers to their associated frames.
for {
if curdefer == nil || i >= len(frames) {
return
}
if curdefer.Unreadable != nil {
// Current defer is unreadable, stick it into the first available frame
// (so that it can be reported to the user) and exit
frames[i].Defers = append(frames[i].Defers, curdefer)
return
}
if frames[i].Err != nil {
return
}
if frames[i].TopmostDefer == nil {
frames[i].TopmostDefer = curdefer
}
if frames[i].SystemStack || curdefer.SP >= uint64(frames[i].Regs.CFA) {
// frames[i].Regs.CFA is the value that SP had before the function of
// frames[i] was called.
// This means that when curdefer.SP == frames[i].Regs.CFA then curdefer
// was added by the previous frame.
//
// curdefer.SP < frames[i].Regs.CFA means curdefer was added by a
// function further down the stack.
//
// SystemStack frames live on a different physical stack and can't be
// compared with deferred frames.
i++
} else {
frames[i].Defers = append(frames[i].Defers, curdefer)
curdefer = curdefer.Next()
}
}
}
func (d *Defer) load() {
d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1})
if d.variable.Unreadable != nil {
d.Unreadable = d.variable.Unreadable
return
}
fnvar := d.variable.fieldVariable("fn").maybeDereference()
if fnvar.Addr != 0 {
fnvar = fnvar.loadFieldNamed("fn")
if fnvar.Unreadable == nil {
d.DeferredPC, _ = constant.Uint64Val(fnvar.Value)
}
}
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value)
d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value)
linkvar := d.variable.fieldVariable("link").maybeDereference()
if linkvar.Addr != 0 {
d.link = &Defer{variable: linkvar}
}
}
// errSPDecreased is used when (*Defer).Next detects a corrupted linked
// list, specifically when after followin a link pointer the value of SP
// decreases rather than increasing or staying the same (the defer list is a
// FIFO list, nodes further down the list have been added by function calls
// further down the call stack and therefore the SP should always increase).
var errSPDecreased = errors.New("corrupted defer list: SP decreased")
// Next returns the next defer in the linked list
func (d *Defer) Next() *Defer {
if d.link == nil {
return nil
}
d.link.load()
if d.link.SP < d.SP {
d.link.Unreadable = errSPDecreased
}
return d.link
}

568
vendor/github.com/derekparker/delve/pkg/proc/threads.go generated vendored Normal file
View File

@ -0,0 +1,568 @@
package proc
import (
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/token"
"path/filepath"
"reflect"
"strings"
"github.com/derekparker/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/reader"
)
// Thread represents a thread.
type Thread interface {
MemoryReadWriter
Location() (*Location, error)
// Breakpoint will return the breakpoint that this thread is stopped at or
// nil if the thread is not stopped at any breakpoint.
Breakpoint() BreakpointState
ThreadID() int
// Registers returns the CPU registers of this thread. The contents of the
// variable returned may or may not change to reflect the new CPU status
// when the thread is resumed or the registers are changed by calling
// SetPC/SetSP/etc.
// To insure that the the returned variable won't change call the Copy
// method of Registers.
Registers(floatingPoint bool) (Registers, error)
// RestoreRegisters restores saved registers
RestoreRegisters(Registers) error
Arch() Arch
BinInfo() *BinaryInfo
StepInstruction() error
// Blocked returns true if the thread is blocked
Blocked() bool
// SetCurrentBreakpoint updates the current breakpoint of this thread
SetCurrentBreakpoint() error
// Common returns the CommonThread structure for this thread
Common() *CommonThread
SetPC(uint64) error
SetSP(uint64) error
SetDX(uint64) error
}
// Location represents the location of a thread.
// Holds information on the current instruction
// address, the source file:line, and the function.
type Location struct {
PC uint64
File string
Line int
Fn *Function
}
// ErrThreadBlocked is returned when the thread
// is blocked in the scheduler.
type ErrThreadBlocked struct{}
func (tbe ErrThreadBlocked) Error() string {
return "thread blocked"
}
// CommonThread contains fields used by this package, common to all
// implementations of the Thread interface.
type CommonThread struct {
returnValues []*Variable
}
// ReturnValues reads the return values from the function executing on
// this thread using the provided LoadConfig.
func (t *CommonThread) ReturnValues(cfg LoadConfig) []*Variable {
loadValues(t.returnValues, cfg)
return t.returnValues
}
// topframe returns the two topmost frames of g, or thread if g is nil.
func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
var frames []Stackframe
var err error
if g == nil {
if thread.Blocked() {
return Stackframe{}, Stackframe{}, ErrThreadBlocked{}
}
frames, err = ThreadStacktrace(thread, 1)
} else {
frames, err = g.Stacktrace(1, true)
}
if err != nil {
return Stackframe{}, Stackframe{}, err
}
switch len(frames) {
case 0:
return Stackframe{}, Stackframe{}, errors.New("empty stack trace")
case 1:
return frames[0], Stackframe{}, nil
default:
return frames[0], frames[1], nil
}
}
// ErrNoSourceForPC is returned when the given address
// does not correspond with a source file location.
type ErrNoSourceForPC struct {
pc uint64
}
func (err *ErrNoSourceForPC) Error() string {
return fmt.Sprintf("no source for PC %#x", err.pc)
}
// Set breakpoints at every line, and the return address. Also look for
// a deferred function and set a breakpoint there too.
// If stepInto is true it will also set breakpoints inside all
// functions called on the current source line, for non-absolute CALLs
// a breakpoint of kind StepBreakpoint is set on the CALL instruction,
// Continue will take care of setting a breakpoint to the destination
// once the CALL is reached.
//
// Regardless of stepInto the following breakpoints will be set:
// - a breakpoint on the first deferred function with NextDeferBreakpoint
// kind, the list of all the addresses to deferreturn calls in this function
// and condition checking that we remain on the same goroutine
// - a breakpoint on each line of the function, with a condition checking
// that we stay on the same stack frame and goroutine.
// - a breakpoint on the return address of the function, with a condition
// checking that we move to the previous stack frame and stay on the same
// goroutine.
//
// The breakpoint on the return address is *not* set if the current frame is
// an inlined call. For inlined calls topframe.Current.Fn is the function
// where the inlining happened and the second set of breakpoints will also
// cover the "return address".
//
// If inlinedStepOut is true this function implements the StepOut operation
// for an inlined function call. Everything works the same as normal except
// when removing instructions belonging to inlined calls we also remove all
// instructions belonging to the current inlined call.
func next(dbp Process, stepInto, inlinedStepOut bool) error {
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread)
if err != nil {
return err
}
if topframe.Current.Fn == nil {
return &ErrNoSourceForPC{topframe.Current.PC}
}
// sanity check
if inlinedStepOut && !topframe.Inlined {
panic("next called with inlinedStepOut but topframe was not inlined")
}
success := false
defer func() {
if !success {
dbp.ClearInternalBreakpoints()
}
}()
ext := filepath.Ext(topframe.Current.File)
csource := ext != ".go" && ext != ".s"
var thread MemoryReadWriter = curthread
var regs Registers
if selg != nil && selg.Thread != nil {
thread = selg.Thread
regs, err = selg.Thread.Registers(false)
if err != nil {
return err
}
}
text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false)
if err != nil && stepInto {
return err
}
sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
sameFrameCond := andFrameoffCondition(sameGCond, topframe.FrameOffset())
var sameOrRetFrameCond ast.Expr
if sameGCond != nil {
if topframe.Inlined {
sameOrRetFrameCond = sameFrameCond
} else {
sameOrRetFrameCond = &ast.BinaryExpr{
Op: token.LAND,
X: sameGCond,
Y: &ast.BinaryExpr{
Op: token.LOR,
X: frameoffCondition(topframe.FrameOffset()),
Y: frameoffCondition(retframe.FrameOffset()),
},
}
}
}
if stepInto {
for _, instr := range text {
if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
continue
}
if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
return err
}
} else {
// Non-absolute call instruction, set a StepBreakpoint here
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
}
}
}
if !csource {
deferreturns := []uint64{}
// Find all runtime.deferreturn locations in the function
// See documentation of Breakpoint.DeferCond for why this is necessary
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == "runtime.deferreturn" {
deferreturns = append(deferreturns, instr.Loc.PC)
}
}
// Set breakpoint on the most recently deferred function (if any)
var deferpc uint64
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
var err error
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil {
return err
}
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
if bp != nil && stepInto {
bp.DeferReturns = deferreturns
}
}
}
// Add breakpoints on all the lines in the current function
pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1, topframe.Current.File, topframe.Current.Line)
if err != nil {
return err
}
if !stepInto {
// Removing any PC range belonging to an inlined call
frame := topframe
if inlinedStepOut {
frame = retframe
}
pcs, err = removeInlinedCalls(dbp, pcs, frame)
if err != nil {
return err
}
}
if !csource {
var covered bool
for i := range pcs {
if topframe.Current.Fn.Entry <= pcs[i] && pcs[i] < topframe.Current.Fn.End {
covered = true
break
}
}
if !covered {
fn := dbp.BinInfo().PCToFunc(topframe.Ret)
if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
return nil
}
}
}
for _, pc := range pcs {
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
}
if !topframe.Inlined {
// Add a breakpoint on the return address for the current frame.
// For inlined functions there is no need to do this, the set of PCs
// returned by the AllPCsBetween call above already cover all instructions
// of the containing function.
bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
if err != nil {
if _, isexists := err.(BreakpointExistsError); isexists {
if bp.Kind == NextBreakpoint {
// If the return address shares the same address with one of the lines
// of the function (because we are stepping through a recursive
// function) then the corresponding breakpoint should be active both on
// this frame and on the return frame.
bp.Cond = sameOrRetFrameCond
}
}
// Return address could be wrong, if we are unable to set a breakpoint
// there it's ok.
}
if bp != nil {
configureReturnBreakpoint(dbp.BinInfo(), bp, &topframe, retFrameCond)
}
}
if bp := curthread.Breakpoint(); bp.Breakpoint == nil {
curthread.SetCurrentBreakpoint()
}
success = true
return nil
}
// Removes instructions belonging to inlined calls of topframe from pcs.
// If includeCurrentFn is true it will also remove all instructions
// belonging to the current function.
func removeInlinedCalls(dbp Process, pcs []uint64, topframe Stackframe) ([]uint64, error) {
bi := dbp.BinInfo()
irdr := reader.InlineStack(bi.dwarf, topframe.Call.Fn.offset, 0)
for irdr.Next() {
e := irdr.Entry()
if e.Offset == topframe.Call.Fn.offset {
continue
}
ranges, err := bi.dwarf.Ranges(e)
if err != nil {
return pcs, err
}
for _, rng := range ranges {
pcs = removePCsBetween(pcs, rng[0], rng[1], bi.staticBase)
}
irdr.SkipChildren()
}
return pcs, irdr.Err()
}
func removePCsBetween(pcs []uint64, start, end, staticBase uint64) []uint64 {
out := pcs[:0]
for _, pc := range pcs {
if pc < start+staticBase || pc >= end+staticBase {
out = append(out, pc)
}
}
return out
}
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
if len(text) <= 0 {
return nil
}
instr := text[0]
if instr.DestLoc == nil || instr.DestLoc.Fn == nil {
return nil
}
fn := instr.DestLoc.Fn
// Ensure PC and Entry match, otherwise StepInto is likely to set
// its breakpoint before DestLoc.PC and hence run too far ahead.
// Calls to runtime.duffzero and duffcopy have this problem.
if fn.Entry != instr.DestLoc.PC {
return nil
}
// Skip unexported runtime functions
if strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
return nil
}
//TODO(aarzilli): if we want to let users hide functions
// or entire packages from being stepped into with 'step'
// those extra checks should be done here.
// Set a breakpoint after the function's prologue
pc, _ := FirstPCAfterPrologue(dbp, fn, false)
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
return nil
}
func getGVariable(thread Thread) (*Variable, error) {
regs, err := thread.Registers(false)
if err != nil {
return nil, err
}
gaddr, hasgaddr := regs.GAddr()
if !hasgaddr {
gaddrbs := make([]byte, thread.Arch().PtrSize())
_, err := thread.ReadMemory(gaddrbs, uintptr(regs.TLS()+thread.BinInfo().GStructOffset()))
if err != nil {
return nil, err
}
gaddr = binary.LittleEndian.Uint64(gaddrbs)
}
return newGVariable(thread, uintptr(gaddr), thread.Arch().DerefTLS())
}
func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
typ, err := thread.BinInfo().findType("runtime.g")
if err != nil {
return nil, err
}
name := ""
if deref {
typ = &godwarf.PtrType{
CommonType: godwarf.CommonType{
ByteSize: int64(thread.Arch().PtrSize()),
Name: "",
ReflectKind: reflect.Ptr,
Offset: 0,
},
Type: typ,
}
} else {
name = "runtime.curg"
}
return newVariableFromThread(thread, name, gaddr, typ), nil
}
// GetG returns information on the G (goroutine) that is executing on this thread.
//
// The G structure for a thread is stored in thread local storage. Here we simply
// calculate the address and read and parse the G struct.
//
// We cannot simply use the allg linked list in order to find the M that represents
// the given OS thread and follow its G pointer because on Darwin mach ports are not
// universal, so our port for this thread would not map to the `id` attribute of the M
// structure. Also, when linked against libc, Go prefers the libc version of clone as
// opposed to the runtime version. This has the consequence of not setting M.id for
// any thread, regardless of OS.
//
// In order to get around all this craziness, we read the address of the G structure for
// the current thread from the thread local storage area.
func GetG(thread Thread) (*G, error) {
gaddr, err := getGVariable(thread)
if err != nil {
return nil, err
}
g, err := gaddr.parseG()
if err != nil {
return nil, err
}
if g.ID == 0 {
// The runtime uses a special goroutine with ID == 0 to mark that the
// current goroutine is executing on the system stack (sometimes also
// referred to as the g0 stack or scheduler stack, I'm not sure if there's
// actually any difference between those).
// For our purposes it's better if we always return the real goroutine
// since the rest of the code assumes the goroutine ID is univocal.
// The real 'current goroutine' is stored in g0.m.curg
curgvar, err := g.variable.fieldVariable("m").structMember("curg")
if err != nil {
return nil, err
}
g, err = curgvar.parseG()
if err != nil {
return nil, err
}
g.SystemStack = true
}
g.Thread = thread
if loc, err := thread.Location(); err == nil {
g.CurrentLoc = *loc
}
return g, nil
}
// ThreadScope returns an EvalScope for this thread.
func ThreadScope(thread Thread) (*EvalScope, error) {
locations, err := ThreadStacktrace(thread, 1)
if err != nil {
return nil, err
}
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
return FrameToScope(thread.BinInfo(), thread, nil, locations...), nil
}
// GoroutineScope returns an EvalScope for the goroutine running on this thread.
func GoroutineScope(thread Thread) (*EvalScope, error) {
locations, err := ThreadStacktrace(thread, 1)
if err != nil {
return nil, err
}
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
g, err := GetG(thread)
if err != nil {
return nil, err
}
return FrameToScope(thread.BinInfo(), thread, g, locations...), nil
}
// onNextGoroutine returns true if this thread is on the goroutine requested by the current 'next' command
func onNextGoroutine(thread Thread, breakpoints *BreakpointMap) (bool, error) {
var bp *Breakpoint
for i := range breakpoints.M {
if breakpoints.M[i].Kind != UserBreakpoint && breakpoints.M[i].internalCond != nil {
bp = breakpoints.M[i]
break
}
}
if bp == nil {
return false, nil
}
// Internal breakpoint conditions can take multiple different forms:
// Step into breakpoints:
// runtime.curg.goid == X
// Next or StepOut breakpoints:
// runtime.curg.goid == X && runtime.frameoff == Y
// Breakpoints that can be hit either by stepping on a line in the same
// function or by returning from the function:
// runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z)
// Here we are only interested in testing the runtime.curg.goid clause.
w := onNextGoroutineWalker{thread: thread}
ast.Walk(&w, bp.internalCond)
return w.ret, w.err
}
type onNextGoroutineWalker struct {
thread Thread
ret bool
err error
}
func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor {
if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL && exprToString(binx.X) == "runtime.curg.goid" {
w.ret, w.err = evalBreakpointCondition(w.thread, n.(ast.Expr))
return nil
}
return w
}

1288
vendor/github.com/derekparker/delve/pkg/proc/types.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

2119
vendor/github.com/derekparker/delve/pkg/proc/variables.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,191 @@
package terminal
import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"text/tabwriter"
"github.com/derekparker/delve/pkg/config"
)
func configureCmd(t *Term, ctx callContext, args string) error {
switch args {
case "-list":
return configureList(t)
case "-save":
return config.SaveConfig(t.conf)
case "":
return fmt.Errorf("wrong number of arguments to \"config\"")
default:
return configureSet(t, args)
}
}
type configureIterator struct {
cfgValue reflect.Value
cfgType reflect.Type
i int
}
func iterateConfiguration(conf *config.Config) *configureIterator {
cfgValue := reflect.ValueOf(conf).Elem()
cfgType := cfgValue.Type()
return &configureIterator{cfgValue, cfgType, -1}
}
func (it *configureIterator) Next() bool {
it.i++
return it.i < it.cfgValue.NumField()
}
func (it *configureIterator) Field() (name string, field reflect.Value) {
name = it.cfgType.Field(it.i).Tag.Get("yaml")
if comma := strings.Index(name, ","); comma >= 0 {
name = name[:comma]
}
field = it.cfgValue.Field(it.i)
return
}
func configureFindFieldByName(conf *config.Config, name string) reflect.Value {
it := iterateConfiguration(conf)
for it.Next() {
fieldName, field := it.Field()
if fieldName == name {
return field
}
}
return reflect.ValueOf(nil)
}
func configureList(t *Term) error {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 1, ' ', 0)
it := iterateConfiguration(t.conf)
for it.Next() {
fieldName, field := it.Field()
if fieldName == "" {
continue
}
if field.Kind() == reflect.Ptr {
if !field.IsNil() {
fmt.Fprintf(w, "%s\t%v\n", fieldName, field.Elem())
} else {
fmt.Fprintf(w, "%s\t<not defined>\n", fieldName)
}
} else {
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
}
}
return w.Flush()
}
func configureSet(t *Term, args string) error {
v := strings.SplitN(args, " ", 2)
cfgname := v[0]
var rest string
if len(v) == 2 {
rest = v[1]
}
if cfgname == "alias" {
return configureSetAlias(t, rest)
}
field := configureFindFieldByName(t.conf, cfgname)
if !field.CanAddr() {
return fmt.Errorf("%q is not a configuration parameter", cfgname)
}
if field.Kind() == reflect.Slice && field.Type().Elem().Name() == "SubstitutePathRule" {
return configureSetSubstitutePath(t, rest)
}
simpleArg := func(typ reflect.Type) (reflect.Value, error) {
switch typ.Kind() {
case reflect.Int:
n, err := strconv.Atoi(rest)
if err != nil {
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number", cfgname)
}
return reflect.ValueOf(&n), nil
case reflect.Bool:
v := rest == "true"
return reflect.ValueOf(&v), nil
default:
return reflect.ValueOf(nil), fmt.Errorf("unsupported type for configuration key %q", cfgname)
}
}
if field.Kind() == reflect.Ptr {
val, err := simpleArg(field.Type().Elem())
if err != nil {
return err
}
field.Set(val)
} else {
val, err := simpleArg(field.Type())
if err != nil {
return err
}
field.Set(val.Elem())
}
return nil
}
func configureSetSubstitutePath(t *Term, rest string) error {
argv := config.SplitQuotedFields(rest, '"')
switch len(argv) {
case 1: // delete substitute-path rule
for i := range t.conf.SubstitutePath {
if t.conf.SubstitutePath[i].From == argv[0] {
copy(t.conf.SubstitutePath[i:], t.conf.SubstitutePath[i+1:])
t.conf.SubstitutePath = t.conf.SubstitutePath[:len(t.conf.SubstitutePath)-1]
return nil
}
}
return fmt.Errorf("could not find rule for %q", argv[0])
case 2: // add substitute-path rule
for i := range t.conf.SubstitutePath {
if t.conf.SubstitutePath[i].From == argv[0] {
t.conf.SubstitutePath[i].To = argv[1]
return nil
}
}
t.conf.SubstitutePath = append(t.conf.SubstitutePath, config.SubstitutePathRule{argv[0], argv[1]})
default:
return fmt.Errorf("too many arguments to \"config substitute-path\"")
}
return nil
}
func configureSetAlias(t *Term, rest string) error {
argv := config.SplitQuotedFields(rest, '"')
switch len(argv) {
case 1: // delete alias rule
for k := range t.conf.Aliases {
v := t.conf.Aliases[k]
for i := range v {
if v[i] == argv[0] {
copy(v[i:], v[i+1:])
t.conf.Aliases[k] = v[:len(v)-1]
}
}
}
case 2: // add alias rule
alias, cmd := argv[1], argv[0]
if t.conf.Aliases == nil {
t.conf.Aliases = make(map[string][]string)
}
t.conf.Aliases[cmd] = append(t.conf.Aliases[cmd], alias)
}
t.cmds.Merge(t.conf.Aliases)
return nil
}

View File

@ -3,17 +3,18 @@ package terminal
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/derekparker/delve/service/api"
"io" "io"
"path/filepath" "path/filepath"
"text/tabwriter" "text/tabwriter"
"github.com/derekparker/delve/service/api"
) )
func DisasmPrint(dv api.AsmInstructions, out io.Writer) { func DisasmPrint(dv api.AsmInstructions, out io.Writer) {
bw := bufio.NewWriter(out) bw := bufio.NewWriter(out)
defer bw.Flush() defer bw.Flush()
if len(dv) > 0 && dv[0].Loc.Function != nil { if len(dv) > 0 && dv[0].Loc.Function != nil {
fmt.Fprintf(bw, "TEXT %s(SB) %s\n", dv[0].Loc.Function.Name, dv[0].Loc.File) fmt.Fprintf(bw, "TEXT %s(SB) %s\n", dv[0].Loc.Function.Name(), dv[0].Loc.File)
} }
tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0) tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0)
defer tw.Flush() defer tw.Flush()

View File

@ -27,10 +27,10 @@ func replaceDocPath(s string) string {
} }
func (commands *Commands) WriteMarkdown(w io.Writer) { func (commands *Commands) WriteMarkdown(w io.Writer) {
fmt.Fprintf(w, "# Commands\n\n") fmt.Fprint(w, "# Commands\n\n")
fmt.Fprintf(w, "Command | Description\n") fmt.Fprint(w, "Command | Description\n")
fmt.Fprintf(w, "--------|------------\n") fmt.Fprint(w, "--------|------------\n")
for _, cmd := range commands.cmds { for _, cmd := range commands.cmds {
h := cmd.helpMsg h := cmd.helpMsg
if idx := strings.Index(h, "\n"); idx >= 0 { if idx := strings.Index(h, "\n"); idx >= 0 {
@ -38,17 +38,17 @@ func (commands *Commands) WriteMarkdown(w io.Writer) {
} }
fmt.Fprintf(w, "[%s](#%s) | %s\n", cmd.aliases[0], cmd.aliases[0], h) fmt.Fprintf(w, "[%s](#%s) | %s\n", cmd.aliases[0], cmd.aliases[0], h)
} }
fmt.Fprintf(w, "\n") fmt.Fprint(w, "\n")
for _, cmd := range commands.cmds { for _, cmd := range commands.cmds {
fmt.Fprintf(w, "## %s\n%s\n\n", cmd.aliases[0], replaceDocPath(cmd.helpMsg)) fmt.Fprintf(w, "## %s\n%s\n\n", cmd.aliases[0], replaceDocPath(cmd.helpMsg))
if len(cmd.aliases) > 1 { if len(cmd.aliases) > 1 {
fmt.Fprintf(w, "Aliases:") fmt.Fprint(w, "Aliases:")
for _, alias := range cmd.aliases[1:] { for _, alias := range cmd.aliases[1:] {
fmt.Fprintf(w, " %s", alias) fmt.Fprintf(w, " %s", alias)
} }
fmt.Fprintf(w, "\n") fmt.Fprint(w, "\n")
} }
fmt.Fprintf(w, "\n") fmt.Fprint(w, "\n")
} }
} }

View File

@ -7,19 +7,40 @@ import (
"os/signal" "os/signal"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"github.com/peterh/liner" "github.com/peterh/liner"
"github.com/derekparker/delve/config" "github.com/derekparker/delve/pkg/config"
"github.com/derekparker/delve/service" "github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
) )
const ( const (
historyFile string = ".dbg_history" historyFile string = ".dbg_history"
terminalBlueEscapeCode string = "\033[34m" terminalHighlightEscapeCode string = "\033[%2dm"
terminalResetEscapeCode string = "\033[0m" terminalResetEscapeCode string = "\033[0m"
)
const (
ansiBlack = 30
ansiRed = 31
ansiGreen = 32
ansiYellow = 33
ansiBlue = 34
ansiMagenta = 35
ansiCyan = 36
ansiWhite = 37
ansiBrBlack = 90
ansiBrRed = 91
ansiBrGreen = 92
ansiBrYellow = 93
ansiBrBlue = 94
ansiBrMagenta = 95
ansiBrCyan = 96
ansiBrWhite = 97
) )
// Term represents the terminal running dlv. // Term represents the terminal running dlv.
@ -32,15 +53,40 @@ type Term struct {
dumb bool dumb bool
stdout io.Writer stdout io.Writer
InitFile string InitFile string
// quitContinue is set to true by exitCommand to signal that the process
// should be resumed before quitting.
quitContinue bool
quittingMutex sync.Mutex
quitting bool
} }
// New returns a new Term. // New returns a new Term.
func New(client service.Client, conf *config.Config) *Term { func New(client service.Client, conf *config.Config) *Term {
if client != nil && client.IsMulticlient() {
state, _ := client.GetStateNonBlocking()
// The error return of GetState will usually be the ErrProcessExited,
// which we don't care about. If there are other errors they will show up
// later, here we are only concerned about stopping a running target so
// that we can initialize our connection.
if state != nil && state.Running {
_, err := client.Halt()
if err != nil {
fmt.Fprintf(os.Stderr, "could not halt: %v", err)
return nil
}
}
}
cmds := DebugCommands(client) cmds := DebugCommands(client)
if conf != nil && conf.Aliases != nil { if conf != nil && conf.Aliases != nil {
cmds.Merge(conf.Aliases) cmds.Merge(conf.Aliases)
} }
if conf == nil {
conf = &config.Config{}
}
var w io.Writer var w io.Writer
dumb := strings.ToLower(os.Getenv("TERM")) == "dumb" dumb := strings.ToLower(os.Getenv("TERM")) == "dumb"
@ -50,6 +96,17 @@ func New(client service.Client, conf *config.Config) *Term {
w = getColorableWriter() w = getColorableWriter()
} }
if client != nil {
client.SetReturnValuesLoadConfig(&LongLoadConfig)
}
if (conf.SourceListLineColor > ansiWhite &&
conf.SourceListLineColor < ansiBrBlack) ||
conf.SourceListLineColor < ansiBlack ||
conf.SourceListLineColor > ansiBrWhite {
conf.SourceListLineColor = ansiBlue
}
return &Term{ return &Term{
client: client, client: client,
conf: conf, conf: conf,
@ -66,22 +123,55 @@ func (t *Term) Close() {
t.line.Close() t.line.Close()
} }
// Run begins running dlv in the terminal. func (t *Term) sigintGuard(ch <-chan os.Signal, multiClient bool) {
func (t *Term) Run() (int, error) { for range ch {
defer t.Close() if multiClient {
answer, err := t.line.Prompt("Would you like to [s]top the target or [q]uit this client, leaving the target running [s/q]? ")
if err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
continue
}
answer = strings.TrimSpace(answer)
switch answer {
case "s":
_, err := t.client.Halt()
if err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
}
case "q":
t.quittingMutex.Lock()
t.quitting = true
t.quittingMutex.Unlock()
err := t.client.Disconnect(false)
if err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
} else {
t.Close()
}
default:
fmt.Println("only s or q allowed")
}
// Send the debugger a halt command on SIGINT } else {
ch := make(chan os.Signal) fmt.Printf("received SIGINT, stopping process (will not forward signal)\n")
signal.Notify(ch, syscall.SIGINT)
go func() {
for range ch {
fmt.Printf("received SIGINT, stopping process (will not forward signal)")
_, err := t.client.Halt() _, err := t.client.Halt()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%v", err) fmt.Fprintf(os.Stderr, "%v", err)
} }
} }
}() }
}
// Run begins running dlv in the terminal.
func (t *Term) Run() (int, error) {
defer t.Close()
multiClient := t.client.IsMulticlient()
// Send the debugger a halt command on SIGINT
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT)
go t.sigintGuard(ch, multiClient)
t.line.SetCompleter(func(line string) (c []string) { t.line.SetCompleter(func(line string) (c []string) {
for _, cmd := range t.cmds.cmds { for _, cmd := range t.cmds.cmds {
@ -128,8 +218,7 @@ func (t *Term) Run() (int, error) {
return 1, fmt.Errorf("Prompt for input failed.\n") return 1, fmt.Errorf("Prompt for input failed.\n")
} }
cmdstr, args := parseCommand(cmdstr) if err := t.cmds.Call(cmdstr, t); err != nil {
if err := t.cmds.Call(cmdstr, args, t); err != nil {
if _, ok := err.(ExitRequestError); ok { if _, ok := err.(ExitRequestError); ok {
return t.handleExit() return t.handleExit()
} }
@ -139,6 +228,12 @@ func (t *Term) Run() (int, error) {
if strings.Contains(err.Error(), "exited") { if strings.Contains(err.Error(), "exited") {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
} else { } else {
t.quittingMutex.Lock()
quitting := t.quitting
t.quittingMutex.Unlock()
if quitting {
return t.handleExit()
}
fmt.Fprintf(os.Stderr, "Command failed: %s\n", err) fmt.Fprintf(os.Stderr, "Command failed: %s\n", err)
} }
} }
@ -148,14 +243,15 @@ func (t *Term) Run() (int, error) {
// Println prints a line to the terminal. // Println prints a line to the terminal.
func (t *Term) Println(prefix, str string) { func (t *Term) Println(prefix, str string) {
if !t.dumb { if !t.dumb {
prefix = fmt.Sprintf("%s%s%s", terminalBlueEscapeCode, prefix, terminalResetEscapeCode) terminalColorEscapeCode := fmt.Sprintf(terminalHighlightEscapeCode, t.conf.SourceListLineColor)
prefix = fmt.Sprintf("%s%s%s", terminalColorEscapeCode, prefix, terminalResetEscapeCode)
} }
fmt.Fprintf(t.stdout, "%s%s\n", prefix, str) fmt.Fprintf(t.stdout, "%s%s\n", prefix, str)
} }
// Substitues directory to source file. // Substitutes directory to source file.
// //
// Ensures that only directory is substitued, for example: // Ensures that only directory is substituted, for example:
// substitute from `/dir/subdir`, substitute to `/new` // substitute from `/dir/subdir`, substitute to `/new`
// for file path `/dir/subdir/file` will return file path `/new/file`. // for file path `/dir/subdir/file` will return file path `/new/file`.
// for file path `/dir/subdir-2/file` substitution will not be applied. // for file path `/dir/subdir-2/file` substitution will not be applied.
@ -187,7 +283,7 @@ func (t *Term) substitutePath(path string) string {
} }
func crossPlatformPath(path string) string { func crossPlatformPath(path string) string {
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return strings.ToLower(path) return strings.ToLower(path)
} }
return path return path
@ -207,6 +303,22 @@ func (t *Term) promptForInput() (string, error) {
return l, nil return l, nil
} }
func yesno(line *liner.State, question string) (bool, error) {
for {
answer, err := line.Prompt(question)
if err != nil {
return false, err
}
answer = strings.ToLower(strings.TrimSpace(answer))
switch answer {
case "n", "no":
return false, nil
case "y", "yes":
return true, nil
}
}
}
func (t *Term) handleExit() (int, error) { func (t *Term) handleExit() (int, error) {
fullHistoryFile, err := config.GetConfigFilePath(historyFile) fullHistoryFile, err := config.GetConfigFilePath(historyFile)
if err != nil { if err != nil {
@ -221,31 +333,63 @@ func (t *Term) handleExit() (int, error) {
} }
} }
t.quittingMutex.Lock()
quitting := t.quitting
t.quittingMutex.Unlock()
if quitting {
return 0, nil
}
s, err := t.client.GetState() s, err := t.client.GetState()
if err != nil { if err != nil {
return 1, err return 1, err
} }
if !s.Exited { if !s.Exited {
kill := true if t.quitContinue {
if t.client.AttachedToExistingProcess() { err := t.client.Disconnect(true)
answer, err := t.line.Prompt("Would you like to kill the process? [Y/n] ") if err != nil {
return 2, err
}
return 0, nil
}
doDetach := true
if t.client.IsMulticlient() {
answer, err := yesno(t.line, "Would you like to kill the headless instance? [Y/n] ")
if err != nil { if err != nil {
return 2, io.EOF return 2, io.EOF
} }
answer = strings.ToLower(strings.TrimSpace(answer)) doDetach = answer
kill = (answer != "n" && answer != "no")
} }
if err := t.client.Detach(kill); err != nil {
return 1, err if doDetach {
kill := true
if t.client.AttachedToExistingProcess() {
answer, err := yesno(t.line, "Would you like to kill the process? [Y/n] ")
if err != nil {
return 2, io.EOF
}
kill = answer
}
if err := t.client.Detach(kill); err != nil {
return 1, err
}
} }
} }
return 0, nil return 0, nil
} }
func parseCommand(cmdstr string) (string, string) { // loadConfig returns an api.LoadConfig with the parameterss specified in
vals := strings.SplitN(cmdstr, " ", 2) // the configuration file.
if len(vals) == 1 { func (t *Term) loadConfig() api.LoadConfig {
return vals[0], "" r := api.LoadConfig{true, 1, 64, 64, -1}
if t.conf != nil && t.conf.MaxStringLen != nil {
r.MaxStringLen = *t.conf.MaxStringLen
} }
return vals[0], strings.TrimSpace(vals[1]) if t.conf != nil && t.conf.MaxArrayValues != nil {
r.MaxArrayValues = *t.conf.MaxArrayValues
}
return r
} }

View File

@ -13,7 +13,10 @@ type Version struct {
var ( var (
// DelveVersion is the current version of Delve. // DelveVersion is the current version of Delve.
DelveVersion = Version{Major: "0", Minor: "12", Patch: "1", Metadata: ""} DelveVersion = Version{
Major: "1", Minor: "1", Patch: "0", Metadata: "",
Build: "$Id: 1990ba12450cab9425a2ae62e6ab988725023d5c $",
}
) )
func (v Version) String() string { func (v Version) String() string {

View File

@ -1,79 +0,0 @@
package proc
import "runtime"
// Arch defines an interface for representing a
// CPU architecture.
type Arch interface {
SetGStructOffset(ver GoVersion, iscgo bool)
PtrSize() int
BreakpointInstruction() []byte
BreakpointSize() int
GStructOffset() uint64
}
// AMD64 represents the AMD64 CPU architecture.
type AMD64 struct {
ptrSize int
breakInstruction []byte
breakInstructionLen int
gStructOffset uint64
hardwareBreakpointUsage []bool
}
// AMD64Arch returns an initialized AMD64
// struct.
func AMD64Arch() *AMD64 {
var breakInstr = []byte{0xCC}
return &AMD64{
ptrSize: 8,
breakInstruction: breakInstr,
breakInstructionLen: len(breakInstr),
hardwareBreakpointUsage: make([]bool, 4),
}
}
// SetGStructOffset sets the offset of the G struct on the AMD64
// arch struct. The offset is dependent on the Go compiler Version
// and whether or not the target program was externally linked.
func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) {
switch runtime.GOOS {
case "darwin":
a.gStructOffset = 0x8a0
case "linux":
a.gStructOffset = 0xfffffffffffffff0
if isextld || ver.AfterOrEqual(GoVersion{1, 5, -1, 2, 0}) || ver.IsDevel() {
a.gStructOffset += 8
}
case "windows":
// Use ArbitraryUserPointer (0x28) as pointer to pointer
// to G struct per:
// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
a.gStructOffset = 0x28
}
}
// PtrSize returns the size of a pointer
// on this architecture.
func (a *AMD64) PtrSize() int {
return a.ptrSize
}
// BreakpointInstruction returns the Breakpoint
// instruction for this architecture.
func (a *AMD64) BreakpointInstruction() []byte {
return a.breakInstruction
}
// BreakpointSize returns the size of the
// breakpoint instruction on this architecture.
func (a *AMD64) BreakpointSize() int {
return a.breakInstructionLen
}
// GStructOffset returns the offset of the G
// struct in thread local storage.
func (a *AMD64) GStructOffset() uint64 {
return a.gStructOffset
}

View File

@ -1,163 +0,0 @@
package proc
import (
"errors"
"fmt"
"go/ast"
"go/constant"
"reflect"
)
// Breakpoint represents a breakpoint. Stores information on the break
// point including the byte of data that originally was stored at that
// address.
type Breakpoint struct {
// File & line information for printing.
FunctionName string
File string
Line int
Addr uint64 // Address breakpoint is set for.
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
Name string // User defined name of the breakpoint
ID int // Monotonically increasing ID.
Kind BreakpointKind // Whether this is an internal breakpoint (for next'ing or stepping).
// Breakpoint information
Tracepoint bool // Tracepoint flag
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached
// DeferReturns: when kind == NextDeferBreakpoint this breakpoint
// will also check if the caller is runtime.gopanic or if the return
// address is in the DeferReturns array.
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
// deferred function, DeferReturns is populated with the
// addresses of calls to runtime.deferreturn in the current
// function. This insures that the breakpoint on the deferred
// function only triggers on panic or on the defer call to
// the function, not when the function is called directly
DeferReturns []uint64
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
}
// Breakpoint Kind determines the behavior of delve when the
// breakpoint is reached.
type BreakpointKind int
const (
// UserBreakpoint is a user set breakpoint
UserBreakpoint BreakpointKind = iota
// NextBreakpoint is a breakpoint set by Next, Continue
// will stop on it and delete it
NextBreakpoint
// NextDeferBreakpoint is a breakpoint set by Next on the
// first deferred function. In addition to checking their condition
// breakpoints of this kind will also check that the function has been
// called by runtime.gopanic or through runtime.deferreturn.
NextDeferBreakpoint
// StepBreakpoint is a breakpoint set by Step on a CALL instruction,
// Continue will set a new breakpoint (of NextBreakpoint kind) on the
// destination of CALL, delete this breakpoint and then continue again
StepBreakpoint
)
func (bp *Breakpoint) String() string {
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.ID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
}
// Clear this breakpoint appropriately depending on whether it is a
// hardware or software breakpoint.
func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
if _, err := thread.writeMemory(uintptr(bp.Addr), bp.OriginalData); err != nil {
return nil, fmt.Errorf("could not clear breakpoint %s", err)
}
return bp, nil
}
// BreakpointExistsError is returned when trying to set a breakpoint at
// an address that already has a breakpoint set for it.
type BreakpointExistsError struct {
file string
line int
addr uint64
}
func (bpe BreakpointExistsError) Error() string {
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
}
// InvalidAddressError represents the result of
// attempting to set a breakpoint at an invalid address.
type InvalidAddressError struct {
address uint64
}
func (iae InvalidAddressError) Error() string {
return fmt.Sprintf("Invalid address %#v\n", iae.address)
}
func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
_, err := thread.writeMemory(uintptr(addr), dbp.arch.BreakpointInstruction())
return err
}
func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) {
if bp.Cond == nil {
return true, nil
}
if bp.Kind == NextDeferBreakpoint {
frames, err := thread.Stacktrace(2)
if err == nil {
ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
isdeferreturn := false
if len(frames) >= 1 {
for _, pc := range bp.DeferReturns {
if frames[0].Ret == pc {
isdeferreturn = true
break
}
}
}
if !ispanic && !isdeferreturn {
return false, nil
}
}
}
scope, err := thread.Scope()
if err != nil {
return true, err
}
v, err := scope.evalAST(bp.Cond)
if err != nil {
return true, fmt.Errorf("error evaluating expression: %v", err)
}
if v.Unreadable != nil {
return true, fmt.Errorf("condition expression unreadable: %v", v.Unreadable)
}
if v.Kind != reflect.Bool {
return true, errors.New("condition expression not boolean")
}
return constant.BoolVal(v.Value), nil
}
// Internal returns true for breakpoints not set directly by the user.
func (bp *Breakpoint) Internal() bool {
return bp.Kind != UserBreakpoint
}
// NoBreakpointError is returned when trying to
// clear a breakpoint that does not exist.
type NoBreakpointError struct {
addr uint64
}
func (nbp NoBreakpointError) Error() string {
return fmt.Sprintf("no breakpoint at %#v", nbp.addr)
}

View File

@ -1,67 +0,0 @@
package proc
type AsmInstruction struct {
Loc Location
DestLoc *Location
Bytes []byte
Breakpoint bool
AtPC bool
Inst *ArchInst
}
type AssemblyFlavour int
const (
GNUFlavour = AssemblyFlavour(iota)
IntelFlavour
)
// Disassemble disassembles target memory between startPC and endPC
// If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble will evaluate the argument of the CALL instruction using the thread's registers
// Be aware that the Bytes field of each returned instruction is a slice of a larger array of size endPC - startPC
func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool) ([]AsmInstruction, error) {
if thread.dbp.exited {
return nil, &ProcessExitedError{}
}
mem, err := thread.readMemory(uintptr(startPC), int(endPC-startPC))
if err != nil {
return nil, err
}
r := make([]AsmInstruction, 0, len(mem)/15)
pc := startPC
var curpc uint64
var regs Registers
if currentGoroutine {
regs, _ = thread.Registers(false)
if regs != nil {
curpc = regs.PC()
}
}
for len(mem) > 0 {
bp, atbp := thread.dbp.Breakpoints[pc]
if atbp {
for i := range bp.OriginalData {
mem[i] = bp.OriginalData[i]
}
}
file, line, fn := thread.dbp.PCToLine(pc)
loc := Location{PC: pc, File: file, Line: line, Fn: fn}
inst, err := asmDecode(mem, pc)
if err == nil {
atpc := currentGoroutine && (curpc == pc)
destloc := thread.resolveCallArg(inst, atpc, regs)
r = append(r, AsmInstruction{Loc: loc, DestLoc: destloc, Bytes: mem[:inst.Len], Breakpoint: atbp, AtPC: atpc, Inst: inst})
pc += uint64(inst.Size())
mem = mem[inst.Size():]
} else {
r = append(r, AsmInstruction{Loc: loc, Bytes: mem[:1], Breakpoint: atbp, Inst: nil})
pc++
mem = mem[1:]
}
}
return r, nil
}

View File

@ -1,113 +0,0 @@
package proc
import (
"strconv"
"strings"
)
// GoVersion represents the Go version of
// the Go compiler version used to compile
// the target binary.
type GoVersion struct {
Major int
Minor int
Rev int
Beta int
RC int
}
func ParseVersionString(ver string) (GoVersion, bool) {
var r GoVersion
var err1, err2, err3 error
if strings.HasPrefix(ver, "devel") {
return GoVersion{-1, 0, 0, 0, 0}, true
}
if strings.HasPrefix(ver, "go") {
ver := strings.Split(ver, " ")[0]
v := strings.SplitN(ver[2:], ".", 3)
switch len(v) {
case 2:
r.Major, err1 = strconv.Atoi(v[0])
vr := strings.SplitN(v[1], "beta", 2)
if len(vr) == 2 {
r.Beta, err3 = strconv.Atoi(vr[1])
} else {
vr = strings.SplitN(v[1], "rc", 2)
if len(vr) == 2 {
r.RC, err3 = strconv.Atoi(vr[1])
} else {
r.Minor, err2 = strconv.Atoi(v[1])
if err2 != nil {
return GoVersion{}, false
}
return r, true
}
}
r.Minor, err2 = strconv.Atoi(vr[0])
r.Rev = -1
if err1 != nil || err2 != nil || err3 != nil {
return GoVersion{}, false
}
return r, true
case 3:
r.Major, err1 = strconv.Atoi(v[0])
r.Minor, err2 = strconv.Atoi(v[1])
r.Rev, err3 = strconv.Atoi(v[2])
if err1 != nil || err2 != nil || err3 != nil {
return GoVersion{}, false
}
return r, true
default:
return GoVersion{}, false
}
}
return GoVersion{}, false
}
// AfterOrEqual returns whether one GoVersion is after or
// equal to the other.
func (v *GoVersion) AfterOrEqual(b GoVersion) bool {
if v.Major < b.Major {
return false
} else if v.Major > b.Major {
return true
}
if v.Minor < b.Minor {
return false
} else if v.Minor > b.Minor {
return true
}
if v.Rev < b.Rev {
return false
} else if v.Rev > b.Rev {
return true
}
if v.Beta < b.Beta {
return false
}
if v.RC < b.RC {
return false
}
return true
}
// IsDevel returns whether the GoVersion
// is a development version.
func (v *GoVersion) IsDevel() bool {
return v.Major < 0
}

View File

@ -1,57 +0,0 @@
package proc
const cacheEnabled = true
type memoryReadWriter interface {
readMemory(addr uintptr, size int) (data []byte, err error)
writeMemory(addr uintptr, data []byte) (written int, err error)
}
type memCache struct {
cacheAddr uintptr
cache []byte
mem memoryReadWriter
}
func (m *memCache) contains(addr uintptr, size int) bool {
return addr >= m.cacheAddr && addr <= (m.cacheAddr+uintptr(len(m.cache)-size))
}
func (m *memCache) readMemory(addr uintptr, size int) (data []byte, err error) {
if m.contains(addr, size) {
d := make([]byte, size)
copy(d, m.cache[addr-m.cacheAddr:])
return d, nil
}
return m.mem.readMemory(addr, size)
}
func (m *memCache) writeMemory(addr uintptr, data []byte) (written int, err error) {
return m.mem.writeMemory(addr, data)
}
func cacheMemory(mem memoryReadWriter, addr uintptr, size int) memoryReadWriter {
if !cacheEnabled {
return mem
}
if size <= 0 {
return mem
}
if cacheMem, isCache := mem.(*memCache); isCache {
if cacheMem.contains(addr, size) {
return mem
} else {
cache, err := cacheMem.mem.readMemory(addr, size)
if err != nil {
return mem
}
return &memCache{addr, cache, mem}
}
}
cache, err := mem.readMemory(addr, size)
if err != nil {
return mem
}
return &memCache{addr, cache, mem}
}

View File

@ -1,942 +0,0 @@
package proc
import (
"debug/gosym"
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/token"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/dwarf/reader"
"golang.org/x/debug/dwarf"
)
// Process represents all of the information the debugger
// is holding onto regarding the process we are debugging.
type Process struct {
Pid int // Process Pid
Process *os.Process // Pointer to process struct for the actual process we are debugging
LastModified time.Time // Time the executable of this process was last modified
// Breakpoint table, holds information on breakpoints.
// Maps instruction address to Breakpoint struct.
Breakpoints map[uint64]*Breakpoint
// List of threads mapped as such: pid -> *Thread
Threads map[int]*Thread
// Active thread
CurrentThread *Thread
// Goroutine that will be used by default to set breakpoint, eval variables, etc...
// Normally SelectedGoroutine is CurrentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
SelectedGoroutine *G
// Maps package names to package paths, needed to lookup types inside DWARF info
packageMap map[string]string
allGCache []*G
dwarf *dwarf.Data
goSymTable *gosym.Table
frameEntries frame.FrameDescriptionEntries
lineInfo line.DebugLines
os *OSProcessDetails
arch Arch
breakpointIDCounter int
internalBreakpointIDCounter int
firstStart bool
halt bool
exited bool
ptraceChan chan func()
ptraceDoneChan chan interface{}
types map[string]dwarf.Offset
loadModuleDataOnce sync.Once
moduleData []moduleData
nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry
}
var NotExecutableErr = errors.New("not an executable file")
// New returns an initialized Process struct. Before returning,
// it will also launch a goroutine in order to handle ptrace(2)
// functions. For more information, see the documentation on
// `handlePtraceFuncs`.
func New(pid int) *Process {
dbp := &Process{
Pid: pid,
Threads: make(map[int]*Thread),
Breakpoints: make(map[uint64]*Breakpoint),
firstStart: true,
os: new(OSProcessDetails),
ptraceChan: make(chan func()),
ptraceDoneChan: make(chan interface{}),
nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry),
}
// TODO: find better way to determine proc arch (perhaps use executable file info)
switch runtime.GOARCH {
case "amd64":
dbp.arch = AMD64Arch()
}
go dbp.handlePtraceFuncs()
return dbp
}
// ProcessExitedError indicates that the process has exited and contains both
// process id and exit status.
type ProcessExitedError struct {
Pid int
Status int
}
func (pe ProcessExitedError) Error() string {
return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status)
}
// Detach from the process being debugged, optionally killing it.
func (dbp *Process) Detach(kill bool) (err error) {
if dbp.Running() {
if err = dbp.Halt(); err != nil {
return
}
}
if !kill {
// Clean up any breakpoints we've set.
for _, bp := range dbp.Breakpoints {
if bp != nil {
_, err := dbp.ClearBreakpoint(bp.Addr)
if err != nil {
return err
}
}
}
}
dbp.execPtraceFunc(func() {
err = PtraceDetach(dbp.Pid, 0)
if err != nil {
return
}
if kill {
err = killProcess(dbp.Pid)
}
})
return
}
// Exited returns whether the debugged
// process has exited.
func (dbp *Process) Exited() bool {
return dbp.exited
}
// Running returns whether the debugged
// process is currently executing.
func (dbp *Process) Running() bool {
for _, th := range dbp.Threads {
if th.running {
return true
}
}
return false
}
// LoadInformation finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *Process) LoadInformation(path string) error {
var wg sync.WaitGroup
exe, path, err := dbp.findExecutable(path)
if err != nil {
return err
}
fi, err := os.Stat(path)
if err == nil {
dbp.LastModified = fi.ModTime()
}
wg.Add(5)
go dbp.loadProcessInformation(&wg)
go dbp.parseDebugFrame(exe, &wg)
go dbp.obtainGoSymbols(exe, &wg)
go dbp.parseDebugLineInfo(exe, &wg)
go dbp.loadTypeMap(&wg)
wg.Wait()
return nil
}
// FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normailzed to lower case and '/' on Windows.
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
pc, fn, err := dbp.goSymTable.LineToPC(fileName, lineno)
if err != nil {
return 0, err
}
if fn.Entry == pc {
pc, _ = dbp.FirstPCAfterPrologue(fn, true)
}
return pc, nil
}
// FindFunctionLocation finds address of a function's line
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
// If lineOffset is passed FindFunctionLocation will return the address of that line
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
// Note that setting breakpoints at that address will cause surprising behavior:
// https://github.com/derekparker/delve/issues/170
func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) {
origfn := dbp.goSymTable.LookupFunc(funcName)
if origfn == nil {
return 0, fmt.Errorf("Could not find function %s\n", funcName)
}
if firstLine {
return dbp.FirstPCAfterPrologue(origfn, false)
} else if lineOffset > 0 {
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
return breakAddr, err
}
return origfn.Entry, nil
}
// CurrentLocation returns the location of the current thread.
func (dbp *Process) CurrentLocation() (*Location, error) {
return dbp.CurrentThread.Location()
}
// RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error {
if dbp.exited {
return &ProcessExitedError{}
}
dbp.halt = true
return dbp.requestManualStop()
}
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
// break point table. Setting a break point must be thread specific due to
// ptrace actions needing the thread to be in a signal-delivery-stop.
func (dbp *Process) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) {
tid := dbp.CurrentThread.ID
if bp, ok := dbp.FindBreakpoint(addr); ok {
return nil, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
}
f, l, fn := dbp.goSymTable.PCToLine(uint64(addr))
if fn == nil {
return nil, InvalidAddressError{address: addr}
}
newBreakpoint := &Breakpoint{
FunctionName: fn.Name,
File: f,
Line: l,
Addr: addr,
Kind: kind,
Cond: cond,
HitCount: map[int]uint64{},
}
if kind != UserBreakpoint {
dbp.internalBreakpointIDCounter++
newBreakpoint.ID = dbp.internalBreakpointIDCounter
} else {
dbp.breakpointIDCounter++
newBreakpoint.ID = dbp.breakpointIDCounter
}
thread := dbp.Threads[tid]
originalData, err := thread.readMemory(uintptr(addr), dbp.arch.BreakpointSize())
if err != nil {
return nil, err
}
if err := dbp.writeSoftwareBreakpoint(thread, addr); err != nil {
return nil, err
}
newBreakpoint.OriginalData = originalData
dbp.Breakpoints[addr] = newBreakpoint
return newBreakpoint, nil
}
// ClearBreakpoint clears the breakpoint at addr.
func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
if dbp.exited {
return nil, &ProcessExitedError{}
}
bp, ok := dbp.FindBreakpoint(addr)
if !ok {
return nil, NoBreakpointError{addr: addr}
}
if _, err := bp.Clear(dbp.CurrentThread); err != nil {
return nil, err
}
delete(dbp.Breakpoints, addr)
return bp, nil
}
// Status returns the status of the current main thread context.
func (dbp *Process) Status() *WaitStatus {
return dbp.CurrentThread.Status
}
// Next continues execution until the next source line.
func (dbp *Process) Next() (err error) {
if dbp.exited {
return &ProcessExitedError{}
}
for i := range dbp.Breakpoints {
if dbp.Breakpoints[i].Internal() {
return fmt.Errorf("next while nexting")
}
}
if err = dbp.next(false); err != nil {
switch err.(type) {
case ThreadBlockedError, NoReturnAddr: // Noop
default:
dbp.ClearInternalBreakpoints()
return
}
}
return dbp.Continue()
}
// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// or is otherwise stopped.
func (dbp *Process) Continue() error {
if dbp.exited {
return &ProcessExitedError{}
}
for {
if err := dbp.resume(); err != nil {
return err
}
dbp.allGCache = nil
for _, th := range dbp.Threads {
th.clearBreakpointState()
}
trapthread, err := dbp.trapWait(-1)
if err != nil {
return err
}
if err := dbp.Halt(); err != nil {
return dbp.exitGuard(err)
}
if err := dbp.setCurrentBreakpoints(trapthread); err != nil {
return err
}
if err := dbp.pickCurrentThread(trapthread); err != nil {
return err
}
switch {
case dbp.CurrentThread.CurrentBreakpoint == nil:
// runtime.Breakpoint or manual stop
if dbp.CurrentThread.onRuntimeBreakpoint() {
for i := 0; i < 2; i++ {
if err = dbp.CurrentThread.StepInstruction(); err != nil {
return err
}
}
}
return dbp.conditionErrors()
case dbp.CurrentThread.onTriggeredInternalBreakpoint():
if dbp.CurrentThread.CurrentBreakpoint.Kind == StepBreakpoint {
// See description of proc.(*Process).next for the meaning of StepBreakpoints
if err := dbp.conditionErrors(); err != nil {
return err
}
pc, err := dbp.CurrentThread.PC()
if err != nil {
return err
}
text, err := dbp.CurrentThread.Disassemble(pc, pc+maxInstructionLength, true)
if err != nil {
return err
}
// here we either set a breakpoint into the destination of the CALL
// instruction or we determined that the called function is hidden,
// either way we need to resume execution
if err = dbp.setStepIntoBreakpoint(text, sameGoroutineCondition(dbp.SelectedGoroutine)); err != nil {
return err
}
} else {
if err := dbp.ClearInternalBreakpoints(); err != nil {
return err
}
return dbp.conditionErrors()
}
case dbp.CurrentThread.onTriggeredBreakpoint():
onNextGoroutine, err := dbp.CurrentThread.onNextGoroutine()
if err != nil {
return err
}
if onNextGoroutine {
err := dbp.ClearInternalBreakpoints()
if err != nil {
return err
}
}
return dbp.conditionErrors()
default:
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
}
}
}
func (dbp *Process) conditionErrors() error {
var condErr error
for _, th := range dbp.Threads {
if th.CurrentBreakpoint != nil && th.BreakpointConditionError != nil {
if condErr == nil {
condErr = th.BreakpointConditionError
} else {
return fmt.Errorf("multiple errors evaluating conditions")
}
}
}
return condErr
}
// pick a new dbp.CurrentThread, with the following priority:
// - a thread with onTriggeredInternalBreakpoint() == true
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
// - trapthread
func (dbp *Process) pickCurrentThread(trapthread *Thread) error {
for _, th := range dbp.Threads {
if th.onTriggeredInternalBreakpoint() {
return dbp.SwitchThread(th.ID)
}
}
if trapthread.onTriggeredBreakpoint() {
return dbp.SwitchThread(trapthread.ID)
}
for _, th := range dbp.Threads {
if th.onTriggeredBreakpoint() {
return dbp.SwitchThread(th.ID)
}
}
return dbp.SwitchThread(trapthread.ID)
}
// Step will continue until another source line is reached.
// Will step into functions.
func (dbp *Process) Step() (err error) {
if dbp.exited {
return &ProcessExitedError{}
}
for i := range dbp.Breakpoints {
if dbp.Breakpoints[i].Internal() {
return fmt.Errorf("next while nexting")
}
}
if err = dbp.next(true); err != nil {
switch err.(type) {
case ThreadBlockedError, NoReturnAddr: // Noop
default:
dbp.ClearInternalBreakpoints()
return
}
}
return dbp.Continue()
}
// Returns an expression that evaluates to true when the current goroutine is g
func sameGoroutineCondition(g *G) ast.Expr {
if g == nil {
return nil
}
return &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{
X: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "curg"},
},
Sel: &ast.Ident{Name: "goid"},
},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(g.ID)},
}
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// asssociated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Process) StepInstruction() (err error) {
if dbp.SelectedGoroutine == nil {
return errors.New("cannot single step: no selected goroutine")
}
if dbp.SelectedGoroutine.thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(dbp.SelectedGoroutine.PC, NextBreakpoint, sameGoroutineCondition(dbp.SelectedGoroutine)); err != nil {
return err
}
return dbp.Continue()
}
dbp.allGCache = nil
if dbp.exited {
return &ProcessExitedError{}
}
dbp.SelectedGoroutine.thread.clearBreakpointState()
err = dbp.SelectedGoroutine.thread.StepInstruction()
if err != nil {
return err
}
return dbp.SelectedGoroutine.thread.SetCurrentBreakpoint()
}
// StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed
func (dbp *Process) StepOut() error {
cond := sameGoroutineCondition(dbp.SelectedGoroutine)
topframe, err := topframe(dbp.SelectedGoroutine, dbp.CurrentThread)
if err != nil {
return err
}
pcs := []uint64{}
var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" {
if dbp.SelectedGoroutine != nil && dbp.SelectedGoroutine.DeferPC != 0 {
_, _, deferfn := dbp.goSymTable.PCToLine(dbp.SelectedGoroutine.DeferPC)
deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false)
if err != nil {
return err
}
pcs = append(pcs, deferpc)
}
}
if topframe.Ret == 0 && deferpc == 0 {
return errors.New("nothing to stepout to")
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
if bp != nil {
// For StepOut we do not want to step into the deferred function
// when it's called by runtime.deferreturn so we do not populate
// DeferReturns.
bp.DeferReturns = []uint64{}
}
}
if topframe.Ret != 0 {
if err := dbp.setInternalBreakpoints(topframe.Current.PC, []uint64{topframe.Ret}, NextBreakpoint, cond); err != nil {
return err
}
}
return dbp.Continue()
}
// SwitchThread changes from current thread to the thread specified by `tid`.
func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited {
return &ProcessExitedError{}
}
if th, ok := dbp.Threads[tid]; ok {
dbp.CurrentThread = th
dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG()
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// SwitchGoroutine changes from current thread to the thread
// running the specified goroutine.
func (dbp *Process) SwitchGoroutine(gid int) error {
if dbp.exited {
return &ProcessExitedError{}
}
g, err := dbp.FindGoroutine(gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and SelectedGoroutine is nil
return nil
}
if g.thread != nil {
return dbp.SwitchThread(g.thread.ID)
}
dbp.SelectedGoroutine = g
return nil
}
// GoroutinesInfo returns an array of G structures representing the information
// Delve cares about from the internal runtime G structure.
func (dbp *Process) GoroutinesInfo() ([]*G, error) {
if dbp.exited {
return nil, &ProcessExitedError{}
}
if dbp.allGCache != nil {
return dbp.allGCache, nil
}
var (
threadg = map[int]*Thread{}
allg []*G
rdr = dbp.DwarfReader()
)
for i := range dbp.Threads {
if dbp.Threads[i].blocked() {
continue
}
g, _ := dbp.Threads[i].GetG()
if g != nil {
threadg[g.ID] = dbp.Threads[i]
}
}
addr, err := rdr.AddrFor("runtime.allglen")
if err != nil {
return nil, err
}
allglenBytes, err := dbp.CurrentThread.readMemory(uintptr(addr), 8)
if err != nil {
return nil, err
}
allglen := binary.LittleEndian.Uint64(allglenBytes)
rdr.Seek(0)
allgentryaddr, err := rdr.AddrFor("runtime.allgs")
if err != nil {
// try old name (pre Go 1.6)
allgentryaddr, err = rdr.AddrFor("runtime.allg")
if err != nil {
return nil, err
}
}
faddr, err := dbp.CurrentThread.readMemory(uintptr(allgentryaddr), dbp.arch.PtrSize())
allgptr := binary.LittleEndian.Uint64(faddr)
for i := uint64(0); i < allglen; i++ {
gvar, err := dbp.CurrentThread.newGVariable(uintptr(allgptr+(i*uint64(dbp.arch.PtrSize()))), true)
if err != nil {
return nil, err
}
g, err := gvar.parseG()
if err != nil {
return nil, err
}
if thread, allocated := threadg[g.ID]; allocated {
loc, err := thread.Location()
if err != nil {
return nil, err
}
g.thread = thread
// Prefer actual thread location information.
g.CurrentLoc = *loc
}
if g.Status != Gdead {
allg = append(allg, g)
}
}
dbp.allGCache = allg
return allg, nil
}
func (g *G) Thread() *Thread {
return g.thread
}
// Halt stops all threads.
func (dbp *Process) Halt() (err error) {
if dbp.exited {
return &ProcessExitedError{}
}
for _, th := range dbp.Threads {
if err := th.Halt(); err != nil {
return err
}
}
return nil
}
// Registers obtains register values from the
// "current" thread of the traced process.
func (dbp *Process) Registers() (Registers, error) {
return dbp.CurrentThread.Registers(false)
}
// PC returns the PC of the current thread.
func (dbp *Process) PC() (uint64, error) {
return dbp.CurrentThread.PC()
}
// CurrentBreakpoint returns the breakpoint the current thread
// is stopped at.
func (dbp *Process) CurrentBreakpoint() *Breakpoint {
return dbp.CurrentThread.CurrentBreakpoint
}
// DwarfReader returns a reader for the dwarf data
func (dbp *Process) DwarfReader() *reader.Reader {
return reader.New(dbp.dwarf)
}
// Sources returns list of source files that comprise the debugged binary.
func (dbp *Process) Sources() map[string]*gosym.Obj {
return dbp.goSymTable.Files
}
// Funcs returns list of functions present in the debugged program.
func (dbp *Process) Funcs() []gosym.Func {
return dbp.goSymTable.Funcs
}
// Types returns list of types present in the debugged program.
func (dbp *Process) Types() ([]string, error) {
types := make([]string, 0, len(dbp.types))
for k := range dbp.types {
types = append(types, k)
}
return types, nil
}
// PCToLine converts an instruction address to a file/line/function.
func (dbp *Process) PCToLine(pc uint64) (string, int, *gosym.Func) {
return dbp.goSymTable.PCToLine(pc)
}
// FindBreakpointByID finds the breakpoint for the given ID.
func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) {
for _, bp := range dbp.Breakpoints {
if bp.ID == id {
return bp, true
}
}
return nil, false
}
// FindBreakpoint finds the breakpoint for the given pc.
func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
if bp, ok := dbp.Breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok {
return bp, true
}
// Directly use addr to lookup breakpoint.
if bp, ok := dbp.Breakpoints[pc]; ok {
return bp, true
}
return nil, false
}
// Returns a new Process struct.
func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, error) {
if attach {
var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.Pid) })
if err != nil {
return nil, err
}
_, _, err = dbp.wait(dbp.Pid, 0)
if err != nil {
return nil, err
}
}
proc, err := os.FindProcess(dbp.Pid)
if err != nil {
return nil, err
}
dbp.Process = proc
err = dbp.LoadInformation(path)
if err != nil {
return nil, err
}
if err := dbp.updateThreadList(); err != nil {
return nil, err
}
ver, isextld, err := dbp.getGoInformation()
if err != nil {
return nil, err
}
dbp.arch.SetGStructOffset(ver, isextld)
// SelectedGoroutine can not be set correctly by the call to updateThreadList
// because without calling SetGStructOffset we can not read the G struct of CurrentThread
// but without calling updateThreadList we can not examine memory to determine
// the offset of g struct inside TLS
dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG()
panicpc, err := dbp.FindFunctionLocation("runtime.startpanic", true, 0)
if err == nil {
bp, err := dbp.SetBreakpoint(panicpc, UserBreakpoint, nil)
if err == nil {
bp.Name = "unrecovered-panic"
bp.ID = -1
dbp.breakpointIDCounter--
}
}
return dbp, nil
}
func (dbp *Process) ClearInternalBreakpoints() error {
for _, bp := range dbp.Breakpoints {
if !bp.Internal() {
continue
}
if _, err := dbp.ClearBreakpoint(bp.Addr); err != nil {
return err
}
}
for i := range dbp.Threads {
if dbp.Threads[i].CurrentBreakpoint != nil && dbp.Threads[i].CurrentBreakpoint.Internal() {
dbp.Threads[i].CurrentBreakpoint = nil
}
}
return nil
}
func (dbp *Process) handlePtraceFuncs() {
// We must ensure here that we are running on the same thread during
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
for fn := range dbp.ptraceChan {
fn()
dbp.ptraceDoneChan <- nil
}
}
func (dbp *Process) execPtraceFunc(fn func()) {
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) {
vv, err := dbp.EvalPackageVariable("runtime.buildVersion", LoadConfig{true, 0, 64, 0, 0})
if err != nil {
err = fmt.Errorf("Could not determine version number: %v\n", err)
return
}
if vv.Unreadable != nil {
err = fmt.Errorf("Unreadable version number: %v\n", vv.Unreadable)
return
}
ver, ok := ParseVersionString(constant.StringVal(vv.Value))
if !ok {
err = fmt.Errorf("Could not parse version number: %v\n", vv.Value)
return
}
rdr := dbp.DwarfReader()
rdr.Seek(0)
for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() {
if err != nil {
return ver, isextld, err
}
if prod, ok := entry.Val(dwarf.AttrProducer).(string); ok && (strings.HasPrefix(prod, "GNU AS")) {
isextld = true
break
}
}
return
}
// FindGoroutine returns a G struct representing the goroutine
// specified by `gid`.
func (dbp *Process) FindGoroutine(gid int) (*G, error) {
if gid == -1 {
return dbp.SelectedGoroutine, nil
}
gs, err := dbp.GoroutinesInfo()
if err != nil {
return nil, err
}
for i := range gs {
if gs[i].ID == gid {
return gs[i], nil
}
}
return nil, fmt.Errorf("Unknown goroutine %d", gid)
}
// ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame.
func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) {
if dbp.exited {
return nil, &ProcessExitedError{}
}
g, err := dbp.FindGoroutine(gid)
if err != nil {
return nil, err
}
if g == nil {
return dbp.CurrentThread.Scope()
}
var out EvalScope
if g.thread == nil {
out.Thread = dbp.CurrentThread
} else {
out.Thread = g.thread
}
locs, err := g.Stacktrace(frame)
if err != nil {
return nil, err
}
if frame >= len(locs) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
}
out.PC, out.CFA = locs[frame].Current.PC, locs[frame].CFA
return &out, nil
}
func (dbp *Process) postExit() {
dbp.exited = true
close(dbp.ptraceChan)
close(dbp.ptraceDoneChan)
}

View File

@ -1,643 +0,0 @@
package proc
import (
"debug/gosym"
"debug/pe"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"unsafe"
sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"golang.org/x/debug/dwarf"
)
// OSProcessDetails holds Windows specific information.
type OSProcessDetails struct {
hProcess syscall.Handle
breakThread int
}
// Launch creates and begins debugging a new process.
func Launch(cmd []string, wd string) (*Process, error) {
argv0Go, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
}
// Make sure the binary exists and is an executable file
if filepath.Base(cmd[0]) == cmd[0] {
if _, err := exec.LookPath(cmd[0]); err != nil {
return nil, err
}
}
peFile, err := openExecutablePath(argv0Go)
if err != nil {
return nil, NotExecutableErr
}
peFile.Close()
// Duplicate the stdin/stdout/stderr handles
files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)}
p, _ := syscall.GetCurrentProcess()
fd := make([]syscall.Handle, len(files))
for i := range files {
err := syscall.DuplicateHandle(p, syscall.Handle(files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(syscall.Handle(fd[i]))
}
argv0, err := syscall.UTF16PtrFromString(argv0Go)
if err != nil {
return nil, err
}
// create suitable command line for CreateProcess
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L326
// adapted from standard library makeCmdLine
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L86
var cmdLineGo string
if len(cmd) >= 1 {
for _, v := range cmd {
if cmdLineGo != "" {
cmdLineGo += " "
}
cmdLineGo += syscall.EscapeArg(v)
}
}
var cmdLine *uint16
if cmdLineGo != "" {
if cmdLine, err = syscall.UTF16PtrFromString(cmdLineGo); err != nil {
return nil, err
}
}
var workingDir *uint16
if wd != "" {
if workingDir, err = syscall.UTF16PtrFromString(wd); err != nil {
return nil, err
}
}
// Initialize the startup info and create process
si := new(sys.StartupInfo)
si.Cb = uint32(unsafe.Sizeof(*si))
si.Flags = syscall.STARTF_USESTDHANDLES
si.StdInput = sys.Handle(fd[0])
si.StdOutput = sys.Handle(fd[1])
si.StdErr = sys.Handle(fd[2])
pi := new(sys.ProcessInformation)
dbp := New(0)
dbp.execPtraceFunc(func() {
if wd == "" {
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi)
} else {
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, workingDir, si, pi)
}
})
if err != nil {
return nil, err
}
sys.CloseHandle(sys.Handle(pi.Process))
sys.CloseHandle(sys.Handle(pi.Thread))
dbp.Pid = int(pi.ProcessId)
return newDebugProcess(dbp, argv0Go)
}
// newDebugProcess prepares process pid for debugging.
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
// It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
// immediately after launching under DEBUG_ONLY_THIS_PROCESS.
// Attaching with DebugActiveProcess has similar effect.
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
}
if tid == 0 {
dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
}
// Suspend all threads so that the call to _ContinueDebugEvent will
// not resume the target.
for _, thread := range dbp.Threads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
})
if err != nil {
return nil, err
}
return initializeDebugProcess(dbp, exepath, false)
}
// findExePath searches for process pid, and returns its executable path.
func findExePath(pid int) (string, error) {
// Original code suggested different approach (see below).
// Maybe it could be useful in the future.
//
// Find executable path from PID/handle on Windows:
// https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
p, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid))
if err != nil {
return "", err
}
defer syscall.CloseHandle(p)
n := uint32(128)
for {
buf := make([]uint16, int(n))
err = _QueryFullProcessImageName(p, 0, &buf[0], &n)
switch err {
case syscall.ERROR_INSUFFICIENT_BUFFER:
// try bigger buffer
n *= 2
// but stop if it gets too big
if n > 10000 {
return "", err
}
case nil:
return syscall.UTF16ToString(buf[:n]), nil
default:
return "", err
}
}
}
// Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) {
// TODO: Probably should have SeDebugPrivilege before starting here.
err := _DebugActiveProcess(uint32(pid))
if err != nil {
return nil, err
}
exepath, err := findExePath(pid)
if err != nil {
return nil, err
}
return newDebugProcess(New(pid), exepath)
}
// Kill kills the process.
func (dbp *Process) Kill() error {
if dbp.exited {
return nil
}
if !dbp.Threads[dbp.Pid].Stopped() {
return errors.New("process must be stopped in order to kill it")
}
// TODO: Should not have to ignore failures here,
// but some tests appear to Kill twice causing
// this to fail on second attempt.
_ = syscall.TerminateProcess(dbp.os.hProcess, 1)
dbp.exited = true
return nil
}
func (dbp *Process) requestManualStop() error {
return _DebugBreakProcess(dbp.os.hProcess)
}
func (dbp *Process) updateThreadList() error {
// We ignore this request since threads are being
// tracked as they are created/killed in waitForDebugEvent.
return nil
}
func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, suspendNewThreads bool) (*Thread, error) {
if thread, ok := dbp.Threads[threadID]; ok {
return thread, nil
}
thread := &Thread{
ID: threadID,
dbp: dbp,
os: new(OSSpecificDetails),
}
thread.os.hThread = hThread
dbp.Threads[threadID] = thread
if dbp.CurrentThread == nil {
dbp.SwitchThread(thread.ID)
}
if suspendNewThreads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
return thread, nil
}
func (dbp *Process) parseDebugFrame(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameSec := exe.Section(".debug_frame")
debugInfoSec := exe.Section(".debug_info")
if debugFrameSec != nil && debugInfoSec != nil {
debugFrame, err := debugFrameSec.Data()
if err != nil && uint32(len(debugFrame)) < debugFrameSec.Size {
fmt.Println("could not get .debug_frame section", err)
os.Exit(1)
}
if 0 < debugFrameSec.VirtualSize && debugFrameSec.VirtualSize < debugFrameSec.Size {
debugFrame = debugFrame[:debugFrameSec.VirtualSize]
}
dat, err := debugInfoSec.Data()
if err != nil {
fmt.Println("could not get .debug_info section", err)
os.Exit(1)
}
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
} else {
fmt.Println("could not find .debug_frame section in binary")
os.Exit(1)
}
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
for _, s := range f.Symbols {
if s.Name != name {
continue
}
if s.SectionNumber <= 0 {
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
}
if len(f.Sections) < int(s.SectionNumber) {
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
}
return s, nil
}
return nil, fmt.Errorf("no %s symbol found", name)
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func loadPETable(f *pe.File, sname, ename string) ([]byte, error) {
ssym, err := findPESymbol(f, sname)
if err != nil {
return nil, err
}
esym, err := findPESymbol(f, ename)
if err != nil {
return nil, err
}
if ssym.SectionNumber != esym.SectionNumber {
return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename)
}
sect := f.Sections[ssym.SectionNumber-1]
data, err := sect.Data()
if err != nil {
return nil, err
}
return data[ssym.Value:esym.Value], nil
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func pcln(exe *pe.File) (textStart uint64, symtab, pclntab []byte, err error) {
var imageBase uint64
switch oh := exe.OptionalHeader.(type) {
case *pe.OptionalHeader32:
imageBase = uint64(oh.ImageBase)
case *pe.OptionalHeader64:
imageBase = oh.ImageBase
default:
return 0, nil, nil, fmt.Errorf("pe file format not recognized")
}
if sect := exe.Section(".text"); sect != nil {
textStart = imageBase + uint64(sect.VirtualAddress)
}
if pclntab, err = loadPETable(exe, "runtime.pclntab", "runtime.epclntab"); err != nil {
// We didn't find the symbols, so look for the names used in 1.3 and earlier.
// TODO: Remove code looking for the old symbols when we no longer care about 1.3.
var err2 error
if pclntab, err2 = loadPETable(exe, "pclntab", "epclntab"); err2 != nil {
return 0, nil, nil, err
}
}
if symtab, err = loadPETable(exe, "runtime.symtab", "runtime.esymtab"); err != nil {
// Same as above.
var err2 error
if symtab, err2 = loadPETable(exe, "symtab", "esymtab"); err2 != nil {
return 0, nil, nil, err
}
}
return textStart, symtab, pclntab, nil
}
func (dbp *Process) obtainGoSymbols(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
_, symdat, pclndat, err := pcln(exe)
if err != nil {
fmt.Println("could not get Go symbols", err)
os.Exit(1)
}
pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset))
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.goSymTable = tab
}
func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
if sec := exe.Section(".debug_line"); sec != nil {
debugLine, err := sec.Data()
if err != nil && uint32(len(debugLine)) < sec.Size {
fmt.Println("could not get .debug_line section", err)
os.Exit(1)
}
if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size {
debugLine = debugLine[:sec.VirtualSize]
}
dbp.lineInfo = line.Parse(debugLine)
} else {
fmt.Println("could not find .debug_line section in binary")
os.Exit(1)
}
}
var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
func (dbp *Process) findExecutable(path string) (*pe.File, string, error) {
peFile, err := openExecutablePath(path)
if err != nil {
return nil, path, err
}
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
return nil, path, UnsupportedArchErr
}
dbp.dwarf, err = dwarfFromPE(peFile)
if err != nil {
return nil, path, err
}
return peFile, path, nil
}
func openExecutablePath(path string) (*pe.File, error) {
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return nil, err
}
return pe.NewFile(f)
}
// Adapted from src/debug/pe/file.go: pe.(*File).DWARF()
func dwarfFromPE(f *pe.File) (*dwarf.Data, error) {
// There are many other DWARF sections, but these
// are the ones the debug/dwarf package uses.
// Don't bother loading others.
var names = [...]string{"abbrev", "info", "line", "str"}
var dat [len(names)][]byte
for i, name := range names {
name = ".debug_" + name
s := f.Section(name)
if s == nil {
continue
}
b, err := s.Data()
if err != nil && uint32(len(b)) < s.Size {
return nil, err
}
if 0 < s.VirtualSize && s.VirtualSize < s.Size {
b = b[:s.VirtualSize]
}
dat[i] = b
}
abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
}
type waitForDebugEventFlags int
const (
waitBlocking waitForDebugEventFlags = 1 << iota
waitSuspendNewThreads
)
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
var debugEvent _DEBUG_EVENT
shouldExit := false
for {
continueStatus := uint32(_DBG_CONTINUE)
var milliseconds uint32 = 0
if flags&waitBlocking != 0 {
milliseconds = syscall.INFINITE
}
// Wait for a debug event...
err := _WaitForDebugEvent(&debugEvent, milliseconds)
if err != nil {
return 0, 0, err
}
// ... handle each event kind ...
unionPtr := unsafe.Pointer(&debugEvent.U[0])
switch debugEvent.DebugEventCode {
case _CREATE_PROCESS_DEBUG_EVENT:
debugInfo := (*_CREATE_PROCESS_DEBUG_INFO)(unionPtr)
hFile := debugInfo.File
if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile)
if err != nil {
return 0, 0, err
}
}
dbp.os.hProcess = debugInfo.Process
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _CREATE_THREAD_DEBUG_EVENT:
debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr)
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _EXIT_THREAD_DEBUG_EVENT:
delete(dbp.Threads, int(debugEvent.ThreadId))
break
case _OUTPUT_DEBUG_STRING_EVENT:
//TODO: Handle debug output strings
break
case _LOAD_DLL_DEBUG_EVENT:
debugInfo := (*_LOAD_DLL_DEBUG_INFO)(unionPtr)
hFile := debugInfo.File
if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile)
if err != nil {
return 0, 0, err
}
}
break
case _UNLOAD_DLL_DEBUG_EVENT:
break
case _RIP_EVENT:
break
case _EXCEPTION_DEBUG_EVENT:
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
if code := exception.ExceptionRecord.ExceptionCode; code == _EXCEPTION_BREAKPOINT || code == _EXCEPTION_SINGLE_STEP {
tid := int(debugEvent.ThreadId)
dbp.os.breakThread = tid
return tid, 0, nil
} else {
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
}
case _EXIT_PROCESS_DEBUG_EVENT:
debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr)
exitCode = int(debugInfo.ExitCode)
shouldExit = true
default:
return 0, 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode)
}
// .. and then continue unless we received an event that indicated we should break into debugger.
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
if err != nil {
return 0, 0, err
}
if shouldExit {
return 0, exitCode, nil
}
}
}
func (dbp *Process) trapWait(pid int) (*Thread, error) {
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
}
if tid == 0 {
dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
}
th := dbp.Threads[tid]
return th, nil
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
return 0, nil, fmt.Errorf("not implemented: wait")
}
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
// While the debug event that stopped the target was being propagated
// other target threads could generate other debug events.
// After this function we need to know about all the threads
// stopped on a breakpoint. To do that we first suspend all target
// threads and then repeatedly call _ContinueDebugEvent followed by
// waitForDebugEvent in non-blocking mode.
// We need to explicitly call SuspendThread because otherwise the
// call to _ContinueDebugEvent will resume execution of some of the
// target threads.
err := trapthread.SetCurrentBreakpoint()
if err != nil {
return err
}
for _, thread := range dbp.Threads {
thread.running = false
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return err
}
}
for {
var err error
var tid int
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
if err == nil {
tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads)
}
})
if err != nil {
return err
}
if tid == 0 {
break
}
err = dbp.Threads[tid].SetCurrentBreakpoint()
if err != nil {
return err
}
}
return nil
}
func (dbp *Process) exitGuard(err error) error {
return err
}
func (dbp *Process) resume() error {
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
}
}
for _, thread := range dbp.Threads {
thread.running = true
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}
return nil
}
func killProcess(pid int) error {
p, err := os.FindProcess(pid)
if err != nil {
return err
}
return p.Kill()
}

View File

@ -1,13 +0,0 @@
package proc
import (
"fmt"
)
func PtraceAttach(pid int) error {
return fmt.Errorf("not implemented: PtraceAttach")
}
func PtraceDetach(tid, sig int) error {
return _DebugActiveProcessStop(uint32(tid))
}

View File

@ -1,230 +0,0 @@
package proc
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"strings"
)
// Registers is an interface for a generic register type. The
// interface encapsulates the generic values / actions
// we need independent of arch. The concrete register types
// will be different depending on OS/Arch.
type Registers interface {
PC() uint64
SP() uint64
CX() uint64
TLS() uint64
Get(int) (uint64, error)
SetPC(*Thread, uint64) error
Slice() []Register
}
type Register struct {
Name string
Value string
}
func appendWordReg(regs []Register, name string, value uint16) []Register {
return append(regs, Register{name, fmt.Sprintf("%#04x", value)})
}
func appendDwordReg(regs []Register, name string, value uint32) []Register {
return append(regs, Register{name, fmt.Sprintf("%#08x", value)})
}
func appendQwordReg(regs []Register, name string, value uint64) []Register {
return append(regs, Register{name, fmt.Sprintf("%#016x", value)})
}
func appendFlagReg(regs []Register, name string, value uint64, descr flagRegisterDescr, size int) []Register {
return append(regs, Register{name, descr.Describe(value, size)})
}
func appendX87Reg(regs []Register, index int, exponent uint16, mantissa uint64) []Register {
var f float64
fset := false
const (
_SIGNBIT = 1 << 15
_EXP_BIAS = (1 << 14) - 1 // 2^(n-1) - 1 = 16383
_SPECIALEXP = (1 << 15) - 1 // all bits set
_HIGHBIT = 1 << 63
_QUIETBIT = 1 << 62
)
sign := 1.0
if exponent&_SIGNBIT != 0 {
sign = -1.0
}
exponent &= ^uint16(_SIGNBIT)
NaN := math.NaN()
Inf := math.Inf(+1)
switch exponent {
case 0:
switch {
case mantissa == 0:
f = sign * 0.0
fset = true
case mantissa&_HIGHBIT != 0:
f = NaN
fset = true
}
case _SPECIALEXP:
switch {
case mantissa&_HIGHBIT == 0:
f = sign * Inf
fset = true
default:
f = NaN // signaling NaN
fset = true
}
default:
if mantissa&_HIGHBIT == 0 {
f = NaN
fset = true
}
}
if !fset {
significand := float64(mantissa) / (1 << 63)
f = sign * math.Ldexp(significand, int(exponent-_EXP_BIAS))
}
return append(regs, Register{fmt.Sprintf("ST(%d)", index), fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)})
}
func appendSSEReg(regs []Register, name string, xmm []byte) []Register {
buf := bytes.NewReader(xmm)
var out bytes.Buffer
var vi [16]uint8
for i := range vi {
binary.Read(buf, binary.LittleEndian, &vi[i])
}
fmt.Fprintf(&out, "0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8], vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0])
fmt.Fprintf(&out, "\tv2_int={ %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x }", vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0], vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8])
fmt.Fprintf(&out, "\tv4_int={ %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x }", vi[3], vi[2], vi[1], vi[0], vi[7], vi[6], vi[5], vi[4], vi[11], vi[10], vi[9], vi[8], vi[15], vi[14], vi[13], vi[12])
fmt.Fprintf(&out, "\tv8_int={ %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x }", vi[1], vi[0], vi[3], vi[2], vi[5], vi[4], vi[7], vi[6], vi[9], vi[8], vi[11], vi[10], vi[13], vi[12], vi[15], vi[14])
fmt.Fprintf(&out, "\tv16_int={ %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x }", vi[0], vi[1], vi[2], vi[3], vi[4], vi[5], vi[6], vi[7], vi[8], vi[9], vi[10], vi[11], vi[12], vi[13], vi[14], vi[15])
buf.Seek(0, os.SEEK_SET)
var v2 [2]float64
for i := range v2 {
binary.Read(buf, binary.LittleEndian, &v2[i])
}
fmt.Fprintf(&out, "\tv2_float={ %g %g }", v2[0], v2[1])
buf.Seek(0, os.SEEK_SET)
var v4 [4]float32
for i := range v4 {
binary.Read(buf, binary.LittleEndian, &v4[i])
}
fmt.Fprintf(&out, "\tv4_float={ %g %g %g %g }", v4[0], v4[1], v4[2], v4[3])
return append(regs, Register{name, out.String()})
}
var UnknownRegisterError = errors.New("unknown register")
// Registers obtains register values from the debugged process.
func (t *Thread) Registers(floatingPoint bool) (Registers, error) {
return registers(t, floatingPoint)
}
// PC returns the current PC for this thread.
func (t *Thread) PC() (uint64, error) {
regs, err := t.Registers(false)
if err != nil {
return 0, err
}
return regs.PC(), nil
}
type flagRegisterDescr []flagDescr
type flagDescr struct {
name string
mask uint64
}
var mxcsrDescription flagRegisterDescr = []flagDescr{
{"FZ", 1 << 15},
{"RZ/RN", 1<<14 | 1<<13},
{"PM", 1 << 12},
{"UM", 1 << 11},
{"OM", 1 << 10},
{"ZM", 1 << 9},
{"DM", 1 << 8},
{"IM", 1 << 7},
{"DAZ", 1 << 6},
{"PE", 1 << 5},
{"UE", 1 << 4},
{"OE", 1 << 3},
{"ZE", 1 << 2},
{"DE", 1 << 1},
{"IE", 1 << 0},
}
var eflagsDescription flagRegisterDescr = []flagDescr{
{"CF", 1 << 0},
{"", 1 << 1},
{"PF", 1 << 2},
{"AF", 1 << 4},
{"ZF", 1 << 6},
{"SF", 1 << 7},
{"TF", 1 << 8},
{"IF", 1 << 9},
{"DF", 1 << 10},
{"OF", 1 << 11},
{"IOPL", 1<<12 | 1<<13},
{"NT", 1 << 14},
{"RF", 1 << 16},
{"VM", 1 << 17},
{"AC", 1 << 18},
{"VIF", 1 << 19},
{"VIP", 1 << 20},
{"ID", 1 << 21},
}
func (descr flagRegisterDescr) Mask() uint64 {
var r uint64
for _, f := range descr {
r = r | f.mask
}
return r
}
func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
var r []string
for _, f := range descr {
if f.name == "" {
continue
}
// rbm is f.mask with only the right-most bit set:
// 0001 1100 -> 0000 0100
rbm := f.mask & -f.mask
if rbm == f.mask {
if reg&f.mask != 0 {
r = append(r, f.name)
}
} else {
x := (reg & f.mask) >> uint64(math.Log2(float64(rbm)))
r = append(r, fmt.Sprintf("%s=%x", f.name, x))
}
}
if reg & ^descr.Mask() != 0 {
r = append(r, fmt.Sprintf("unknown_flags=%x", reg&^descr.Mask()))
}
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
}

Some files were not shown because too many files have changed in this diff Show More