mirror of
https://github.com/beego/bee.git
synced 2025-11-05 00:44:00 +00:00
Update vendors
This commit is contained in:
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 (
|
||||
@@ -20,7 +20,7 @@ type SubstitutePathRule struct {
|
||||
// Directory path will be substituted if it matches `From`.
|
||||
From string
|
||||
// Path to which substitution is performed.
|
||||
To string
|
||||
To string
|
||||
}
|
||||
|
||||
// Slice of source code path substitution rules.
|
||||
@@ -29,9 +29,24 @@ type SubstitutePathRules []SubstitutePathRule
|
||||
// Config defines all configuration options available to be set through the config file.
|
||||
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,8 +64,11 @@ func LoadConfig() *Config {
|
||||
|
||||
f, err := os.Open(fullConfigFile)
|
||||
if err != nil {
|
||||
createDefaultConfig(fullConfigFile)
|
||||
return nil
|
||||
f, err = createDefaultConfig(fullConfigFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating default config file: %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
err := f.Close()
|
||||
@@ -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
|
||||
}
|
||||
878
vendor/github.com/derekparker/delve/pkg/dwarf/godwarf/type.go
generated
vendored
Normal file
878
vendor/github.com/derekparker/delve/pkg/dwarf/godwarf/type.go
generated
vendored
Normal file
@@ -0,0 +1,878 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// DWARF type information structures.
|
||||
// The format is heavily biased toward C, but for simplicity
|
||||
// the String methods use a pseudo-Go syntax.
|
||||
|
||||
// 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
|
||||
Size() int64
|
||||
}
|
||||
|
||||
// A CommonType holds fields common to multiple types.
|
||||
// If a field is not known or not applicable for a given type,
|
||||
// the zero value is used.
|
||||
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 dwarf.Offset // the offset at which this type was read
|
||||
}
|
||||
|
||||
func (c *CommonType) Common() *CommonType { return c }
|
||||
|
||||
func (c *CommonType) Size() int64 { return c.ByteSize }
|
||||
|
||||
// Basic types
|
||||
|
||||
// A BasicType holds fields common to all basic types.
|
||||
type BasicType struct {
|
||||
CommonType
|
||||
BitSize int64
|
||||
BitOffset int64
|
||||
}
|
||||
|
||||
func (b *BasicType) Basic() *BasicType { return b }
|
||||
|
||||
func (t *BasicType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
// A CharType represents a signed character type.
|
||||
type CharType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A UcharType represents an unsigned character type.
|
||||
type UcharType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// An IntType represents a signed integer type.
|
||||
type IntType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A UintType represents an unsigned integer type.
|
||||
type UintType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A FloatType represents a floating point type.
|
||||
type FloatType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A ComplexType represents a complex floating point type.
|
||||
type ComplexType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// A BoolType represents a boolean type.
|
||||
type BoolType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// An AddrType represents a machine address type.
|
||||
type AddrType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// An UnspecifiedType represents an implicit, unknown, ambiguous or nonexistent type.
|
||||
type UnspecifiedType struct {
|
||||
BasicType
|
||||
}
|
||||
|
||||
// qualifiers
|
||||
|
||||
// A QualType represents a type that has the C/C++ "const", "restrict", or "volatile" qualifier.
|
||||
type QualType struct {
|
||||
CommonType
|
||||
Qual string
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (t *QualType) String() string { return t.Qual + " " + t.Type.String() }
|
||||
|
||||
func (t *QualType) Size() int64 { return t.Type.Size() }
|
||||
|
||||
// An ArrayType represents a fixed size array type.
|
||||
type ArrayType struct {
|
||||
CommonType
|
||||
Type Type
|
||||
StrideBitSize int64 // if > 0, number of bits to hold each element
|
||||
Count int64 // if == -1, an incomplete array, like char x[].
|
||||
}
|
||||
|
||||
func (t *ArrayType) String() string {
|
||||
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.String()
|
||||
}
|
||||
|
||||
func (t *ArrayType) Size() int64 { return t.Count * t.Type.Size() }
|
||||
|
||||
// A VoidType represents the C void type.
|
||||
type VoidType struct {
|
||||
CommonType
|
||||
}
|
||||
|
||||
func (t *VoidType) String() string { return "void" }
|
||||
|
||||
// A PtrType represents a pointer type.
|
||||
type PtrType struct {
|
||||
CommonType
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (t *PtrType) String() string { return "*" + t.Type.String() }
|
||||
|
||||
// A StructType represents a struct, union, or C++ class type.
|
||||
type StructType struct {
|
||||
CommonType
|
||||
StructName string
|
||||
Kind string // "struct", "union", or "class".
|
||||
Field []*StructField
|
||||
Incomplete bool // if true, struct, union, class is declared but not defined
|
||||
}
|
||||
|
||||
// A StructField represents a field in a struct, union, or C++ class type.
|
||||
type StructField struct {
|
||||
Name string
|
||||
Type Type
|
||||
ByteOffset int64
|
||||
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 {
|
||||
if t.StructName != "" {
|
||||
return t.Kind + " " + t.StructName
|
||||
}
|
||||
return t.Defn()
|
||||
}
|
||||
|
||||
func (t *StructType) Defn() string {
|
||||
s := t.Kind
|
||||
if t.StructName != "" {
|
||||
s += " " + t.StructName
|
||||
}
|
||||
if t.Incomplete {
|
||||
s += " /*incomplete*/"
|
||||
return s
|
||||
}
|
||||
s += " {"
|
||||
for i, f := range t.Field {
|
||||
if i > 0 {
|
||||
s += "; "
|
||||
}
|
||||
s += f.Name + " " + f.Type.String()
|
||||
s += "@" + strconv.FormatInt(f.ByteOffset, 10)
|
||||
if f.BitSize > 0 {
|
||||
s += " : " + strconv.FormatInt(f.BitSize, 10)
|
||||
s += "@" + strconv.FormatInt(f.BitOffset, 10)
|
||||
}
|
||||
}
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
// A SliceType represents a Go slice type. It looks like a StructType, describing
|
||||
// the runtime-internal structure, with extra fields.
|
||||
type SliceType struct {
|
||||
StructType
|
||||
ElemType Type
|
||||
}
|
||||
|
||||
func (t *SliceType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "[]" + t.ElemType.String()
|
||||
}
|
||||
|
||||
// A StringType represents a Go string type. It looks like a StructType, describing
|
||||
// the runtime-internal structure, but we wrap it for neatness.
|
||||
type StringType struct {
|
||||
StructType
|
||||
}
|
||||
|
||||
func (t *StringType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "string"
|
||||
}
|
||||
|
||||
// An InterfaceType represents a Go interface.
|
||||
type InterfaceType struct {
|
||||
TypedefType
|
||||
}
|
||||
|
||||
func (t *InterfaceType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "Interface"
|
||||
}
|
||||
|
||||
// An EnumType represents an enumerated type.
|
||||
// The only indication of its native integer type is its ByteSize
|
||||
// (inside CommonType).
|
||||
type EnumType struct {
|
||||
CommonType
|
||||
EnumName string
|
||||
Val []*EnumValue
|
||||
}
|
||||
|
||||
// An EnumValue represents a single enumeration value.
|
||||
type EnumValue struct {
|
||||
Name string
|
||||
Val int64
|
||||
}
|
||||
|
||||
func (t *EnumType) String() string {
|
||||
s := "enum"
|
||||
if t.EnumName != "" {
|
||||
s += " " + t.EnumName
|
||||
}
|
||||
s += " {"
|
||||
for i, v := range t.Val {
|
||||
if i > 0 {
|
||||
s += "; "
|
||||
}
|
||||
s += v.Name + "=" + strconv.FormatInt(v.Val, 10)
|
||||
}
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
// A FuncType represents a function type.
|
||||
type FuncType struct {
|
||||
CommonType
|
||||
ReturnType Type
|
||||
ParamType []Type
|
||||
}
|
||||
|
||||
func (t *FuncType) String() string {
|
||||
s := "func("
|
||||
for i, t := range t.ParamType {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += t.String()
|
||||
}
|
||||
s += ")"
|
||||
if t.ReturnType != nil {
|
||||
s += " " + t.ReturnType.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// A DotDotDotType represents the variadic ... function parameter.
|
||||
type DotDotDotType struct {
|
||||
CommonType
|
||||
}
|
||||
|
||||
func (t *DotDotDotType) String() string { return "..." }
|
||||
|
||||
// A TypedefType represents a named type.
|
||||
type TypedefType struct {
|
||||
CommonType
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (t *TypedefType) String() string { return t.Name }
|
||||
|
||||
func (t *TypedefType) Size() int64 { return t.Type.Size() }
|
||||
|
||||
// A MapType represents a Go map type. It looks like a TypedefType, describing
|
||||
// the runtime-internal structure, with extra fields.
|
||||
type MapType struct {
|
||||
TypedefType
|
||||
KeyType Type
|
||||
ElemType Type
|
||||
}
|
||||
|
||||
func (t *MapType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "map[" + t.KeyType.String() + "]" + t.ElemType.String()
|
||||
}
|
||||
|
||||
// A ChanType represents a Go channel type.
|
||||
type ChanType struct {
|
||||
TypedefType
|
||||
ElemType Type
|
||||
}
|
||||
|
||||
func (t *ChanType) String() string {
|
||||
if t.Name != "" {
|
||||
return t.Name
|
||||
}
|
||||
return "chan " + t.ElemType.String()
|
||||
}
|
||||
|
||||
// Type reads the type at off in the DWARF ``info'' section.
|
||||
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 *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 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
|
||||
}
|
||||
r.Seek(off)
|
||||
e, err := r.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addressSize := r.AddressSize()
|
||||
if e == nil || e.Offset != off {
|
||||
return nil, dwarf.DecodeError{name, off, "no type at offset"}
|
||||
}
|
||||
|
||||
// Parse type from dwarf.Entry.
|
||||
// Must always set typeCache[off] before calling
|
||||
// d.Type recursively, to handle circular types correctly.
|
||||
var typ Type
|
||||
|
||||
nextDepth := 0
|
||||
|
||||
// Get next child; set err if error happens.
|
||||
next := func() *dwarf.Entry {
|
||||
if !e.Children {
|
||||
return nil
|
||||
}
|
||||
// Only return direct children.
|
||||
// Skip over composite entries that happen to be nested
|
||||
// inside this one. Most DWARF generators wouldn't generate
|
||||
// such a thing, but clang does.
|
||||
// See golang.org/issue/6472.
|
||||
for {
|
||||
kid, err1 := r.Next()
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return nil
|
||||
}
|
||||
if kid.Tag == 0 {
|
||||
if nextDepth > 0 {
|
||||
nextDepth--
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if kid.Children {
|
||||
nextDepth++
|
||||
}
|
||||
if nextDepth > 0 {
|
||||
continue
|
||||
}
|
||||
return kid
|
||||
}
|
||||
}
|
||||
|
||||
// Get Type referred to by dwarf.Entry's attr.
|
||||
// Set err if error happens. Not having a type is an error.
|
||||
typeOf := func(e *dwarf.Entry, attr dwarf.Attr) Type {
|
||||
tval := e.Val(attr)
|
||||
var t Type
|
||||
switch toff := tval.(type) {
|
||||
case dwarf.Offset:
|
||||
if t, err = readType(d, name, d.Reader(), toff, typeCache); err != nil {
|
||||
return nil
|
||||
}
|
||||
case uint64:
|
||||
err = dwarf.DecodeError{name, e.Offset, "DWARFv4 section debug_types unsupported"}
|
||||
return nil
|
||||
default:
|
||||
// It appears that no Type means "void".
|
||||
return new(VoidType)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
switch e.Tag {
|
||||
case dwarf.TagArrayType:
|
||||
// Multi-dimensional array. (DWARF v2 §5.4)
|
||||
// Attributes:
|
||||
// AttrType:subtype [required]
|
||||
// AttrStrideSize: distance in bits between each element of the array
|
||||
// AttrStride: distance in bytes between each element of the array
|
||||
// AttrByteSize: size of entire array
|
||||
// Children:
|
||||
// TagSubrangeType or TagEnumerationType giving one dimension.
|
||||
// dimensions are in left to right order.
|
||||
t := new(ArrayType)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.Type = typeOf(e, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
if bytes, ok := e.Val(dwarf.AttrStride).(int64); ok {
|
||||
t.StrideBitSize = 8 * bytes
|
||||
} 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
|
||||
// array's element type.
|
||||
t.StrideBitSize = 8 * t.Type.Size()
|
||||
}
|
||||
|
||||
// Accumulate dimensions,
|
||||
ndim := 0
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
// TODO(rsc): Can also be TagEnumerationType
|
||||
// but haven't seen that in the wild yet.
|
||||
switch kid.Tag {
|
||||
case dwarf.TagSubrangeType:
|
||||
count, ok := kid.Val(dwarf.AttrCount).(int64)
|
||||
if !ok {
|
||||
// Old binaries may have an upper bound instead.
|
||||
count, ok = kid.Val(dwarf.AttrUpperBound).(int64)
|
||||
if ok {
|
||||
count++ // Length is one more than upper bound.
|
||||
} else {
|
||||
count = -1 // As in x[].
|
||||
}
|
||||
}
|
||||
if ndim == 0 {
|
||||
t.Count = count
|
||||
} else {
|
||||
// Multidimensional array.
|
||||
// Create new array type underneath this one.
|
||||
t.Type = &ArrayType{Type: t.Type, Count: count}
|
||||
}
|
||||
ndim++
|
||||
case dwarf.TagEnumerationType:
|
||||
err = dwarf.DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}
|
||||
goto Error
|
||||
}
|
||||
}
|
||||
if ndim == 0 {
|
||||
// LLVM generates this for x[].
|
||||
t.Count = -1
|
||||
}
|
||||
|
||||
case dwarf.TagBaseType:
|
||||
// Basic type. (DWARF v2 §5.1)
|
||||
// Attributes:
|
||||
// AttrName: name of base type in programming language of the compilation unit [required]
|
||||
// AttrEncoding: encoding value for type (encFloat etc) [required]
|
||||
// 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(dwarf.AttrName).(string)
|
||||
enc, ok := e.Val(dwarf.AttrEncoding).(int64)
|
||||
if !ok {
|
||||
err = dwarf.DecodeError{name, e.Offset, "missing encoding attribute for " + name}
|
||||
goto Error
|
||||
}
|
||||
switch enc {
|
||||
default:
|
||||
err = dwarf.DecodeError{name, e.Offset, "unrecognized encoding attribute value"}
|
||||
goto Error
|
||||
|
||||
case encAddress:
|
||||
typ = new(AddrType)
|
||||
case encBoolean:
|
||||
typ = new(BoolType)
|
||||
case encComplexFloat:
|
||||
typ = new(ComplexType)
|
||||
if name == "complex" {
|
||||
// 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(dwarf.AttrByteSize).(int64); byteSize {
|
||||
case 8:
|
||||
name = "complex float"
|
||||
case 16:
|
||||
name = "complex double"
|
||||
}
|
||||
}
|
||||
case encFloat:
|
||||
typ = new(FloatType)
|
||||
case encSigned:
|
||||
typ = new(IntType)
|
||||
case encUnsigned:
|
||||
typ = new(UintType)
|
||||
case encSignedChar:
|
||||
typ = new(CharType)
|
||||
case encUnsignedChar:
|
||||
typ = new(UcharType)
|
||||
}
|
||||
typeCache[off] = typ
|
||||
t := typ.(interface {
|
||||
Basic() *BasicType
|
||||
}).Basic()
|
||||
t.Name = name
|
||||
t.BitSize, _ = e.Val(dwarf.AttrBitSize).(int64)
|
||||
t.BitOffset, _ = e.Val(dwarf.AttrBitOffset).(int64)
|
||||
t.ReflectKind = getKind(e)
|
||||
|
||||
case dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType:
|
||||
// Structure, union, or class type. (DWARF v2 §5.5)
|
||||
// Also Slices and Strings (Go-specific).
|
||||
// Attributes:
|
||||
// AttrName: name of struct, union, or class
|
||||
// AttrByteSize: byte size [required]
|
||||
// AttrDeclaration: if true, struct/union/class is incomplete
|
||||
// AttrGoElem: present for slices only.
|
||||
// Children:
|
||||
// TagMember to describe one member.
|
||||
// AttrName: name of member [required]
|
||||
// AttrType: type of member [required]
|
||||
// AttrByteSize: size in bytes
|
||||
// AttrBitOffset: bit offset within bytes for bit fields
|
||||
// AttrBitSize: bit size for bit fields
|
||||
// AttrDataMemberLoc: location within struct [required for struct, class]
|
||||
// There is much more to handle C++, all ignored for now.
|
||||
t := new(StructType)
|
||||
t.ReflectKind = getKind(e)
|
||||
switch t.ReflectKind {
|
||||
case reflect.Slice:
|
||||
slice := new(SliceType)
|
||||
slice.ElemType = typeOf(e, AttrGoElem)
|
||||
t = &slice.StructType
|
||||
typ = slice
|
||||
case reflect.String:
|
||||
str := new(StringType)
|
||||
t = &str.StructType
|
||||
typ = str
|
||||
default:
|
||||
typ = t
|
||||
}
|
||||
typeCache[off] = typ
|
||||
switch e.Tag {
|
||||
case dwarf.TagClassType:
|
||||
t.Kind = "class"
|
||||
case dwarf.TagStructType:
|
||||
t.Kind = "struct"
|
||||
case dwarf.TagUnionType:
|
||||
t.Kind = "union"
|
||||
}
|
||||
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 == dwarf.TagMember {
|
||||
f := new(StructField)
|
||||
if f.Type = typeOf(kid, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
switch loc := kid.Val(dwarf.AttrDataMemberLoc).(type) {
|
||||
case []byte:
|
||||
// TODO: Should have original compilation
|
||||
// unit here, not unknownFormat.
|
||||
if len(loc) == 0 {
|
||||
// Empty exprloc. f.ByteOffset=0.
|
||||
break
|
||||
}
|
||||
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 op.DW_OP_consts:
|
||||
// Handle opcode sequence [DW_OP_consts <sleb128> DW_OP_plus]
|
||||
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()
|
||||
default:
|
||||
err = dwarf.DecodeError{name, kid.Offset, fmt.Sprintf("unexpected opcode 0x%x", op_)}
|
||||
goto Error
|
||||
}
|
||||
if b.Err != nil {
|
||||
err = b.Err
|
||||
goto Error
|
||||
}
|
||||
case int64:
|
||||
f.ByteOffset = loc
|
||||
}
|
||||
|
||||
haveBitOffset := false
|
||||
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
|
||||
if !haveBitOffset {
|
||||
bito = f.ByteOffset * 8
|
||||
}
|
||||
if bito == lastFieldBitOffset && t.Kind != "union" {
|
||||
// Last field was zero width. Fix array length.
|
||||
// (DWARF writes out 0-length arrays as if they were 1-length arrays.)
|
||||
zeroArray(lastFieldType)
|
||||
}
|
||||
lastFieldType = f.Type
|
||||
lastFieldBitOffset = bito
|
||||
}
|
||||
}
|
||||
if t.Kind != "union" {
|
||||
b, ok := e.Val(dwarf.AttrByteSize).(int64)
|
||||
if ok && b*8 == lastFieldBitOffset {
|
||||
// Final field must be zero width. Fix array length.
|
||||
zeroArray(lastFieldType)
|
||||
}
|
||||
}
|
||||
|
||||
case dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType:
|
||||
// Type modifier (DWARF v2 §5.2)
|
||||
// Attributes:
|
||||
// AttrType: subtype
|
||||
t := new(QualType)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.Type = typeOf(e, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
switch e.Tag {
|
||||
case dwarf.TagConstType:
|
||||
t.Qual = "const"
|
||||
case dwarf.TagRestrictType:
|
||||
t.Qual = "restrict"
|
||||
case dwarf.TagVolatileType:
|
||||
t.Qual = "volatile"
|
||||
}
|
||||
|
||||
case dwarf.TagEnumerationType:
|
||||
// Enumeration type (DWARF v2 §5.6)
|
||||
// Attributes:
|
||||
// AttrName: enum name if any
|
||||
// AttrByteSize: bytes required to represent largest value
|
||||
// Children:
|
||||
// TagEnumerator:
|
||||
// AttrName: name of constant
|
||||
// AttrConstValue: value of constant
|
||||
t := new(EnumType)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
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 == dwarf.TagEnumerator {
|
||||
f := new(EnumValue)
|
||||
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)
|
||||
copy(val, t.Val)
|
||||
t.Val = val
|
||||
}
|
||||
t.Val = t.Val[0 : n+1]
|
||||
t.Val[n] = f
|
||||
}
|
||||
}
|
||||
|
||||
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(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if e.Val(dwarf.AttrType) == nil {
|
||||
t.Type = &VoidType{}
|
||||
break
|
||||
}
|
||||
t.Type = typeOf(e, dwarf.AttrType)
|
||||
|
||||
case dwarf.TagSubroutineType:
|
||||
// Subroutine type. (DWARF v2 §5.7)
|
||||
// Attributes:
|
||||
// AttrType: type of return value if any
|
||||
// AttrName: possible name of type [ignored]
|
||||
// AttrPrototyped: whether used ANSI C prototype [ignored]
|
||||
// Children:
|
||||
// TagFormalParameter: typed parameter
|
||||
// AttrType: type of parameter
|
||||
// TagUnspecifiedParameter: final ...
|
||||
t := new(FuncType)
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.ReflectKind = getKind(e)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
if t.ReturnType = typeOf(e, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
t.ParamType = make([]Type, 0, 8)
|
||||
for kid := next(); kid != nil; kid = next() {
|
||||
var tkid Type
|
||||
switch kid.Tag {
|
||||
default:
|
||||
continue
|
||||
case dwarf.TagFormalParameter:
|
||||
if tkid = typeOf(kid, dwarf.AttrType); err != nil {
|
||||
goto Error
|
||||
}
|
||||
case dwarf.TagUnspecifiedParameters:
|
||||
tkid = &DotDotDotType{}
|
||||
}
|
||||
t.ParamType = append(t.ParamType, tkid)
|
||||
}
|
||||
|
||||
case dwarf.TagTypedef:
|
||||
// Typedef (DWARF v2 §5.3)
|
||||
// Also maps and channels (Go-specific).
|
||||
// Attributes:
|
||||
// AttrName: name [required]
|
||||
// AttrType: type definition [required]
|
||||
// AttrGoKey: present for maps.
|
||||
// AttrGoElem: present for maps and channels.
|
||||
t := new(TypedefType)
|
||||
t.ReflectKind = getKind(e)
|
||||
switch t.ReflectKind {
|
||||
case reflect.Map:
|
||||
m := new(MapType)
|
||||
m.KeyType = typeOf(e, AttrGoKey)
|
||||
m.ElemType = typeOf(e, AttrGoElem)
|
||||
t = &m.TypedefType
|
||||
typ = m
|
||||
case reflect.Chan:
|
||||
c := new(ChanType)
|
||||
c.ElemType = typeOf(e, AttrGoElem)
|
||||
t = &c.TypedefType
|
||||
typ = c
|
||||
case reflect.Interface:
|
||||
it := new(InterfaceType)
|
||||
t = &it.TypedefType
|
||||
typ = it
|
||||
default:
|
||||
typ = t
|
||||
}
|
||||
typeCache[off] = typ
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
t.Type = typeOf(e, dwarf.AttrType)
|
||||
|
||||
case dwarf.TagUnspecifiedType:
|
||||
// Unspecified type (DWARF v3 §5.2)
|
||||
// Attributes:
|
||||
// AttrName: name
|
||||
t := new(UnspecifiedType)
|
||||
typ = t
|
||||
typeCache[off] = t
|
||||
t.Name, _ = e.Val(dwarf.AttrName).(string)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
|
||||
typ.Common().Offset = off
|
||||
|
||||
{
|
||||
b, ok := e.Val(dwarf.AttrByteSize).(int64)
|
||||
if !ok {
|
||||
b = -1
|
||||
switch t := typ.(type) {
|
||||
case *TypedefType:
|
||||
b = t.Type.Size()
|
||||
case *MapType:
|
||||
b = t.Type.Size()
|
||||
case *ChanType:
|
||||
b = t.Type.Size()
|
||||
case *InterfaceType:
|
||||
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
|
||||
}
|
||||
return typ, nil
|
||||
|
||||
Error:
|
||||
// If the parse fails, take the type out of the cache
|
||||
// so that the next call with this offset doesn't hit
|
||||
// the cache and return success.
|
||||
delete(typeCache, off)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func zeroArray(t Type) {
|
||||
for {
|
||||
at, ok := t.(*ArrayType)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
at.Count = 0
|
||||
t = at.Type
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
151
vendor/github.com/derekparker/delve/pkg/dwarf/util/buf.go
generated
vendored
Normal file
151
vendor/github.com/derekparker/delve/pkg/dwarf/util/buf.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Buffered reading and decoding of DWARF data streams.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Data buffer being decoded.
|
||||
type buf struct {
|
||||
dwarf *dwarf.Data
|
||||
format dataFormat
|
||||
name string
|
||||
off dwarf.Offset
|
||||
data []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
// Data format, other than byte order. This affects the handling of
|
||||
// certain field formats.
|
||||
type dataFormat interface {
|
||||
// DWARF version number. Zero means unknown.
|
||||
version() int
|
||||
|
||||
// 64-bit DWARF format?
|
||||
dwarf64() (dwarf64 bool, isKnown bool)
|
||||
|
||||
// Size of an address, in bytes. Zero means unknown.
|
||||
addrsize() int
|
||||
}
|
||||
|
||||
// Some parts of DWARF have no data format, e.g., abbrevs.
|
||||
type UnknownFormat struct{}
|
||||
|
||||
func (u UnknownFormat) version() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u UnknownFormat) dwarf64() (bool, bool) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (u UnknownFormat) addrsize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
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 {
|
||||
n := *b
|
||||
data := b.data
|
||||
b.skip(length) // Will validate length.
|
||||
n.data = data[:length]
|
||||
return n
|
||||
}
|
||||
|
||||
func (b *buf) Uint8() uint8 {
|
||||
if len(b.data) < 1 {
|
||||
b.error("underflow")
|
||||
return 0
|
||||
}
|
||||
val := b.data[0]
|
||||
b.data = b.data[1:]
|
||||
b.off++
|
||||
return val
|
||||
}
|
||||
|
||||
func (b *buf) bytes(n int) []byte {
|
||||
if len(b.data) < n {
|
||||
b.error("underflow")
|
||||
return nil
|
||||
}
|
||||
data := b.data[0:n]
|
||||
b.data = b.data[n:]
|
||||
b.off += dwarf.Offset(n)
|
||||
return data
|
||||
}
|
||||
|
||||
func (b *buf) skip(n int) { b.bytes(n) }
|
||||
|
||||
// string returns the NUL-terminated (C-like) string at the start of the buffer.
|
||||
// The terminal NUL is discarded.
|
||||
func (b *buf) string() string {
|
||||
for i := 0; i < len(b.data); i++ {
|
||||
if b.data[i] == 0 {
|
||||
s := string(b.data[0:i])
|
||||
b.data = b.data[i+1:]
|
||||
b.off += dwarf.Offset(i + 1)
|
||||
return s
|
||||
}
|
||||
}
|
||||
b.error("underflow")
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 += dwarf.Offset(i + 1)
|
||||
b.data = b.data[i+1:]
|
||||
return c, bits
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Unsigned int is just a 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()
|
||||
x := int64(ux)
|
||||
if x&(1<<(bits-1)) != 0 {
|
||||
x |= -1 << bits
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// AssertEmpty checks that everything has been read from b.
|
||||
func (b *buf) AssertEmpty() {
|
||||
if len(b.data) == 0 {
|
||||
return
|
||||
}
|
||||
if len(b.data) > 5 {
|
||||
b.error(fmt.Sprintf("unexpected extra data: %x...", b.data[0:5]))
|
||||
}
|
||||
b.error(fmt.Sprintf("unexpected extra data: %x", b.data))
|
||||
}
|
||||
|
||||
func (b *buf) error(s string) {
|
||||
if b.Err == nil {
|
||||
b.data = nil
|
||||
b.Err = dwarf.DecodeError{b.name, b.off, s}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
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)
|
||||
// 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,12 +87,17 @@ 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)))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err != couldNotGetThreadCount && err != couldNotGetThreadList {
|
||||
return nil, err
|
||||
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
|
||||
}
|
||||
if err != couldNotGetThreadCount && err != couldNotGetThreadList {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,26 +105,21 @@ func Launch(cmd []string, wd string) (*Process, error) {
|
||||
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
|
||||
err error
|
||||
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
|
||||
regs *sys.PtraceRegs
|
||||
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,19 +7,40 @@ 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"
|
||||
terminalResetEscapeCode string = "\033[0m"
|
||||
historyFile string = ".dbg_history"
|
||||
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.
|
||||
@@ -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()
|
||||
func (t *Term) sigintGuard(ch <-chan os.Signal, multiClient bool) {
|
||||
for range ch {
|
||||
if multiClient {
|
||||
answer, err := t.line.Prompt("Would you like to [s]top the target or [q]uit this client, leaving the target running [s/q]? ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
continue
|
||||
}
|
||||
answer = strings.TrimSpace(answer)
|
||||
switch answer {
|
||||
case "s":
|
||||
_, err := t.client.Halt()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
}
|
||||
case "q":
|
||||
t.quittingMutex.Lock()
|
||||
t.quitting = true
|
||||
t.quittingMutex.Unlock()
|
||||
err := t.client.Disconnect(false)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
} else {
|
||||
t.Close()
|
||||
}
|
||||
default:
|
||||
fmt.Println("only s or q allowed")
|
||||
}
|
||||
|
||||
// Send the debugger a halt command on SIGINT
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, syscall.SIGINT)
|
||||
go func() {
|
||||
for range ch {
|
||||
fmt.Printf("received SIGINT, stopping process (will not forward signal)")
|
||||
} 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 err := t.client.Detach(kill); err != nil {
|
||||
return 1, err
|
||||
|
||||
if doDetach {
|
||||
kill := true
|
||||
if t.client.AttachedToExistingProcess() {
|
||||
answer, err := yesno(t.line, "Would you like to kill the process? [Y/n] ")
|
||||
if err != nil {
|
||||
return 2, io.EOF
|
||||
}
|
||||
kill = answer
|
||||
}
|
||||
if err := t.client.Detach(kill); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
230
vendor/github.com/derekparker/delve/proc/registers.go
generated
vendored
230
vendor/github.com/derekparker/delve/proc/registers.go
generated
vendored
@@ -1,230 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Registers is an interface for a generic register type. The
|
||||
// interface encapsulates the generic values / actions
|
||||
// we need independent of arch. The concrete register types
|
||||
// will be different depending on OS/Arch.
|
||||
type Registers interface {
|
||||
PC() uint64
|
||||
SP() uint64
|
||||
CX() uint64
|
||||
TLS() uint64
|
||||
Get(int) (uint64, error)
|
||||
SetPC(*Thread, uint64) error
|
||||
Slice() []Register
|
||||
}
|
||||
|
||||
type Register struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
func appendWordReg(regs []Register, name string, value uint16) []Register {
|
||||
return append(regs, Register{name, fmt.Sprintf("%#04x", value)})
|
||||
}
|
||||
|
||||
func appendDwordReg(regs []Register, name string, value uint32) []Register {
|
||||
return append(regs, Register{name, fmt.Sprintf("%#08x", value)})
|
||||
}
|
||||
|
||||
func appendQwordReg(regs []Register, name string, value uint64) []Register {
|
||||
return append(regs, Register{name, fmt.Sprintf("%#016x", value)})
|
||||
}
|
||||
|
||||
func appendFlagReg(regs []Register, name string, value uint64, descr flagRegisterDescr, size int) []Register {
|
||||
return append(regs, Register{name, descr.Describe(value, size)})
|
||||
}
|
||||
|
||||
func appendX87Reg(regs []Register, index int, exponent uint16, mantissa uint64) []Register {
|
||||
var f float64
|
||||
fset := false
|
||||
|
||||
const (
|
||||
_SIGNBIT = 1 << 15
|
||||
_EXP_BIAS = (1 << 14) - 1 // 2^(n-1) - 1 = 16383
|
||||
_SPECIALEXP = (1 << 15) - 1 // all bits set
|
||||
_HIGHBIT = 1 << 63
|
||||
_QUIETBIT = 1 << 62
|
||||
)
|
||||
|
||||
sign := 1.0
|
||||
if exponent&_SIGNBIT != 0 {
|
||||
sign = -1.0
|
||||
}
|
||||
exponent &= ^uint16(_SIGNBIT)
|
||||
|
||||
NaN := math.NaN()
|
||||
Inf := math.Inf(+1)
|
||||
|
||||
switch exponent {
|
||||
case 0:
|
||||
switch {
|
||||
case mantissa == 0:
|
||||
f = sign * 0.0
|
||||
fset = true
|
||||
case mantissa&_HIGHBIT != 0:
|
||||
f = NaN
|
||||
fset = true
|
||||
}
|
||||
case _SPECIALEXP:
|
||||
switch {
|
||||
case mantissa&_HIGHBIT == 0:
|
||||
f = sign * Inf
|
||||
fset = true
|
||||
default:
|
||||
f = NaN // signaling NaN
|
||||
fset = true
|
||||
}
|
||||
default:
|
||||
if mantissa&_HIGHBIT == 0 {
|
||||
f = NaN
|
||||
fset = true
|
||||
}
|
||||
}
|
||||
|
||||
if !fset {
|
||||
significand := float64(mantissa) / (1 << 63)
|
||||
f = sign * math.Ldexp(significand, int(exponent-_EXP_BIAS))
|
||||
}
|
||||
|
||||
return append(regs, Register{fmt.Sprintf("ST(%d)", index), fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)})
|
||||
}
|
||||
|
||||
func appendSSEReg(regs []Register, name string, xmm []byte) []Register {
|
||||
buf := bytes.NewReader(xmm)
|
||||
|
||||
var out bytes.Buffer
|
||||
var vi [16]uint8
|
||||
for i := range vi {
|
||||
binary.Read(buf, binary.LittleEndian, &vi[i])
|
||||
}
|
||||
|
||||
fmt.Fprintf(&out, "0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8], vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0])
|
||||
|
||||
fmt.Fprintf(&out, "\tv2_int={ %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x }", vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0], vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8])
|
||||
|
||||
fmt.Fprintf(&out, "\tv4_int={ %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x }", vi[3], vi[2], vi[1], vi[0], vi[7], vi[6], vi[5], vi[4], vi[11], vi[10], vi[9], vi[8], vi[15], vi[14], vi[13], vi[12])
|
||||
|
||||
fmt.Fprintf(&out, "\tv8_int={ %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x }", vi[1], vi[0], vi[3], vi[2], vi[5], vi[4], vi[7], vi[6], vi[9], vi[8], vi[11], vi[10], vi[13], vi[12], vi[15], vi[14])
|
||||
|
||||
fmt.Fprintf(&out, "\tv16_int={ %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x }", vi[0], vi[1], vi[2], vi[3], vi[4], vi[5], vi[6], vi[7], vi[8], vi[9], vi[10], vi[11], vi[12], vi[13], vi[14], vi[15])
|
||||
|
||||
buf.Seek(0, os.SEEK_SET)
|
||||
var v2 [2]float64
|
||||
for i := range v2 {
|
||||
binary.Read(buf, binary.LittleEndian, &v2[i])
|
||||
}
|
||||
fmt.Fprintf(&out, "\tv2_float={ %g %g }", v2[0], v2[1])
|
||||
|
||||
buf.Seek(0, os.SEEK_SET)
|
||||
var v4 [4]float32
|
||||
for i := range v4 {
|
||||
binary.Read(buf, binary.LittleEndian, &v4[i])
|
||||
}
|
||||
fmt.Fprintf(&out, "\tv4_float={ %g %g %g %g }", v4[0], v4[1], v4[2], v4[3])
|
||||
|
||||
return append(regs, Register{name, out.String()})
|
||||
}
|
||||
|
||||
var UnknownRegisterError = errors.New("unknown register")
|
||||
|
||||
// Registers obtains register values from the debugged process.
|
||||
func (t *Thread) Registers(floatingPoint bool) (Registers, error) {
|
||||
return registers(t, floatingPoint)
|
||||
}
|
||||
|
||||
// PC returns the current PC for this thread.
|
||||
func (t *Thread) PC() (uint64, error) {
|
||||
regs, err := t.Registers(false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return regs.PC(), nil
|
||||
}
|
||||
|
||||
type flagRegisterDescr []flagDescr
|
||||
type flagDescr struct {
|
||||
name string
|
||||
mask uint64
|
||||
}
|
||||
|
||||
var mxcsrDescription flagRegisterDescr = []flagDescr{
|
||||
{"FZ", 1 << 15},
|
||||
{"RZ/RN", 1<<14 | 1<<13},
|
||||
{"PM", 1 << 12},
|
||||
{"UM", 1 << 11},
|
||||
{"OM", 1 << 10},
|
||||
{"ZM", 1 << 9},
|
||||
{"DM", 1 << 8},
|
||||
{"IM", 1 << 7},
|
||||
{"DAZ", 1 << 6},
|
||||
{"PE", 1 << 5},
|
||||
{"UE", 1 << 4},
|
||||
{"OE", 1 << 3},
|
||||
{"ZE", 1 << 2},
|
||||
{"DE", 1 << 1},
|
||||
{"IE", 1 << 0},
|
||||
}
|
||||
|
||||
var eflagsDescription flagRegisterDescr = []flagDescr{
|
||||
{"CF", 1 << 0},
|
||||
{"", 1 << 1},
|
||||
{"PF", 1 << 2},
|
||||
{"AF", 1 << 4},
|
||||
{"ZF", 1 << 6},
|
||||
{"SF", 1 << 7},
|
||||
{"TF", 1 << 8},
|
||||
{"IF", 1 << 9},
|
||||
{"DF", 1 << 10},
|
||||
{"OF", 1 << 11},
|
||||
{"IOPL", 1<<12 | 1<<13},
|
||||
{"NT", 1 << 14},
|
||||
{"RF", 1 << 16},
|
||||
{"VM", 1 << 17},
|
||||
{"AC", 1 << 18},
|
||||
{"VIF", 1 << 19},
|
||||
{"VIP", 1 << 20},
|
||||
{"ID", 1 << 21},
|
||||
}
|
||||
|
||||
func (descr flagRegisterDescr) Mask() uint64 {
|
||||
var r uint64
|
||||
for _, f := range descr {
|
||||
r = r | f.mask
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
|
||||
var r []string
|
||||
for _, f := range descr {
|
||||
if f.name == "" {
|
||||
continue
|
||||
}
|
||||
// rbm is f.mask with only the right-most bit set:
|
||||
// 0001 1100 -> 0000 0100
|
||||
rbm := f.mask & -f.mask
|
||||
if rbm == f.mask {
|
||||
if reg&f.mask != 0 {
|
||||
r = append(r, f.name)
|
||||
}
|
||||
} else {
|
||||
x := (reg & f.mask) >> uint64(math.Log2(float64(rbm)))
|
||||
r = append(r, fmt.Sprintf("%s=%x", f.name, x))
|
||||
}
|
||||
}
|
||||
if reg & ^descr.Mask() != 0 {
|
||||
r = append(r, fmt.Sprintf("unknown_flags=%x", reg&^descr.Mask()))
|
||||
}
|
||||
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
|
||||
}
|
||||
211
vendor/github.com/derekparker/delve/proc/stack.go
generated
vendored
211
vendor/github.com/derekparker/delve/proc/stack.go
generated
vendored
@@ -1,211 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
)
|
||||
|
||||
// NoReturnAddr is returned when return address
|
||||
// could not be found during stack trace.
|
||||
type NoReturnAddr struct {
|
||||
fn string
|
||||
}
|
||||
|
||||
func (nra NoReturnAddr) Error() string {
|
||||
return fmt.Sprintf("could not find return address for %s", nra.fn)
|
||||
}
|
||||
|
||||
// Stackframe represents a frame in a system stack.
|
||||
type Stackframe struct {
|
||||
// Address the function above this one on the call stack will return to.
|
||||
Current Location
|
||||
// Address of the call instruction for the function above on the call stack.
|
||||
Call Location
|
||||
// Start address of the stack frame.
|
||||
CFA int64
|
||||
// Description of the stack frame.
|
||||
FDE *frame.FrameDescriptionEntry
|
||||
// Return address for this stack frame (as read from the stack frame itself).
|
||||
Ret uint64
|
||||
}
|
||||
|
||||
// Scope returns a new EvalScope using this frame.
|
||||
func (frame *Stackframe) Scope(thread *Thread) *EvalScope {
|
||||
return &EvalScope{Thread: thread, PC: frame.Current.PC, CFA: frame.CFA}
|
||||
}
|
||||
|
||||
// ReturnAddress returns the return address of the function
|
||||
// this thread is executing.
|
||||
func (t *Thread) ReturnAddress() (uint64, error) {
|
||||
locations, err := t.Stacktrace(2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(locations) < 2 {
|
||||
return 0, NoReturnAddr{locations[0].Current.Fn.BaseName()}
|
||||
}
|
||||
return locations[1].Current.PC, nil
|
||||
}
|
||||
|
||||
func (t *Thread) stackIterator() (*stackIterator, error) {
|
||||
regs, err := t.Registers(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStackIterator(t.dbp, regs.PC(), regs.SP()), nil
|
||||
}
|
||||
|
||||
// Stacktrace returns the stack trace for thread.
|
||||
// Note the locations in the array are return addresses not call addresses.
|
||||
func (t *Thread) Stacktrace(depth int) ([]Stackframe, error) {
|
||||
it, err := t.stackIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return it.stacktrace(depth)
|
||||
}
|
||||
|
||||
func (g *G) stackIterator() (*stackIterator, error) {
|
||||
if g.thread != nil {
|
||||
return g.thread.stackIterator()
|
||||
}
|
||||
return newStackIterator(g.dbp, g.PC, g.SP), 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) ([]Stackframe, error) {
|
||||
it, err := g.stackIterator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return it.stacktrace(depth)
|
||||
}
|
||||
|
||||
// GoroutineLocation returns the location of the given
|
||||
// goroutine.
|
||||
func (dbp *Process) GoroutineLocation(g *G) *Location {
|
||||
f, l, fn := dbp.PCToLine(g.PC)
|
||||
return &Location{PC: g.PC, File: f, Line: l, Fn: fn}
|
||||
}
|
||||
|
||||
// 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, sp uint64
|
||||
top bool
|
||||
atend bool
|
||||
frame Stackframe
|
||||
dbp *Process
|
||||
err error
|
||||
}
|
||||
|
||||
func newStackIterator(dbp *Process, pc, sp uint64) *stackIterator {
|
||||
return &stackIterator{pc: pc, sp: sp, top: true, dbp: dbp, err: nil, atend: false}
|
||||
}
|
||||
|
||||
// Next points the iterator to the next stack frame.
|
||||
func (it *stackIterator) Next() bool {
|
||||
if it.err != nil || it.atend {
|
||||
return false
|
||||
}
|
||||
it.frame, it.err = it.dbp.frameInfo(it.pc, it.sp, it.top)
|
||||
if it.err != nil {
|
||||
if _, nofde := it.err.(*frame.NoFDEForPCError); nofde && !it.top {
|
||||
it.frame = Stackframe{Current: Location{PC: it.pc, File: "?", Line: -1}, Call: Location{PC: it.pc, File: "?", Line: -1}, CFA: 0, Ret: 0}
|
||||
it.atend = true
|
||||
it.err = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if it.frame.Current.Fn == nil {
|
||||
if it.top {
|
||||
it.err = fmt.Errorf("PC not associated to any function")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if it.frame.Ret <= 0 {
|
||||
it.atend = true
|
||||
return true
|
||||
}
|
||||
// Look for "top of stack" functions.
|
||||
if it.frame.Current.Fn.Name == "runtime.goexit" || it.frame.Current.Fn.Name == "runtime.rt0_go" || it.frame.Current.Fn.Name == "runtime.mcall" {
|
||||
it.atend = true
|
||||
return true
|
||||
}
|
||||
|
||||
it.top = false
|
||||
it.pc = it.frame.Ret
|
||||
it.sp = uint64(it.frame.CFA)
|
||||
return true
|
||||
}
|
||||
|
||||
// Frame returns the frame the iterator is pointing at.
|
||||
func (it *stackIterator) Frame() Stackframe {
|
||||
if it.err != nil {
|
||||
panic(it.err)
|
||||
}
|
||||
return it.frame
|
||||
}
|
||||
|
||||
// Err returns the error encountered during stack iteration.
|
||||
func (it *stackIterator) Err() error {
|
||||
return it.err
|
||||
}
|
||||
|
||||
func (dbp *Process) frameInfo(pc, sp uint64, top bool) (Stackframe, error) {
|
||||
f, l, fn := dbp.PCToLine(pc)
|
||||
fde, err := dbp.frameEntries.FDEForPC(pc)
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
}
|
||||
spoffset, retoffset := fde.ReturnAddressOffset(pc)
|
||||
cfa := int64(sp) + spoffset
|
||||
|
||||
retaddr := uintptr(cfa + retoffset)
|
||||
if retaddr == 0 {
|
||||
return Stackframe{}, NullAddrError{}
|
||||
}
|
||||
data, err := dbp.CurrentThread.readMemory(retaddr, dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
}
|
||||
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: binary.LittleEndian.Uint64(data)}
|
||||
if !top {
|
||||
r.Call.File, r.Call.Line, r.Call.Fn = dbp.PCToLine(pc - 1)
|
||||
r.Call.PC, _, _ = dbp.goSymTable.LineToPC(r.Call.File, r.Call.Line)
|
||||
} else {
|
||||
r.Call = r.Current
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
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 = append(frames, it.Frame())
|
||||
if len(frames) >= depth+1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return frames, nil
|
||||
}
|
||||
504
vendor/github.com/derekparker/delve/proc/threads.go
generated
vendored
504
vendor/github.com/derekparker/delve/proc/threads.go
generated
vendored
@@ -1,504 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"debug/gosym"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/debug/dwarf"
|
||||
)
|
||||
|
||||
// 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 *Breakpoint // Breakpoint thread is currently stopped at
|
||||
BreakpointConditionMet bool // Output of evaluating the breakpoint's condition
|
||||
BreakpointConditionError error // Error evaluating the breakpoint's condition
|
||||
|
||||
dbp *Process
|
||||
singleStepping bool
|
||||
running bool
|
||||
os *OSSpecificDetails
|
||||
}
|
||||
|
||||
// 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 *gosym.Func
|
||||
}
|
||||
|
||||
// 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 (thread *Thread) Continue() error {
|
||||
pc, err := thread.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 := thread.dbp.FindBreakpoint(pc); ok {
|
||||
if err := thread.StepInstruction(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return thread.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 (thread *Thread) StepInstruction() (err error) {
|
||||
thread.running = true
|
||||
thread.singleStepping = true
|
||||
defer func() {
|
||||
thread.singleStepping = false
|
||||
thread.running = false
|
||||
}()
|
||||
pc, err := thread.PC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bp, ok := thread.dbp.FindBreakpoint(pc)
|
||||
if ok {
|
||||
// Clear the breakpoint so that we can continue execution.
|
||||
_, err = bp.Clear(thread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore breakpoint now that we have passed it.
|
||||
defer func() {
|
||||
err = thread.dbp.writeSoftwareBreakpoint(thread, bp.Addr)
|
||||
}()
|
||||
}
|
||||
|
||||
err = thread.singleStep()
|
||||
if err != nil {
|
||||
if _, exited := err.(ProcessExitedError); 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 (thread *Thread) Location() (*Location, error) {
|
||||
pc, err := thread.PC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, l, fn := thread.dbp.PCToLine(pc)
|
||||
return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
|
||||
}
|
||||
|
||||
// ThreadBlockedError is returned when the thread
|
||||
// is blocked in the scheduler.
|
||||
type ThreadBlockedError struct{}
|
||||
|
||||
func (tbe ThreadBlockedError) Error() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// returns topmost frame of g or thread if g is nil
|
||||
func topframe(g *G, thread *Thread) (Stackframe, error) {
|
||||
var frames []Stackframe
|
||||
var err error
|
||||
|
||||
if g == nil {
|
||||
if thread.blocked() {
|
||||
return Stackframe{}, ThreadBlockedError{}
|
||||
}
|
||||
frames, err = thread.Stacktrace(0)
|
||||
} else {
|
||||
frames, err = g.Stacktrace(0)
|
||||
}
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
}
|
||||
if len(frames) < 1 {
|
||||
return Stackframe{}, errors.New("empty stack trace")
|
||||
}
|
||||
return frames[0], nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (dbp *Process) next(stepInto bool) error {
|
||||
topframe, err := topframe(dbp.SelectedGoroutine, dbp.CurrentThread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
success := false
|
||||
defer func() {
|
||||
if !success {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
}
|
||||
}()
|
||||
|
||||
csource := filepath.Ext(topframe.Current.File) != ".go"
|
||||
thread := dbp.CurrentThread
|
||||
currentGoroutine := false
|
||||
if dbp.SelectedGoroutine != nil && dbp.SelectedGoroutine.thread != nil {
|
||||
thread = dbp.SelectedGoroutine.thread
|
||||
currentGoroutine = true
|
||||
}
|
||||
|
||||
text, err := thread.Disassemble(topframe.FDE.Begin(), topframe.FDE.End(), currentGoroutine)
|
||||
if err != nil && stepInto {
|
||||
return err
|
||||
}
|
||||
|
||||
cond := sameGoroutineCondition(dbp.SelectedGoroutine)
|
||||
|
||||
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 := dbp.setStepIntoBreakpoint([]AsmInstruction{instr}, cond); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Non-absolute call instruction, set a StepBreakpoint here
|
||||
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, cond); 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 = 0
|
||||
if dbp.SelectedGoroutine != nil && dbp.SelectedGoroutine.DeferPC != 0 {
|
||||
_, _, deferfn := dbp.goSymTable.PCToLine(dbp.SelectedGoroutine.DeferPC)
|
||||
var err error
|
||||
deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if deferpc != 0 && deferpc != topframe.Current.PC {
|
||||
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if bp != nil {
|
||||
bp.DeferReturns = deferreturns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add breakpoints on all the lines in the current function
|
||||
pcs, err := dbp.lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !csource {
|
||||
var covered bool
|
||||
for i := range pcs {
|
||||
if topframe.FDE.Cover(pcs[i]) {
|
||||
covered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !covered {
|
||||
fn := dbp.goSymTable.PCToFunc(topframe.Ret)
|
||||
if dbp.SelectedGoroutine != nil && fn != nil && fn.Name == "runtime.goexit" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a breakpoint on the return address for the current frame
|
||||
pcs = append(pcs, topframe.Ret)
|
||||
success = true
|
||||
return dbp.setInternalBreakpoints(topframe.Current.PC, pcs, NextBreakpoint, cond)
|
||||
}
|
||||
|
||||
func (dbp *Process) setStepIntoBreakpoint(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, _ := dbp.FirstPCAfterPrologue(fn, false)
|
||||
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setInternalBreakpoints sets a breakpoint to all addresses specified in pcs
|
||||
// skipping over curpc and curpc-1
|
||||
func (dbp *Process) setInternalBreakpoints(curpc uint64, pcs []uint64, kind BreakpointKind, cond ast.Expr) error {
|
||||
for i := range pcs {
|
||||
if pcs[i] == curpc || pcs[i] == curpc-1 {
|
||||
continue
|
||||
}
|
||||
if _, err := dbp.SetBreakpoint(pcs[i], kind, cond); err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPC sets the PC for this thread.
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
regs, err := thread.Registers(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return regs.SetPC(thread, pc)
|
||||
}
|
||||
|
||||
func (thread *Thread) getGVariable() (*Variable, error) {
|
||||
regs, err := thread.Registers(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if thread.dbp.arch.GStructOffset() == 0 {
|
||||
// GetG was called through SwitchThread / updateThreadList during initialization
|
||||
// thread.dbp.arch isn't setup yet (it needs a CurrentThread to read global variables from)
|
||||
return nil, fmt.Errorf("g struct offset not initialized")
|
||||
}
|
||||
|
||||
gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gaddr := uintptr(binary.LittleEndian.Uint64(gaddrbs))
|
||||
|
||||
// On Windows, the value at TLS()+GStructOffset() is a
|
||||
// pointer to the G struct.
|
||||
needsDeref := runtime.GOOS == "windows"
|
||||
|
||||
return thread.newGVariable(gaddr, needsDeref)
|
||||
}
|
||||
|
||||
func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) {
|
||||
typ, err := thread.dbp.findType("runtime.g")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := ""
|
||||
|
||||
if deref {
|
||||
typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.dbp.arch.PtrSize()), "", reflect.Ptr, 0}, typ}
|
||||
} else {
|
||||
name = "runtime.curg"
|
||||
}
|
||||
|
||||
return thread.newVariable(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 (thread *Thread) GetG() (g *G, err error) {
|
||||
gaddr, err := thread.getGVariable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g, err = gaddr.parseG()
|
||||
if err == nil {
|
||||
g.thread = thread
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stopped returns whether the thread is stopped at
|
||||
// the operating system level. Actual implementation
|
||||
// is OS dependant, look in OS thread file.
|
||||
func (thread *Thread) Stopped() bool {
|
||||
return thread.stopped()
|
||||
}
|
||||
|
||||
// Halt stops this thread from executing. Actual
|
||||
// implementation is OS dependant. Look in OS
|
||||
// thread file.
|
||||
func (thread *Thread) Halt() (err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
thread.running = false
|
||||
}
|
||||
}()
|
||||
if thread.Stopped() {
|
||||
return
|
||||
}
|
||||
err = thread.halt()
|
||||
return
|
||||
}
|
||||
|
||||
// Scope returns the current EvalScope for this thread.
|
||||
func (thread *Thread) Scope() (*EvalScope, error) {
|
||||
locations, err := thread.Stacktrace(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(locations) < 1 {
|
||||
return nil, errors.New("could not decode first frame")
|
||||
}
|
||||
return locations[0].Scope(thread), nil
|
||||
}
|
||||
|
||||
// SetCurrentBreakpoint sets the current breakpoint that this
|
||||
// thread is stopped at as CurrentBreakpoint on the thread struct.
|
||||
func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
thread.CurrentBreakpoint = nil
|
||||
pc, err := thread.PC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bp, ok := thread.dbp.FindBreakpoint(pc); ok {
|
||||
thread.CurrentBreakpoint = bp
|
||||
if err = thread.SetPC(bp.Addr); err != nil {
|
||||
return err
|
||||
}
|
||||
thread.BreakpointConditionMet, thread.BreakpointConditionError = bp.checkCondition(thread)
|
||||
if thread.onTriggeredBreakpoint() {
|
||||
if g, err := thread.GetG(); err == nil {
|
||||
thread.CurrentBreakpoint.HitCount[g.ID]++
|
||||
}
|
||||
thread.CurrentBreakpoint.TotalHitCount++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (thread *Thread) clearBreakpointState() {
|
||||
thread.CurrentBreakpoint = nil
|
||||
thread.BreakpointConditionMet = false
|
||||
thread.BreakpointConditionError = nil
|
||||
}
|
||||
|
||||
func (thread *Thread) onTriggeredBreakpoint() bool {
|
||||
return (thread.CurrentBreakpoint != nil) && thread.BreakpointConditionMet
|
||||
}
|
||||
|
||||
func (thread *Thread) onTriggeredInternalBreakpoint() bool {
|
||||
return thread.onTriggeredBreakpoint() && thread.CurrentBreakpoint.Internal()
|
||||
}
|
||||
|
||||
func (thread *Thread) onRuntimeBreakpoint() bool {
|
||||
loc, err := thread.Location()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return loc.Fn != nil && loc.Fn.Name == "runtime.breakpoint"
|
||||
}
|
||||
|
||||
// onNextGorutine returns true if this thread is on the goroutine requested by the current 'next' command
|
||||
func (thread *Thread) onNextGoroutine() (bool, error) {
|
||||
var bp *Breakpoint
|
||||
for i := range thread.dbp.Breakpoints {
|
||||
if thread.dbp.Breakpoints[i].Internal() {
|
||||
bp = thread.dbp.Breakpoints[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if bp == nil {
|
||||
return false, nil
|
||||
}
|
||||
if bp.Kind == NextDeferBreakpoint {
|
||||
// we just want to check the condition on the goroutine id here
|
||||
bp.Kind = NextBreakpoint
|
||||
defer func() {
|
||||
bp.Kind = NextDeferBreakpoint
|
||||
}()
|
||||
}
|
||||
return bp.checkCondition(thread)
|
||||
}
|
||||
108
vendor/github.com/derekparker/delve/proc/threads_linux.go
generated
vendored
108
vendor/github.com/derekparker/delve/proc/threads_linux.go
generated
vendored
@@ -1,108 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type WaitStatus sys.WaitStatus
|
||||
|
||||
// OSSpecificDetails hold Linux specific
|
||||
// process details.
|
||||
type OSSpecificDetails struct {
|
||||
registers sys.PtraceRegs
|
||||
}
|
||||
|
||||
func (t *Thread) halt() (err error) {
|
||||
err = sys.Tgkill(t.dbp.Pid, t.ID, sys.SIGSTOP)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("halt err %s on thread %d", err, t.ID)
|
||||
return
|
||||
}
|
||||
_, _, err = t.dbp.wait(t.ID, 0)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("wait err %s on thread %d", err, t.ID)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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.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.wait(t.ID, 0)
|
||||
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 ProcessExitedError{Pid: t.dbp.Pid, Status: rs}
|
||||
}
|
||||
if wpid == t.ID && status.StopSignal() == sys.SIGTRAP {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Thread) blocked() bool {
|
||||
pc, _ := t.PC()
|
||||
fn := t.dbp.goSymTable.PCToFunc(pc)
|
||||
if fn != nil && ((fn.Name == "runtime.futex") || (fn.Name == "runtime.usleep") || (fn.Name == "runtime.clone")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Thread) saveRegisters() (Registers, error) {
|
||||
var err error
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(t.ID, &t.os.registers) })
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not save register contents")
|
||||
}
|
||||
return &Regs{&t.os.registers, nil}, nil
|
||||
}
|
||||
|
||||
func (t *Thread) restoreRegisters() (err error) {
|
||||
t.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(t.ID, &t.os.registers) })
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Thread) writeMemory(addr uintptr, data []byte) (written int, err error) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
t.dbp.execPtraceFunc(func() { written, err = sys.PtracePokeData(t.ID, addr, data) })
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Thread) readMemory(addr uintptr, size int) (data []byte, err error) {
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
data = make([]byte, size)
|
||||
t.dbp.execPtraceFunc(func() { _, err = sys.PtracePeekData(t.ID, addr, data) })
|
||||
return
|
||||
}
|
||||
715
vendor/github.com/derekparker/delve/proc/types.go
generated
vendored
715
vendor/github.com/derekparker/delve/proc/types.go
generated
vendored
@@ -1,715 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/reader"
|
||||
|
||||
"golang.org/x/debug/dwarf"
|
||||
)
|
||||
|
||||
// The kind field in runtime._type is a reflect.Kind value plus
|
||||
// some extra flags defined here.
|
||||
// See equivalent declaration in $GOROOT/src/reflect/type.go
|
||||
const (
|
||||
kindDirectIface = 1 << 5
|
||||
kindGCProg = 1 << 6 // Type.gc points to GC program
|
||||
kindNoPointers = 1 << 7
|
||||
kindMask = (1 << 5) - 1
|
||||
)
|
||||
|
||||
// Value of tflag field in runtime._type.
|
||||
// See $GOROOT/reflect/type.go for a description of these flags.
|
||||
const (
|
||||
tflagUncommon = 1 << 0
|
||||
tflagExtraStar = 1 << 1
|
||||
tflagNamed = 1 << 2
|
||||
)
|
||||
|
||||
// Do not call this function directly it isn't able to deal correctly with package paths
|
||||
func (dbp *Process) findType(name string) (dwarf.Type, error) {
|
||||
off, found := dbp.types[name]
|
||||
if !found {
|
||||
return nil, reader.TypeNotFoundErr
|
||||
}
|
||||
return dbp.dwarf.Type(off)
|
||||
}
|
||||
|
||||
func (dbp *Process) pointerTo(typ dwarf.Type) dwarf.Type {
|
||||
return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), "*" + typ.Common().Name, reflect.Ptr, 0}, typ}
|
||||
}
|
||||
|
||||
func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) {
|
||||
dbp.loadPackageMap()
|
||||
if lit, islit := expr.(*ast.BasicLit); islit && lit.Kind == token.STRING {
|
||||
// Allow users to specify type names verbatim as quoted
|
||||
// string. Useful as a catch-all workaround for cases where we don't
|
||||
// parse/serialize types correctly or can not resolve package paths.
|
||||
typn, _ := strconv.Unquote(lit.Value)
|
||||
return dbp.findType(typn)
|
||||
}
|
||||
dbp.expandPackagesInType(expr)
|
||||
if snode, ok := expr.(*ast.StarExpr); ok {
|
||||
// Pointer types only appear in the dwarf informations when
|
||||
// a pointer to the type is used in the target program, here
|
||||
// we create a pointer type on the fly so that the user can
|
||||
// specify a pointer to any variable used in the target program
|
||||
ptyp, err := dbp.findTypeExpr(snode.X)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dbp.pointerTo(ptyp), nil
|
||||
}
|
||||
return dbp.findType(exprToString(expr))
|
||||
}
|
||||
|
||||
func complexType(typename string) bool {
|
||||
for _, ch := range typename {
|
||||
switch ch {
|
||||
case '*', '[', '<', '{', '(', ' ':
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dbp *Process) loadPackageMap() error {
|
||||
if dbp.packageMap != nil {
|
||||
return nil
|
||||
}
|
||||
dbp.packageMap = map[string]string{}
|
||||
reader := dbp.DwarfReader()
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry.Tag != dwarf.TagTypedef && entry.Tag != dwarf.TagBaseType && entry.Tag != dwarf.TagClassType && entry.Tag != dwarf.TagStructType {
|
||||
continue
|
||||
}
|
||||
|
||||
typename, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok || complexType(typename) {
|
||||
continue
|
||||
}
|
||||
|
||||
dot := strings.LastIndex(typename, ".")
|
||||
if dot < 0 {
|
||||
continue
|
||||
}
|
||||
path := typename[:dot]
|
||||
slash := strings.LastIndex(path, "/")
|
||||
if slash < 0 || slash+1 >= len(path) {
|
||||
continue
|
||||
}
|
||||
name := path[slash+1:]
|
||||
dbp.packageMap[name] = path
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbp *Process) loadTypeMap(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
dbp.types = make(map[string]dwarf.Offset)
|
||||
reader := dbp.DwarfReader()
|
||||
for entry, err := reader.NextType(); entry != nil; entry, err = reader.NextType() {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
name, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, exists := dbp.types[name]; !exists {
|
||||
dbp.types[name] = entry.Offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dbp *Process) expandPackagesInType(expr ast.Expr) {
|
||||
switch e := expr.(type) {
|
||||
case *ast.ArrayType:
|
||||
dbp.expandPackagesInType(e.Elt)
|
||||
case *ast.ChanType:
|
||||
dbp.expandPackagesInType(e.Value)
|
||||
case *ast.FuncType:
|
||||
for i := range e.Params.List {
|
||||
dbp.expandPackagesInType(e.Params.List[i].Type)
|
||||
}
|
||||
if e.Results != nil {
|
||||
for i := range e.Results.List {
|
||||
dbp.expandPackagesInType(e.Results.List[i].Type)
|
||||
}
|
||||
}
|
||||
case *ast.MapType:
|
||||
dbp.expandPackagesInType(e.Key)
|
||||
dbp.expandPackagesInType(e.Value)
|
||||
case *ast.ParenExpr:
|
||||
dbp.expandPackagesInType(e.X)
|
||||
case *ast.SelectorExpr:
|
||||
switch x := e.X.(type) {
|
||||
case *ast.Ident:
|
||||
if path, ok := dbp.packageMap[x.Name]; ok {
|
||||
x.Name = path
|
||||
}
|
||||
default:
|
||||
dbp.expandPackagesInType(e.X)
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
dbp.expandPackagesInType(e.X)
|
||||
default:
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
|
||||
type nameOfRuntimeTypeEntry struct {
|
||||
typename string
|
||||
kind int64
|
||||
}
|
||||
|
||||
// Returns the type name of the type described in _type.
|
||||
// _type is a non-loaded Variable pointing to runtime._type struct in the target.
|
||||
// The returned string is in the format that's used in DWARF data
|
||||
func nameOfRuntimeType(_type *Variable) (typename string, kind int64, err error) {
|
||||
if e, ok := _type.dbp.nameOfRuntimeType[_type.Addr]; ok {
|
||||
return e.typename, e.kind, nil
|
||||
}
|
||||
|
||||
var tflag int64
|
||||
|
||||
if tflagField := _type.toFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil {
|
||||
tflag, _ = constant.Int64Val(tflagField.Value)
|
||||
}
|
||||
if kindField := _type.toFieldNamed("kind"); kindField != nil && kindField.Value != nil {
|
||||
kind, _ = constant.Int64Val(kindField.Value)
|
||||
}
|
||||
|
||||
// Named types are defined by a 'type' expression, everything else
|
||||
// (for example pointers to named types) are not considered named.
|
||||
if tflag&tflagNamed != 0 {
|
||||
typename, err = nameOfNamedRuntimeType(_type, kind, tflag)
|
||||
return typename, kind, err
|
||||
} else {
|
||||
typename, err = nameOfUnnamedRuntimeType(_type, kind, tflag)
|
||||
return typename, kind, err
|
||||
}
|
||||
|
||||
_type.dbp.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename, kind}
|
||||
|
||||
return typename, kind, nil
|
||||
}
|
||||
|
||||
// The layout of a runtime._type struct is as follows:
|
||||
//
|
||||
// <runtime._type><kind specific struct fields><runtime.uncommontype>
|
||||
//
|
||||
// with the 'uncommon type struct' being optional
|
||||
//
|
||||
// For named types first we extract the type name from the 'str'
|
||||
// field in the runtime._type struct.
|
||||
// Then we prepend the package path from the runtime.uncommontype
|
||||
// struct, when it exists.
|
||||
//
|
||||
// To find out the memory address of the runtime.uncommontype struct
|
||||
// we first cast the Variable pointing to the runtime._type struct
|
||||
// to a struct specific to the type's kind (for example, if the type
|
||||
// being described is a slice type the variable will be specialized
|
||||
// to a runtime.slicetype).
|
||||
func nameOfNamedRuntimeType(_type *Variable, kind, tflag int64) (typename string, err error) {
|
||||
var strOff int64
|
||||
if strField := _type.toFieldNamed("str"); strField != nil && strField.Value != nil {
|
||||
strOff, _ = constant.Int64Val(strField.Value)
|
||||
} else {
|
||||
return "", errors.New("could not find str field")
|
||||
}
|
||||
|
||||
// The following code is adapted from reflect.(*rtype).Name.
|
||||
// For a description of how memory is organized for type names read
|
||||
// the comment to 'type name struct' in $GOROOT/src/reflect/type.go
|
||||
|
||||
typename, _, _, err = _type.dbp.resolveNameOff(_type.Addr, uintptr(strOff))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tflag&tflagExtraStar != 0 {
|
||||
typename = typename[1:]
|
||||
}
|
||||
|
||||
if i := strings.Index(typename, "."); i >= 0 {
|
||||
typename = typename[i+1:]
|
||||
} else {
|
||||
return typename, nil
|
||||
}
|
||||
|
||||
// The following code is adapted from reflect.(*rtype).PkgPath in
|
||||
// $GOROOT/src/reflect/type.go
|
||||
|
||||
_type, err = specificRuntimeType(_type, kind)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if ut := uncommon(_type, tflag); ut != nil {
|
||||
if pkgPathField := ut.toFieldNamed("pkgpath"); pkgPathField != nil && pkgPathField.Value != nil {
|
||||
pkgPathOff, _ := constant.Int64Val(pkgPathField.Value)
|
||||
pkgPath, _, _, err := _type.dbp.resolveNameOff(_type.Addr, uintptr(pkgPathOff))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
typename = pkgPath + "." + typename
|
||||
}
|
||||
}
|
||||
|
||||
return typename, nil
|
||||
}
|
||||
|
||||
func nameOfUnnamedRuntimeType(_type *Variable, kind, tflag int64) (string, error) {
|
||||
_type, err := specificRuntimeType(_type, kind)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The types referred to here are defined in $GOROOT/src/runtime/type.go
|
||||
switch reflect.Kind(kind & kindMask) {
|
||||
case reflect.Array:
|
||||
var len int64
|
||||
if lenField := _type.toFieldNamed("len"); lenField != nil && lenField.Value != nil {
|
||||
len, _ = constant.Int64Val(lenField.Value)
|
||||
}
|
||||
elemname, err := fieldToType(_type, "elem")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("[%d]%s", len, elemname), nil
|
||||
case reflect.Chan:
|
||||
elemname, err := fieldToType(_type, "elem")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "chan " + elemname, nil
|
||||
case reflect.Func:
|
||||
return nameOfFuncRuntimeType(_type, tflag, true)
|
||||
case reflect.Interface:
|
||||
return nameOfInterfaceRuntimeType(_type, kind, tflag)
|
||||
case reflect.Map:
|
||||
keyname, err := fieldToType(_type, "key")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
elemname, err := fieldToType(_type, "elem")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "map[" + keyname + "]" + elemname, nil
|
||||
case reflect.Ptr:
|
||||
elemname, err := fieldToType(_type, "elem")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "*" + elemname, nil
|
||||
case reflect.Slice:
|
||||
elemname, err := fieldToType(_type, "elem")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "[]" + elemname, nil
|
||||
case reflect.Struct:
|
||||
return nameOfStructRuntimeType(_type, kind, tflag)
|
||||
default:
|
||||
return nameOfNamedRuntimeType(_type, kind, tflag)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the expression describing an anonymous function type.
|
||||
// A runtime.functype is followed by a runtime.uncommontype
|
||||
// (optional) and then by an array of pointers to runtime._type,
|
||||
// one for each input and output argument.
|
||||
func nameOfFuncRuntimeType(_type *Variable, tflag int64, anonymous bool) (string, error) {
|
||||
rtyp, err := _type.dbp.findType("runtime._type")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
prtyp := _type.dbp.pointerTo(rtyp)
|
||||
|
||||
uadd := _type.RealType.Common().ByteSize
|
||||
if ut := uncommon(_type, tflag); ut != nil {
|
||||
uadd += ut.RealType.Common().ByteSize
|
||||
}
|
||||
|
||||
var inCount, outCount int64
|
||||
if inCountField := _type.toFieldNamed("inCount"); inCountField != nil && inCountField.Value != nil {
|
||||
inCount, _ = constant.Int64Val(inCountField.Value)
|
||||
}
|
||||
if outCountField := _type.toFieldNamed("outCount"); outCountField != nil && outCountField.Value != nil {
|
||||
outCount, _ = constant.Int64Val(outCountField.Value)
|
||||
// only the lowest 15 bits of outCount are used, rest are flags
|
||||
outCount = outCount & (1<<15 - 1)
|
||||
}
|
||||
|
||||
cursortyp := _type.newVariable("", _type.Addr+uintptr(uadd), prtyp)
|
||||
var buf bytes.Buffer
|
||||
if anonymous {
|
||||
buf.WriteString("func(")
|
||||
} else {
|
||||
buf.WriteString("(")
|
||||
}
|
||||
|
||||
for i := int64(0); i < inCount; i++ {
|
||||
argtype := cursortyp.maybeDereference()
|
||||
cursortyp.Addr += uintptr(_type.dbp.arch.PtrSize())
|
||||
argtypename, _, err := nameOfRuntimeType(argtype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf.WriteString(argtypename)
|
||||
if i != inCount-1 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
}
|
||||
buf.WriteString(")")
|
||||
|
||||
switch outCount {
|
||||
case 0:
|
||||
// nothing to do
|
||||
case 1:
|
||||
buf.WriteString(" ")
|
||||
argtype := cursortyp.maybeDereference()
|
||||
argtypename, _, err := nameOfRuntimeType(argtype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf.WriteString(argtypename)
|
||||
default:
|
||||
buf.WriteString(" (")
|
||||
for i := int64(0); i < outCount; i++ {
|
||||
argtype := cursortyp.maybeDereference()
|
||||
cursortyp.Addr += uintptr(_type.dbp.arch.PtrSize())
|
||||
argtypename, _, err := nameOfRuntimeType(argtype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf.WriteString(argtypename)
|
||||
if i != inCount-1 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
}
|
||||
buf.WriteString(")")
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("interface {")
|
||||
|
||||
methods, _ := _type.structMember("methods")
|
||||
methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1})
|
||||
if methods.Unreadable != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(methods.Children) == 0 {
|
||||
buf.WriteString("}")
|
||||
return buf.String(), nil
|
||||
} else {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
|
||||
for i, im := range methods.Children {
|
||||
var methodname, methodtype string
|
||||
for i := range im.Children {
|
||||
switch im.Children[i].Name {
|
||||
case "name":
|
||||
nameoff, _ := constant.Int64Val(im.Children[i].Value)
|
||||
var err error
|
||||
methodname, _, _, err = _type.dbp.resolveNameOff(_type.Addr, uintptr(nameoff))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
case "typ":
|
||||
typeoff, _ := constant.Int64Val(im.Children[i].Value)
|
||||
typ, err := _type.dbp.resolveTypeOff(_type.Addr, uintptr(typeoff))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
typ, err = specificRuntimeType(typ, int64(reflect.Func))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var tflag int64
|
||||
if tflagField := typ.toFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil {
|
||||
tflag, _ = constant.Int64Val(tflagField.Value)
|
||||
}
|
||||
methodtype, err = nameOfFuncRuntimeType(typ, tflag, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(methodname)
|
||||
buf.WriteString(methodtype)
|
||||
|
||||
if i != len(methods.Children)-1 {
|
||||
buf.WriteString("; ")
|
||||
} else {
|
||||
buf.WriteString(" }")
|
||||
}
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("struct {")
|
||||
|
||||
fields, _ := _type.structMember("fields")
|
||||
fields.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1})
|
||||
if fields.Unreadable != nil {
|
||||
return "", fields.Unreadable
|
||||
}
|
||||
|
||||
if len(fields.Children) == 0 {
|
||||
buf.WriteString("}")
|
||||
return buf.String(), nil
|
||||
} else {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
|
||||
for i, field := range fields.Children {
|
||||
var fieldname, fieldtypename string
|
||||
var typeField *Variable
|
||||
for i := range field.Children {
|
||||
switch field.Children[i].Name {
|
||||
case "name":
|
||||
nameoff, _ := constant.Int64Val(field.Children[i].Value)
|
||||
var err error
|
||||
fieldname, _, _, err = _type.dbp.loadName(uintptr(nameoff))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
case "typ":
|
||||
typeField = field.Children[i].maybeDereference()
|
||||
var err error
|
||||
fieldtypename, _, err = nameOfRuntimeType(typeField)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fieldname will be the empty string for anonymous fields
|
||||
if fieldname != "" {
|
||||
buf.WriteString(fieldname)
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
buf.WriteString(fieldtypename)
|
||||
if i != len(fields.Children)-1 {
|
||||
buf.WriteString("; ")
|
||||
} else {
|
||||
buf.WriteString(" }")
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func fieldToType(_type *Variable, fieldName string) (string, error) {
|
||||
typeField, err := _type.structMember(fieldName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
typeField = typeField.maybeDereference()
|
||||
typename, _, err := nameOfRuntimeType(typeField)
|
||||
return typename, err
|
||||
}
|
||||
|
||||
func specificRuntimeType(_type *Variable, kind int64) (*Variable, error) {
|
||||
rtyp, err := _type.dbp.findType("runtime._type")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prtyp := _type.dbp.pointerTo(rtyp)
|
||||
|
||||
uintptrtyp, err := _type.dbp.findType("uintptr")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uint32typ := &dwarf.UintType{dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 4, Name: "uint32"}}}
|
||||
uint16typ := &dwarf.UintType{dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 2, Name: "uint16"}}}
|
||||
|
||||
newStructType := func(name string, sz uintptr) *dwarf.StructType {
|
||||
return &dwarf.StructType{dwarf.CommonType{Name: name, ByteSize: int64(sz)}, name, "struct", nil, false}
|
||||
}
|
||||
|
||||
appendField := func(typ *dwarf.StructType, name string, fieldtype dwarf.Type, off uintptr) {
|
||||
typ.Field = append(typ.Field, &dwarf.StructField{Name: name, ByteOffset: int64(off), Type: fieldtype})
|
||||
}
|
||||
|
||||
newSliceType := func(elemtype dwarf.Type) *dwarf.SliceType {
|
||||
r := newStructType("[]"+elemtype.Common().Name, uintptr(3*uintptrtyp.Size()))
|
||||
appendField(r, "array", _type.dbp.pointerTo(elemtype), 0)
|
||||
appendField(r, "len", uintptrtyp, uintptr(uintptrtyp.Size()))
|
||||
appendField(r, "cap", uintptrtyp, uintptr(2*uintptrtyp.Size()))
|
||||
return &dwarf.SliceType{StructType: *r, ElemType: elemtype}
|
||||
}
|
||||
|
||||
var typ *dwarf.StructType
|
||||
|
||||
type rtype struct {
|
||||
size uintptr
|
||||
ptrdata uintptr
|
||||
hash uint32 // hash of type; avoids computation in hash tables
|
||||
tflag uint8 // extra type information flags
|
||||
align uint8 // alignment of variable with this type
|
||||
fieldAlign uint8 // alignment of struct field with this type
|
||||
kind uint8 // enumeration for C
|
||||
alg *byte // algorithm table
|
||||
gcdata *byte // garbage collection data
|
||||
str int32 // string form
|
||||
ptrToThis int32 // type for pointer to this type, may be zero
|
||||
}
|
||||
|
||||
switch reflect.Kind(kind & kindMask) {
|
||||
case reflect.Array:
|
||||
// runtime.arraytype
|
||||
var a struct {
|
||||
rtype
|
||||
elem *rtype // array element type
|
||||
slice *rtype // slice type
|
||||
len uintptr
|
||||
}
|
||||
typ = newStructType("runtime.arraytype", unsafe.Sizeof(a))
|
||||
appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem))
|
||||
appendField(typ, "len", uintptrtyp, unsafe.Offsetof(a.len))
|
||||
case reflect.Chan:
|
||||
// runtime.chantype
|
||||
var a struct {
|
||||
rtype
|
||||
elem *rtype // channel element type
|
||||
dir uintptr // channel direction (ChanDir)
|
||||
}
|
||||
typ = newStructType("runtime.chantype", unsafe.Sizeof(a))
|
||||
appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem))
|
||||
case reflect.Func:
|
||||
// runtime.functype
|
||||
var a struct {
|
||||
rtype `reflect:"func"`
|
||||
inCount uint16
|
||||
outCount uint16 // top bit is set if last input parameter is ...
|
||||
}
|
||||
typ = newStructType("runtime.functype", unsafe.Sizeof(a))
|
||||
appendField(typ, "inCount", uint16typ, unsafe.Offsetof(a.inCount))
|
||||
appendField(typ, "outCount", uint16typ, unsafe.Offsetof(a.outCount))
|
||||
case reflect.Interface:
|
||||
// runtime.imethod
|
||||
type imethod struct {
|
||||
name uint32 // name of method
|
||||
typ uint32 // .(*FuncType) underneath
|
||||
}
|
||||
|
||||
var im imethod
|
||||
|
||||
// runtime.interfacetype
|
||||
var a struct {
|
||||
rtype `reflect:"interface"`
|
||||
pkgPath *byte // import path
|
||||
methods []imethod // sorted by hash
|
||||
}
|
||||
|
||||
imethodtype := newStructType("runtime.imethod", unsafe.Sizeof(im))
|
||||
appendField(imethodtype, "name", uint32typ, unsafe.Offsetof(im.name))
|
||||
appendField(imethodtype, "typ", uint32typ, unsafe.Offsetof(im.typ))
|
||||
typ = newStructType("runtime.interfacetype", unsafe.Sizeof(a))
|
||||
appendField(typ, "methods", newSliceType(imethodtype), unsafe.Offsetof(a.methods))
|
||||
case reflect.Map:
|
||||
// runtime.maptype
|
||||
var a struct {
|
||||
rtype `reflect:"map"`
|
||||
key *rtype // map key type
|
||||
elem *rtype // map element (value) type
|
||||
bucket *rtype // internal bucket structure
|
||||
hmap *rtype // internal map header
|
||||
keysize uint8 // size of key slot
|
||||
indirectkey uint8 // store ptr to key instead of key itself
|
||||
valuesize uint8 // size of value slot
|
||||
indirectvalue uint8 // store ptr to value instead of value itself
|
||||
bucketsize uint16 // size of bucket
|
||||
reflexivekey bool // true if k==k for all keys
|
||||
needkeyupdate bool // true if we need to update key on an overwrite
|
||||
}
|
||||
typ = newStructType("runtime.maptype", unsafe.Sizeof(a))
|
||||
appendField(typ, "key", prtyp, unsafe.Offsetof(a.key))
|
||||
appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem))
|
||||
case reflect.Ptr:
|
||||
// runtime.ptrtype
|
||||
var a struct {
|
||||
rtype `reflect:"ptr"`
|
||||
elem *rtype // pointer element (pointed at) type
|
||||
}
|
||||
typ = newStructType("runtime.ptrtype", unsafe.Sizeof(a))
|
||||
appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem))
|
||||
case reflect.Slice:
|
||||
// runtime.slicetype
|
||||
var a struct {
|
||||
rtype `reflect:"slice"`
|
||||
elem *rtype // slice element type
|
||||
}
|
||||
|
||||
typ = newStructType("runtime.slicetype", unsafe.Sizeof(a))
|
||||
appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem))
|
||||
case reflect.Struct:
|
||||
// runtime.structtype
|
||||
type structField struct {
|
||||
name *byte // name is empty for embedded fields
|
||||
typ *rtype // type of field
|
||||
offset uintptr // byte offset of field within struct
|
||||
}
|
||||
|
||||
var sf structField
|
||||
|
||||
var a struct {
|
||||
rtype `reflect:"struct"`
|
||||
pkgPath *byte
|
||||
fields []structField // sorted by offset
|
||||
}
|
||||
|
||||
fieldtype := newStructType("runtime.structtype", unsafe.Sizeof(sf))
|
||||
appendField(fieldtype, "name", uintptrtyp, unsafe.Offsetof(sf.name))
|
||||
appendField(fieldtype, "typ", prtyp, unsafe.Offsetof(sf.typ))
|
||||
typ = newStructType("runtime.structtype", unsafe.Sizeof(a))
|
||||
appendField(typ, "fields", newSliceType(fieldtype), unsafe.Offsetof(a.fields))
|
||||
default:
|
||||
return _type, nil
|
||||
}
|
||||
|
||||
return _type.newVariable(_type.Name, _type.Addr, typ), nil
|
||||
}
|
||||
|
||||
// See reflect.(*rtype).uncommon in $GOROOT/src/reflect/type.go
|
||||
func uncommon(_type *Variable, tflag int64) *Variable {
|
||||
if tflag&tflagUncommon == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
typ, err := _type.dbp.findType("runtime.uncommontype")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return _type.newVariable(_type.Name, _type.Addr+uintptr(_type.RealType.Size()), typ)
|
||||
}
|
||||
1572
vendor/github.com/derekparker/delve/proc/variables.go
generated
vendored
1572
vendor/github.com/derekparker/delve/proc/variables.go
generated
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user