Skip to content

Commit

Permalink
dwarf/frame,proc: use eh_frame section (#2344)
Browse files Browse the repository at this point in the history
The eh_frame section is similar to debug_frame but uses a slightly
different format. Gcc and clang by default only emit eh_frame.
  • Loading branch information
aarzilli committed Mar 5, 2021
1 parent 6a70d53 commit 314ae66
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 47 deletions.
61 changes: 60 additions & 1 deletion pkg/dwarf/frame/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ type CommonInformationEntry struct {
ReturnAddressRegister uint64
InitialInstructions []byte
staticBase uint64

// eh_frame pointer encoding
ptrEncAddr ptrEnc
}

// FrameDescriptionEntry represents a Frame Descriptor Entry in the
Expand Down Expand Up @@ -80,8 +83,64 @@ func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry,
// Append appends otherFDEs to fdes and returns the result.
func (fdes FrameDescriptionEntries) Append(otherFDEs FrameDescriptionEntries) FrameDescriptionEntries {
r := append(fdes, otherFDEs...)
sort.Slice(r, func(i, j int) bool {
sort.SliceStable(r, func(i, j int) bool {
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)
}
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
}
2 changes: 1 addition & 1 deletion pkg/dwarf/frame/entries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func BenchmarkFDEForPC(b *testing.B) {
if err != nil {
b.Fatal(err)
}
fdes := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
fdes, _ := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch(), 0)

for i := 0; i < b.N; i++ {
// bench worst case, exhaustive search
Expand Down
189 changes: 168 additions & 21 deletions pkg/dwarf/frame/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package frame
import (
"bytes"
"encoding/binary"
"fmt"
"io"

"github.com/go-delve/delve/pkg/dwarf/util"
)
Expand All @@ -15,82 +17,135 @@ type parsefunc func(*parseContext) parsefunc
type parseContext struct {
staticBase uint64

buf *bytes.Buffer
entries FrameDescriptionEntries
common *CommonInformationEntry
frame *FrameDescriptionEntry
length uint32
ptrSize int
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.
func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int) FrameDescriptionEntries {
// 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, entries: newFrameIndex(), staticBase: staticBase, ptrSize: ptrSize}
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
return pctx.entries, nil
}

func cieEntry(data []byte) bool {
return bytes.Equal(data, []byte{0xff, 0xff, 0xff, 0xff})
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 {
binary.Read(ctx.buf, binary.LittleEndian, &ctx.length)
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 data = ctx.buf.Next(4)
var cieid uint32
binary.Read(ctx.buf, binary.LittleEndian, &cieid)

ctx.length -= 4 // take off the length of the CIE id / CIE pointer.

if cieEntry(data) {
if ctx.cieEntry(cieid) {
ctx.common = &CommonInformationEntry{Length: ctx.length, staticBase: ctx.staticBase}
ctx.ciemap[start] = ctx.common
return parseCIE
}

ctx.frame = &FrameDescriptionEntry{Length: ctx.length, CIE: ctx.common}
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 {
var num uint64
startOff := ctx.offset()
r := ctx.buf.Next(int(ctx.length))

reader := bytes.NewReader(r)
num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
num := ctx.readEncodedPtr(addrSum(ctx.ehFrameAddr+uint64(startOff), reader), reader, ctx.frame.CIE.ptrEncAddr)
ctx.frame.begin = num + ctx.staticBase
num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
ctx.frame.size = num

// 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.
ctx.frame.Instructions = r[2*ctx.ptrSize:]

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)
Expand All @@ -100,14 +155,64 @@ func parseCIE(ctx *parseContext) parsefunc {
// 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
ctx.common.ReturnAddressRegister, _ = util.DecodeULEB128(buf)
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
Expand All @@ -119,6 +224,48 @@ func parseCIE(ctx *parseContext) parsefunc {
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 {
Expand Down
3 changes: 2 additions & 1 deletion pkg/dwarf/frame/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestParseCIE(t *testing.T) {
common: &CommonInformationEntry{Length: 12},
length: 12,
}
ctx.totalLen = ctx.buf.Len()
_ = parseCIE(ctx)

common := ctx.common
Expand Down Expand Up @@ -53,6 +54,6 @@ func BenchmarkParse(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch(), 0)
}
}
Loading

0 comments on commit 314ae66

Please sign in to comment.