
This adds a workaround for the bug described at: https://github.com/golang/go/issues/25841 Because dsymutil running on PIE does not adjust the address of debug_frame entries (but adjusts debug_info entries) we try to do the adjustment ourselves. Updates #2346
287 lines
8.6 KiB
Go
287 lines
8.6 KiB
Go
// Package frame contains data structures and
|
|
// related functions for parsing and searching
|
|
// through Dwarf .debug_frame data.
|
|
package frame
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
|
)
|
|
|
|
type parsefunc func(*parseContext) parsefunc
|
|
|
|
type parseContext struct {
|
|
staticBase uint64
|
|
|
|
buf *bytes.Buffer
|
|
totalLen int
|
|
entries FrameDescriptionEntries
|
|
ciemap map[int]*CommonInformationEntry
|
|
common *CommonInformationEntry
|
|
frame *FrameDescriptionEntry
|
|
length uint32
|
|
ptrSize int
|
|
ehFrameAddr uint64
|
|
err error
|
|
}
|
|
|
|
// Parse takes in data (a byte slice) and returns FrameDescriptionEntries,
|
|
// which is a slice of FrameDescriptionEntry. Each FrameDescriptionEntry
|
|
// has a pointer to CommonInformationEntry.
|
|
// If ehFrameAddr is not zero the .eh_frame format will be used, a minor variant of DWARF described at https://www.airs.com/blog/archives/460.
|
|
// The value of ehFrameAddr will be used as the address at which eh_frame will be mapped into memory
|
|
func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int, ehFrameAddr uint64) (FrameDescriptionEntries, error) {
|
|
var (
|
|
buf = bytes.NewBuffer(data)
|
|
pctx = &parseContext{buf: buf, totalLen: len(data), entries: newFrameIndex(), staticBase: staticBase, ptrSize: ptrSize, ehFrameAddr: ehFrameAddr, ciemap: map[int]*CommonInformationEntry{}}
|
|
)
|
|
|
|
for fn := parselength; buf.Len() != 0; {
|
|
fn = fn(pctx)
|
|
if pctx.err != nil {
|
|
return nil, pctx.err
|
|
}
|
|
}
|
|
|
|
for i := range pctx.entries {
|
|
pctx.entries[i].order = order
|
|
}
|
|
|
|
return pctx.entries, nil
|
|
}
|
|
|
|
func (ctx *parseContext) parsingEHFrame() bool {
|
|
return ctx.ehFrameAddr > 0
|
|
}
|
|
|
|
func (ctx *parseContext) cieEntry(cieid uint32) bool {
|
|
if ctx.parsingEHFrame() {
|
|
return cieid == 0x00
|
|
}
|
|
return cieid == 0xffffffff
|
|
}
|
|
|
|
func (ctx *parseContext) offset() int {
|
|
return ctx.totalLen - ctx.buf.Len()
|
|
}
|
|
|
|
func parselength(ctx *parseContext) parsefunc {
|
|
start := ctx.offset()
|
|
binary.Read(ctx.buf, binary.LittleEndian, &ctx.length) //TODO(aarzilli): this does not support 64bit DWARF
|
|
|
|
if ctx.length == 0 {
|
|
// ZERO terminator
|
|
return parselength
|
|
}
|
|
|
|
var cieid uint32
|
|
binary.Read(ctx.buf, binary.LittleEndian, &cieid)
|
|
|
|
ctx.length -= 4 // take off the length of the CIE id / CIE pointer.
|
|
|
|
if ctx.cieEntry(cieid) {
|
|
ctx.common = &CommonInformationEntry{Length: ctx.length, staticBase: ctx.staticBase, CIE_id: cieid}
|
|
ctx.ciemap[start] = ctx.common
|
|
return parseCIE
|
|
}
|
|
|
|
if ctx.ehFrameAddr > 0 {
|
|
cieid = uint32(start - int(cieid) + 4)
|
|
}
|
|
|
|
common := ctx.ciemap[int(cieid)]
|
|
|
|
if common == nil {
|
|
ctx.err = fmt.Errorf("unknown CIE_id %#x at %#x", cieid, start)
|
|
}
|
|
|
|
ctx.frame = &FrameDescriptionEntry{Length: ctx.length, CIE: common}
|
|
return parseFDE
|
|
}
|
|
|
|
func parseFDE(ctx *parseContext) parsefunc {
|
|
startOff := ctx.offset()
|
|
r := ctx.buf.Next(int(ctx.length))
|
|
|
|
reader := bytes.NewReader(r)
|
|
num := ctx.readEncodedPtr(addrSum(ctx.ehFrameAddr+uint64(startOff), reader), reader, ctx.frame.CIE.ptrEncAddr)
|
|
ctx.frame.begin = num + ctx.staticBase
|
|
|
|
// For the size field in .eh_frame only the size encoding portion of the
|
|
// address pointer encoding is considered.
|
|
// See decode_frame_entry_1 in gdb/dwarf2-frame.c.
|
|
// For .debug_frame ptrEncAddr is always ptrEncAbs and never has flags.
|
|
sizePtrEnc := ctx.frame.CIE.ptrEncAddr & 0x0f
|
|
ctx.frame.size = ctx.readEncodedPtr(0, reader, sizePtrEnc)
|
|
|
|
// Insert into the tree after setting address range begin
|
|
// otherwise compares won't work.
|
|
ctx.entries = append(ctx.entries, ctx.frame)
|
|
|
|
if ctx.parsingEHFrame() && len(ctx.frame.CIE.Augmentation) > 0 {
|
|
// If we are parsing a .eh_frame and we saw an agumentation string then we
|
|
// need to read the augmentation data, which are encoded as a ULEB128
|
|
// size followed by 'size' bytes.
|
|
n, _ := util.DecodeULEB128(reader)
|
|
reader.Seek(int64(n), io.SeekCurrent)
|
|
}
|
|
|
|
// The rest of this entry consists of the instructions
|
|
// so we can just grab all of the data from the buffer
|
|
// cursor to length.
|
|
|
|
off, _ := reader.Seek(0, io.SeekCurrent)
|
|
ctx.frame.Instructions = r[off:]
|
|
ctx.length = 0
|
|
|
|
return parselength
|
|
}
|
|
|
|
func addrSum(base uint64, buf *bytes.Reader) uint64 {
|
|
n, _ := buf.Seek(0, io.SeekCurrent)
|
|
return base + uint64(n)
|
|
}
|
|
|
|
func parseCIE(ctx *parseContext) parsefunc {
|
|
data := ctx.buf.Next(int(ctx.length))
|
|
buf := bytes.NewBuffer(data)
|
|
// parse version
|
|
ctx.common.Version, _ = buf.ReadByte()
|
|
|
|
// parse augmentation
|
|
ctx.common.Augmentation, _ = util.ParseString(buf)
|
|
|
|
if ctx.parsingEHFrame() {
|
|
if ctx.common.Augmentation == "eh" {
|
|
ctx.err = fmt.Errorf("unsupported 'eh' augmentation at %#x", ctx.offset())
|
|
}
|
|
if len(ctx.common.Augmentation) > 0 && ctx.common.Augmentation[0] != 'z' {
|
|
ctx.err = fmt.Errorf("unsupported augmentation at %#x (does not start with 'z')", ctx.offset())
|
|
}
|
|
}
|
|
|
|
// parse code alignment factor
|
|
ctx.common.CodeAlignmentFactor, _ = util.DecodeULEB128(buf)
|
|
|
|
// parse data alignment factor
|
|
ctx.common.DataAlignmentFactor, _ = util.DecodeSLEB128(buf)
|
|
|
|
// parse return address register
|
|
if ctx.parsingEHFrame() && ctx.common.Version == 1 {
|
|
b, _ := buf.ReadByte()
|
|
ctx.common.ReturnAddressRegister = uint64(b)
|
|
} else {
|
|
ctx.common.ReturnAddressRegister, _ = util.DecodeULEB128(buf)
|
|
}
|
|
|
|
ctx.common.ptrEncAddr = ptrEncAbs
|
|
|
|
if ctx.parsingEHFrame() && len(ctx.common.Augmentation) > 0 {
|
|
_, _ = util.DecodeULEB128(buf) // augmentation data length
|
|
for i := 1; i < len(ctx.common.Augmentation); i++ {
|
|
switch ctx.common.Augmentation[i] {
|
|
case 'L':
|
|
_, _ = buf.ReadByte() // LSDA pointer encoding, we don't support this.
|
|
case 'R':
|
|
// Pointer encoding, describes how begin and size fields of FDEs are encoded.
|
|
b, _ := buf.ReadByte()
|
|
ctx.common.ptrEncAddr = ptrEnc(b)
|
|
if !ctx.common.ptrEncAddr.Supported() {
|
|
ctx.err = fmt.Errorf("pointer encoding not supported %#x at %#x", ctx.common.ptrEncAddr, ctx.offset())
|
|
return nil
|
|
}
|
|
case 'S':
|
|
// Signal handler invocation frame, we don't support this but there is no associated data to read.
|
|
case 'P':
|
|
// Personality function encoded as a pointer encoding byte followed by
|
|
// the pointer to the personality function encoded as specified by the
|
|
// pointer encoding.
|
|
// We don't support this but have to read it anyway.
|
|
e, _ := buf.ReadByte()
|
|
if !ptrEnc(e).Supported() {
|
|
ctx.err = fmt.Errorf("pointer encoding not supported %#x at %#x", e, ctx.offset())
|
|
return nil
|
|
}
|
|
ctx.readEncodedPtr(0, buf, ptrEnc(e))
|
|
default:
|
|
ctx.err = fmt.Errorf("unsupported augmentation character %c at %#x", ctx.common.Augmentation[i], ctx.offset())
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse initial instructions
|
|
// The rest of this entry consists of the instructions
|
|
// so we can just grab all of the data from the buffer
|
|
// cursor to length.
|
|
ctx.common.InitialInstructions = buf.Bytes() //ctx.buf.Next(int(ctx.length))
|
|
ctx.length = 0
|
|
|
|
return parselength
|
|
}
|
|
|
|
// readEncodedPtr reads a pointer from buf encoded as specified by ptrEnc.
|
|
// This function is used to read pointers from a .eh_frame section, when
|
|
// used to parse a .debug_frame section ptrEnc will always be ptrEncAbs.
|
|
// The parameter addr is the address that the current byte of 'buf' will be
|
|
// mapped to when the executable file containing the eh_frame section being
|
|
// parse is loaded in memory.
|
|
func (ctx *parseContext) readEncodedPtr(addr uint64, buf util.ByteReaderWithLen, ptrEnc ptrEnc) uint64 {
|
|
if ptrEnc == ptrEncOmit {
|
|
return 0
|
|
}
|
|
|
|
var ptr uint64
|
|
|
|
switch ptrEnc & 0xf {
|
|
case ptrEncAbs, ptrEncSigned:
|
|
ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, ctx.ptrSize)
|
|
case ptrEncUleb:
|
|
ptr, _ = util.DecodeULEB128(buf)
|
|
case ptrEncUdata2:
|
|
ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2)
|
|
case ptrEncSdata2:
|
|
ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2)
|
|
ptr = uint64(int16(ptr))
|
|
case ptrEncUdata4:
|
|
ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 4)
|
|
case ptrEncSdata4:
|
|
ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 4)
|
|
ptr = uint64(int32(ptr))
|
|
case ptrEncUdata8, ptrEncSdata8:
|
|
ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 8)
|
|
case ptrEncSleb:
|
|
n, _ := util.DecodeSLEB128(buf)
|
|
ptr = uint64(n)
|
|
}
|
|
|
|
if ptrEnc&0xf0 == ptrEncPCRel {
|
|
ptr += addr
|
|
}
|
|
|
|
return ptr
|
|
}
|
|
|
|
// DwarfEndian determines the endianness of the DWARF by using the version number field in the debug_info section
|
|
// Trick borrowed from "debug/dwarf".New()
|
|
func DwarfEndian(infoSec []byte) binary.ByteOrder {
|
|
if len(infoSec) < 6 {
|
|
return binary.BigEndian
|
|
}
|
|
x, y := infoSec[4], infoSec[5]
|
|
switch {
|
|
case x == 0 && y == 0:
|
|
return binary.BigEndian
|
|
case x == 0:
|
|
return binary.BigEndian
|
|
case y == 0:
|
|
return binary.LittleEndian
|
|
default:
|
|
return binary.BigEndian
|
|
}
|
|
}
|