Skip to content

Commit

Permalink
use DWARF info, if available, to get goroot and goversion
Browse files Browse the repository at this point in the history
  • Loading branch information
monoidic committed Feb 9, 2024
1 parent 8dfb9dc commit 62fb40e
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 0 deletions.
156 changes: 156 additions & 0 deletions elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions goroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions goversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 62fb40e

Please sign in to comment.