2017-05-04 14:35:31 +00:00
|
|
|
package reader
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"debug/dwarf"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2017-09-08 10:31:03 +00:00
|
|
|
line int
|
2017-05-04 14:35:31 +00:00
|
|
|
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.
|
2017-09-08 10:31:03 +00:00
|
|
|
func Variables(dwarf *dwarf.Data, off dwarf.Offset, pc uint64, line int, onlyVisible bool) *VariableReader {
|
2017-05-04 14:35:31 +00:00
|
|
|
reader := dwarf.Reader()
|
|
|
|
reader.Seek(off)
|
2017-09-08 10:31:03 +00:00
|
|
|
return &VariableReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, onlyVisible: onlyVisible, pc: pc, line: line, err: nil}
|
2017-05-04 14:35:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
proc: support inlining
Go 1.10 added inlined calls to debug_info, this commit adds support
for DW_TAG_inlined_call to delve, both for stack traces (where
inlined calls will appear as normal stack frames) and to correct
the behavior of next, step and stepout.
The calls to Next and Frame of stackIterator continue to work
unchanged and only return real stack frames, after reading each line
appendInlinedCalls is called to unpacked all the inlined calls that
involve the current PC.
The fake stack frames produced by appendInlinedCalls are
distinguished from real stack frames by having the Inlined attribute
set to true. Also their Current and Call locations are treated
differently. The Call location will be changed to represent the
position inside the inlined call, while the Current location will
always reference the real stack frame. This is done because:
* next, step and stepout need to access the debug_info entry of
the real function they are stepping through
* we are already manipulating Call in different ways while Current
is just what we read from the call stack
The strategy remains mostly the same, we disassemble the function
and we set a breakpoint on each instruction corresponding to a
different file:line. The function in question will be the one
corresponding to the first real (i.e. non-inlined) stack frame.
* If the current function contains inlined calls, 'next' will not
set any breakpoints on instructions that belong to inlined calls. We
do not do this for 'step'.
* If we are inside an inlined call that makes other inlined
functions, 'next' will not set any breakpoints that belong to
inlined calls that are children of the current inlined call.
* If the current function is inlined the breakpoint on the return
address won't be set, because inlined frames don't have a return
address.
* The code we use for stepout doesn't work at all if we are inside
an inlined call, instead we call 'next' but instruct it to remove
all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
|
|
|
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
|
2017-05-04 14:35:31 +00:00
|
|
|
recur := true
|
|
|
|
if vrdr.onlyVisible {
|
proc: support inlining
Go 1.10 added inlined calls to debug_info, this commit adds support
for DW_TAG_inlined_call to delve, both for stack traces (where
inlined calls will appear as normal stack frames) and to correct
the behavior of next, step and stepout.
The calls to Next and Frame of stackIterator continue to work
unchanged and only return real stack frames, after reading each line
appendInlinedCalls is called to unpacked all the inlined calls that
involve the current PC.
The fake stack frames produced by appendInlinedCalls are
distinguished from real stack frames by having the Inlined attribute
set to true. Also their Current and Call locations are treated
differently. The Call location will be changed to represent the
position inside the inlined call, while the Current location will
always reference the real stack frame. This is done because:
* next, step and stepout need to access the debug_info entry of
the real function they are stepping through
* we are already manipulating Call in different ways while Current
is just what we read from the call stack
The strategy remains mostly the same, we disassemble the function
and we set a breakpoint on each instruction corresponding to a
different file:line. The function in question will be the one
corresponding to the first real (i.e. non-inlined) stack frame.
* If the current function contains inlined calls, 'next' will not
set any breakpoints on instructions that belong to inlined calls. We
do not do this for 'step'.
* If we are inside an inlined call that makes other inlined
functions, 'next' will not set any breakpoints that belong to
inlined calls that are children of the current inlined call.
* If the current function is inlined the breakpoint on the return
address won't be set, because inlined frames don't have a return
address.
* The code we use for stepout doesn't work at all if we are inside
an inlined call, instead we call 'next' but instruct it to remove
all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
|
|
|
recur, vrdr.err = entryRangesContains(vrdr.dwarf, vrdr.entry, vrdr.pc)
|
2017-05-04 14:35:31 +00:00
|
|
|
if vrdr.err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-04 10:05:05 +00:00
|
|
|
if recur && vrdr.entry.Children {
|
2017-05-04 14:35:31 +00:00
|
|
|
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
|
|
|
|
}
|
2017-09-08 10:31:03 +00:00
|
|
|
if declLine, ok := vrdr.entry.Val(dwarf.AttrDeclLine).(int64); !ok || vrdr.line >= int(declLine) {
|
|
|
|
return true
|
|
|
|
}
|
2017-05-04 14:35:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
proc: support inlining
Go 1.10 added inlined calls to debug_info, this commit adds support
for DW_TAG_inlined_call to delve, both for stack traces (where
inlined calls will appear as normal stack frames) and to correct
the behavior of next, step and stepout.
The calls to Next and Frame of stackIterator continue to work
unchanged and only return real stack frames, after reading each line
appendInlinedCalls is called to unpacked all the inlined calls that
involve the current PC.
The fake stack frames produced by appendInlinedCalls are
distinguished from real stack frames by having the Inlined attribute
set to true. Also their Current and Call locations are treated
differently. The Call location will be changed to represent the
position inside the inlined call, while the Current location will
always reference the real stack frame. This is done because:
* next, step and stepout need to access the debug_info entry of
the real function they are stepping through
* we are already manipulating Call in different ways while Current
is just what we read from the call stack
The strategy remains mostly the same, we disassemble the function
and we set a breakpoint on each instruction corresponding to a
different file:line. The function in question will be the one
corresponding to the first real (i.e. non-inlined) stack frame.
* If the current function contains inlined calls, 'next' will not
set any breakpoints on instructions that belong to inlined calls. We
do not do this for 'step'.
* If we are inside an inlined call that makes other inlined
functions, 'next' will not set any breakpoints that belong to
inlined calls that are children of the current inlined call.
* If the current function is inlined the breakpoint on the return
address won't be set, because inlined frames don't have a return
address.
* The code we use for stepout doesn't work at all if we are inside
an inlined call, instead we call 'next' but instruct it to remove
all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
|
|
|
func entryRangesContains(dwarf *dwarf.Data, entry *dwarf.Entry, pc uint64) (bool, error) {
|
|
|
|
rngs, err := dwarf.Ranges(entry)
|
2017-05-04 14:35:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
for _, rng := range rngs {
|
proc: support inlining
Go 1.10 added inlined calls to debug_info, this commit adds support
for DW_TAG_inlined_call to delve, both for stack traces (where
inlined calls will appear as normal stack frames) and to correct
the behavior of next, step and stepout.
The calls to Next and Frame of stackIterator continue to work
unchanged and only return real stack frames, after reading each line
appendInlinedCalls is called to unpacked all the inlined calls that
involve the current PC.
The fake stack frames produced by appendInlinedCalls are
distinguished from real stack frames by having the Inlined attribute
set to true. Also their Current and Call locations are treated
differently. The Call location will be changed to represent the
position inside the inlined call, while the Current location will
always reference the real stack frame. This is done because:
* next, step and stepout need to access the debug_info entry of
the real function they are stepping through
* we are already manipulating Call in different ways while Current
is just what we read from the call stack
The strategy remains mostly the same, we disassemble the function
and we set a breakpoint on each instruction corresponding to a
different file:line. The function in question will be the one
corresponding to the first real (i.e. non-inlined) stack frame.
* If the current function contains inlined calls, 'next' will not
set any breakpoints on instructions that belong to inlined calls. We
do not do this for 'step'.
* If we are inside an inlined call that makes other inlined
functions, 'next' will not set any breakpoints that belong to
inlined calls that are children of the current inlined call.
* If the current function is inlined the breakpoint on the return
address won't be set, because inlined frames don't have a return
address.
* The code we use for stepout doesn't work at all if we are inside
an inlined call, instead we call 'next' but instruct it to remove
all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
|
|
|
if pc >= rng[0] && pc < rng[1] {
|
2017-05-04 14:35:31 +00:00
|
|
|
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
|
|
|
|
}
|