package dwarf
import (
"encoding/binary"
"errors"
"fmt"
"strconv"
)
type abbrev struct {
tag Tag
children bool
field []afield
}
type afield struct {
attr Attr
fmt format
class Class
val int64
}
type abbrevTable map [uint32 ]abbrev
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 )
m := make (abbrevTable )
for {
id := uint32 (b .uint ())
if id == 0 {
break
}
n := 0
b1 := 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
}
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
}
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 ,
}
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 ,
AttrStrOffsetsBase : ClassStrOffsetsPtr ,
AttrAddrBase : ClassAddrPtr ,
AttrRnglistsBase : ClassRngListsPtr ,
AttrLoclistsBase : ClassLocListPtr ,
}
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 :
if attrIsExprloc [attr ] {
return ClassExprLoc
}
return ClassBlock
case formData1 , formData2 , formData4 , formData8 , formSdata , formUdata , formData16 , formImplicitConst :
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 :
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
}
}
type Entry struct {
Offset Offset
Tag Tag
Children bool
Field []Field
}
type Field struct {
Attr Attr
Val any
Class Class
}
type Class int
const (
ClassUnknown Class = iota
ClassAddress
ClassBlock
ClassConstant
ClassExprLoc
ClassFlag
ClassLinePtr
ClassLocListPtr
ClassMacPtr
ClassRangeListPtr
ClassReference
ClassReferenceSig
ClassString
ClassReferenceAlt
ClassStringAlt
ClassAddrPtr
ClassLocList
ClassRngList
ClassRngListsPtr
ClassStrOffsetsPtr
)
func (i Class ) GoString () string {
return "dwarf." + i .String ()
}
func (e *Entry ) Val (a Attr ) any {
if f := e .AttrField (a ); f != nil {
return f .Val
}
return nil
}
func (e *Entry ) AttrField (a Attr ) *Field {
for i , f := range e .Field {
if f .Attr == a {
return &e .Field [i ]
}
}
return nil
}
type Offset uint32
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 )),
}
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 ))
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
}
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
}
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 ()))
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
case formFlag :
val = b .uint8 () == 1
case formFlagPresent :
val = true
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
case formString :
val = b .string ()
case formStrp , formLineStrp :
var off uint64
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
}
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 ()
}
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 ())
}
case formExprloc :
val = b .bytes (int (b .uint ()))
case formRefSig8 :
val = b .uint64 ()
case formRefSup4 :
val = b .uint32 ()
case formRefSup8 :
val = b .uint64 ()
case formLoclistx :
val = b .uint ()
case formRnglistx :
off := b .uint ()
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
}
type Reader struct {
b buf
d *Data
err error
unit int
lastUnit bool
lastChildren bool
lastSibling Offset
cu *Entry
}
func (d *Data ) Reader () *Reader {
r := &Reader {d : d }
r .Seek (0 )
return r
}
func (r *Reader ) AddressSize () int {
return r .d .unit [r .unit ].asize
}
func (r *Reader ) ByteOrder () binary .ByteOrder {
return r .b .order
}
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 :])
}
func (r *Reader ) maybeNextUnit () {
for len (r .b .data ) == 0 && r .unit +1 < len (r .d .unit ) {
r .nextUnit ()
}
}
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
}
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
}
func (r *Reader ) SkipChildren () {
if r .err != nil || !r .lastChildren {
return
}
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 ()
}
}
}
func (r *Reader ) clone () typeReader {
return r .d .Reader ()
}
func (r *Reader ) offset () Offset {
return r .b .off
}
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
}
if e == nil || e .Tag == 0 {
return nil , ErrUnknownPC
}
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
}
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 {
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
}
}
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
}
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 ) {
if ranges < 0 || ranges > int64 (len (d .ranges )) {
return nil , fmt .Errorf ("invalid range offset %d (max %d)" , ranges , len (d .ranges ))
}
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
}
func (d *Data ) dwarf5Ranges (u *unit , cu *Entry , base uint64 , ranges int64 , ret [][2 ]uint64 ) ([][2 ]uint64 , error ) {
if ranges < 0 || ranges > int64 (len (d .rngLists )) {
return nil , fmt .Errorf ("invalid rnglist offset %d (max %d)" , ranges , len (d .ranges ))
}
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 })
}
}
}
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
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .