1212 lines
30 KiB
Go
1212 lines
30 KiB
Go
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
// DWARF debug information entry parser.
|
||
|
// An entry is a sequence of data items of a given format.
|
||
|
// The first word in the entry is an index into what DWARF
|
||
|
// calls the ``abbreviation table.'' An abbreviation is really
|
||
|
// just a type descriptor: it's an array of attribute tag/value format pairs.
|
||
|
|
||
|
package dwarf
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// a single entry's description: a sequence of attributes
|
||
|
type abbrev struct {
|
||
|
tag Tag
|
||
|
children bool
|
||
|
field []afield
|
||
|
}
|
||
|
|
||
|
type afield struct {
|
||
|
attr Attr
|
||
|
fmt format
|
||
|
class Class
|
||
|
val int64 // for formImplicitConst
|
||
|
}
|
||
|
|
||
|
// a map from entry format ids to their descriptions
|
||
|
type abbrevTable map[uint32]abbrev
|
||
|
|
||
|
// ParseAbbrev returns the abbreviation table that starts at byte off
|
||
|
// in the .debug_abbrev section.
|
||
|
func (d *Data) parseAbbrev(off uint64, vers int) (abbrevTable, error) {
|
||
|
if m, ok := d.abbrevCache[off]; ok {
|
||
|
return m, nil
|
||
|
}
|
||
|
|
||
|
data := d.abbrev
|
||
|
if off > uint64(len(data)) {
|
||
|
data = nil
|
||
|
} else {
|
||
|
data = data[off:]
|
||
|
}
|
||
|
b := makeBuf(d, unknownFormat{}, "abbrev", 0, data)
|
||
|
|
||
|
// Error handling is simplified by the buf getters
|
||
|
// returning an endless stream of 0s after an error.
|
||
|
m := make(abbrevTable)
|
||
|
for {
|
||
|
// Table ends with id == 0.
|
||
|
id := uint32(b.uint())
|
||
|
if id == 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
// Walk over attributes, counting.
|
||
|
n := 0
|
||
|
b1 := b // Read from copy of b.
|
||
|
b1.uint()
|
||
|
b1.uint8()
|
||
|
for {
|
||
|
tag := b1.uint()
|
||
|
fmt := b1.uint()
|
||
|
if tag == 0 && fmt == 0 {
|
||
|
break
|
||
|
}
|
||
|
if format(fmt) == formImplicitConst {
|
||
|
b1.int()
|
||
|
}
|
||
|
n++
|
||
|
}
|
||
|
if b1.err != nil {
|
||
|
return nil, b1.err
|
||
|
}
|
||
|
|
||
|
// Walk over attributes again, this time writing them down.
|
||
|
var a abbrev
|
||
|
a.tag = Tag(b.uint())
|
||
|
a.children = b.uint8() != 0
|
||
|
a.field = make([]afield, n)
|
||
|
for i := range a.field {
|
||
|
a.field[i].attr = Attr(b.uint())
|
||
|
a.field[i].fmt = format(b.uint())
|
||
|
a.field[i].class = formToClass(a.field[i].fmt, a.field[i].attr, vers, &b)
|
||
|
if a.field[i].fmt == formImplicitConst {
|
||
|
a.field[i].val = b.int()
|
||
|
}
|
||
|
}
|
||
|
b.uint()
|
||
|
b.uint()
|
||
|
|
||
|
m[id] = a
|
||
|
}
|
||
|
if b.err != nil {
|
||
|
return nil, b.err
|
||
|
}
|
||
|
d.abbrevCache[off] = m
|
||
|
return m, nil
|
||
|
}
|
||
|
|
||
|
// attrIsExprloc indicates attributes that allow exprloc values that
|
||
|
// are encoded as block values in DWARF 2 and 3. See DWARF 4, Figure
|
||
|
// 20.
|
||
|
var attrIsExprloc = map[Attr]bool{
|
||
|
AttrLocation: true,
|
||
|
AttrByteSize: true,
|
||
|
AttrBitOffset: true,
|
||
|
AttrBitSize: true,
|
||
|
AttrStringLength: true,
|
||
|
AttrLowerBound: true,
|
||
|
AttrReturnAddr: true,
|
||
|
AttrStrideSize: true,
|
||
|
AttrUpperBound: true,
|
||
|
AttrCount: true,
|
||
|
AttrDataMemberLoc: true,
|
||
|
AttrFrameBase: true,
|
||
|
AttrSegment: true,
|
||
|
AttrStaticLink: true,
|
||
|
AttrUseLocation: true,
|
||
|
AttrVtableElemLoc: true,
|
||
|
AttrAllocated: true,
|
||
|
AttrAssociated: true,
|
||
|
AttrDataLocation: true,
|
||
|
AttrStride: true,
|
||
|
}
|
||
|
|
||
|
// attrPtrClass indicates the *ptr class of attributes that have
|
||
|
// encoding formSecOffset in DWARF 4 or formData* in DWARF 2 and 3.
|
||
|
var attrPtrClass = map[Attr]Class{
|
||
|
AttrLocation: ClassLocListPtr,
|
||
|
AttrStmtList: ClassLinePtr,
|
||
|
AttrStringLength: ClassLocListPtr,
|
||
|
AttrReturnAddr: ClassLocListPtr,
|
||
|
AttrStartScope: ClassRangeListPtr,
|
||
|
AttrDataMemberLoc: ClassLocListPtr,
|
||
|
AttrFrameBase: ClassLocListPtr,
|
||
|
AttrMacroInfo: ClassMacPtr,
|
||
|
AttrSegment: ClassLocListPtr,
|
||
|
AttrStaticLink: ClassLocListPtr,
|
||
|
AttrUseLocation: ClassLocListPtr,
|
||
|
AttrVtableElemLoc: ClassLocListPtr,
|
||
|
AttrRanges: ClassRangeListPtr,
|
||
|
// The following are new in DWARF 5.
|
||
|
AttrStrOffsetsBase: ClassStrOffsetsPtr,
|
||
|
AttrAddrBase: ClassAddrPtr,
|
||
|
AttrRnglistsBase: ClassRngListsPtr,
|
||
|
AttrLoclistsBase: ClassLocListPtr,
|
||
|
}
|
||
|
|
||
|
// formToClass returns the DWARF 4 Class for the given form. If the
|
||
|
// DWARF version is less then 4, it will disambiguate some forms
|
||
|
// depending on the attribute.
|
||
|
func formToClass(form format, attr Attr, vers int, b *buf) Class {
|
||
|
switch form {
|
||
|
default:
|
||
|
b.error("cannot determine class of unknown attribute form")
|
||
|
return 0
|
||
|
|
||
|
case formIndirect:
|
||
|
return ClassUnknown
|
||
|
|
||
|
case formAddr, formAddrx, formAddrx1, formAddrx2, formAddrx3, formAddrx4:
|
||
|
return ClassAddress
|
||
|
|
||
|
case formDwarfBlock1, formDwarfBlock2, formDwarfBlock4, formDwarfBlock:
|
||
|
// In DWARF 2 and 3, ClassExprLoc was encoded as a
|
||
|
// block. DWARF 4 distinguishes ClassBlock and
|
||
|
// ClassExprLoc, but there are no attributes that can
|
||
|
// be both, so we also promote ClassBlock values in
|
||
|
// DWARF 4 that should be ClassExprLoc in case
|
||
|
// producers get this wrong.
|
||
|
if attrIsExprloc[attr] {
|
||
|
return ClassExprLoc
|
||
|
}
|
||
|
return ClassBlock
|
||
|
|
||
|
case formData1, formData2, formData4, formData8, formSdata, formUdata, formData16, formImplicitConst:
|
||
|
// In DWARF 2 and 3, ClassPtr was encoded as a
|
||
|
// constant. Unlike ClassExprLoc/ClassBlock, some
|
||
|
// DWARF 4 attributes need to distinguish Class*Ptr
|
||
|
// from ClassConstant, so we only do this promotion
|
||
|
// for versions 2 and 3.
|
||
|
if class, ok := attrPtrClass[attr]; vers < 4 && ok {
|
||
|
return class
|
||
|
}
|
||
|
return ClassConstant
|
||
|
|
||
|
case formFlag, formFlagPresent:
|
||
|
return ClassFlag
|
||
|
|
||
|
case formRefAddr, formRef1, formRef2, formRef4, formRef8, formRefUdata, formRefSup4, formRefSup8:
|
||
|
return ClassReference
|
||
|
|
||
|
case formRefSig8:
|
||
|
return ClassReferenceSig
|
||
|
|
||
|
case formString, formStrp, formStrx, formStrpSup, formLineStrp, formStrx1, formStrx2, formStrx3, formStrx4:
|
||
|
return ClassString
|
||
|
|
||
|
case formSecOffset:
|
||
|
// DWARF 4 defines four *ptr classes, but doesn't
|
||
|
// distinguish them in the encoding. Disambiguate
|
||
|
// these classes using the attribute.
|
||
|
if class, ok := attrPtrClass[attr]; ok {
|
||
|
return class
|
||
|
}
|
||
|
return ClassUnknown
|
||
|
|
||
|
case formExprloc:
|
||
|
return ClassExprLoc
|
||
|
|
||
|
case formGnuRefAlt:
|
||
|
return ClassReferenceAlt
|
||
|
|
||
|
case formGnuStrpAlt:
|
||
|
return ClassStringAlt
|
||
|
|
||
|
case formLoclistx:
|
||
|
return ClassLocList
|
||
|
|
||
|
case formRnglistx:
|
||
|
return ClassRngList
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// An entry is a sequence of attribute/value pairs.
|
||
|
type Entry struct {
|
||
|
Offset Offset // offset of Entry in DWARF info
|
||
|
Tag Tag // tag (kind of Entry)
|
||
|
Children bool // whether Entry is followed by children
|
||
|
Field []Field
|
||
|
}
|
||
|
|
||
|
// A Field is a single attribute/value pair in an Entry.
|
||
|
//
|
||
|
// A value can be one of several "attribute classes" defined by DWARF.
|
||
|
// The Go types corresponding to each class are:
|
||
|
//
|
||
|
// DWARF class Go type Class
|
||
|
// ----------- ------- -----
|
||
|
// address uint64 ClassAddress
|
||
|
// block []byte ClassBlock
|
||
|
// constant int64 ClassConstant
|
||
|
// flag bool ClassFlag
|
||
|
// reference
|
||
|
// to info dwarf.Offset ClassReference
|
||
|
// to type unit uint64 ClassReferenceSig
|
||
|
// string string ClassString
|
||
|
// exprloc []byte ClassExprLoc
|
||
|
// lineptr int64 ClassLinePtr
|
||
|
// loclistptr int64 ClassLocListPtr
|
||
|
// macptr int64 ClassMacPtr
|
||
|
// rangelistptr int64 ClassRangeListPtr
|
||
|
//
|
||
|
// For unrecognized or vendor-defined attributes, Class may be
|
||
|
// ClassUnknown.
|
||
|
type Field struct {
|
||
|
Attr Attr
|
||
|
Val any
|
||
|
Class Class
|
||
|
}
|
||
|
|
||
|
// A Class is the DWARF 4 class of an attribute value.
|
||
|
//
|
||
|
// In general, a given attribute's value may take on one of several
|
||
|
// possible classes defined by DWARF, each of which leads to a
|
||
|
// slightly different interpretation of the attribute.
|
||
|
//
|
||
|
// DWARF version 4 distinguishes attribute value classes more finely
|
||
|
// than previous versions of DWARF. The reader will disambiguate
|
||
|
// coarser classes from earlier versions of DWARF into the appropriate
|
||
|
// DWARF 4 class. For example, DWARF 2 uses "constant" for constants
|
||
|
// as well as all types of section offsets, but the reader will
|
||
|
// canonicalize attributes in DWARF 2 files that refer to section
|
||
|
// offsets to one of the Class*Ptr classes, even though these classes
|
||
|
// were only defined in DWARF 3.
|
||
|
type Class int
|
||
|
|
||
|
const (
|
||
|
// ClassUnknown represents values of unknown DWARF class.
|
||
|
ClassUnknown Class = iota
|
||
|
|
||
|
// ClassAddress represents values of type uint64 that are
|
||
|
// addresses on the target machine.
|
||
|
ClassAddress
|
||
|
|
||
|
// ClassBlock represents values of type []byte whose
|
||
|
// interpretation depends on the attribute.
|
||
|
ClassBlock
|
||
|
|
||
|
// ClassConstant represents values of type int64 that are
|
||
|
// constants. The interpretation of this constant depends on
|
||
|
// the attribute.
|
||
|
ClassConstant
|
||
|
|
||
|
// ClassExprLoc represents values of type []byte that contain
|
||
|
// an encoded DWARF expression or location description.
|
||
|
ClassExprLoc
|
||
|
|
||
|
// ClassFlag represents values of type bool.
|
||
|
ClassFlag
|
||
|
|
||
|
// ClassLinePtr represents values that are an int64 offset
|
||
|
// into the "line" section.
|
||
|
ClassLinePtr
|
||
|
|
||
|
// ClassLocListPtr represents values that are an int64 offset
|
||
|
// into the "loclist" section.
|
||
|
ClassLocListPtr
|
||
|
|
||
|
// ClassMacPtr represents values that are an int64 offset into
|
||
|
// the "mac" section.
|
||
|
ClassMacPtr
|
||
|
|
||
|
// ClassRangeListPtr represents values that are an int64 offset into
|
||
|
// the "rangelist" section.
|
||
|
ClassRangeListPtr
|
||
|
|
||
|
// ClassReference represents values that are an Offset offset
|
||
|
// of an Entry in the info section (for use with Reader.Seek).
|
||
|
// The DWARF specification combines ClassReference and
|
||
|
// ClassReferenceSig into class "reference".
|
||
|
ClassReference
|
||
|
|
||
|
// ClassReferenceSig represents values that are a uint64 type
|
||
|
// signature referencing a type Entry.
|
||
|
ClassReferenceSig
|
||
|
|
||
|
// ClassString represents values that are strings. If the
|
||
|
// compilation unit specifies the AttrUseUTF8 flag (strongly
|
||
|
// recommended), the string value will be encoded in UTF-8.
|
||
|
// Otherwise, the encoding is unspecified.
|
||
|
ClassString
|
||
|
|
||
|
// ClassReferenceAlt represents values of type int64 that are
|
||
|
// an offset into the DWARF "info" section of an alternate
|
||
|
// object file.
|
||
|
ClassReferenceAlt
|
||
|
|
||
|
// ClassStringAlt represents values of type int64 that are an
|
||
|
// offset into the DWARF string section of an alternate object
|
||
|
// file.
|
||
|
ClassStringAlt
|
||
|
|
||
|
// ClassAddrPtr represents values that are an int64 offset
|
||
|
// into the "addr" section.
|
||
|
ClassAddrPtr
|
||
|
|
||
|
// ClassLocList represents values that are an int64 offset
|
||
|
// into the "loclists" section.
|
||
|
ClassLocList
|
||
|
|
||
|
// ClassRngList represents values that are a uint64 offset
|
||
|
// from the base of the "rnglists" section.
|
||
|
ClassRngList
|
||
|
|
||
|
// ClassRngListsPtr represents values that are an int64 offset
|
||
|
// into the "rnglists" section. These are used as the base for
|
||
|
// ClassRngList values.
|
||
|
ClassRngListsPtr
|
||
|
|
||
|
// ClassStrOffsetsPtr represents values that are an int64
|
||
|
// offset into the "str_offsets" section.
|
||
|
ClassStrOffsetsPtr
|
||
|
)
|
||
|
|
||
|
//go:generate stringer -type=Class
|
||
|
|
||
|
func (i Class) GoString() string {
|
||
|
return "dwarf." + i.String()
|
||
|
}
|
||
|
|
||
|
// Val returns the value associated with attribute Attr in Entry,
|
||
|
// or nil if there is no such attribute.
|
||
|
//
|
||
|
// A common idiom is to merge the check for nil return with
|
||
|
// the check that the value has the expected dynamic type, as in:
|
||
|
// v, ok := e.Val(AttrSibling).(int64)
|
||
|
//
|
||
|
func (e *Entry) Val(a Attr) any {
|
||
|
if f := e.AttrField(a); f != nil {
|
||
|
return f.Val
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// AttrField returns the Field associated with attribute Attr in
|
||
|
// Entry, or nil if there is no such attribute.
|
||
|
func (e *Entry) AttrField(a Attr) *Field {
|
||
|
for i, f := range e.Field {
|
||
|
if f.Attr == a {
|
||
|
return &e.Field[i]
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// An Offset represents the location of an Entry within the DWARF info.
|
||
|
// (See Reader.Seek.)
|
||
|
type Offset uint32
|
||
|
|
||
|
// Entry reads a single entry from buf, decoding
|
||
|
// according to the given abbreviation table.
|
||
|
func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry {
|
||
|
off := b.off
|
||
|
id := uint32(b.uint())
|
||
|
if id == 0 {
|
||
|
return &Entry{}
|
||
|
}
|
||
|
a, ok := atab[id]
|
||
|
if !ok {
|
||
|
b.error("unknown abbreviation table index")
|
||
|
return nil
|
||
|
}
|
||
|
e := &Entry{
|
||
|
Offset: off,
|
||
|
Tag: a.tag,
|
||
|
Children: a.children,
|
||
|
Field: make([]Field, len(a.field)),
|
||
|
}
|
||
|
|
||
|
// If we are currently parsing the compilation unit,
|
||
|
// we can't evaluate Addrx or Strx until we've seen the
|
||
|
// relevant base entry.
|
||
|
type delayed struct {
|
||
|
idx int
|
||
|
off uint64
|
||
|
fmt format
|
||
|
}
|
||
|
var delay []delayed
|
||
|
|
||
|
resolveStrx := func(strBase, off uint64) string {
|
||
|
off += strBase
|
||
|
if uint64(int(off)) != off {
|
||
|
b.error("DW_FORM_strx offset out of range")
|
||
|
}
|
||
|
|
||
|
b1 := makeBuf(b.dwarf, b.format, "str_offsets", 0, b.dwarf.strOffsets)
|
||
|
b1.skip(int(off))
|
||
|
is64, _ := b.format.dwarf64()
|
||
|
if is64 {
|
||
|
off = b1.uint64()
|
||
|
} else {
|
||
|
off = uint64(b1.uint32())
|
||
|
}
|
||
|
if b1.err != nil {
|
||
|
b.err = b1.err
|
||
|
return ""
|
||
|
}
|
||
|
if uint64(int(off)) != off {
|
||
|
b.error("DW_FORM_strx indirect offset out of range")
|
||
|
}
|
||
|
b1 = makeBuf(b.dwarf, b.format, "str", 0, b.dwarf.str)
|
||
|
b1.skip(int(off))
|
||
|
val := b1.string()
|
||
|
if b1.err != nil {
|
||
|
b.err = b1.err
|
||
|
}
|
||
|
return val
|
||
|
}
|
||
|
|
||
|
resolveRnglistx := func(rnglistsBase, off uint64) uint64 {
|
||
|
is64, _ := b.format.dwarf64()
|
||
|
if is64 {
|
||
|
off *= 8
|
||
|
} else {
|
||
|
off *= 4
|
||
|
}
|
||
|
off += rnglistsBase
|
||
|
if uint64(int(off)) != off {
|
||
|
b.error("DW_FORM_rnglistx offset out of range")
|
||
|
}
|
||
|
|
||
|
b1 := makeBuf(b.dwarf, b.format, "rnglists", 0, b.dwarf.rngLists)
|
||
|
b1.skip(int(off))
|
||
|
if is64 {
|
||
|
off = b1.uint64()
|
||
|
} else {
|
||
|
off = uint64(b1.uint32())
|
||
|
}
|
||
|
if b1.err != nil {
|
||
|
b.err = b1.err
|
||
|
return 0
|
||
|
}
|
||
|
if uint64(int(off)) != off {
|
||
|
b.error("DW_FORM_rnglistx indirect offset out of range")
|
||
|
}
|
||
|
return rnglistsBase + off
|
||
|
}
|
||
|
|
||
|
for i := range e.Field {
|
||
|
e.Field[i].Attr = a.field[i].attr
|
||
|
e.Field[i].Class = a.field[i].class
|
||
|
fmt := a.field[i].fmt
|
||
|
if fmt == formIndirect {
|
||
|
fmt = format(b.uint())
|
||
|
e.Field[i].Class = formToClass(fmt, a.field[i].attr, vers, b)
|
||
|
}
|
||
|
var val any
|
||
|
switch fmt {
|
||
|
default:
|
||
|
b.error("unknown entry attr format 0x" + strconv.FormatInt(int64(fmt), 16))
|
||
|
|
||
|
// address
|
||
|
case formAddr:
|
||
|
val = b.addr()
|
||
|
case formAddrx, formAddrx1, formAddrx2, formAddrx3, formAddrx4:
|
||
|
var off uint64
|
||
|
switch fmt {
|
||
|
case formAddrx:
|
||
|
off = b.uint()
|
||
|
case formAddrx1:
|
||
|
off = uint64(b.uint8())
|
||
|
case formAddrx2:
|
||
|
off = uint64(b.uint16())
|
||
|
case formAddrx3:
|
||
|
off = uint64(b.uint24())
|
||
|
case formAddrx4:
|
||
|
off = uint64(b.uint32())
|
||
|
}
|
||
|
if b.dwarf.addr == nil {
|
||
|
b.error("DW_FORM_addrx with no .debug_addr section")
|
||
|
}
|
||
|
if b.err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// We have to adjust by the offset of the
|
||
|
// compilation unit. This won't work if the
|
||
|
// program uses Reader.Seek to skip over the
|
||
|
// unit. Not much we can do about that.
|
||
|
var addrBase int64
|
||
|
if cu != nil {
|
||
|
addrBase, _ = cu.Val(AttrAddrBase).(int64)
|
||
|
} else if a.tag == TagCompileUnit {
|
||
|
delay = append(delay, delayed{i, off, formAddrx})
|
||
|
break
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
val, err = b.dwarf.debugAddr(b.format, uint64(addrBase), off)
|
||
|
if err != nil {
|
||
|
if b.err == nil {
|
||
|
b.err = err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// block
|
||
|
case formDwarfBlock1:
|
||
|
val = b.bytes(int(b.uint8()))
|
||
|
case formDwarfBlock2:
|
||
|
val = b.bytes(int(b.uint16()))
|
||
|
case formDwarfBlock4:
|
||
|
val = b.bytes(int(b.uint32()))
|
||
|
case formDwarfBlock:
|
||
|
val = b.bytes(int(b.uint()))
|
||
|
|
||
|
// constant
|
||
|
case formData1:
|
||
|
val = int64(b.uint8())
|
||
|
case formData2:
|
||
|
val = int64(b.uint16())
|
||
|
case formData4:
|
||
|
val = int64(b.uint32())
|
||
|
case formData8:
|
||
|
val = int64(b.uint64())
|
||
|
case formData16:
|
||
|
val = b.bytes(16)
|
||
|
case formSdata:
|
||
|
val = int64(b.int())
|
||
|
case formUdata:
|
||
|
val = int64(b.uint())
|
||
|
case formImplicitConst:
|
||
|
val = a.field[i].val
|
||
|
|
||
|
// flag
|
||
|
case formFlag:
|
||
|
val = b.uint8() == 1
|
||
|
// New in DWARF 4.
|
||
|
case formFlagPresent:
|
||
|
// The attribute is implicitly indicated as present, and no value is
|
||
|
// encoded in the debugging information entry itself.
|
||
|
val = true
|
||
|
|
||
|
// reference to other entry
|
||
|
case formRefAddr:
|
||
|
vers := b.format.version()
|
||
|
if vers == 0 {
|
||
|
b.error("unknown version for DW_FORM_ref_addr")
|
||
|
} else if vers == 2 {
|
||
|
val = Offset(b.addr())
|
||
|
} else {
|
||
|
is64, known := b.format.dwarf64()
|
||
|
if !known {
|
||
|
b.error("unknown size for DW_FORM_ref_addr")
|
||
|
} else if is64 {
|
||
|
val = Offset(b.uint64())
|
||
|
} else {
|
||
|
val = Offset(b.uint32())
|
||
|
}
|
||
|
}
|
||
|
case formRef1:
|
||
|
val = Offset(b.uint8()) + ubase
|
||
|
case formRef2:
|
||
|
val = Offset(b.uint16()) + ubase
|
||
|
case formRef4:
|
||
|
val = Offset(b.uint32()) + ubase
|
||
|
case formRef8:
|
||
|
val = Offset(b.uint64()) + ubase
|
||
|
case formRefUdata:
|
||
|
val = Offset(b.uint()) + ubase
|
||
|
|
||
|
// string
|
||
|
case formString:
|
||
|
val = b.string()
|
||
|
case formStrp, formLineStrp:
|
||
|
var off uint64 // offset into .debug_str
|
||
|
is64, known := b.format.dwarf64()
|
||
|
if !known {
|
||
|
b.error("unknown size for DW_FORM_strp/line_strp")
|
||
|
} else if is64 {
|
||
|
off = b.uint64()
|
||
|
} else {
|
||
|
off = uint64(b.uint32())
|
||
|
}
|
||
|
if uint64(int(off)) != off {
|
||
|
b.error("DW_FORM_strp/line_strp offset out of range")
|
||
|
}
|
||
|
if b.err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
var b1 buf
|
||
|
if fmt == formStrp {
|
||
|
b1 = makeBuf(b.dwarf, b.format, "str", 0, b.dwarf.str)
|
||
|
} else {
|
||
|
if len(b.dwarf.lineStr) == 0 {
|
||
|
b.error("DW_FORM_line_strp with no .debug_line_str section")
|
||
|
return nil
|
||
|
}
|
||
|
b1 = makeBuf(b.dwarf, b.format, "line_str", 0, b.dwarf.lineStr)
|
||
|
}
|
||
|
b1.skip(int(off))
|
||
|
val = b1.string()
|
||
|
if b1.err != nil {
|
||
|
b.err = b1.err
|
||
|
return nil
|
||
|
}
|
||
|
case formStrx, formStrx1, formStrx2, formStrx3, formStrx4:
|
||
|
var off uint64
|
||
|
switch fmt {
|
||
|
case formStrx:
|
||
|
off = b.uint()
|
||
|
case formStrx1:
|
||
|
off = uint64(b.uint8())
|
||
|
case formStrx2:
|
||
|
off = uint64(b.uint16())
|
||
|
case formStrx3:
|
||
|
off = uint64(b.uint24())
|
||
|
case formStrx4:
|
||
|
off = uint64(b.uint32())
|
||
|
}
|
||
|
if len(b.dwarf.strOffsets) == 0 {
|
||
|
b.error("DW_FORM_strx with no .debug_str_offsets section")
|
||
|
}
|
||
|
is64, known := b.format.dwarf64()
|
||
|
if !known {
|
||
|
b.error("unknown offset size for DW_FORM_strx")
|
||
|
}
|
||
|
if b.err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
if is64 {
|
||
|
off *= 8
|
||
|
} else {
|
||
|
off *= 4
|
||
|
}
|
||
|
|
||
|
// We have to adjust by the offset of the
|
||
|
// compilation unit. This won't work if the
|
||
|
// program uses Reader.Seek to skip over the
|
||
|
// unit. Not much we can do about that.
|
||
|
var strBase int64
|
||
|
if cu != nil {
|
||
|
strBase, _ = cu.Val(AttrStrOffsetsBase).(int64)
|
||
|
} else if a.tag == TagCompileUnit {
|
||
|
delay = append(delay, delayed{i, off, formStrx})
|
||
|
break
|
||
|
}
|
||
|
|
||
|
val = resolveStrx(uint64(strBase), off)
|
||
|
|
||
|
case formStrpSup:
|
||
|
is64, known := b.format.dwarf64()
|
||
|
if !known {
|
||
|
b.error("unknown size for DW_FORM_strp_sup")
|
||
|
} else if is64 {
|
||
|
val = b.uint64()
|
||
|
} else {
|
||
|
val = b.uint32()
|
||
|
}
|
||
|
|
||
|
// lineptr, loclistptr, macptr, rangelistptr
|
||
|
// New in DWARF 4, but clang can generate them with -gdwarf-2.
|
||
|
// Section reference, replacing use of formData4 and formData8.
|
||
|
case formSecOffset, formGnuRefAlt, formGnuStrpAlt:
|
||
|
is64, known := b.format.dwarf64()
|
||
|
if !known {
|
||
|
b.error("unknown size for form 0x" + strconv.FormatInt(int64(fmt), 16))
|
||
|
} else if is64 {
|
||
|
val = int64(b.uint64())
|
||
|
} else {
|
||
|
val = int64(b.uint32())
|
||
|
}
|
||
|
|
||
|
// exprloc
|
||
|
// New in DWARF 4.
|
||
|
case formExprloc:
|
||
|
val = b.bytes(int(b.uint()))
|
||
|
|
||
|
// reference
|
||
|
// New in DWARF 4.
|
||
|
case formRefSig8:
|
||
|
// 64-bit type signature.
|
||
|
val = b.uint64()
|
||
|
case formRefSup4:
|
||
|
val = b.uint32()
|
||
|
case formRefSup8:
|
||
|
val = b.uint64()
|
||
|
|
||
|
// loclist
|
||
|
case formLoclistx:
|
||
|
val = b.uint()
|
||
|
|
||
|
// rnglist
|
||
|
case formRnglistx:
|
||
|
off := b.uint()
|
||
|
|
||
|
// We have to adjust by the rnglists_base of
|
||
|
// the compilation unit. This won't work if
|
||
|
// the program uses Reader.Seek to skip over
|
||
|
// the unit. Not much we can do about that.
|
||
|
var rnglistsBase int64
|
||
|
if cu != nil {
|
||
|
rnglistsBase, _ = cu.Val(AttrRnglistsBase).(int64)
|
||
|
} else if a.tag == TagCompileUnit {
|
||
|
delay = append(delay, delayed{i, off, formRnglistx})
|
||
|
break
|
||
|
}
|
||
|
|
||
|
val = resolveRnglistx(uint64(rnglistsBase), off)
|
||
|
}
|
||
|
|
||
|
e.Field[i].Val = val
|
||
|
}
|
||
|
if b.err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
for _, del := range delay {
|
||
|
switch del.fmt {
|
||
|
case formAddrx:
|
||
|
addrBase, _ := e.Val(AttrAddrBase).(int64)
|
||
|
val, err := b.dwarf.debugAddr(b.format, uint64(addrBase), del.off)
|
||
|
if err != nil {
|
||
|
b.err = err
|
||
|
return nil
|
||
|
}
|
||
|
e.Field[del.idx].Val = val
|
||
|
case formStrx:
|
||
|
strBase, _ := e.Val(AttrStrOffsetsBase).(int64)
|
||
|
e.Field[del.idx].Val = resolveStrx(uint64(strBase), del.off)
|
||
|
if b.err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
case formRnglistx:
|
||
|
rnglistsBase, _ := e.Val(AttrRnglistsBase).(int64)
|
||
|
e.Field[del.idx].Val = resolveRnglistx(uint64(rnglistsBase), del.off)
|
||
|
if b.err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return e
|
||
|
}
|
||
|
|
||
|
// A Reader allows reading Entry structures from a DWARF ``info'' section.
|
||
|
// The Entry structures are arranged in a tree. The Reader's Next function
|
||
|
// return successive entries from a pre-order traversal of the tree.
|
||
|
// If an entry has children, its Children field will be true, and the children
|
||
|
// follow, terminated by an Entry with Tag 0.
|
||
|
type Reader struct {
|
||
|
b buf
|
||
|
d *Data
|
||
|
err error
|
||
|
unit int
|
||
|
lastUnit bool // set if last entry returned by Next is TagCompileUnit/TagPartialUnit
|
||
|
lastChildren bool // .Children of last entry returned by Next
|
||
|
lastSibling Offset // .Val(AttrSibling) of last entry returned by Next
|
||
|
cu *Entry // current compilation unit
|
||
|
}
|
||
|
|
||
|
// Reader returns a new Reader for Data.
|
||
|
// The reader is positioned at byte offset 0 in the DWARF ``info'' section.
|
||
|
func (d *Data) Reader() *Reader {
|
||
|
r := &Reader{d: d}
|
||
|
r.Seek(0)
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// AddressSize returns the size in bytes of addresses in the current compilation
|
||
|
// unit.
|
||
|
func (r *Reader) AddressSize() int {
|
||
|
return r.d.unit[r.unit].asize
|
||
|
}
|
||
|
|
||
|
// ByteOrder returns the byte order in the current compilation unit.
|
||
|
func (r *Reader) ByteOrder() binary.ByteOrder {
|
||
|
return r.b.order
|
||
|
}
|
||
|
|
||
|
// Seek positions the Reader at offset off in the encoded entry stream.
|
||
|
// Offset 0 can be used to denote the first entry.
|
||
|
func (r *Reader) Seek(off Offset) {
|
||
|
d := r.d
|
||
|
r.err = nil
|
||
|
r.lastChildren = false
|
||
|
if off == 0 {
|
||
|
if len(d.unit) == 0 {
|
||
|
return
|
||
|
}
|
||
|
u := &d.unit[0]
|
||
|
r.unit = 0
|
||
|
r.b = makeBuf(r.d, u, "info", u.off, u.data)
|
||
|
r.cu = nil
|
||
|
return
|
||
|
}
|
||
|
|
||
|
i := d.offsetToUnit(off)
|
||
|
if i == -1 {
|
||
|
r.err = errors.New("offset out of range")
|
||
|
return
|
||
|
}
|
||
|
if i != r.unit {
|
||
|
r.cu = nil
|
||
|
}
|
||
|
u := &d.unit[i]
|
||
|
r.unit = i
|
||
|
r.b = makeBuf(r.d, u, "info", off, u.data[off-u.off:])
|
||
|
}
|
||
|
|
||
|
// maybeNextUnit advances to the next unit if this one is finished.
|
||
|
func (r *Reader) maybeNextUnit() {
|
||
|
for len(r.b.data) == 0 && r.unit+1 < len(r.d.unit) {
|
||
|
r.nextUnit()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// nextUnit advances to the next unit.
|
||
|
func (r *Reader) nextUnit() {
|
||
|
r.unit++
|
||
|
u := &r.d.unit[r.unit]
|
||
|
r.b = makeBuf(r.d, u, "info", u.off, u.data)
|
||
|
r.cu = nil
|
||
|
}
|
||
|
|
||
|
// Next reads the next entry from the encoded entry stream.
|
||
|
// It returns nil, nil when it reaches the end of the section.
|
||
|
// It returns an error if the current offset is invalid or the data at the
|
||
|
// offset cannot be decoded as a valid Entry.
|
||
|
func (r *Reader) Next() (*Entry, error) {
|
||
|
if r.err != nil {
|
||
|
return nil, r.err
|
||
|
}
|
||
|
r.maybeNextUnit()
|
||
|
if len(r.b.data) == 0 {
|
||
|
return nil, nil
|
||
|
}
|
||
|
u := &r.d.unit[r.unit]
|
||
|
e := r.b.entry(r.cu, u.atable, u.base, u.vers)
|
||
|
if r.b.err != nil {
|
||
|
r.err = r.b.err
|
||
|
return nil, r.err
|
||
|
}
|
||
|
r.lastUnit = false
|
||
|
if e != nil {
|
||
|
r.lastChildren = e.Children
|
||
|
if r.lastChildren {
|
||
|
r.lastSibling, _ = e.Val(AttrSibling).(Offset)
|
||
|
}
|
||
|
if e.Tag == TagCompileUnit || e.Tag == TagPartialUnit {
|
||
|
r.lastUnit = true
|
||
|
r.cu = e
|
||
|
}
|
||
|
} else {
|
||
|
r.lastChildren = false
|
||
|
}
|
||
|
return e, nil
|
||
|
}
|
||
|
|
||
|
// SkipChildren skips over the child entries associated with
|
||
|
// the last Entry returned by Next. If that Entry did not have
|
||
|
// children or Next has not been called, SkipChildren is a no-op.
|
||
|
func (r *Reader) SkipChildren() {
|
||
|
if r.err != nil || !r.lastChildren {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// If the last entry had a sibling attribute,
|
||
|
// that attribute gives the offset of the next
|
||
|
// sibling, so we can avoid decoding the
|
||
|
// child subtrees.
|
||
|
if r.lastSibling >= r.b.off {
|
||
|
r.Seek(r.lastSibling)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if r.lastUnit && r.unit+1 < len(r.d.unit) {
|
||
|
r.nextUnit()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
e, err := r.Next()
|
||
|
if err != nil || e == nil || e.Tag == 0 {
|
||
|
break
|
||
|
}
|
||
|
if e.Children {
|
||
|
r.SkipChildren()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// clone returns a copy of the reader. This is used by the typeReader
|
||
|
// interface.
|
||
|
func (r *Reader) clone() typeReader {
|
||
|
return r.d.Reader()
|
||
|
}
|
||
|
|
||
|
// offset returns the current buffer offset. This is used by the
|
||
|
// typeReader interface.
|
||
|
func (r *Reader) offset() Offset {
|
||
|
return r.b.off
|
||
|
}
|
||
|
|
||
|
// SeekPC returns the Entry for the compilation unit that includes pc,
|
||
|
// and positions the reader to read the children of that unit. If pc
|
||
|
// is not covered by any unit, SeekPC returns ErrUnknownPC and the
|
||
|
// position of the reader is undefined.
|
||
|
//
|
||
|
// Because compilation units can describe multiple regions of the
|
||
|
// executable, in the worst case SeekPC must search through all the
|
||
|
// ranges in all the compilation units. Each call to SeekPC starts the
|
||
|
// search at the compilation unit of the last call, so in general
|
||
|
// looking up a series of PCs will be faster if they are sorted. If
|
||
|
// the caller wishes to do repeated fast PC lookups, it should build
|
||
|
// an appropriate index using the Ranges method.
|
||
|
func (r *Reader) SeekPC(pc uint64) (*Entry, error) {
|
||
|
unit := r.unit
|
||
|
for i := 0; i < len(r.d.unit); i++ {
|
||
|
if unit >= len(r.d.unit) {
|
||
|
unit = 0
|
||
|
}
|
||
|
r.err = nil
|
||
|
r.lastChildren = false
|
||
|
r.unit = unit
|
||
|
r.cu = nil
|
||
|
u := &r.d.unit[unit]
|
||
|
r.b = makeBuf(r.d, u, "info", u.off, u.data)
|
||
|
e, err := r.Next()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ranges, err := r.d.Ranges(e)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
for _, pcs := range ranges {
|
||
|
if pcs[0] <= pc && pc < pcs[1] {
|
||
|
return e, nil
|
||
|
}
|
||
|
}
|
||
|
unit++
|
||
|
}
|
||
|
return nil, ErrUnknownPC
|
||
|
}
|
||
|
|
||
|
// Ranges returns the PC ranges covered by e, a slice of [low,high) pairs.
|
||
|
// Only some entry types, such as TagCompileUnit or TagSubprogram, have PC
|
||
|
// ranges; for others, this will return nil with no error.
|
||
|
func (d *Data) Ranges(e *Entry) ([][2]uint64, error) {
|
||
|
var ret [][2]uint64
|
||
|
|
||
|
low, lowOK := e.Val(AttrLowpc).(uint64)
|
||
|
|
||
|
var high uint64
|
||
|
var highOK bool
|
||
|
highField := e.AttrField(AttrHighpc)
|
||
|
if highField != nil {
|
||
|
switch highField.Class {
|
||
|
case ClassAddress:
|
||
|
high, highOK = highField.Val.(uint64)
|
||
|
case ClassConstant:
|
||
|
off, ok := highField.Val.(int64)
|
||
|
if ok {
|
||
|
high = low + uint64(off)
|
||
|
highOK = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if lowOK && highOK {
|
||
|
ret = append(ret, [2]uint64{low, high})
|
||
|
}
|
||
|
|
||
|
var u *unit
|
||
|
if uidx := d.offsetToUnit(e.Offset); uidx >= 0 && uidx < len(d.unit) {
|
||
|
u = &d.unit[uidx]
|
||
|
}
|
||
|
|
||
|
if u != nil && u.vers >= 5 && d.rngLists != nil {
|
||
|
// DWARF version 5 and later
|
||
|
field := e.AttrField(AttrRanges)
|
||
|
if field == nil {
|
||
|
return ret, nil
|
||
|
}
|
||
|
switch field.Class {
|
||
|
case ClassRangeListPtr:
|
||
|
ranges, rangesOK := field.Val.(int64)
|
||
|
if !rangesOK {
|
||
|
return ret, nil
|
||
|
}
|
||
|
cu, base, err := d.baseAddressForEntry(e)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return d.dwarf5Ranges(u, cu, base, ranges, ret)
|
||
|
|
||
|
case ClassRngList:
|
||
|
rnglist, ok := field.Val.(uint64)
|
||
|
if !ok {
|
||
|
return ret, nil
|
||
|
}
|
||
|
cu, base, err := d.baseAddressForEntry(e)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return d.dwarf5Ranges(u, cu, base, int64(rnglist), ret)
|
||
|
|
||
|
default:
|
||
|
return ret, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// DWARF version 2 through 4
|
||
|
ranges, rangesOK := e.Val(AttrRanges).(int64)
|
||
|
if rangesOK && d.ranges != nil {
|
||
|
_, base, err := d.baseAddressForEntry(e)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return d.dwarf2Ranges(u, base, ranges, ret)
|
||
|
}
|
||
|
|
||
|
return ret, nil
|
||
|
}
|
||
|
|
||
|
// baseAddressForEntry returns the initial base address to be used when
|
||
|
// looking up the range list of entry e.
|
||
|
// DWARF specifies that this should be the lowpc attribute of the enclosing
|
||
|
// compilation unit, however comments in gdb/dwarf2read.c say that some
|
||
|
// versions of GCC use the entrypc attribute, so we check that too.
|
||
|
func (d *Data) baseAddressForEntry(e *Entry) (*Entry, uint64, error) {
|
||
|
var cu *Entry
|
||
|
if e.Tag == TagCompileUnit {
|
||
|
cu = e
|
||
|
} else {
|
||
|
i := d.offsetToUnit(e.Offset)
|
||
|
if i == -1 {
|
||
|
return nil, 0, errors.New("no unit for entry")
|
||
|
}
|
||
|
u := &d.unit[i]
|
||
|
b := makeBuf(d, u, "info", u.off, u.data)
|
||
|
cu = b.entry(nil, u.atable, u.base, u.vers)
|
||
|
if b.err != nil {
|
||
|
return nil, 0, b.err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if cuEntry, cuEntryOK := cu.Val(AttrEntrypc).(uint64); cuEntryOK {
|
||
|
return cu, cuEntry, nil
|
||
|
} else if cuLow, cuLowOK := cu.Val(AttrLowpc).(uint64); cuLowOK {
|
||
|
return cu, cuLow, nil
|
||
|
}
|
||
|
|
||
|
return cu, 0, nil
|
||
|
}
|
||
|
|
||
|
func (d *Data) dwarf2Ranges(u *unit, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) {
|
||
|
buf := makeBuf(d, u, "ranges", Offset(ranges), d.ranges[ranges:])
|
||
|
for len(buf.data) > 0 {
|
||
|
low := buf.addr()
|
||
|
high := buf.addr()
|
||
|
|
||
|
if low == 0 && high == 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if low == ^uint64(0)>>uint((8-u.addrsize())*8) {
|
||
|
base = high
|
||
|
} else {
|
||
|
ret = append(ret, [2]uint64{base + low, base + high})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret, nil
|
||
|
}
|
||
|
|
||
|
// dwarf5Ranges interpets a debug_rnglists sequence, see DWARFv5 section
|
||
|
// 2.17.3 (page 53).
|
||
|
func (d *Data) dwarf5Ranges(u *unit, cu *Entry, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) {
|
||
|
var addrBase int64
|
||
|
if cu != nil {
|
||
|
addrBase, _ = cu.Val(AttrAddrBase).(int64)
|
||
|
}
|
||
|
|
||
|
buf := makeBuf(d, u, "rnglists", 0, d.rngLists)
|
||
|
buf.skip(int(ranges))
|
||
|
for {
|
||
|
opcode := buf.uint8()
|
||
|
switch opcode {
|
||
|
case rleEndOfList:
|
||
|
if buf.err != nil {
|
||
|
return nil, buf.err
|
||
|
}
|
||
|
return ret, nil
|
||
|
|
||
|
case rleBaseAddressx:
|
||
|
baseIdx := buf.uint()
|
||
|
var err error
|
||
|
base, err = d.debugAddr(u, uint64(addrBase), baseIdx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
case rleStartxEndx:
|
||
|
startIdx := buf.uint()
|
||
|
endIdx := buf.uint()
|
||
|
|
||
|
start, err := d.debugAddr(u, uint64(addrBase), startIdx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
end, err := d.debugAddr(u, uint64(addrBase), endIdx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ret = append(ret, [2]uint64{start, end})
|
||
|
|
||
|
case rleStartxLength:
|
||
|
startIdx := buf.uint()
|
||
|
len := buf.uint()
|
||
|
start, err := d.debugAddr(u, uint64(addrBase), startIdx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ret = append(ret, [2]uint64{start, start + len})
|
||
|
|
||
|
case rleOffsetPair:
|
||
|
off1 := buf.uint()
|
||
|
off2 := buf.uint()
|
||
|
ret = append(ret, [2]uint64{base + off1, base + off2})
|
||
|
|
||
|
case rleBaseAddress:
|
||
|
base = buf.addr()
|
||
|
|
||
|
case rleStartEnd:
|
||
|
start := buf.addr()
|
||
|
end := buf.addr()
|
||
|
ret = append(ret, [2]uint64{start, end})
|
||
|
|
||
|
case rleStartLength:
|
||
|
start := buf.addr()
|
||
|
len := buf.uint()
|
||
|
ret = append(ret, [2]uint64{start, start + len})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// debugAddr returns the address at idx in debug_addr
|
||
|
func (d *Data) debugAddr(format dataFormat, addrBase, idx uint64) (uint64, error) {
|
||
|
off := idx*uint64(format.addrsize()) + addrBase
|
||
|
|
||
|
if uint64(int(off)) != off {
|
||
|
return 0, errors.New("offset out of range")
|
||
|
}
|
||
|
|
||
|
b := makeBuf(d, format, "addr", 0, d.addr)
|
||
|
b.skip(int(off))
|
||
|
val := b.addr()
|
||
|
if b.err != nil {
|
||
|
return 0, b.err
|
||
|
}
|
||
|
return val, nil
|
||
|
}
|