mirror of
https://github.com/beego/bee.git
synced 2025-01-23 07:37:13 +00:00
Merge pull request #552 from MZIchenjl/fix-mojave-dlv
Fix delve build error on mojave
This commit is contained in:
commit
10bb0454f6
@ -28,10 +28,10 @@ import (
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"github.com/beego/bee/utils"
|
||||
"github.com/derekparker/delve/pkg/terminal"
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/rpc2"
|
||||
"github.com/derekparker/delve/service/rpccommon"
|
||||
"github.com/derekparker/delve/terminal"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
@ -152,7 +152,8 @@ func startDelveDebugger(addr string, ch chan int) int {
|
||||
APIVersion: 2,
|
||||
WorkingDir: ".",
|
||||
ProcessArgs: []string{abs},
|
||||
}, false)
|
||||
Backend: "default",
|
||||
})
|
||||
if err := server.Run(); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not start debugger server: %v", err)
|
||||
}
|
||||
@ -182,7 +183,7 @@ func startDelveDebugger(addr string, ch chan int) int {
|
||||
}
|
||||
|
||||
// Stop and kill the debugger server once user quits the REPL
|
||||
if err := server.Stop(true); err != nil {
|
||||
if err := server.Stop(); err != nil {
|
||||
beeLogger.Log.Fatalf("Could not stop Delve server: %v", err)
|
||||
}
|
||||
return status
|
||||
|
24
vendor/github.com/cosiner/argv/LICENSE
generated
vendored
Normal file
24
vendor/github.com/cosiner/argv/LICENSE
generated
vendored
Normal 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
34
vendor/github.com/cosiner/argv/argv.go
generated
vendored
Normal 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
79
vendor/github.com/cosiner/argv/cmd.go
generated
vendored
Normal 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
222
vendor/github.com/cosiner/argv/parser.go
generated
vendored
Normal 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
282
vendor/github.com/cosiner/argv/scanner.go
generated
vendored
Normal 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
|
||||
}
|
122
vendor/github.com/derekparker/delve/dwarf/line/line_parser.go
generated
vendored
122
vendor/github.com/derekparker/delve/dwarf/line/line_parser.go
generated
vendored
@ -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
|
||||
}
|
||||
}
|
252
vendor/github.com/derekparker/delve/dwarf/line/state_machine.go
generated
vendored
252
vendor/github.com/derekparker/delve/dwarf/line/state_machine.go
generated
vendored
@ -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.
|
||||
}
|
84
vendor/github.com/derekparker/delve/dwarf/op/op.go
generated
vendored
84
vendor/github.com/derekparker/delve/dwarf/op/op.go
generated
vendored
@ -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
|
||||
}
|
@ -7,7 +7,7 @@ import (
|
||||
"os/user"
|
||||
"path"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,9 +29,24 @@ type SubstitutePathRules []SubstitutePathRule
|
||||
// Config defines all configuration options available to be set through the config file.
|
||||
type Config struct {
|
||||
// Commands aliases.
|
||||
Aliases map[string][]string
|
||||
Aliases map[string][]string `yaml:"aliases"`
|
||||
// Source code path substitution rules.
|
||||
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.
|
||||
@ -49,9 +64,12 @@ func LoadConfig() *Config {
|
||||
|
||||
f, err := os.Open(fullConfigFile)
|
||||
if err != nil {
|
||||
createDefaultConfig(fullConfigFile)
|
||||
f, err = createDefaultConfig(fullConfigFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating default config file: %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
@ -75,22 +93,37 @@ func LoadConfig() *Config {
|
||||
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)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to create config file: %v.", err)
|
||||
return
|
||||
return nil, fmt.Errorf("Unable to create config file: %v.", err)
|
||||
}
|
||||
defer func() {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("Closing config file failed: %v.", err)
|
||||
}
|
||||
}()
|
||||
err = writeDefaultConfig(f)
|
||||
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 {
|
||||
@ -100,6 +133,11 @@ func writeDefaultConfig(f *os.File) error {
|
||||
# This is the default configuration file. Available options are provided, but disabled.
|
||||
# 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.
|
||||
aliases:
|
||||
# command: ["alias1", "alias2"]
|
||||
@ -111,6 +149,15 @@ aliases:
|
||||
# commands.
|
||||
substitute-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
|
||||
}
|
63
vendor/github.com/derekparker/delve/pkg/config/split.go
generated
vendored
Normal file
63
vendor/github.com/derekparker/delve/pkg/config/split.go
generated
vendored
Normal 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
|
||||
}
|
@ -17,6 +17,7 @@ type CommonInformationEntry struct {
|
||||
DataAlignmentFactor int64
|
||||
ReturnAddressRegister uint64
|
||||
InitialInstructions []byte
|
||||
staticBase uint64
|
||||
}
|
||||
|
||||
// Represents a Frame Descriptor Entry in the
|
||||
@ -25,17 +26,14 @@ type FrameDescriptionEntry struct {
|
||||
Length uint32
|
||||
CIE *CommonInformationEntry
|
||||
Instructions []byte
|
||||
begin, end uint64
|
||||
begin, size uint64
|
||||
order binary.ByteOrder
|
||||
}
|
||||
|
||||
// Returns whether or not the given address is within the
|
||||
// bounds of this frame.
|
||||
func (fde *FrameDescriptionEntry) Cover(addr uint64) bool {
|
||||
if (addr - fde.begin) < fde.end {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return (addr - fde.begin) < fde.size
|
||||
}
|
||||
|
||||
// Address of first location for this frame.
|
||||
@ -45,7 +43,7 @@ func (fde *FrameDescriptionEntry) Begin() uint64 {
|
||||
|
||||
// Address of last location for this frame.
|
||||
func (fde *FrameDescriptionEntry) End() uint64 {
|
||||
return fde.begin + fde.end
|
||||
return fde.begin + fde.size
|
||||
}
|
||||
|
||||
// Set up frame for the given PC.
|
||||
@ -53,43 +51,27 @@ func (fde *FrameDescriptionEntry) EstablishFrame(pc uint64) *FrameContext {
|
||||
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
|
||||
|
||||
func NewFrameIndex() FrameDescriptionEntries {
|
||||
return make(FrameDescriptionEntries, 0, 1000)
|
||||
}
|
||||
|
||||
type NoFDEForPCError struct {
|
||||
type ErrNoFDEForPC struct {
|
||||
PC uint64
|
||||
}
|
||||
|
||||
func (err *NoFDEForPCError) Error() string {
|
||||
func (err *ErrNoFDEForPC) Error() string {
|
||||
return fmt.Sprintf("could not find FDE for PC %#v", err.PC)
|
||||
}
|
||||
|
||||
// Returns the Frame Description Entry for the given PC.
|
||||
func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) {
|
||||
idx := sort.Search(len(fdes), func(i int) bool {
|
||||
if fdes[i].Cover(pc) {
|
||||
return true
|
||||
}
|
||||
if fdes[i].LessThan(pc) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return fdes[i].Cover(pc) || fdes[i].Begin() >= pc
|
||||
})
|
||||
if idx == len(fdes) {
|
||||
return nil, &NoFDEForPCError{pc}
|
||||
if idx == len(fdes) || !fdes[idx].Cover(pc) {
|
||||
return nil, &ErrNoFDEForPC{pc}
|
||||
}
|
||||
return fdes[idx], nil
|
||||
}
|
||||
|
||||
func (frame *FrameDescriptionEntry) LessThan(pc uint64) bool {
|
||||
return frame.End() <= pc
|
||||
}
|
@ -7,12 +7,14 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/util"
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
type parsefunc func(*parseContext) parsefunc
|
||||
|
||||
type parseContext struct {
|
||||
staticBase uint64
|
||||
|
||||
buf *bytes.Buffer
|
||||
entries FrameDescriptionEntries
|
||||
common *CommonInformationEntry
|
||||
@ -23,10 +25,10 @@ type parseContext struct {
|
||||
// Parse takes in data (a byte slice) and returns a slice of
|
||||
// commonInformationEntry structures. Each commonInformationEntry
|
||||
// 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 (
|
||||
buf = bytes.NewBuffer(data)
|
||||
pctx = &parseContext{buf: buf, entries: NewFrameIndex()}
|
||||
pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase}
|
||||
)
|
||||
|
||||
for fn := parselength; buf.Len() != 0; {
|
||||
@ -45,12 +47,19 @@ func cieEntry(data []byte) bool {
|
||||
}
|
||||
|
||||
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:]) {
|
||||
ctx.common = &CommonInformationEntry{Length: ctx.length}
|
||||
var data = ctx.buf.Next(4)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -61,8 +70,8 @@ func parselength(ctx *parseContext) parsefunc {
|
||||
func parseFDE(ctx *parseContext) parsefunc {
|
||||
r := ctx.buf.Next(int(ctx.length))
|
||||
|
||||
ctx.frame.begin = binary.LittleEndian.Uint64(r[:8])
|
||||
ctx.frame.end = binary.LittleEndian.Uint64(r[8:16])
|
||||
ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) + ctx.staticBase
|
||||
ctx.frame.size = binary.LittleEndian.Uint64(r[8:16])
|
||||
|
||||
// Insert into the tree after setting address range begin
|
||||
// otherwise compares won't work.
|
@ -5,41 +5,31 @@ import (
|
||||
"encoding/binary"
|
||||
"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 {
|
||||
rule byte
|
||||
offset int64
|
||||
newreg uint64
|
||||
expression []byte
|
||||
Rule Rule
|
||||
Offset int64
|
||||
Reg uint64
|
||||
Expression []byte
|
||||
}
|
||||
|
||||
type FrameContext struct {
|
||||
loc uint64
|
||||
order binary.ByteOrder
|
||||
address uint64
|
||||
cfa CurrentFrameAddress
|
||||
regs map[uint64]DWRule
|
||||
CFA DWRule
|
||||
Regs map[uint64]DWRule
|
||||
initialRegs map[uint64]DWRule
|
||||
prevRegs map[uint64]DWRule
|
||||
buf *bytes.Buffer
|
||||
cie *CommonInformationEntry
|
||||
RetAddrReg uint64
|
||||
codeAlignment uint64
|
||||
dataAlignment int64
|
||||
}
|
||||
|
||||
func (fctx *FrameContext) CFAOffset() int64 {
|
||||
return fctx.cfa.offset
|
||||
}
|
||||
|
||||
// Instructions used to recreate the table from the .debug_frame data.
|
||||
const (
|
||||
DW_CFA_nop = 0x0 // No ops
|
||||
@ -73,15 +63,19 @@ const (
|
||||
)
|
||||
|
||||
// Rules defined for register values.
|
||||
type Rule byte
|
||||
|
||||
const (
|
||||
rule_undefined = iota
|
||||
rule_sameval
|
||||
rule_offset
|
||||
rule_valoffset
|
||||
rule_register
|
||||
rule_expression
|
||||
rule_valexpression
|
||||
rule_architectural
|
||||
RuleUndefined Rule = iota
|
||||
RuleSameVal
|
||||
RuleOffset
|
||||
RuleValOffset
|
||||
RuleRegister
|
||||
RuleExpression
|
||||
RuleValExpression
|
||||
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
|
||||
@ -124,7 +118,8 @@ func executeCIEInstructions(cie *CommonInformationEntry) *FrameContext {
|
||||
copy(initialInstructions, cie.InitialInstructions)
|
||||
frame := &FrameContext{
|
||||
cie: cie,
|
||||
regs: make(map[uint64]DWRule),
|
||||
Regs: make(map[uint64]DWRule),
|
||||
RetAddrReg: cie.ReturnAddressRegister,
|
||||
initialRegs: make(map[uint64]DWRule),
|
||||
prevRegs: make(map[uint64]DWRule),
|
||||
codeAlignment: cie.CodeAlignmentFactor,
|
||||
@ -142,9 +137,7 @@ func executeDwarfProgramUntilPC(fde *FrameDescriptionEntry, pc uint64) *FrameCon
|
||||
frame.order = fde.order
|
||||
frame.loc = fde.Begin()
|
||||
frame.address = pc
|
||||
fdeInstructions := make([]byte, len(fde.Instructions))
|
||||
copy(fdeInstructions, fde.Instructions)
|
||||
frame.ExecuteUntilPC(fdeInstructions)
|
||||
frame.ExecuteUntilPC(fde.Instructions)
|
||||
|
||||
return frame
|
||||
}
|
||||
@ -161,7 +154,7 @@ func (frame *FrameContext) ExecuteUntilPC(instructions []byte) {
|
||||
frame.buf.Write(instructions)
|
||||
|
||||
// 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).
|
||||
for frame.address >= frame.loc && frame.buf.Len() > 0 {
|
||||
executeDwarfInstruction(frame)
|
||||
@ -262,7 +255,7 @@ func offset(frame *FrameContext) {
|
||||
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) {
|
||||
@ -274,9 +267,9 @@ func restore(frame *FrameContext) {
|
||||
reg := uint64(b & low_6_offset)
|
||||
oldrule, ok := frame.initialRegs[reg]
|
||||
if ok {
|
||||
frame.regs[reg] = DWRule{offset: oldrule.offset, rule: rule_offset}
|
||||
frame.Regs[reg] = DWRule{Offset: oldrule.Offset, Rule: RuleOffset}
|
||||
} 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
|
||||
binary.Read(frame.buf, frame.order, &loc)
|
||||
|
||||
frame.loc = loc
|
||||
frame.loc = loc + frame.cie.staticBase
|
||||
}
|
||||
|
||||
func offsetextended(frame *FrameContext) {
|
||||
@ -293,31 +286,31 @@ func offsetextended(frame *FrameContext) {
|
||||
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) {
|
||||
reg, _ := util.DecodeULEB128(frame.buf)
|
||||
frame.regs[reg] = DWRule{rule: rule_undefined}
|
||||
frame.Regs[reg] = DWRule{Rule: RuleUndefined}
|
||||
}
|
||||
|
||||
func samevalue(frame *FrameContext) {
|
||||
reg, _ := util.DecodeULEB128(frame.buf)
|
||||
frame.regs[reg] = DWRule{rule: rule_sameval}
|
||||
frame.Regs[reg] = DWRule{Rule: RuleSameVal}
|
||||
}
|
||||
|
||||
func register(frame *FrameContext) {
|
||||
reg1, _ := 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) {
|
||||
frame.prevRegs = frame.regs
|
||||
frame.prevRegs = frame.Regs
|
||||
}
|
||||
|
||||
func restorestate(frame *FrameContext) {
|
||||
frame.regs = frame.prevRegs
|
||||
frame.Regs = frame.prevRegs
|
||||
}
|
||||
|
||||
func restoreextended(frame *FrameContext) {
|
||||
@ -325,9 +318,9 @@ func restoreextended(frame *FrameContext) {
|
||||
|
||||
oldrule, ok := frame.initialRegs[reg]
|
||||
if ok {
|
||||
frame.regs[reg] = DWRule{offset: oldrule.offset, rule: rule_offset}
|
||||
frame.Regs[reg] = DWRule{Offset: oldrule.Offset, Rule: RuleOffset}
|
||||
} 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)
|
||||
offset, _ := util.DecodeULEB128(frame.buf)
|
||||
|
||||
frame.cfa.register = reg
|
||||
frame.cfa.offset = int64(offset)
|
||||
frame.CFA.Rule = RuleCFA
|
||||
frame.CFA.Reg = reg
|
||||
frame.CFA.Offset = int64(offset)
|
||||
}
|
||||
|
||||
func defcfaregister(frame *FrameContext) {
|
||||
reg, _ := util.DecodeULEB128(frame.buf)
|
||||
frame.cfa.register = reg
|
||||
frame.CFA.Reg = reg
|
||||
}
|
||||
|
||||
func defcfaoffset(frame *FrameContext) {
|
||||
offset, _ := util.DecodeULEB128(frame.buf)
|
||||
frame.cfa.offset = int64(offset)
|
||||
frame.CFA.Offset = int64(offset)
|
||||
}
|
||||
|
||||
func defcfasf(frame *FrameContext) {
|
||||
reg, _ := util.DecodeULEB128(frame.buf)
|
||||
offset, _ := util.DecodeSLEB128(frame.buf)
|
||||
|
||||
frame.cfa.register = reg
|
||||
frame.cfa.offset = offset * frame.dataAlignment
|
||||
frame.CFA.Rule = RuleCFA
|
||||
frame.CFA.Reg = reg
|
||||
frame.CFA.Offset = offset * frame.dataAlignment
|
||||
}
|
||||
|
||||
func defcfaoffsetsf(frame *FrameContext) {
|
||||
offset, _ := util.DecodeSLEB128(frame.buf)
|
||||
offset *= frame.dataAlignment
|
||||
frame.cfa.offset = offset
|
||||
frame.CFA.Offset = offset
|
||||
}
|
||||
|
||||
func defcfaexpression(frame *FrameContext) {
|
||||
@ -369,8 +364,8 @@ func defcfaexpression(frame *FrameContext) {
|
||||
expr = frame.buf.Next(int(l))
|
||||
)
|
||||
|
||||
frame.cfa.expression = expr
|
||||
frame.cfa.rule = rule_expression
|
||||
frame.CFA.Expression = expr
|
||||
frame.CFA.Rule = RuleExpression
|
||||
}
|
||||
|
||||
func expression(frame *FrameContext) {
|
||||
@ -380,7 +375,7 @@ func expression(frame *FrameContext) {
|
||||
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) {
|
||||
@ -389,7 +384,7 @@ func offsetextendedsf(frame *FrameContext) {
|
||||
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) {
|
||||
@ -398,7 +393,7 @@ func valoffset(frame *FrameContext) {
|
||||
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) {
|
||||
@ -407,7 +402,7 @@ func valoffsetsf(frame *FrameContext) {
|
||||
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) {
|
||||
@ -417,7 +412,7 @@ func valexpression(frame *FrameContext) {
|
||||
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) {
|
107
vendor/github.com/derekparker/delve/pkg/dwarf/godwarf/sections.go
generated
vendored
Normal file
107
vendor/github.com/derekparker/delve/pkg/dwarf/godwarf/sections.go
generated
vendored
Normal 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
|
||||
}
|
@ -6,16 +6,44 @@
|
||||
// The format is heavily biased toward C, but for simplicity
|
||||
// the String methods use a pseudo-Go syntax.
|
||||
|
||||
package dwarf
|
||||
// Borrowed from golang.org/x/debug/dwarf/type.go
|
||||
|
||||
package godwarf
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"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
|
||||
// specific Type structures (CharType, StructType, etc.).
|
||||
//TODO: remove this use dwarf.Type
|
||||
type Type interface {
|
||||
Common() *CommonType
|
||||
String() string
|
||||
@ -29,7 +57,7 @@ type CommonType struct {
|
||||
ByteSize int64 // size of value of this type, in bytes
|
||||
Name string // name that can be used to refer to 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 }
|
||||
@ -158,6 +186,7 @@ type StructField struct {
|
||||
ByteSize int64
|
||||
BitOffset int64 // within the ByteSize bytes at ByteOffset
|
||||
BitSize int64 // zero if not a bit field
|
||||
Embedded bool
|
||||
}
|
||||
|
||||
func (t *StructType) String() string {
|
||||
@ -329,31 +358,19 @@ func (t *ChanType) String() 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.
|
||||
func (d *Data) Type(off Offset) (Type, error) {
|
||||
return d.readType("info", d.Reader(), off, d.typeCache)
|
||||
func ReadType(d *dwarf.Data, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) {
|
||||
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)
|
||||
return reflect.Kind(integer)
|
||||
}
|
||||
|
||||
// readType reads a type from r at off of name using and updating a
|
||||
// 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 {
|
||||
return t, nil
|
||||
}
|
||||
@ -364,10 +381,10 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
}
|
||||
addressSize := r.AddressSize()
|
||||
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
|
||||
// d.Type recursively, to handle circular types correctly.
|
||||
var typ Type
|
||||
@ -375,7 +392,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
nextDepth := 0
|
||||
|
||||
// Get next child; set err if error happens.
|
||||
next := func() *Entry {
|
||||
next := func() *dwarf.Entry {
|
||||
if !e.Children {
|
||||
return nil
|
||||
}
|
||||
@ -390,10 +407,6 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
err = err1
|
||||
return nil
|
||||
}
|
||||
if kid == nil {
|
||||
err = DecodeError{name, r.offset(), "unexpected end of DWARF entries"}
|
||||
return nil
|
||||
}
|
||||
if kid.Tag == 0 {
|
||||
if nextDepth > 0 {
|
||||
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.
|
||||
typeOf := func(e *Entry, attr Attr) Type {
|
||||
typeOf := func(e *dwarf.Entry, attr dwarf.Attr) Type {
|
||||
tval := e.Val(attr)
|
||||
var t Type
|
||||
switch toff := tval.(type) {
|
||||
case Offset:
|
||||
if t, err = d.readType(name, r.clone(), toff, typeCache); err != nil {
|
||||
case dwarf.Offset:
|
||||
if t, err = readType(d, name, d.Reader(), toff, typeCache); err != nil {
|
||||
return nil
|
||||
}
|
||||
case uint64:
|
||||
if t, err = d.sigToType(toff); err != nil {
|
||||
err = dwarf.DecodeError{name, e.Offset, "DWARFv4 section debug_types unsupported"}
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
// It appears that no Type means "void".
|
||||
return new(VoidType)
|
||||
@ -433,7 +445,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
}
|
||||
|
||||
switch e.Tag {
|
||||
case TagArrayType:
|
||||
case dwarf.TagArrayType:
|
||||
// Multi-dimensional array. (DWARF v2 §5.4)
|
||||
// Attributes:
|
||||
// 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.
|
||||
// dimensions are in left to right order.
|
||||
t := new(ArrayType)
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.Type = typeOf(e, AttrType); err != nil {
|
||||
if t.Type = typeOf(e, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
if bytes, ok := e.Val(AttrStride).(int64); ok {
|
||||
if bytes, ok := e.Val(dwarf.AttrStride).(int64); ok {
|
||||
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
|
||||
} else {
|
||||
// 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
|
||||
// but haven't seen that in the wild yet.
|
||||
switch kid.Tag {
|
||||
case TagSubrangeType:
|
||||
count, ok := kid.Val(AttrCount).(int64)
|
||||
case dwarf.TagSubrangeType:
|
||||
count, ok := kid.Val(dwarf.AttrCount).(int64)
|
||||
if !ok {
|
||||
// Old binaries may have an upper bound instead.
|
||||
count, ok = kid.Val(AttrUpperBound).(int64)
|
||||
count, ok = kid.Val(dwarf.AttrUpperBound).(int64)
|
||||
if ok {
|
||||
count++ // Length is one more than upper bound.
|
||||
} 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}
|
||||
}
|
||||
ndim++
|
||||
case TagEnumerationType:
|
||||
err = DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}
|
||||
case dwarf.TagEnumerationType:
|
||||
err = dwarf.DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}
|
||||
goto Error
|
||||
}
|
||||
}
|
||||
@ -496,7 +508,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
t.Count = -1
|
||||
}
|
||||
|
||||
case TagBaseType:
|
||||
case dwarf.TagBaseType:
|
||||
// Basic type. (DWARF v2 §5.1)
|
||||
// Attributes:
|
||||
// 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]
|
||||
// AttrBitOffset: for sub-byte types, size in bits
|
||||
// AttrBitSize: for sub-byte types, bit offset of high order bit in the AttrByteSize bytes
|
||||
name, _ := e.Val(AttrName).(string)
|
||||
enc, ok := e.Val(AttrEncoding).(int64)
|
||||
name, _ := e.Val(dwarf.AttrName).(string)
|
||||
enc, ok := e.Val(dwarf.AttrEncoding).(int64)
|
||||
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
|
||||
}
|
||||
switch enc {
|
||||
default:
|
||||
err = DecodeError{name, e.Offset, "unrecognized encoding attribute value"}
|
||||
err = dwarf.DecodeError{name, e.Offset, "unrecognized encoding attribute value"}
|
||||
goto Error
|
||||
|
||||
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 also writes out a byte size that we can use to distinguish.
|
||||
// See issue 8694.
|
||||
switch byteSize, _ := e.Val(AttrByteSize).(int64); byteSize {
|
||||
switch byteSize, _ := e.Val(dwarf.AttrByteSize).(int64); byteSize {
|
||||
case 8:
|
||||
name = "complex float"
|
||||
case 16:
|
||||
@ -548,11 +560,11 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
Basic() *BasicType
|
||||
}).Basic()
|
||||
t.Name = name
|
||||
t.BitSize, _ = e.Val(AttrBitSize).(int64)
|
||||
t.BitOffset, _ = e.Val(AttrBitOffset).(int64)
|
||||
t.BitSize, _ = e.Val(dwarf.AttrBitSize).(int64)
|
||||
t.BitOffset, _ = e.Val(dwarf.AttrBitOffset).(int64)
|
||||
t.ReflectKind = getKind(e)
|
||||
|
||||
case TagClassType, TagStructType, TagUnionType:
|
||||
case dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType:
|
||||
// Structure, union, or class type. (DWARF v2 §5.5)
|
||||
// Also Slices and Strings (Go-specific).
|
||||
// Attributes:
|
||||
@ -586,26 +598,26 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
}
|
||||
typeCache[off] = typ
|
||||
switch e.Tag {
|
||||
case TagClassType:
|
||||
case dwarf.TagClassType:
|
||||
t.Kind = "class"
|
||||
case TagStructType:
|
||||
case dwarf.TagStructType:
|
||||
t.Kind = "struct"
|
||||
case TagUnionType:
|
||||
case dwarf.TagUnionType:
|
||||
t.Kind = "union"
|
||||
}
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.StructName, _ = e.Val(AttrName).(string)
|
||||
t.Incomplete = e.Val(AttrDeclaration) != nil
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.StructName, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.Incomplete = e.Val(dwarf.AttrDeclaration) != nil
|
||||
t.Field = make([]*StructField, 0, 8)
|
||||
var lastFieldType Type
|
||||
var lastFieldBitOffset int64
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
if kid.Tag == TagMember {
|
||||
if kid.Tag == dwarf.TagMember {
|
||||
f := new(StructField)
|
||||
if f.Type = typeOf(kid, AttrType); err != nil {
|
||||
if f.Type = typeOf(kid, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
switch loc := kid.Val(AttrDataMemberLoc).(type) {
|
||||
switch loc := kid.Val(dwarf.AttrDataMemberLoc).(type) {
|
||||
case []byte:
|
||||
// TODO: Should have original compilation
|
||||
// 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.
|
||||
break
|
||||
}
|
||||
b := makeBuf(d, unknownFormat{}, "location", 0, loc)
|
||||
op := b.uint8()
|
||||
switch op {
|
||||
case opPlusUconst:
|
||||
b := util.MakeBuf(d, util.UnknownFormat{}, "location", 0, loc)
|
||||
op_ := op.Opcode(b.Uint8())
|
||||
switch op_ {
|
||||
case op.DW_OP_plus_uconst:
|
||||
// Handle opcode sequence [DW_OP_plus_uconst <uleb128>]
|
||||
f.ByteOffset = int64(b.uint())
|
||||
b.assertEmpty()
|
||||
case opConsts:
|
||||
f.ByteOffset = int64(b.Uint())
|
||||
b.AssertEmpty()
|
||||
case op.DW_OP_consts:
|
||||
// Handle opcode sequence [DW_OP_consts <sleb128> DW_OP_plus]
|
||||
f.ByteOffset = b.int()
|
||||
op = b.uint8()
|
||||
if op != opPlus {
|
||||
err = DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op)}
|
||||
f.ByteOffset = b.Int()
|
||||
op_ = op.Opcode(b.Uint8())
|
||||
if op_ != op.DW_OP_plus {
|
||||
err = dwarf.DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op_)}
|
||||
goto Error
|
||||
}
|
||||
b.assertEmpty()
|
||||
b.AssertEmpty()
|
||||
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
|
||||
}
|
||||
if b.err != nil {
|
||||
err = b.err
|
||||
if b.Err != nil {
|
||||
err = b.Err
|
||||
goto Error
|
||||
}
|
||||
case int64:
|
||||
@ -642,10 +654,11 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
}
|
||||
|
||||
haveBitOffset := false
|
||||
f.Name, _ = kid.Val(AttrName).(string)
|
||||
f.ByteSize, _ = kid.Val(AttrByteSize).(int64)
|
||||
f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64)
|
||||
f.BitSize, _ = kid.Val(AttrBitSize).(int64)
|
||||
f.Name, _ = kid.Val(dwarf.AttrName).(string)
|
||||
f.ByteSize, _ = kid.Val(dwarf.AttrByteSize).(int64)
|
||||
f.BitOffset, haveBitOffset = kid.Val(dwarf.AttrBitOffset).(int64)
|
||||
f.BitSize, _ = kid.Val(dwarf.AttrBitSize).(int64)
|
||||
f.Embedded, _ = kid.Val(AttrGoEmbeddedField).(bool)
|
||||
t.Field = append(t.Field, f)
|
||||
|
||||
bito := f.BitOffset
|
||||
@ -662,35 +675,35 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
}
|
||||
}
|
||||
if t.Kind != "union" {
|
||||
b, ok := e.Val(AttrByteSize).(int64)
|
||||
b, ok := e.Val(dwarf.AttrByteSize).(int64)
|
||||
if ok && b*8 == lastFieldBitOffset {
|
||||
// Final field must be zero width. Fix array length.
|
||||
zeroArray(lastFieldType)
|
||||
}
|
||||
}
|
||||
|
||||
case TagConstType, TagVolatileType, TagRestrictType:
|
||||
case dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType:
|
||||
// Type modifier (DWARF v2 §5.2)
|
||||
// Attributes:
|
||||
// AttrType: subtype
|
||||
t := new(QualType)
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.Type = typeOf(e, AttrType); err != nil {
|
||||
if t.Type = typeOf(e, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
switch e.Tag {
|
||||
case TagConstType:
|
||||
case dwarf.TagConstType:
|
||||
t.Qual = "const"
|
||||
case TagRestrictType:
|
||||
case dwarf.TagRestrictType:
|
||||
t.Qual = "restrict"
|
||||
case TagVolatileType:
|
||||
case dwarf.TagVolatileType:
|
||||
t.Qual = "volatile"
|
||||
}
|
||||
|
||||
case TagEnumerationType:
|
||||
case dwarf.TagEnumerationType:
|
||||
// Enumeration type (DWARF v2 §5.6)
|
||||
// Attributes:
|
||||
// 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)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.EnumName, _ = e.Val(AttrName).(string)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.EnumName, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.Val = make([]*EnumValue, 0, 8)
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
if kid.Tag == TagEnumerator {
|
||||
if kid.Tag == dwarf.TagEnumerator {
|
||||
f := new(EnumValue)
|
||||
f.Name, _ = kid.Val(AttrName).(string)
|
||||
f.Val, _ = kid.Val(AttrConstValue).(int64)
|
||||
f.Name, _ = kid.Val(dwarf.AttrName).(string)
|
||||
f.Val, _ = kid.Val(dwarf.AttrConstValue).(int64)
|
||||
n := len(t.Val)
|
||||
if n >= cap(t.Val) {
|
||||
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)
|
||||
// Attributes:
|
||||
// AttrType: subtype [not required! void* has no AttrType]
|
||||
// AttrAddrClass: address class [ignored]
|
||||
t := new(PtrType)
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if e.Val(AttrType) == nil {
|
||||
if e.Val(dwarf.AttrType) == nil {
|
||||
t.Type = &VoidType{}
|
||||
break
|
||||
}
|
||||
t.Type = typeOf(e, AttrType)
|
||||
t.Type = typeOf(e, dwarf.AttrType)
|
||||
|
||||
case TagSubroutineType:
|
||||
case dwarf.TagSubroutineType:
|
||||
// Subroutine type. (DWARF v2 §5.7)
|
||||
// Attributes:
|
||||
// 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
|
||||
// TagUnspecifiedParameter: final ...
|
||||
t := new(FuncType)
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.ReturnType = typeOf(e, AttrType); err != nil {
|
||||
if t.ReturnType = typeOf(e, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
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 {
|
||||
default:
|
||||
continue
|
||||
case TagFormalParameter:
|
||||
if tkid = typeOf(kid, AttrType); err != nil {
|
||||
case dwarf.TagFormalParameter:
|
||||
if tkid = typeOf(kid, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
case TagUnspecifiedParameters:
|
||||
case dwarf.TagUnspecifiedParameters:
|
||||
tkid = &DotDotDotType{}
|
||||
}
|
||||
t.ParamType = append(t.ParamType, tkid)
|
||||
}
|
||||
|
||||
case TagTypedef:
|
||||
case dwarf.TagTypedef:
|
||||
// Typedef (DWARF v2 §5.3)
|
||||
// Also maps and channels (Go-specific).
|
||||
// Attributes:
|
||||
@ -802,17 +815,17 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
typ = t
|
||||
}
|
||||
typeCache[off] = typ
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Type = typeOf(e, AttrType)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.Type = typeOf(e, dwarf.AttrType)
|
||||
|
||||
case TagUnspecifiedType:
|
||||
case dwarf.TagUnspecifiedType:
|
||||
// Unspecified type (DWARF v3 §5.2)
|
||||
// Attributes:
|
||||
// AttrName: name
|
||||
t := new(UnspecifiedType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.Name, _ = e.Val(AttrName).(string)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -822,7 +835,7 @@ func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Off
|
||||
typ.Common().Offset = off
|
||||
|
||||
{
|
||||
b, ok := e.Val(AttrByteSize).(int64)
|
||||
b, ok := e.Val(dwarf.AttrByteSize).(int64)
|
||||
if !ok {
|
||||
b = -1
|
||||
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()
|
||||
case *PtrType:
|
||||
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
|
152
vendor/github.com/derekparker/delve/pkg/dwarf/line/line_parser.go
generated
vendored
Normal file
152
vendor/github.com/derekparker/delve/pkg/dwarf/line/line_parser.go
generated
vendored
Normal 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
|
||||
}
|
450
vendor/github.com/derekparker/delve/pkg/dwarf/line/state_machine.go
generated
vendored
Normal file
450
vendor/github.com/derekparker/delve/pkg/dwarf/line/state_machine.go
generated
vendored
Normal 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
197
vendor/github.com/derekparker/delve/pkg/dwarf/op/op.go
generated
vendored
Normal 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
|
||||
}
|
515
vendor/github.com/derekparker/delve/pkg/dwarf/op/opcodes.go
generated
vendored
Normal file
515
vendor/github.com/derekparker/delve/pkg/dwarf/op/opcodes.go
generated
vendored
Normal 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,
|
||||
}
|
175
vendor/github.com/derekparker/delve/pkg/dwarf/op/opcodes.table
generated
vendored
Normal file
175
vendor/github.com/derekparker/delve/pkg/dwarf/op/opcodes.table
generated
vendored
Normal 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 ""
|
102
vendor/github.com/derekparker/delve/pkg/dwarf/op/regs.go
generated
vendored
Normal file
102
vendor/github.com/derekparker/delve/pkg/dwarf/op/regs.go
generated
vendored
Normal 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}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package reader
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/debug/dwarf"
|
||||
"github.com/derekparker/delve/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
)
|
||||
|
||||
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
|
||||
// specified program counter.
|
||||
func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
|
||||
func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) {
|
||||
reader.Seek(0)
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
if err != nil {
|
||||
@ -55,7 +55,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if lowpc <= pc && highpc > pc {
|
||||
if lowpc <= uint64(pc) && highpc > uint64(pc) {
|
||||
return entry, nil
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -73,7 +73,7 @@ func (reader *Reader) AddrFor(name string) (uint64, error) {
|
||||
if !ok {
|
||||
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 {
|
||||
return 0, err
|
||||
}
|
||||
@ -99,7 +99,7 @@ func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
addr, err := op.ExecuteStackProgram(0, append(initialInstructions, instructions...))
|
||||
addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...))
|
||||
return uint64(addr), err
|
||||
}
|
||||
}
|
||||
@ -257,31 +257,7 @@ func (reader *Reader) InstructionsForEntry(entry *dwarf.Entry) ([]byte, error) {
|
||||
return append([]byte{}, instructions...), nil
|
||||
}
|
||||
|
||||
// NextScopeVariable moves the reader to the next debug entry that describes a local 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.
|
||||
// NextMemberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
|
||||
func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) {
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
if err != nil {
|
||||
@ -343,3 +319,128 @@ func (reader *Reader) NextCompileUnit() (*dwarf.Entry, error) {
|
||||
|
||||
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()
|
||||
}
|
114
vendor/github.com/derekparker/delve/pkg/dwarf/reader/variables.go
generated
vendored
Normal file
114
vendor/github.com/derekparker/delve/pkg/dwarf/reader/variables.go
generated
vendored
Normal 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
|
||||
}
|
@ -4,23 +4,21 @@
|
||||
|
||||
// Buffered reading and decoding of DWARF data streams.
|
||||
|
||||
package dwarf
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"debug/dwarf"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Data buffer being decoded.
|
||||
type buf struct {
|
||||
dwarf *Data
|
||||
order binary.ByteOrder
|
||||
dwarf *dwarf.Data
|
||||
format dataFormat
|
||||
name string
|
||||
off Offset
|
||||
off dwarf.Offset
|
||||
data []byte
|
||||
err error
|
||||
Err error
|
||||
}
|
||||
|
||||
// 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.
|
||||
type unknownFormat struct{}
|
||||
type UnknownFormat struct{}
|
||||
|
||||
func (u unknownFormat) version() int {
|
||||
func (u UnknownFormat) version() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u unknownFormat) dwarf64() (bool, bool) {
|
||||
func (u UnknownFormat) dwarf64() (bool, bool) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (u unknownFormat) addrsize() int {
|
||||
func (u UnknownFormat) addrsize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func makeBuf(d *Data, format dataFormat, name string, off Offset, data []byte) buf {
|
||||
return buf{d, d.order, format, name, off, data, nil}
|
||||
func MakeBuf(d *dwarf.Data, format dataFormat, name string, off dwarf.Offset, data []byte) buf {
|
||||
return buf{d, format, name, off, data, nil}
|
||||
}
|
||||
|
||||
func (b *buf) slice(length int) buf {
|
||||
@ -63,7 +61,7 @@ func (b *buf) slice(length int) buf {
|
||||
return n
|
||||
}
|
||||
|
||||
func (b *buf) uint8() uint8 {
|
||||
func (b *buf) Uint8() uint8 {
|
||||
if len(b.data) < 1 {
|
||||
b.error("underflow")
|
||||
return 0
|
||||
@ -81,7 +79,7 @@ func (b *buf) bytes(n int) []byte {
|
||||
}
|
||||
data := b.data[0:n]
|
||||
b.data = b.data[n:]
|
||||
b.off += Offset(n)
|
||||
b.off += dwarf.Offset(n)
|
||||
return data
|
||||
}
|
||||
|
||||
@ -94,7 +92,7 @@ func (b *buf) string() string {
|
||||
if b.data[i] == 0 {
|
||||
s := string(b.data[0:i])
|
||||
b.data = b.data[i+1:]
|
||||
b.off += Offset(i + 1)
|
||||
b.off += dwarf.Offset(i + 1)
|
||||
return s
|
||||
}
|
||||
}
|
||||
@ -102,39 +100,15 @@ func (b *buf) string() string {
|
||||
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.
|
||||
// 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++ {
|
||||
byte := b.data[i]
|
||||
c |= uint64(byte&0x7F) << bits
|
||||
bits += 7
|
||||
if byte&0x80 == 0 {
|
||||
b.off += Offset(i + 1)
|
||||
b.off += dwarf.Offset(i + 1)
|
||||
b.data = b.data[i+1:]
|
||||
return c, bits
|
||||
}
|
||||
@ -143,14 +117,14 @@ func (b *buf) varint() (c uint64, bits uint) {
|
||||
}
|
||||
|
||||
// Unsigned int is just a varint.
|
||||
func (b *buf) uint() uint64 {
|
||||
x, _ := b.varint()
|
||||
func (b *buf) Uint() uint64 {
|
||||
x, _ := b.Varint()
|
||||
return x
|
||||
}
|
||||
|
||||
// Signed int is a sign-extended varint.
|
||||
func (b *buf) int() int64 {
|
||||
ux, bits := b.varint()
|
||||
func (b *buf) Int() int64 {
|
||||
ux, bits := b.Varint()
|
||||
x := int64(ux)
|
||||
if x&(1<<(bits-1)) != 0 {
|
||||
x |= -1 << bits
|
||||
@ -158,24 +132,8 @@ func (b *buf) int() int64 {
|
||||
return x
|
||||
}
|
||||
|
||||
// Address-sized uint.
|
||||
func (b *buf) addr() uint64 {
|
||||
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() {
|
||||
// AssertEmpty checks that everything has been read from b.
|
||||
func (b *buf) AssertEmpty() {
|
||||
if len(b.data) == 0 {
|
||||
return
|
||||
}
|
||||
@ -186,18 +144,8 @@ func (b *buf) assertEmpty() {
|
||||
}
|
||||
|
||||
func (b *buf) error(s string) {
|
||||
if b.err == nil {
|
||||
if b.Err == 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
|
||||
}
|
@ -1,6 +1,12 @@
|
||||
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
|
||||
// represented number.
|
||||
@ -71,6 +77,45 @@ func DecodeSLEB128(buf *bytes.Buffer) (int64, uint32) {
|
||||
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) {
|
||||
str, err := data.ReadString(0x0)
|
||||
if err != nil {
|
178
vendor/github.com/derekparker/delve/pkg/goversion/go_version.go
generated
vendored
Normal file
178
vendor/github.com/derekparker/delve/pkg/goversion/go_version.go
generated
vendored
Normal 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, ""})
|
||||
}
|
83
vendor/github.com/derekparker/delve/pkg/logflags/logflags.go
generated
vendored
Normal file
83
vendor/github.com/derekparker/delve/pkg/logflags/logflags.go
generated
vendored
Normal 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
306
vendor/github.com/derekparker/delve/pkg/proc/arch.go
generated
vendored
Normal 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
878
vendor/github.com/derekparker/delve/pkg/proc/bininfo.go
generated
vendored
Normal 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)
|
||||
}
|
437
vendor/github.com/derekparker/delve/pkg/proc/breakpoints.go
generated
vendored
Normal file
437
vendor/github.com/derekparker/delve/pkg/proc/breakpoints.go
generated
vendored
Normal 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}
|
||||
}
|
526
vendor/github.com/derekparker/delve/pkg/proc/core/core.go
generated
vendored
Normal file
526
vendor/github.com/derekparker/delve/pkg/proc/core/core.go
generated
vendored
Normal 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
|
||||
}
|
556
vendor/github.com/derekparker/delve/pkg/proc/core/linux_amd64_core.go
generated
vendored
Normal file
556
vendor/github.com/derekparker/delve/pkg/proc/core/linux_amd64_core.go
generated
vendored
Normal 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
121
vendor/github.com/derekparker/delve/pkg/proc/disasm.go
generated
vendored
Normal 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
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"debug/gosym"
|
||||
"encoding/binary"
|
||||
"rsc.io/x86/x86asm"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patchPCRel(pc, &inst)
|
||||
r := ArchInst(inst)
|
||||
r := archInst(inst)
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func (inst *ArchInst) Size() int {
|
||||
func (inst *archInst) Size() int {
|
||||
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))
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return "?"
|
||||
}
|
||||
@ -44,25 +45,27 @@ func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string {
|
||||
|
||||
switch flavour {
|
||||
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:
|
||||
fallthrough
|
||||
default:
|
||||
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst))
|
||||
}
|
||||
|
||||
if inst.IsCall() && inst.DestLoc != nil && inst.DestLoc.Fn != nil {
|
||||
text += " " + inst.DestLoc.Fn.Name
|
||||
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// IsCall returns true if the instruction is a CALL or LCALL instruction.
|
||||
func (inst *AsmInstruction) IsCall() bool {
|
||||
if inst.Inst == nil {
|
||||
return false
|
||||
}
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -88,10 +91,6 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
|
||||
if arg.Segment != 0 {
|
||||
return nil
|
||||
}
|
||||
regs, err := thread.Registers(false)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
base, err1 := regs.Get(int(arg.Base))
|
||||
index, err2 := regs.Get(int(arg.Index))
|
||||
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)
|
||||
//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 {
|
||||
return nil
|
||||
}
|
||||
@ -108,7 +108,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
|
||||
return nil
|
||||
}
|
||||
|
||||
file, line, fn := thread.dbp.PCToLine(pc)
|
||||
file, line, fn := bininfo.PCToLine(pc)
|
||||
if fn == nil {
|
||||
return nil
|
||||
}
|
||||
@ -142,10 +142,16 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) {
|
||||
text, err := dbp.CurrentThread.Disassemble(fn.Entry, fn.End, false)
|
||||
// firstPCAfterPrologueDisassembly returns the address of the first
|
||||
// instruction after the prologue for function fn by disassembling fn and
|
||||
// matching the instructions against known split-stack prologue patterns.
|
||||
// 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 {
|
||||
return fn.Entry, err
|
||||
}
|
@ -3,6 +3,7 @@ package proc
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
@ -10,11 +11,16 @@ import (
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/reader"
|
||||
"golang.org/x/debug/dwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"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.
|
||||
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
|
||||
t, err := parser.ParseExpr(expr)
|
||||
@ -22,7 +28,10 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable,
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -33,6 +42,123 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable,
|
||||
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) {
|
||||
switch node := t.(type) {
|
||||
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
|
||||
if maybePkg, ok := node.X.(*ast.Ident); ok {
|
||||
if maybePkg.Name == "runtime" && node.Sel.Name == "curg" {
|
||||
return scope.Thread.getGVariable()
|
||||
} else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil {
|
||||
if scope.Gvar == 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
|
||||
}
|
||||
}
|
||||
// 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
|
||||
return scope.evalStructSelector(node)
|
||||
|
||||
@ -102,7 +242,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
|
||||
return scope.evalBinary(node)
|
||||
|
||||
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:
|
||||
return nil, fmt.Errorf("expression %T not implemented", t)
|
||||
@ -116,6 +256,17 @@ func exprToString(t ast.Expr) 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
|
||||
func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
argv, err := scope.evalAST(node.Args[0])
|
||||
@ -130,15 +281,9 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
fnnode := node.Fun
|
||||
|
||||
// remove all enclosing parenthesis from the type name
|
||||
for {
|
||||
p, ok := fnnode.(*ast.ParenExpr)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fnnode = p.X
|
||||
}
|
||||
fnnode = removeParen(fnnode)
|
||||
|
||||
styp, err := scope.Thread.dbp.findTypeExpr(fnnode)
|
||||
styp, err := scope.BinInfo.findTypeExpr(fnnode)
|
||||
if err != nil {
|
||||
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())
|
||||
|
||||
v := newVariable("", 0, styp, scope.Thread.dbp, scope.Thread)
|
||||
v := newVariable("", 0, styp, scope.BinInfo, scope.Mem)
|
||||
v.loaded = true
|
||||
|
||||
switch ttyp := typ.(type) {
|
||||
case *dwarf.PtrType:
|
||||
case *godwarf.PtrType:
|
||||
switch argv.Kind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
// ok
|
||||
@ -162,10 +307,10 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
|
||||
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
|
||||
|
||||
case *dwarf.UintType:
|
||||
case *godwarf.UintType:
|
||||
switch argv.Kind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
n, _ := constant.Int64Val(argv.Value)
|
||||
@ -179,8 +324,11 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
x, _ := constant.Float64Val(argv.Value)
|
||||
v.Value = constant.MakeUint64(uint64(x))
|
||||
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 {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
n, _ := constant.Int64Val(argv.Value)
|
||||
@ -195,7 +343,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
v.Value = constant.MakeInt64(int64(x))
|
||||
return v, nil
|
||||
}
|
||||
case *dwarf.FloatType:
|
||||
case *godwarf.FloatType:
|
||||
switch argv.Kind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fallthrough
|
||||
@ -205,7 +353,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
v.Value = argv.Value
|
||||
return v, nil
|
||||
}
|
||||
case *dwarf.ComplexType:
|
||||
case *godwarf.ComplexType:
|
||||
switch argv.Kind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fallthrough
|
||||
@ -370,10 +518,10 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
|
||||
sz := int64(0)
|
||||
if realev.RealType != nil {
|
||||
sz = realev.RealType.(*dwarf.FloatType).Size()
|
||||
sz = realev.RealType.(*godwarf.FloatType).Size()
|
||||
}
|
||||
if imagev.RealType != nil {
|
||||
isz := imagev.RealType.(*dwarf.FloatType).Size()
|
||||
isz := imagev.RealType.(*godwarf.FloatType).Size()
|
||||
if isz > sz {
|
||||
sz = isz
|
||||
}
|
||||
@ -383,9 +531,9 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
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))
|
||||
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) {
|
||||
switch node.Name {
|
||||
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":
|
||||
return nilVariable, nil
|
||||
}
|
||||
|
||||
// try to interpret this as a local variable
|
||||
v, err := scope.extractVarInfo(node.Name)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
vars, err := scope.Locals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
origErr := err
|
||||
// workaround: sometimes go inserts an entry for '&varname' instead of varname
|
||||
v, err = scope.extractVarInfo("&" + node.Name)
|
||||
if err == nil {
|
||||
v = v.maybeDereference()
|
||||
v.Name = node.Name
|
||||
return v, nil
|
||||
for i := range vars {
|
||||
if vars[i].Name == node.Name && vars[i].Flags&VariableShadowed == 0 {
|
||||
return vars[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 fn != nil {
|
||||
if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil {
|
||||
if scope.Fn != nil {
|
||||
if v, err := scope.findGlobal(scope.Fn.PackageName() + "." + node.Name); err == nil {
|
||||
v.Name = node.Name
|
||||
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
|
||||
@ -467,6 +611,13 @@ func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, e
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -489,13 +640,23 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err
|
||||
if xv.Children[0].Addr == 0 {
|
||||
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
|
||||
// can access the data field of an interface without actually having to
|
||||
// type the concrete type.
|
||||
if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" {
|
||||
typ, err := scope.BinInfo.findTypeExpr(node.Type)
|
||||
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
|
||||
}
|
||||
|
||||
@ -509,12 +670,27 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
|
||||
return nil, xev.Unreadable
|
||||
}
|
||||
|
||||
xev = xev.maybeDereference()
|
||||
|
||||
idxev, err := scope.evalAST(node.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cantindex := fmt.Errorf("expression \"%s\" (%s) does not support indexing", exprToString(node.X), xev.TypeString())
|
||||
|
||||
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:
|
||||
if xev.Base == 0 {
|
||||
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)
|
||||
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")
|
||||
}
|
||||
xev.mapSkip += int(low)
|
||||
xev.loadValue(loadFullValue)
|
||||
if xev.Unreadable != nil {
|
||||
return nil, xev.Unreadable
|
||||
xev.mapIterator() // reads map length
|
||||
if int64(xev.mapSkip) >= xev.Len {
|
||||
return nil, fmt.Errorf("map index out of bounds")
|
||||
}
|
||||
return xev, nil
|
||||
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))
|
||||
}
|
||||
|
||||
xev.OnlyAddr = true
|
||||
return xev.pointerToVariable(), nil
|
||||
}
|
||||
|
||||
typename := "*" + xev.DwarfType.Common().Name
|
||||
rv := scope.newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, Type: xev.DwarfType})
|
||||
rv.Children = []Variable{*xev}
|
||||
func (v *Variable) pointerToVariable() *Variable {
|
||||
v.OnlyAddr = true
|
||||
|
||||
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
|
||||
|
||||
return rv, nil
|
||||
return rv
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, xv.Unreadable
|
||||
}
|
||||
if xv.FloatSpecial != 0 {
|
||||
return nil, errOperationOnSpecialFloat
|
||||
}
|
||||
if xv.Value == nil {
|
||||
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
|
||||
}
|
||||
if xv.DwarfType != nil {
|
||||
r := xv.newVariable("", 0, xv.DwarfType)
|
||||
r := xv.newVariable("", 0, xv.DwarfType, scope.Mem)
|
||||
r.Value = rc
|
||||
return r, 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 {
|
||||
return nil, negotiateTypeNil(op, yv)
|
||||
}
|
||||
@ -777,23 +959,36 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xv.loadValue(loadFullValue)
|
||||
yv.loadValue(loadFullValue)
|
||||
|
||||
if xv.Unreadable != nil {
|
||||
return nil, xv.Unreadable
|
||||
}
|
||||
|
||||
if yv.Unreadable != nil {
|
||||
return nil, yv.Unreadable
|
||||
}
|
||||
|
||||
if xv.FloatSpecial != 0 || yv.FloatSpecial != 0 {
|
||||
return nil, errOperationOnSpecialFloat
|
||||
}
|
||||
|
||||
typ, err := negotiateType(node.Op, xv, yv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -801,8 +996,8 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
|
||||
op := node.Op
|
||||
if typ != nil && (op == token.QUO) {
|
||||
_, isint := typ.(*dwarf.IntType)
|
||||
_, isuint := typ.(*dwarf.UintType)
|
||||
_, isint := typ.(*godwarf.IntType)
|
||||
_, isuint := typ.(*godwarf.UintType)
|
||||
if isint || isuint {
|
||||
// forces integer division if the result type is integer
|
||||
op = token.QUO_ASSIGN
|
||||
@ -835,13 +1030,16 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
return newConstant(rc, xv.mem), nil
|
||||
}
|
||||
|
||||
r := xv.newVariable("", 0, typ)
|
||||
r := xv.newVariable("", 0, typ, scope.Mem)
|
||||
r.Value = rc
|
||||
if r.Kind == reflect.String {
|
||||
r.Len = xv.Len + yv.Len
|
||||
}
|
||||
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)
|
||||
func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) {
|
||||
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:
|
||||
return constantCompare(op, xv.Value, yv.Value)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
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)
|
||||
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
||||
@ -924,7 +1130,7 @@ func (v *Variable) isNil() bool {
|
||||
case reflect.Ptr:
|
||||
return v.Children[0].Addr == 0
|
||||
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:
|
||||
return v.Base == 0
|
||||
}
|
||||
@ -956,7 +1162,7 @@ func (v *Variable) asInt() (int64, error) {
|
||||
if v.Unreadable != nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@ -974,7 +1180,7 @@ func (v *Variable) asUint() (uint64, error) {
|
||||
if v.Unreadable != nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@ -982,10 +1188,18 @@ func (v *Variable) asUint() (uint64, error) {
|
||||
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 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
|
||||
}
|
||||
@ -1010,27 +1224,27 @@ func (v *Variable) isType(typ dwarf.Type, kind reflect.Kind) error {
|
||||
}
|
||||
|
||||
switch typ.(type) {
|
||||
case *dwarf.IntType:
|
||||
case *godwarf.IntType:
|
||||
if v.Value.Kind() != constant.Int {
|
||||
return converr
|
||||
}
|
||||
case *dwarf.UintType:
|
||||
case *godwarf.UintType:
|
||||
if v.Value.Kind() != constant.Int {
|
||||
return converr
|
||||
}
|
||||
case *dwarf.FloatType:
|
||||
case *godwarf.FloatType:
|
||||
if (v.Value.Kind() != constant.Int) && (v.Value.Kind() != constant.Float) {
|
||||
return converr
|
||||
}
|
||||
case *dwarf.BoolType:
|
||||
case *godwarf.BoolType:
|
||||
if v.Value.Kind() != constant.Bool {
|
||||
return converr
|
||||
}
|
||||
case *dwarf.StringType:
|
||||
case *godwarf.StringType:
|
||||
if v.Value.Kind() != constant.String {
|
||||
return converr
|
||||
}
|
||||
case *dwarf.ComplexType:
|
||||
case *godwarf.ComplexType:
|
||||
if v.Value.Kind() != constant.Complex && v.Value.Kind() != constant.Float && v.Value.Kind() != constant.Int {
|
||||
return converr
|
||||
}
|
||||
@ -1045,7 +1259,11 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
||||
if idx < 0 || int64(idx) >= v.Len {
|
||||
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) {
|
||||
@ -1063,7 +1281,7 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
||||
}
|
||||
if first {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1095,22 +1313,16 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||
}
|
||||
|
||||
typ := v.DwarfType
|
||||
if _, isarr := v.DwarfType.(*dwarf.ArrayType); isarr {
|
||||
typ = &dwarf.SliceType{
|
||||
StructType: dwarf.StructType{
|
||||
CommonType: dwarf.CommonType{
|
||||
ByteSize: 24,
|
||||
Name: "",
|
||||
},
|
||||
StructName: fmt.Sprintf("[]%s", v.fieldType.Common().Name),
|
||||
Kind: "struct",
|
||||
Field: nil,
|
||||
},
|
||||
ElemType: v.fieldType,
|
||||
}
|
||||
if _, isarr := v.DwarfType.(*godwarf.ArrayType); isarr {
|
||||
typ = fakeSliceType(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.Len = len
|
||||
r.Base = base
|
||||
@ -1119,3 +1331,147 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||
|
||||
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
561
vendor/github.com/derekparker/delve/pkg/proc/fncall.go
generated
vendored
Normal 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
|
||||
}
|
1877
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver.go
generated
vendored
Normal file
1877
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1233
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver_conn.go
generated
vendored
Normal file
1233
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver_conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver_unix.go
generated
vendored
Normal file
16
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver_unix.go
generated
vendored
Normal 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)
|
||||
}
|
10
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver_windows.go
generated
vendored
Normal file
10
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/gdbserver_windows.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
package gdbserial
|
||||
|
||||
import "syscall"
|
||||
|
||||
func sysProcAttr(foreground bool) *syscall.SysProcAttr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func foregroundSignalsIgnore() {
|
||||
}
|
267
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/rr.go
generated
vendored
Normal file
267
vendor/github.com/derekparker/delve/pkg/proc/gdbserial/rr.go
generated
vendored
Normal 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
|
||||
}
|
129
vendor/github.com/derekparker/delve/pkg/proc/interface.go
generated
vendored
Normal file
129
vendor/github.com/derekparker/delve/pkg/proc/interface.go
generated
vendored
Normal 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
|
||||
}
|
39
vendor/github.com/derekparker/delve/pkg/proc/linutil/auxv.go
generated
vendored
Normal file
39
vendor/github.com/derekparker/delve/pkg/proc/linutil/auxv.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
4
vendor/github.com/derekparker/delve/pkg/proc/linutil/doc.go
generated
vendored
Normal file
4
vendor/github.com/derekparker/delve/pkg/proc/linutil/doc.go
generated
vendored
Normal 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
156
vendor/github.com/derekparker/delve/pkg/proc/mem.go
generated
vendored
Normal 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
|
||||
}
|
@ -11,11 +11,11 @@ type moduleData struct {
|
||||
typemapVar *Variable
|
||||
}
|
||||
|
||||
func (dbp *Process) loadModuleData() (err error) {
|
||||
dbp.loadModuleDataOnce.Do(func() {
|
||||
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
|
||||
func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
|
||||
bi.loadModuleDataOnce.Do(func() {
|
||||
scope := globalScope(bi, mem)
|
||||
var md *Variable
|
||||
md, err = scope.packageVarAddr("runtime.firstmoduledata")
|
||||
md, err = scope.findGlobal("runtime.firstmoduledata")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -43,7 +43,7 @@ func (dbp *Process) loadModuleData() (err error) {
|
||||
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()
|
||||
if md.Unreadable != nil {
|
||||
@ -56,56 +56,65 @@ func (dbp *Process) loadModuleData() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, error) {
|
||||
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
|
||||
if err := dbp.loadModuleData(); err != nil {
|
||||
func findModuleDataForType(bi *BinaryInfo, typeAddr uintptr, mem MemoryReadWriter) (*moduleData, error) {
|
||||
if err := loadModuleData(bi, mem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var md *moduleData
|
||||
for i := range dbp.moduleData {
|
||||
if typeAddr >= dbp.moduleData[i].types && typeAddr < dbp.moduleData[i].etypes {
|
||||
md = &dbp.moduleData[i]
|
||||
for i := range bi.moduleData {
|
||||
if typeAddr >= bi.moduleData[i].types && typeAddr < bi.moduleData[i].etypes {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if md == nil {
|
||||
v, err := dbp.reflectOffsMapAccess(off)
|
||||
v, err := reflectOffsMapAccess(bi, off, mem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.loadValue(LoadConfig{false, 1, 0, 0, -1})
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
if err = dbp.loadModuleData(); err != nil {
|
||||
if err = loadModuleData(bi, mem); err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
|
||||
for _, md := range dbp.moduleData {
|
||||
for _, md := range bi.moduleData {
|
||||
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 {
|
||||
return "", "", 0, err
|
||||
}
|
||||
@ -115,12 +124,12 @@ func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag str
|
||||
return "", "", 0, resv.Unreadable
|
||||
}
|
||||
|
||||
return dbp.loadName(resv.Addr)
|
||||
return loadName(bi, resv.Addr, mem)
|
||||
}
|
||||
|
||||
func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) {
|
||||
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
|
||||
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
|
||||
func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
|
||||
scope := globalScope(bi, mem)
|
||||
reflectOffs, err := scope.findGlobal("runtime.reflectOffs")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -130,7 +139,7 @@ func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread))
|
||||
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem))
|
||||
}
|
||||
|
||||
const (
|
||||
@ -140,17 +149,19 @@ const (
|
||||
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
|
||||
namedata, err := dbp.CurrentThread.readMemory(off, 3)
|
||||
namedata := make([]byte, 3)
|
||||
_, err = mem.ReadMemory(namedata, off)
|
||||
off += 3
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
@ -159,14 +170,16 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32,
|
||||
name = string(rawstr)
|
||||
|
||||
if namedata[0]&nameflagHasTag != 0 {
|
||||
taglendata, err := dbp.CurrentThread.readMemory(off, 2)
|
||||
taglendata := make([]byte, 2)
|
||||
_, err = mem.ReadMemory(taglendata, off)
|
||||
off += 2
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
@ -176,7 +189,8 @@ func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32,
|
||||
}
|
||||
|
||||
if namedata[0]&nameflagHasPkg != 0 {
|
||||
pkgdata, err := dbp.CurrentThread.readMemory(off, 4)
|
||||
pkgdata := make([]byte, 4)
|
||||
_, err = mem.ReadMemory(pkgdata, off)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
/*
|
||||
* IDENTIFICATION:
|
||||
* stub generated Sun Feb 22 20:54:31 2015
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
#include "exec_darwin.h"
|
||||
#include "stdio.h"
|
||||
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
#include "proc_darwin.h"
|
||||
|
||||
#include <unistd.h>
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
/*
|
||||
* IDENTIFICATION:
|
||||
* stub generated Sat Feb 21 18:10:52 2015
|
128
vendor/github.com/derekparker/delve/pkg/proc/native/nonative_darwin.go
generated
vendored
Normal file
128
vendor/github.com/derekparker/delve/pkg/proc/native/nonative_darwin.go
generated
vendored
Normal 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)
|
||||
}
|
453
vendor/github.com/derekparker/delve/pkg/proc/native/proc.go
generated
vendored
Normal file
453
vendor/github.com/derekparker/delve/pkg/proc/native/proc.go
generated
vendored
Normal 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
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
#include "proc_darwin.h"
|
||||
|
||||
static const unsigned char info_plist[]
|
@ -1,4 +1,6 @@
|
||||
package proc
|
||||
//+build darwin,macnative
|
||||
|
||||
package native
|
||||
|
||||
// #include "proc_darwin.h"
|
||||
// #include "threads_darwin.h"
|
||||
@ -6,7 +8,6 @@ package proc
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"debug/gosym"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -15,11 +16,9 @@ import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/debug/macho"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
"github.com/derekparker/delve/dwarf/line"
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// OSProcessDetails holds Darwin specific information.
|
||||
@ -28,6 +27,7 @@ type OSProcessDetails struct {
|
||||
exceptionPort C.mach_port_t // mach port for receiving mach exceptions.
|
||||
notificationPort C.mach_port_t // mach port for dead name notification (process exit).
|
||||
initialized bool
|
||||
halt bool
|
||||
|
||||
// the main port we use, will return messages from both the
|
||||
// exception and notification ports.
|
||||
@ -38,10 +38,10 @@ type OSProcessDetails struct {
|
||||
// custom fork/exec process in order to take advantage of
|
||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||
// 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
|
||||
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])
|
||||
if err != nil {
|
||||
@ -77,7 +77,8 @@ func Launch(cmd []string, wd string) (*Process, error) {
|
||||
if pid <= 0 {
|
||||
return nil, fmt.Errorf("could not fork/exec")
|
||||
}
|
||||
dbp.Pid = pid
|
||||
dbp.pid = pid
|
||||
dbp.childProcess = true
|
||||
for i := range argvSlice {
|
||||
C.free(unsafe.Pointer(argvSlice[i]))
|
||||
}
|
||||
@ -86,7 +87,11 @@ func Launch(cmd []string, wd string) (*Process, error) {
|
||||
// trapWait to wait until the child process calls execve.
|
||||
|
||||
for {
|
||||
err = dbp.updateThreadListForTask(C.get_task_for_pid(C.int(dbp.Pid)))
|
||||
task := C.get_task_for_pid(C.int(dbp.pid))
|
||||
// The task_for_pid call races with the fork call. This can
|
||||
// result in the parent task being returned instead of the child.
|
||||
if task != dbp.os.task {
|
||||
err = dbp.updateThreadListForTask(task)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
@ -94,31 +99,27 @@ func Launch(cmd []string, wd string) (*Process, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := dbp.resume(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp.allGCache = nil
|
||||
for _, th := range dbp.Threads {
|
||||
th.clearBreakpointState()
|
||||
dbp.common.ClearAllGCache()
|
||||
for _, th := range dbp.threads {
|
||||
th.CurrentBreakpoint.Clear()
|
||||
}
|
||||
|
||||
trapthread, err := dbp.trapWait(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := dbp.Halt(); err != nil {
|
||||
return nil, dbp.exitGuard(err)
|
||||
}
|
||||
|
||||
_, err = dbp.waitForStop()
|
||||
if err != nil {
|
||||
if err := dbp.stop(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp.os.initialized = true
|
||||
dbp, err = initializeDebugProcess(dbp, argv0Go, false)
|
||||
dbp, err = initializeDebugProcess(dbp, argv0Go)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -144,19 +145,34 @@ func Attach(pid int) (*Process, error) {
|
||||
|
||||
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.
|
||||
func (dbp *Process) Kill() (err error) {
|
||||
func (dbp *Process) kill() (err error) {
|
||||
if dbp.exited {
|
||||
return nil
|
||||
}
|
||||
err = sys.Kill(-dbp.Pid, sys.SIGKILL)
|
||||
err = sys.Kill(-dbp.pid, sys.SIGKILL)
|
||||
if err != nil {
|
||||
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 {
|
||||
return errors.New("could not resume task")
|
||||
}
|
||||
@ -175,9 +191,10 @@ func (dbp *Process) Kill() (err error) {
|
||||
func (dbp *Process) requestManualStop() (err error) {
|
||||
var (
|
||||
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)
|
||||
)
|
||||
dbp.os.halt = true
|
||||
kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not raise mach exception")
|
||||
@ -218,12 +235,12 @@ func (dbp *Process) updateThreadListForTask(task C.task_t) error {
|
||||
return couldNotGetThreadList
|
||||
}
|
||||
|
||||
for _, thread := range dbp.Threads {
|
||||
for _, thread := range dbp.threads {
|
||||
thread.os.exists = false
|
||||
}
|
||||
|
||||
for _, port := range list {
|
||||
thread, ok := dbp.Threads[int(port)]
|
||||
thread, ok := dbp.threads[int(port)]
|
||||
if !ok {
|
||||
thread, err = dbp.addThread(int(port), false)
|
||||
if err != nil {
|
||||
@ -233,9 +250,9 @@ func (dbp *Process) updateThreadListForTask(task C.task_t) error {
|
||||
thread.os.exists = true
|
||||
}
|
||||
|
||||
for threadID, thread := range dbp.Threads {
|
||||
for threadID, thread := range dbp.threads {
|
||||
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) {
|
||||
if thread, ok := dbp.Threads[port]; ok {
|
||||
if thread, ok := dbp.threads[port]; ok {
|
||||
return thread, nil
|
||||
}
|
||||
thread := &Thread{
|
||||
@ -251,107 +268,19 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
|
||||
dbp: dbp,
|
||||
os: new(OSSpecificDetails),
|
||||
}
|
||||
dbp.Threads[port] = thread
|
||||
dbp.threads[port] = thread
|
||||
thread.os.threadAct = C.thread_act_t(port)
|
||||
if dbp.CurrentThread == nil {
|
||||
if dbp.currentThread == nil {
|
||||
dbp.SwitchThread(thread.ID)
|
||||
}
|
||||
return thread, nil
|
||||
}
|
||||
|
||||
func (dbp *Process) parseDebugFrame(exe *macho.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 *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) {
|
||||
func findExecutable(path string, pid int) string {
|
||||
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)
|
||||
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
|
||||
return path
|
||||
}
|
||||
|
||||
func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
@ -369,19 +298,22 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
_, status, err := dbp.wait(dbp.Pid, 0)
|
||||
_, status, err := dbp.wait(dbp.pid, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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:
|
||||
if !dbp.halt {
|
||||
dbp.stopMu.Lock()
|
||||
halt := dbp.os.halt
|
||||
dbp.stopMu.Unlock()
|
||||
if !halt {
|
||||
// Call trapWait again, it seems
|
||||
// MACH_RCV_INTERRUPTED is emitted before
|
||||
// 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
|
||||
// this is as good a time as any to check for them.
|
||||
dbp.updateThreadList()
|
||||
th, ok := dbp.Threads[int(port)]
|
||||
th, ok := dbp.threads[int(port)]
|
||||
if !ok {
|
||||
if dbp.halt {
|
||||
dbp.halt = false
|
||||
dbp.stopMu.Lock()
|
||||
halt := dbp.os.halt
|
||||
dbp.stopMu.Unlock()
|
||||
if halt {
|
||||
dbp.os.halt = false
|
||||
return th, nil
|
||||
}
|
||||
if dbp.firstStart || th.singleStepping {
|
||||
@ -427,7 +362,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
}
|
||||
|
||||
func (dbp *Process) waitForStop() ([]int, error) {
|
||||
ports := make([]int, 0, len(dbp.Threads))
|
||||
ports := make([]int, 0, len(dbp.threads))
|
||||
count := 0
|
||||
for {
|
||||
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) {
|
||||
wg.Done()
|
||||
}
|
||||
@ -483,29 +401,70 @@ func (dbp *Process) exitGuard(err error) error {
|
||||
if err != ErrContinueThread {
|
||||
return err
|
||||
}
|
||||
_, status, werr := dbp.wait(dbp.Pid, sys.WNOHANG)
|
||||
_, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
|
||||
if werr == nil && status.Exited() {
|
||||
dbp.postExit()
|
||||
return ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
|
||||
return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (dbp *Process) resume() error {
|
||||
// all threads stopped over a breakpoint are made to step over it
|
||||
for _, thread := range dbp.Threads {
|
||||
if thread.CurrentBreakpoint != nil {
|
||||
for _, thread := range dbp.threads {
|
||||
if thread.CurrentBreakpoint.Breakpoint != nil {
|
||||
if err := thread.StepInstruction(); err != nil {
|
||||
return err
|
||||
}
|
||||
thread.CurrentBreakpoint = nil
|
||||
thread.CurrentBreakpoint.Clear()
|
||||
}
|
||||
}
|
||||
// everything is resumed
|
||||
for _, thread := range dbp.Threads {
|
||||
for _, thread := range dbp.threads {
|
||||
if err := thread.resume(); err != nil {
|
||||
return dbp.exitGuard(err)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach.h>
|
@ -1,13 +1,13 @@
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/gosym"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -18,9 +18,9 @@ import (
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
"github.com/derekparker/delve/dwarf/line"
|
||||
"golang.org/x/debug/elf"
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// Process statuses
|
||||
@ -46,55 +46,86 @@ type OSProcessDetails struct {
|
||||
// Launch creates and begins debugging a new process. First entry in
|
||||
// `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.
|
||||
func Launch(cmd []string, wd string) (*Process, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
var (
|
||||
proc *exec.Cmd
|
||||
process *exec.Cmd
|
||||
err error
|
||||
)
|
||||
// check that the argument to Launch is an executable file
|
||||
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.common = proc.NewCommonProcess(true)
|
||||
dbp.execPtraceFunc(func() {
|
||||
proc = exec.Command(cmd[0])
|
||||
proc.Args = cmd
|
||||
proc.Stdout = os.Stdout
|
||||
proc.Stderr = os.Stderr
|
||||
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true}
|
||||
if wd != "" {
|
||||
proc.Dir = wd
|
||||
process = exec.Command(cmd[0])
|
||||
process.Args = cmd
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground}
|
||||
if foreground {
|
||||
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
|
||||
process.Stdin = os.Stdin
|
||||
}
|
||||
err = proc.Start()
|
||||
if wd != "" {
|
||||
process.Dir = wd
|
||||
}
|
||||
err = process.Start()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp.Pid = proc.Process.Pid
|
||||
_, _, err = dbp.wait(proc.Process.Pid, 0)
|
||||
dbp.pid = process.Process.Pid
|
||||
dbp.childProcess = true
|
||||
_, _, err = dbp.wait(process.Process.Pid, 0)
|
||||
if err != nil {
|
||||
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.
|
||||
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.
|
||||
func (dbp *Process) Kill() (err error) {
|
||||
// kill kills the target process.
|
||||
func (dbp *Process) kill() (err error) {
|
||||
if dbp.exited {
|
||||
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")
|
||||
}
|
||||
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())
|
||||
}
|
||||
if _, _, err = dbp.wait(dbp.Pid, 0); err != nil {
|
||||
if _, _, err = dbp.wait(dbp.pid, 0); err != nil {
|
||||
return
|
||||
}
|
||||
dbp.postExit()
|
||||
@ -102,13 +133,13 @@ func (dbp *Process) Kill() (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
|
||||
// known threads.
|
||||
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
|
||||
}
|
||||
|
||||
@ -122,7 +153,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
|
||||
// if we truly don't have permissions.
|
||||
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 {
|
||||
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) })
|
||||
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)
|
||||
}
|
||||
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,
|
||||
dbp: dbp,
|
||||
os: new(OSSpecificDetails),
|
||||
}
|
||||
if dbp.CurrentThread == nil {
|
||||
if dbp.currentThread == nil {
|
||||
dbp.SwitchThread(tid)
|
||||
}
|
||||
return dbp.Threads[tid], nil
|
||||
return dbp.threads[tid], nil
|
||||
}
|
||||
|
||||
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 {
|
||||
tidstr := filepath.Base(tidpath)
|
||||
tid, err := strconv.Atoi(tidstr)
|
||||
if err != nil {
|
||||
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 nil
|
||||
}
|
||||
|
||||
var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported")
|
||||
|
||||
func (dbp *Process) findExecutable(path string) (*elf.File, string, error) {
|
||||
func findExecutable(path string, pid int) string {
|
||||
if path == "" {
|
||||
path = fmt.Sprintf("/proc/%d/exe", dbp.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)
|
||||
path = fmt.Sprintf("/proc/%d/exe", pid)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
return dbp.trapWaitInternal(pid, false)
|
||||
}
|
||||
|
||||
func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) {
|
||||
for {
|
||||
wpid, status, err := dbp.wait(pid, 0)
|
||||
if err != nil {
|
||||
@ -279,16 +222,16 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
if wpid == 0 {
|
||||
continue
|
||||
}
|
||||
th, ok := dbp.Threads[wpid]
|
||||
th, ok := dbp.threads[wpid]
|
||||
if ok {
|
||||
th.Status = (*WaitStatus)(status)
|
||||
}
|
||||
if status.Exited() {
|
||||
if wpid == dbp.Pid {
|
||||
if wpid == dbp.pid {
|
||||
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
|
||||
}
|
||||
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 == sys.ESRCH {
|
||||
// thread died while we were adding it
|
||||
delete(dbp.threads, int(cloned))
|
||||
continue
|
||||
}
|
||||
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 == sys.ESRCH {
|
||||
// thread died while we were adding it
|
||||
delete(dbp.Threads, th.ID)
|
||||
delete(dbp.threads, th.ID)
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
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?
|
||||
continue
|
||||
}
|
||||
if status.StopSignal() == sys.SIGTRAP && dbp.halt {
|
||||
th.running = false
|
||||
dbp.halt = false
|
||||
return th, nil
|
||||
}
|
||||
if status.StopSignal() == sys.SIGTRAP {
|
||||
th.running = false
|
||||
if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) {
|
||||
th.os.running = false
|
||||
return th, nil
|
||||
}
|
||||
if th != nil {
|
||||
// TODO(dp) alert user about unexpected signals here.
|
||||
if err := th.resumeWithSig(int(status.StopSignal())); err != nil {
|
||||
if err == sys.ESRCH {
|
||||
return nil, ProcessExitedError{Pid: dbp.Pid}
|
||||
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@ -354,19 +298,19 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
||||
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 {
|
||||
// removes newline character
|
||||
comm = bytes.TrimSuffix(comm, []byte("\n"))
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Printf("Could not read proc stat: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.Pid)
|
||||
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
|
||||
rexp, err := regexp.Compile(expr)
|
||||
if err != nil {
|
||||
fmt.Printf("Regexp compile error: %v\n", err)
|
||||
@ -374,7 +318,7 @@ func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
||||
}
|
||||
match := rexp.FindSubmatch(stat)
|
||||
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)
|
||||
}
|
||||
comm = match[1]
|
||||
@ -402,9 +346,16 @@ func status(pid int, comm string) rune {
|
||||
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) {
|
||||
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)
|
||||
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 {
|
||||
if err != sys.ESRCH {
|
||||
return err
|
||||
}
|
||||
if status(dbp.Pid, dbp.os.comm) == StatusZombie {
|
||||
_, err := dbp.trapWait(-1)
|
||||
if status(dbp.pid, dbp.os.comm) == StatusZombie {
|
||||
_, err := dbp.trapWaitInternal(-1, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -460,16 +399,16 @@ func (dbp *Process) exitGuard(err error) error {
|
||||
|
||||
func (dbp *Process) resume() error {
|
||||
// all threads stopped over a breakpoint are made to step over it
|
||||
for _, thread := range dbp.Threads {
|
||||
if thread.CurrentBreakpoint != nil {
|
||||
for _, thread := range dbp.threads {
|
||||
if thread.CurrentBreakpoint.Breakpoint != nil {
|
||||
if err := thread.StepInstruction(); err != nil {
|
||||
return err
|
||||
}
|
||||
thread.CurrentBreakpoint = nil
|
||||
thread.CurrentBreakpoint.Clear()
|
||||
}
|
||||
}
|
||||
// everything is resumed
|
||||
for _, thread := range dbp.Threads {
|
||||
for _, thread := range dbp.threads {
|
||||
if err := thread.resume(); err != nil && err != sys.ESRCH {
|
||||
return err
|
||||
}
|
||||
@ -477,6 +416,78 @@ func (dbp *Process) resume() error {
|
||||
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 {
|
||||
return sys.Kill(pid, sys.SIGINT)
|
||||
}
|
497
vendor/github.com/derekparker/delve/pkg/proc/native/proc_windows.go
generated
vendored
Normal file
497
vendor/github.com/derekparker/delve/pkg/proc/native/proc_windows.go
generated
vendored
Normal 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()
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
package proc
|
||||
//+build darwin,macnative
|
||||
|
||||
package native
|
||||
|
||||
import sys "golang.org/x/sys/unix"
|
||||
|
@ -1,11 +1,12 @@
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// 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
|
||||
// and amd64_supply_xsave in gdb/amd64-tdep.c.html
|
||||
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s 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(®set.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
|
||||
}
|
||||
|
||||
@ -66,31 +68,16 @@ func PtraceGetRegset(tid int) (regset PtraceXsave, err error) {
|
||||
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)
|
||||
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
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= iov.Len {
|
||||
return
|
||||
}
|
||||
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
|
||||
regset.Xsave = xstateargs[:iov.Len]
|
||||
err = proc.LinuxX86XstateRead(regset.Xsave, false, ®set)
|
||||
return regset, err
|
||||
}
|
@ -1,12 +1,18 @@
|
||||
package proc
|
||||
//+build darwin,macnative
|
||||
|
||||
package native
|
||||
|
||||
// #include "threads_darwin.h"
|
||||
import "C"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"rsc.io/x86/x86asm"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// Regs represents CPU registers on an AMD64 processor.
|
||||
@ -33,10 +39,10 @@ type Regs struct {
|
||||
fs uint64
|
||||
gs uint64
|
||||
gsBase uint64
|
||||
fpregs []Register
|
||||
fpregs []proc.Register
|
||||
}
|
||||
|
||||
func (r *Regs) Slice() []Register {
|
||||
func (r *Regs) Slice() []proc.Register {
|
||||
var regs = []struct {
|
||||
k string
|
||||
v uint64
|
||||
@ -64,12 +70,12 @@ func (r *Regs) Slice() []Register {
|
||||
{"Gs", r.gs},
|
||||
{"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 {
|
||||
if reg.k == "Rflags" {
|
||||
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
|
||||
out = proc.AppendEflagReg(out, reg.k, reg.v)
|
||||
} else {
|
||||
out = appendQwordReg(out, reg.k, reg.v)
|
||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||
}
|
||||
}
|
||||
out = append(out, r.fpregs...)
|
||||
@ -88,6 +94,10 @@ func (r *Regs) SP() uint64 {
|
||||
return r.rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
return r.rbp
|
||||
}
|
||||
|
||||
// CX returns the value of the RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.rcx
|
||||
@ -100,8 +110,12 @@ func (r *Regs) TLS() uint64 {
|
||||
return r.gsBase
|
||||
}
|
||||
|
||||
func (r *Regs) GAddr() (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// 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))
|
||||
if kret != C.KERN_SUCCESS {
|
||||
return fmt.Errorf("could not set pc")
|
||||
@ -109,6 +123,15 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
|
||||
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) {
|
||||
reg := x86asm.Reg(n)
|
||||
const (
|
||||
@ -130,7 +153,7 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
case x86asm.AH:
|
||||
return (r.rax >> 8) & mask8, nil
|
||||
case x86asm.CH:
|
||||
return (r.rax >> 8) & mask8, nil
|
||||
return (r.rcx >> 8) & mask8, nil
|
||||
case x86asm.DH:
|
||||
return (r.rdx >> 8) & mask8, nil
|
||||
case x86asm.BH:
|
||||
@ -263,10 +286,10 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
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 identity C.thread_identifier_info_data_t
|
||||
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")
|
||||
}
|
||||
|
||||
regs.fpregs = appendWordReg(regs.fpregs, "CW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fcw))))
|
||||
regs.fpregs = appendWordReg(regs.fpregs, "SW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fsw))))
|
||||
regs.fpregs = appendWordReg(regs.fpregs, "TW", uint16(fpstate.__fpu_ftw))
|
||||
regs.fpregs = 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 = appendQwordReg(regs.fpregs, "FDP", uint64(fpstate.__fpu_ds)<<32|uint64(fpstate.__fpu_dp))
|
||||
regs.fpregs = proc.AppendWordReg(regs.fpregs, "CW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fcw))))
|
||||
regs.fpregs = proc.AppendWordReg(regs.fpregs, "SW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fsw))))
|
||||
regs.fpregs = proc.AppendWordReg(regs.fpregs, "TW", uint16(fpstate.__fpu_ftw))
|
||||
regs.fpregs = proc.AppendWordReg(regs.fpregs, "FOP", uint16(fpstate.__fpu_fop))
|
||||
regs.fpregs = proc.AppendQwordReg(regs.fpregs, "FIP", uint64(fpstate.__fpu_cs)<<32|uint64(fpstate.__fpu_ip))
|
||||
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]} {
|
||||
stb := C.GoBytes(unsafe.Pointer(st), 10)
|
||||
mantissa := binary.LittleEndian.Uint64(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 = appendDwordReg(regs.fpregs, "MXCSR_MASK", uint32(fpstate.__fpu_mxcsrmask))
|
||||
regs.fpregs = proc.AppendMxcsrReg(regs.fpregs, "MXCSR", uint64(fpstate.__fpu_mxcsr))
|
||||
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]} {
|
||||
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
|
||||
}
|
||||
|
||||
func (thread *Thread) saveRegisters() (Registers, error) {
|
||||
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers)
|
||||
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")
|
||||
}
|
||||
func (r *Regs) Copy() proc.Registers {
|
||||
//TODO(aarzilli): implement this to support function calls
|
||||
return nil
|
||||
}
|
@ -1,20 +1,22 @@
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"rsc.io/x86/x86asm"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// Regs is a wrapper for sys.PtraceRegs.
|
||||
type Regs struct {
|
||||
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 {
|
||||
k string
|
||||
v uint64
|
||||
@ -47,12 +49,12 @@ func (r *Regs) Slice() []Register {
|
||||
{"Fs", r.regs.Fs},
|
||||
{"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 {
|
||||
if reg.k == "Eflags" {
|
||||
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
|
||||
out = proc.AppendEflagReg(out, reg.k, reg.v)
|
||||
} else {
|
||||
out = appendQwordReg(out, reg.k, reg.v)
|
||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||
}
|
||||
}
|
||||
out = append(out, r.fpregs...)
|
||||
@ -69,6 +71,10 @@ func (r *Regs) SP() uint64 {
|
||||
return r.regs.Rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
return r.regs.Rbp
|
||||
}
|
||||
|
||||
// CX returns the value of RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.regs.Rcx
|
||||
@ -80,10 +86,44 @@ func (r *Regs) TLS() uint64 {
|
||||
return r.regs.Fs_base
|
||||
}
|
||||
|
||||
func (r *Regs) GAddr() (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
@ -108,7 +148,7 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
case x86asm.AH:
|
||||
return (r.regs.Rax >> 8) & mask8, nil
|
||||
case x86asm.CH:
|
||||
return (r.regs.Rax >> 8) & mask8, nil
|
||||
return (r.regs.Rcx >> 8) & mask8, nil
|
||||
case x86asm.DH:
|
||||
return (r.regs.Rdx >> 8) & mask8, nil
|
||||
case x86asm.BH:
|
||||
@ -241,10 +281,10 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
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 (
|
||||
regs sys.PtraceRegs
|
||||
err error
|
||||
@ -253,9 +293,11 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Regs{®s, nil}
|
||||
r := &Regs{®s, nil, nil}
|
||||
if floatingPoint {
|
||||
r.fpregs, err = thread.fpRegisters()
|
||||
var fpregset proc.LinuxX86Xstate
|
||||
r.fpregs, fpregset, err = thread.fpRegisters()
|
||||
r.fpregset = &fpregset
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -263,27 +305,6 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
|
||||
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 (
|
||||
_X86_XSTATE_MAX_SIZE = 2688
|
||||
_NT_X86_XSTATE = 0x202
|
||||
@ -294,32 +315,28 @@ const (
|
||||
_XSAVE_SSE_REGION_LEN = 416
|
||||
)
|
||||
|
||||
func (thread *Thread) fpRegisters() (regs []Register, err error) {
|
||||
var fpregs PtraceXsave
|
||||
func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs proc.LinuxX86Xstate, err error) {
|
||||
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
|
||||
|
||||
// x87 registers
|
||||
regs = appendWordReg(regs, "CW", fpregs.Cwd)
|
||||
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]))
|
||||
regs = fpregs.Decode()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"rsc.io/x86/x86asm"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// Regs represents CPU registers on an AMD64 processor.
|
||||
@ -30,10 +33,11 @@ type Regs struct {
|
||||
fs uint64
|
||||
gs uint64
|
||||
tls uint64
|
||||
context *_CONTEXT
|
||||
fltSave *_XMM_SAVE_AREA32
|
||||
}
|
||||
|
||||
func (r *Regs) Slice() []Register {
|
||||
func (r *Regs) Slice() []proc.Register {
|
||||
var regs = []struct {
|
||||
k string
|
||||
v uint64
|
||||
@ -65,31 +69,31 @@ func (r *Regs) Slice() []Register {
|
||||
if r.fltSave != nil {
|
||||
outlen += 6 + 8 + 2 + 16
|
||||
}
|
||||
out := make([]Register, 0, outlen)
|
||||
out := make([]proc.Register, 0, outlen)
|
||||
for _, reg := range regs {
|
||||
if reg.k == "Eflags" {
|
||||
out = append(out, Register{reg.k, eflagsDescription.Describe(reg.v, 64)})
|
||||
out = proc.AppendEflagReg(out, reg.k, reg.v)
|
||||
} else {
|
||||
out = appendQwordReg(out, reg.k, reg.v)
|
||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||
}
|
||||
}
|
||||
if r.fltSave != nil {
|
||||
out = appendWordReg(out, "CW", r.fltSave.ControlWord)
|
||||
out = appendWordReg(out, "SW", r.fltSave.StatusWord)
|
||||
out = appendWordReg(out, "TW", uint16(r.fltSave.TagWord))
|
||||
out = appendWordReg(out, "FOP", r.fltSave.ErrorOpcode)
|
||||
out = 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.AppendWordReg(out, "CW", r.fltSave.ControlWord)
|
||||
out = proc.AppendWordReg(out, "SW", r.fltSave.StatusWord)
|
||||
out = proc.AppendWordReg(out, "TW", uint16(r.fltSave.TagWord))
|
||||
out = proc.AppendWordReg(out, "FOP", r.fltSave.ErrorOpcode)
|
||||
out = proc.AppendQwordReg(out, "FIP", uint64(r.fltSave.ErrorSelector)<<32|uint64(r.fltSave.ErrorOffset))
|
||||
out = proc.AppendQwordReg(out, "FDP", uint64(r.fltSave.DataSelector)<<32|uint64(r.fltSave.DataOffset))
|
||||
|
||||
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 = appendDwordReg(out, "MXCSR_MASK", r.fltSave.MxCsr_Mask)
|
||||
out = proc.AppendMxcsrReg(out, "MXCSR", uint64(r.fltSave.MxCsr))
|
||||
out = proc.AppendDwordReg(out, "MXCSR_MASK", r.fltSave.MxCsr_Mask)
|
||||
|
||||
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
|
||||
@ -107,6 +111,10 @@ func (r *Regs) SP() uint64 {
|
||||
return r.rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
return r.rbp
|
||||
}
|
||||
|
||||
// CX returns the value of the RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
return r.rcx
|
||||
@ -119,8 +127,12 @@ func (r *Regs) TLS() uint64 {
|
||||
return r.tls
|
||||
}
|
||||
|
||||
func (r *Regs) GAddr() (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// 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.ContextFlags = _CONTEXT_ALL
|
||||
|
||||
@ -134,6 +146,35 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
|
||||
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) {
|
||||
reg := x86asm.Reg(n)
|
||||
const (
|
||||
@ -155,7 +196,7 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
case x86asm.AH:
|
||||
return (r.rax >> 8) & mask8, nil
|
||||
case x86asm.CH:
|
||||
return (r.rax >> 8) & mask8, nil
|
||||
return (r.rcx >> 8) & mask8, nil
|
||||
case x86asm.DH:
|
||||
return (r.rdx >> 8) & mask8, nil
|
||||
case x86asm.BH:
|
||||
@ -288,10 +329,10 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
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.ContextFlags = _CONTEXT_ALL
|
||||
@ -334,14 +375,16 @@ func registers(thread *Thread, floatingPoint bool) (Registers, error) {
|
||||
if floatingPoint {
|
||||
regs.fltSave = &context.FltSave
|
||||
}
|
||||
regs.context = context
|
||||
|
||||
return regs, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) saveRegisters() (Registers, error) {
|
||||
return nil, fmt.Errorf("not implemented: saveRegisters")
|
||||
}
|
||||
|
||||
func (thread *Thread) restoreRegisters() error {
|
||||
return fmt.Errorf("not implemented: restoreRegisters")
|
||||
func (r *Regs) Copy() proc.Registers {
|
||||
var rr Regs
|
||||
rr = *r
|
||||
rr.context = newCONTEXT()
|
||||
*(rr.context) = *(r.context)
|
||||
rr.fltSave = &rr.context.FltSave
|
||||
return &rr
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
|
||||
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"syscall"
|
@ -1,4 +1,4 @@
|
||||
package proc
|
||||
package native
|
||||
|
||||
import "unsafe"
|
||||
|
174
vendor/github.com/derekparker/delve/pkg/proc/native/threads.go
generated
vendored
Normal file
174
vendor/github.com/derekparker/delve/pkg/proc/native/threads.go
generated
vendored
Normal 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
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
#include "threads_darwin.h"
|
||||
|
||||
int
|
@ -1,12 +1,18 @@
|
||||
package proc
|
||||
//+build darwin,macnative
|
||||
|
||||
package native
|
||||
|
||||
// #include "threads_darwin.h"
|
||||
// #include "proc_darwin.h"
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
sys "golang.org/x/sys/unix"
|
||||
"unsafe"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// WaitStatus is a synonym for the platform-specific WaitStatus
|
||||
@ -24,7 +30,7 @@ type OSSpecificDetails struct {
|
||||
// be continued.
|
||||
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)
|
||||
if kret != C.KERN_SUCCESS {
|
||||
errStr := C.GoString(C.mach_error_string(C.mach_error_t(kret)))
|
||||
@ -35,7 +41,7 @@ func (t *Thread) halt() (err error) {
|
||||
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)
|
||||
return
|
||||
}
|
||||
@ -49,7 +55,7 @@ func (t *Thread) singleStep() error {
|
||||
return fmt.Errorf("could not single step")
|
||||
}
|
||||
for {
|
||||
twthread, err := t.dbp.trapWait(t.dbp.Pid)
|
||||
twthread, err := t.dbp.trapWait(t.dbp.pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -66,10 +72,9 @@ func (t *Thread) singleStep() error {
|
||||
}
|
||||
|
||||
func (t *Thread) resume() error {
|
||||
t.running = true
|
||||
// TODO(dp) set flag for ptrace stops
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -80,29 +85,35 @@ func (t *Thread) resume() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Thread) blocked() bool {
|
||||
func (t *Thread) Blocked() bool {
|
||||
// TODO(dp) cache the func pc to remove this lookup
|
||||
pc, err := t.PC()
|
||||
regs, err := t.Registers(false)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fn := t.dbp.goSymTable.PCToFunc(pc)
|
||||
pc := regs.PC()
|
||||
fn := t.BinInfo().PCToFunc(pc)
|
||||
if fn == nil {
|
||||
return false
|
||||
}
|
||||
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
|
||||
default:
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -117,20 +128,26 @@ func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) {
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
func (t *Thread) readMemory(addr uintptr, size int) ([]byte, error) {
|
||||
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 (
|
||||
buf = make([]byte, size)
|
||||
vmData = unsafe.Pointer(&buf[0])
|
||||
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)
|
||||
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")
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//+build darwin,macnative
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <mach/mach.h>
|
132
vendor/github.com/derekparker/delve/pkg/proc/native/threads_linux.go
generated
vendored
Normal file
132
vendor/github.com/derekparker/delve/pkg/proc/native/threads_linux.go
generated
vendored
Normal 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
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
sys "golang.org/x/sys/windows"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// WaitStatus is a synonym for the platform-specific WaitStatus
|
||||
@ -15,16 +18,6 @@ type OSSpecificDetails struct {
|
||||
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 {
|
||||
context := newCONTEXT()
|
||||
context.ContextFlags = _CONTEXT_ALL
|
||||
@ -57,7 +50,7 @@ func (t *Thread) singleStep() error {
|
||||
}
|
||||
if tid == 0 {
|
||||
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 {
|
||||
@ -65,7 +58,7 @@ func (t *Thread) singleStep() error {
|
||||
}
|
||||
|
||||
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() {
|
||||
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 {
|
||||
return err
|
||||
@ -93,24 +86,24 @@ func (t *Thread) singleStep() error {
|
||||
}
|
||||
|
||||
func (t *Thread) resume() error {
|
||||
t.running = true
|
||||
var err error
|
||||
t.dbp.execPtraceFunc(func() {
|
||||
//TODO: Note that we are ignoring the thread we were asked to continue and are continuing the
|
||||
//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
|
||||
}
|
||||
|
||||
func (t *Thread) blocked() bool {
|
||||
func (t *Thread) Blocked() bool {
|
||||
// TODO: Probably incorrect - what are the runtime functions that
|
||||
// indicate blocking on Windows?
|
||||
pc, err := t.PC()
|
||||
regs, err := t.Registers(false)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fn := t.dbp.goSymTable.PCToFunc(pc)
|
||||
pc := regs.PC()
|
||||
fn := t.BinInfo().PCToFunc(pc)
|
||||
if fn == nil {
|
||||
return false
|
||||
}
|
||||
@ -122,13 +115,19 @@ func (t *Thread) blocked() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Thread) stopped() bool {
|
||||
// TODO: We are assuming that threads are always stopped
|
||||
// during command execution.
|
||||
// Stopped returns whether the thread is stopped at the operating system
|
||||
// level. On windows this always returns true.
|
||||
func (t *Thread) Stopped() bool {
|
||||
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
|
||||
err := _WriteProcessMemory(t.dbp.os.hProcess, addr, &data[0], uintptr(len(data)), &count)
|
||||
if err != nil {
|
||||
@ -137,15 +136,23 @@ func (t *Thread) writeMemory(addr uintptr, data []byte) (int, error) {
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
func (t *Thread) readMemory(addr uintptr, size int) ([]byte, error) {
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
var ErrShortRead = errors.New("short read")
|
||||
|
||||
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
|
||||
buf := make([]byte, size)
|
||||
err := _ReadProcessMemory(t.dbp.os.hProcess, addr, &buf[0], uintptr(size), &count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
err := _ReadProcessMemory(t.dbp.os.hProcess, addr, &buf[0], uintptr(len(buf)), &count)
|
||||
if err == nil && count != uintptr(len(buf)) {
|
||||
err = ErrShortRead
|
||||
}
|
||||
return buf[:count], nil
|
||||
return int(count), err
|
||||
}
|
||||
|
||||
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
||||
return _SetThreadContext(t.os.hThread, savedRegs.(*Regs).context)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||
|
||||
package proc
|
||||
package native
|
||||
|
||||
import (
|
||||
"syscall"
|
658
vendor/github.com/derekparker/delve/pkg/proc/proc.go
generated
vendored
Normal file
658
vendor/github.com/derekparker/delve/pkg/proc/proc.go
generated
vendored
Normal 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
|
||||
}
|
348
vendor/github.com/derekparker/delve/pkg/proc/registers.go
generated
vendored
Normal file
348
vendor/github.com/derekparker/delve/pkg/proc/registers.go
generated
vendored
Normal 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 Developer’s
|
||||
// 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 Developer’s 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, ®set.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
|
||||
}
|
90
vendor/github.com/derekparker/delve/pkg/proc/registers_amd64.go
generated
vendored
Normal file
90
vendor/github.com/derekparker/delve/pkg/proc/registers_amd64.go
generated
vendored
Normal 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
687
vendor/github.com/derekparker/delve/pkg/proc/stack.go
generated
vendored
Normal 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 ®, 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
568
vendor/github.com/derekparker/delve/pkg/proc/threads.go
generated
vendored
Normal 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
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
2119
vendor/github.com/derekparker/delve/pkg/proc/variables.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
191
vendor/github.com/derekparker/delve/pkg/terminal/config.go
generated
vendored
Normal file
191
vendor/github.com/derekparker/delve/pkg/terminal/config.go
generated
vendored
Normal 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
|
||||
}
|
@ -3,17 +3,18 @@ package terminal
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/derekparker/delve/service/api"
|
||||
)
|
||||
|
||||
func DisasmPrint(dv api.AsmInstructions, out io.Writer) {
|
||||
bw := bufio.NewWriter(out)
|
||||
defer bw.Flush()
|
||||
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)
|
||||
defer tw.Flush()
|
@ -27,10 +27,10 @@ func replaceDocPath(s string) string {
|
||||
}
|
||||
|
||||
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.Fprintf(w, "--------|------------\n")
|
||||
fmt.Fprint(w, "Command | Description\n")
|
||||
fmt.Fprint(w, "--------|------------\n")
|
||||
for _, cmd := range commands.cmds {
|
||||
h := cmd.helpMsg
|
||||
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, "\n")
|
||||
fmt.Fprint(w, "\n")
|
||||
|
||||
for _, cmd := range commands.cmds {
|
||||
fmt.Fprintf(w, "## %s\n%s\n\n", cmd.aliases[0], replaceDocPath(cmd.helpMsg))
|
||||
if len(cmd.aliases) > 1 {
|
||||
fmt.Fprintf(w, "Aliases:")
|
||||
fmt.Fprint(w, "Aliases:")
|
||||
for _, alias := range cmd.aliases[1:] {
|
||||
fmt.Fprintf(w, " %s", alias)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
}
|
@ -7,21 +7,42 @@ import (
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"syscall"
|
||||
|
||||
"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/api"
|
||||
)
|
||||
|
||||
const (
|
||||
historyFile string = ".dbg_history"
|
||||
terminalBlueEscapeCode string = "\033[34m"
|
||||
terminalHighlightEscapeCode string = "\033[%2dm"
|
||||
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.
|
||||
type Term struct {
|
||||
client service.Client
|
||||
@ -32,15 +53,40 @@ type Term struct {
|
||||
dumb bool
|
||||
stdout io.Writer
|
||||
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.
|
||||
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)
|
||||
if conf != nil && conf.Aliases != nil {
|
||||
cmds.Merge(conf.Aliases)
|
||||
}
|
||||
|
||||
if conf == nil {
|
||||
conf = &config.Config{}
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
|
||||
dumb := strings.ToLower(os.Getenv("TERM")) == "dumb"
|
||||
@ -50,6 +96,17 @@ func New(client service.Client, conf *config.Config) *Term {
|
||||
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{
|
||||
client: client,
|
||||
conf: conf,
|
||||
@ -66,22 +123,55 @@ func (t *Term) Close() {
|
||||
t.line.Close()
|
||||
}
|
||||
|
||||
// Run begins running dlv in the terminal.
|
||||
func (t *Term) Run() (int, error) {
|
||||
defer t.Close()
|
||||
|
||||
// Send the debugger a halt command on SIGINT
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, syscall.SIGINT)
|
||||
go func() {
|
||||
func (t *Term) sigintGuard(ch <-chan os.Signal, multiClient bool) {
|
||||
for range ch {
|
||||
fmt.Printf("received SIGINT, stopping process (will not forward signal)")
|
||||
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")
|
||||
}
|
||||
|
||||
} else {
|
||||
fmt.Printf("received SIGINT, stopping process (will not forward signal)\n")
|
||||
_, err := t.client.Halt()
|
||||
if err != nil {
|
||||
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) {
|
||||
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")
|
||||
}
|
||||
|
||||
cmdstr, args := parseCommand(cmdstr)
|
||||
if err := t.cmds.Call(cmdstr, args, t); err != nil {
|
||||
if err := t.cmds.Call(cmdstr, t); err != nil {
|
||||
if _, ok := err.(ExitRequestError); ok {
|
||||
return t.handleExit()
|
||||
}
|
||||
@ -139,6 +228,12 @@ func (t *Term) Run() (int, error) {
|
||||
if strings.Contains(err.Error(), "exited") {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
} else {
|
||||
t.quittingMutex.Lock()
|
||||
quitting := t.quitting
|
||||
t.quittingMutex.Unlock()
|
||||
if quitting {
|
||||
return t.handleExit()
|
||||
}
|
||||
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.
|
||||
func (t *Term) Println(prefix, str string) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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`
|
||||
// for file path `/dir/subdir/file` will return file path `/new/file`.
|
||||
// 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 {
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
||||
if runtime.GOOS == "windows" {
|
||||
return strings.ToLower(path)
|
||||
}
|
||||
return path
|
||||
@ -207,6 +303,22 @@ func (t *Term) promptForInput() (string, error) {
|
||||
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) {
|
||||
fullHistoryFile, err := config.GetConfigFilePath(historyFile)
|
||||
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()
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if !s.Exited {
|
||||
kill := true
|
||||
if t.client.AttachedToExistingProcess() {
|
||||
answer, err := t.line.Prompt("Would you like to kill the process? [Y/n] ")
|
||||
if t.quitContinue {
|
||||
err := t.client.Disconnect(true)
|
||||
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 {
|
||||
return 2, io.EOF
|
||||
}
|
||||
answer = strings.ToLower(strings.TrimSpace(answer))
|
||||
kill = (answer != "n" && answer != "no")
|
||||
doDetach = answer
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func parseCommand(cmdstr string) (string, string) {
|
||||
vals := strings.SplitN(cmdstr, " ", 2)
|
||||
if len(vals) == 1 {
|
||||
return vals[0], ""
|
||||
// loadConfig returns an api.LoadConfig with the parameterss specified in
|
||||
// the configuration file.
|
||||
func (t *Term) loadConfig() api.LoadConfig {
|
||||
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
|
||||
}
|
@ -13,7 +13,10 @@ type Version struct {
|
||||
|
||||
var (
|
||||
// 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 {
|
79
vendor/github.com/derekparker/delve/proc/arch.go
generated
vendored
79
vendor/github.com/derekparker/delve/proc/arch.go
generated
vendored
@ -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
|
||||
}
|
163
vendor/github.com/derekparker/delve/proc/breakpoints.go
generated
vendored
163
vendor/github.com/derekparker/delve/proc/breakpoints.go
generated
vendored
@ -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)
|
||||
}
|
67
vendor/github.com/derekparker/delve/proc/disasm.go
generated
vendored
67
vendor/github.com/derekparker/delve/proc/disasm.go
generated
vendored
@ -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
|
||||
}
|
113
vendor/github.com/derekparker/delve/proc/go_version.go
generated
vendored
113
vendor/github.com/derekparker/delve/proc/go_version.go
generated
vendored
@ -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
|
||||
}
|
57
vendor/github.com/derekparker/delve/proc/mem.go
generated
vendored
57
vendor/github.com/derekparker/delve/proc/mem.go
generated
vendored
@ -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}
|
||||
}
|
942
vendor/github.com/derekparker/delve/proc/proc.go
generated
vendored
942
vendor/github.com/derekparker/delve/proc/proc.go
generated
vendored
@ -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)
|
||||
}
|
643
vendor/github.com/derekparker/delve/proc/proc_windows.go
generated
vendored
643
vendor/github.com/derekparker/delve/proc/proc_windows.go
generated
vendored
@ -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()
|
||||
}
|
13
vendor/github.com/derekparker/delve/proc/ptrace_windows.go
generated
vendored
13
vendor/github.com/derekparker/delve/proc/ptrace_windows.go
generated
vendored
@ -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))
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user