diff --git a/elf.go b/elf.go index b5fb75e..ad6d3ed 100644 --- a/elf.go +++ b/elf.go @@ -18,13 +18,169 @@ package gore import ( + "bytes" + "debug/dwarf" "debug/elf" "debug/gosym" + "encoding/binary" "errors" "fmt" "os" ) +const ( + // official DWARF language ID for Go + // https://dwarfstd.org/languages.html + dwLangGo int64 = 0x0016 + + // DWARF operation; used to encode type offsets + dwOpAddr = 0x03 +) + +func (e *elfFile) getGoRootFromDwarf() (string, bool) { + return getStringFromDwarf(e, getDwarfStringCheck("runtime.defaultGOROOT")) +} + +func (e *elfFile) getBuildVersionFromDwarf() (string, bool) { + return getStringFromDwarf(e, getDwarfStringCheck("runtime.buildVersion")) +} + +// DWARF entry plus any associated children +type dwarfEntryPlus struct { + entry *dwarf.Entry + children []*dwarfEntryPlus +} + +type dwarfwalkStatus uint8 + +const ( + dwStop dwarfwalkStatus = iota + 1 + dwContinue + dwFound +) + +func getStringFromDwarf(e *elfFile, check func(e *elfFile, entry *dwarfEntryPlus) (string, dwarfwalkStatus)) (string, bool) { + dw, err := e.file.DWARF() + if err != nil { + return "", false + } + r := dw.Reader() + // walk through compilation units +getValOuter: + for cu := dwarfReadEntry(r); cu != nil; cu = dwarfReadEntry(r) { + if langField := cu.entry.AttrField(dwarf.AttrLanguage); langField == nil || langField.Val != dwLangGo { + continue + } + getValInner: + for _, entry := range cu.children { + ret, status := check(e, entry) + switch status { + case dwStop: + break getValOuter + case dwFound: + return ret, true + case dwContinue: + continue getValInner + } + } + } + return "", false +} + +// get, by name, a DWARF entry corresponding to a string constant +func getDwarfStringCheck(name string) func(e *elfFile, entry *dwarfEntryPlus) (string, dwarfwalkStatus) { + return func(e *elfFile, d *dwarfEntryPlus) (string, dwarfwalkStatus) { + entry := d.entry + nameField := entry.AttrField(dwarf.AttrName) + if nameField == nil { + return "", dwContinue + } + + if fieldName := nameField.Val.(string); fieldName != name { + return "", dwContinue + } + + return commonStringCheck(e, entry) + } +} + +func commonStringCheck(e *elfFile, entry *dwarf.Entry) (string, dwarfwalkStatus) { + locationField := entry.AttrField(dwarf.AttrLocation) + if locationField == nil { + // unexpected failure + return "", dwStop + } + location := locationField.Val.([]byte) + // DWARF address operation followed by the machine byte order encoded address + if location[0] != dwOpAddr { + return "", dwStop + } + var addr uint64 + if e.file.Class == elf.ELFCLASS32 { + addr = uint64(e.file.ByteOrder.Uint32(location[1:])) + } else { + addr = e.file.ByteOrder.Uint64(location[1:]) + } + + sectionBase, data, err := e.getSectionDataFromAddress(addr) + if err != nil { + return "", dwStop + } + off := addr - sectionBase + r := bytes.NewReader(data[off:]) + var stringData [2]uint64 + if e.file.Class == elf.ELFCLASS32 { + var stringData32 [2]uint32 + err = binary.Read(r, e.file.ByteOrder, &stringData32) + if err != nil { + return "", dwStop + } + stringData[0] = uint64(stringData32[0]) + stringData[1] = uint64(stringData32[1]) + } else { + err = binary.Read(r, e.file.ByteOrder, &stringData) + if err != nil { + return "", dwStop + } + } + addr = stringData[0] + stringLen := stringData[1] + sectionBase, data, err = e.getSectionDataFromAddress(addr) + if err != nil { + return "", dwStop + } + off = addr - sectionBase + raw := data[off : off+stringLen] + return string(raw), dwFound +} + +func dwarfReadEntry(r *dwarf.Reader) *dwarfEntryPlus { + entry, _ := r.Next() + if entry == nil { + return nil + } + var children []*dwarfEntryPlus + if entry.Children { + children = dwarfReadChildren(r) + } + return &dwarfEntryPlus{ + entry: entry, + children: children, + } +} + +func dwarfReadChildren(r *dwarf.Reader) []*dwarfEntryPlus { + var ret []*dwarfEntryPlus + + for { + e := dwarfReadEntry(r) + if e.entry.Tag == 0 { + return ret + } + ret = append(ret, e) + } +} + func openELF(fp string) (*elfFile, error) { osFile, err := os.Open(fp) if err != nil { diff --git a/goroot.go b/goroot.go index 6d5efde..b42272c 100644 --- a/goroot.go +++ b/goroot.go @@ -327,6 +327,15 @@ pkgLoop: func findGoRootPath(f *GoFile) (string, error) { var goroot string + + // if DWARF debug info exists, then this can simply be obtained from there + if e, ok := f.fh.(*elfFile); ok { + goroot, ok = e.getGoRootFromDwarf() + if ok { + return goroot, nil + } + } + // There is no GOROOT function may be inlined (after go1.16) // at this time GOROOT is obtained through time_init function goroot, err := tryFromGOROOT(f) diff --git a/goversion.go b/goversion.go index eeccfd0..94e4110 100644 --- a/goversion.go +++ b/goversion.go @@ -79,6 +79,16 @@ func buildSemVerString(v string) string { } func findGoCompilerVersion(f *GoFile) (*GoVersion, error) { + // if DWARF debug info exists, then this can simply be obtained from there + if e, ok := f.fh.(*elfFile); ok { + s, ok := e.getBuildVersionFromDwarf() + if ok { + if ver := ResolveGoVersion(s); ver != nil { + return ver, nil + } + } + } + // Try to determine the version based on the schedinit function. if v := tryFromSchedInit(f); v != nil { return v, nil