delve/pkg/dwarf/frame/entries.go

152 lines
4.5 KiB
Go
Raw Normal View History

package frame
2015-01-14 02:37:10 +00:00
import (
"encoding/binary"
2015-01-14 02:37:10 +00:00
"fmt"
"sort"
)
2020-12-14 17:31:11 +00:00
// CommonInformationEntry represents a Common Information Entry in
// the Dwarf .debug_frame section.
type CommonInformationEntry struct {
Length uint32
CIE_id uint32
Version uint8
Augmentation string
CodeAlignmentFactor uint64
DataAlignmentFactor int64
ReturnAddressRegister uint64
InitialInstructions []byte
staticBase uint64
// eh_frame pointer encoding
ptrEncAddr ptrEnc
}
2020-12-14 17:31:11 +00:00
// FrameDescriptionEntry represents a Frame Descriptor Entry in the
// Dwarf .debug_frame section.
type FrameDescriptionEntry struct {
Length uint32
CIE *CommonInformationEntry
Instructions []byte
begin, size uint64
order binary.ByteOrder
2014-10-11 06:05:27 +00:00
}
2020-12-14 17:31:11 +00:00
// Cover returns whether or not the given address is within the
// bounds of this frame.
func (fde *FrameDescriptionEntry) Cover(addr uint64) bool {
return (addr - fde.begin) < fde.size
}
2020-12-14 17:31:11 +00:00
// Begin returns address of first location for this frame.
2014-10-11 06:05:27 +00:00
func (fde *FrameDescriptionEntry) Begin() uint64 {
return fde.begin
}
2020-12-14 17:31:11 +00:00
// End returns address of last location for this frame.
2014-10-11 06:05:27 +00:00
func (fde *FrameDescriptionEntry) End() uint64 {
return fde.begin + fde.size
}
// Translate moves the beginning of fde forward by delta.
func (fde *FrameDescriptionEntry) Translate(delta uint64) {
fde.begin += delta
}
2020-12-14 17:31:11 +00:00
// EstablishFrame set up frame for the given PC.
func (fde *FrameDescriptionEntry) EstablishFrame(pc uint64) *FrameContext {
return executeDwarfProgramUntilPC(fde, pc)
}
type FrameDescriptionEntries []*FrameDescriptionEntry
func newFrameIndex() FrameDescriptionEntries {
return make(FrameDescriptionEntries, 0, 1000)
}
2020-12-14 17:31:11 +00:00
// ErrNoFDEForPC FDE for PC not found error
type ErrNoFDEForPC struct {
PC uint64
}
func (err *ErrNoFDEForPC) Error() string {
return fmt.Sprintf("could not find FDE for PC %#v", err.PC)
}
2020-12-14 17:31:11 +00:00
// FDEForPC returns the Frame Description Entry for the given PC.
func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) {
2015-01-14 02:37:10 +00:00
idx := sort.Search(len(fdes), func(i int) bool {
return fdes[i].Cover(pc) || fdes[i].Begin() >= pc
2015-01-14 02:37:10 +00:00
})
if idx == len(fdes) || !fdes[idx].Cover(pc) {
return nil, &ErrNoFDEForPC{pc}
}
2015-01-14 02:37:10 +00:00
return fdes[idx], nil
}
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
// Append appends otherFDEs to fdes and returns the result.
func (fdes FrameDescriptionEntries) Append(otherFDEs FrameDescriptionEntries) FrameDescriptionEntries {
r := append(fdes, otherFDEs...)
sort.SliceStable(r, func(i, j int) bool {
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
return r[i].Begin() < r[j].Begin()
})
// remove duplicates
uniqFDEs := fdes[:0]
for _, fde := range fdes {
if len(uniqFDEs) > 0 {
last := uniqFDEs[len(uniqFDEs)-1]
if last.Begin() == fde.Begin() && last.End() == fde.End() {
continue
}
}
uniqFDEs = append(uniqFDEs, fde)
}
proc: support debugging plugins (#1414) This change splits the BinaryInfo object into a slice of Image objects containing information about the base executable and each loaded shared library (note: go plugins are shared libraries). Delve backens are supposed to call BinaryInfo.AddImage whenever they detect that a new shared library has been loaded. Member fields of BinaryInfo that are used to speed up access to dwarf (Functions, packageVars, consts, etc...) remain part of BinaryInfo and are updated to reference the correct image object. This simplifies this change. This approach has a few shortcomings: 1. Multiple shared libraries can define functions or globals with the same name and we have no way to disambiguate between them. 2. We don't have a way to handle library unloading. Both of those affect C shared libraries much more than they affect go plugins. Go plugins can't be unloaded at all and a lot of name collisions are prevented by import paths. There's only one problem that is concerning: if two plugins both import the same package they will end up with multiple definition for the same function. For example if two plugins use fmt.Printf the final in-memory image (and therefore our BinaryInfo object) will end up with two copies of fmt.Printf at different memory addresses. If a user types break fmt.Printf a breakpoint should be created at *both* locations. Allowing this is a relatively complex change that should be done in a different PR than this. For this reason I consider this approach an acceptable and sustainable stopgap. Updates #865
2019-05-08 21:06:38 +00:00
return r
}
// ptrEnc represents a pointer encoding value, used during eh_frame decoding
// to determine how pointers were encoded.
// Least significant 4 (0xf) bytes encode the size as well as its
// signed-ness, most significant 4 bytes (0xf0) are flags describing how
// the value should be interpreted (absolute, relative...)
// See https://www.airs.com/blog/archives/460.
type ptrEnc uint8
const (
ptrEncAbs ptrEnc = 0x00 // pointer-sized unsigned integer
ptrEncOmit ptrEnc = 0xff // omitted
ptrEncUleb ptrEnc = 0x01 // ULEB128
ptrEncUdata2 ptrEnc = 0x02 // 2 bytes
ptrEncUdata4 ptrEnc = 0x03 // 4 bytes
ptrEncUdata8 ptrEnc = 0x04 // 8 bytes
ptrEncSigned ptrEnc = 0x08 // pointer-sized signed integer
ptrEncSleb ptrEnc = 0x09 // SLEB128
ptrEncSdata2 ptrEnc = 0x0a // 2 bytes, signed
ptrEncSdata4 ptrEnc = 0x0b // 4 bytes, signed
ptrEncSdata8 ptrEnc = 0x0c // 8 bytes, signed
ptrEncPCRel ptrEnc = 0x10 // value is relative to the memory address where it appears
ptrEncTextRel ptrEnc = 0x20 // value is relative to the address of the text section
ptrEncDataRel ptrEnc = 0x30 // value is relative to the address of the data section
ptrEncFuncRel ptrEnc = 0x40 // value is relative to the start of the function
ptrEncAligned ptrEnc = 0x50 // value should be aligned
ptrEncIndirect ptrEnc = 0x80 // value is an address where the real value of the pointer is stored
)
// Supported returns true if this pointer encoding is supported.
func (ptrEnc ptrEnc) Supported() bool {
if ptrEnc != ptrEncOmit {
szenc := ptrEnc & 0x0f
if ((szenc > ptrEncUdata8) && (szenc < ptrEncSigned)) || (szenc > ptrEncSdata8) {
// These values aren't defined at the moment
return false
}
if ptrEnc&0xf0 != ptrEncPCRel {
// Currently only the PC relative flag is supported
return false
}
}
return true
}