delve/pkg/dwarf/frame/parser.go
Alessandro Arzilli 2d09ea65bc
dwarf/frame: discard indirect flag reading personality func in eh_frame (#3032)
We don't do anything with the personality function so there is no point
in complaining that we don't fully support the pointer encoding flags
used to describe it.

This matches the current level of support of pointer encodings in gdb
(they are discarded when reading the personality function and not
supported for FDEs because gcc doesn't generate them).

Fixes #3015
2022-06-24 06:49:18 -07:00

288 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.
b, _ := buf.ReadByte()
e := ptrEnc(b) &^ ptrEncIndirect
if !e.Supported() {
ctx.err = fmt.Errorf("pointer encoding not supported %#x at %#x", e, ctx.offset())
return nil
}
ctx.readEncodedPtr(0, buf, 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
}
}