diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 188f21d..ae8358d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.15 + go-version: 1.18 - name: Build Linux AMD64 run: go build -v -o dwarf2json-linux-amd64 diff --git a/README.md b/README.md index 4e4e76b..97c5c51 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ analysis. [![build](https://github.com/volatilityfoundation/dwarf2json/workflows/build/badge.svg)](https://github.com/volatilityfoundation/dwarf2json/actions?query=workflow%3Abuild) -To build (Go 1.14+ required): +To build (Go 1.18+ required): ``` $ go build ``` diff --git a/dwarf.go b/dwarf.go deleted file mode 100644 index b921982..0000000 --- a/dwarf.go +++ /dev/null @@ -1,115 +0,0 @@ -// 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. - -package main - -import ( - "bytes" - "compress/zlib" - "debug/dwarf" - "debug/elf" - "encoding/binary" - "fmt" - "io" - "strings" -) - -// DWARF is a copy of Go's debug/elf func (f *File) DWARF(). -// it has been forked here to prevent relocations from being applied -// prior to DWARF .debug_info parsing. -// -// See #15 for more details. -func DWARF(f *elf.File) (*dwarf.Data, error) { - dwarfSuffix := func(s *elf.Section) string { - switch { - case strings.HasPrefix(s.Name, ".debug_"): - return s.Name[7:] - case strings.HasPrefix(s.Name, ".zdebug_"): - return s.Name[8:] - default: - return "" - } - - } - // sectionData gets the data for s, checks its size, and - // applies any applicable relations. - sectionData := func(i int, s *elf.Section) ([]byte, error) { - b, err := s.Data() - if err != nil && uint64(len(b)) < s.Size { - return nil, err - } - - if len(b) >= 12 && string(b[:4]) == "ZLIB" { - dlen := binary.BigEndian.Uint64(b[4:12]) - dbuf := make([]byte, dlen) - r, err := zlib.NewReader(bytes.NewBuffer(b[12:])) - if err != nil { - return nil, err - } - if _, err := io.ReadFull(r, dbuf); err != nil { - return nil, err - } - if err := r.Close(); err != nil { - return nil, err - } - b = dbuf - } - - // NOTE: removed relocations from original code here - - return b, nil - } - - // There are many DWARf sections, but these are the ones - // the debug/dwarf package started with. - var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil} - for i, s := range f.Sections { - suffix := dwarfSuffix(s) - if suffix == "" { - continue - } - if _, ok := dat[suffix]; !ok { - continue - } - b, err := sectionData(i, s) - if err != nil { - return nil, err - } - dat[suffix] = b - } - - d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"]) - if err != nil { - return nil, err - } - - // Look for DWARF4 .debug_types sections and DWARF5 sections. - for i, s := range f.Sections { - suffix := dwarfSuffix(s) - if suffix == "" { - continue - } - if _, ok := dat[suffix]; ok { - // Already handled. - continue - } - - b, err := sectionData(i, s) - if err != nil { - return nil, err - } - - if suffix == "types" { - if err := d.AddTypes(fmt.Sprintf("types-%d", i), b); err != nil { - return nil, err - } - } else { - if err := d.AddSection(".debug_"+suffix, b); err != nil { - return nil, err - } - } - } - - return d, nil -} diff --git a/go.mod b/go.mod index 8c8468f..d8f5840 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/volatilityfoundation/dwarf2json -go 1.14 +go 1.18 require github.com/spf13/pflag v1.0.5 diff --git a/main.go b/main.go index cb84723..6910a01 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ const ( const ( TOOL_NAME = "dwarf2json" - TOOL_VERSION = "0.7.0" + TOOL_VERSION = "0.8.0" FORMAT_VERSION = "6.2.0" ) @@ -194,9 +194,9 @@ type vtypeJson struct { Symbols map[string]*vtypeSymbol `json:"symbols"` } -func (doc *vtypeJson) addStruct(structType *dwarf.StructType, name string) { +func (doc *vtypeJson) addStruct(structType *dwarf.StructType, name, endian string, off dwarf.Offset) error { if structType.Incomplete { - return + return nil } st := @@ -206,6 +206,7 @@ func (doc *vtypeJson) addStruct(structType *dwarf.StructType, name string) { Kind: structType.Kind, } + anonymousCount := 0 for _, field := range structType.Field { if field == nil { continue @@ -221,8 +222,9 @@ func (doc *vtypeJson) addStruct(structType *dwarf.StructType, name string) { vtypeField := vtypeStructField{Offset: field.ByteOffset} fieldName := field.Name if fieldName == "" { - fieldName = fmt.Sprintf("unnamed_field_%x", field.ByteOffset) + fieldName = fmt.Sprintf("unnamed_field_%x", anonymousCount) vtypeField.Anonymous = true + anonymousCount += 1 } if field.BitSize != 0 { @@ -230,9 +232,47 @@ func (doc *vtypeJson) addStruct(structType *dwarf.StructType, name string) { vtypeField.FieldType["kind"] = "bitfield" vtypeField.FieldType["bit_length"] = field.BitSize vtypeField.FieldType["type"] = fieldType - // calculation to change the DWARF offset from MSB to LSB - maxPos := (8 * field.ByteSize) - 1 - vtypeField.FieldType["bit_position"] = maxPos - (field.BitOffset + (field.BitSize - 1)) + + var bitOffset int64 + var byteOffset int64 = field.ByteOffset + + // DWARF4 introduced a new attribute for describing bitfields, + // DW_AT_data_bit_offset. This is exposed by the debug/dwarf + // library as FieldType.DataBitOffset. This new field replaces the + // old FieldType.BitOffset for DWARF versions >= 5. + + // According the the debug/dwarf library, the field with a value > + // 0 should be used. However this is not enough because there are + // cases where both fields can be zero at the same time. + + // Because of this ambiguity, field.ByteSize is used as a heuristic + // to decide between which field to use. The FieldType.ByteSize is + // > 0, when FieldType.BitOffset is used. + if field.ByteSize > 0 { + // assume Dwarf <=4 + if field.DataBitOffset > 0 { + return fmt.Errorf("bitfield heuristic violated at offset %v because DataBitOffset > 0", off) + } + + bitOffset = field.BitOffset + if endian == "little" { + // calculation to change the DWARF offset from MSB to LSB + bitOffset = 8*field.ByteSize - (field.BitOffset + field.BitSize) + } + byteOffset += bitOffset / 8 + bitOffset %= 8 + } else { + // assuming DWARF version > 4 + if field.BitOffset > 0 { + return fmt.Errorf("bitfield heuristic violated at offset %v because BitOffset > 0", off) + } + byteOffset = field.DataBitOffset / 8 + bitOffset = field.DataBitOffset % 8 + } + + vtypeField.Offset = byteOffset + vtypeField.FieldType["bit_position"] = bitOffset + } else { vtypeField.FieldType = fieldType } @@ -241,6 +281,8 @@ func (doc *vtypeJson) addStruct(structType *dwarf.StructType, name string) { } doc.UserTypes[name] = st + + return nil } func (doc *vtypeJson) addDwarf(data *dwarf.Data, endian string, extract Extract) error { @@ -301,7 +343,10 @@ func (doc *vtypeJson) addDwarf(data *dwarf.Data, endian string, extract Extract) return fmt.Errorf("%s is not a StructType?", genericType.String()) } - doc.addStruct(structType, structName(structType)) + err = doc.addStruct(structType, structName(structType), endian, entry.Offset) + if err != nil { + return fmt.Errorf("could not parse struct: %s", err) + } case dwarf.TagEnumerationType: genericType, err := data.Type(entry.Offset) if err != nil { @@ -376,7 +421,10 @@ func (doc *vtypeJson) addDwarf(data *dwarf.Data, endian string, extract Extract) } if structType, ok := typedefType.Type.(*dwarf.StructType); ok && structType.Name == "" { - doc.addStruct(structType, typedefType.Name) + err := doc.addStruct(structType, typedefType.Name, endian, entry.Offset) + if err != nil { + return fmt.Errorf("could not parse struct: %s", err) + } } } @@ -1009,7 +1057,7 @@ func generateLinux(files FilesToProcess) (*vtypeJson, error) { endian = "big" } - data, err := DWARF(elfFile) + data, err := elfFile.DWARF() if err != nil { return nil, fmt.Errorf("could not get DWARF from %s: %v", f.FilePath, err) }