Skip to content

Commit

Permalink
Merge pull request #69 from monoidic/develop
Browse files Browse the repository at this point in the history
tweaks, basic DWARF-based info
  • Loading branch information
TcM1911 authored Feb 14, 2024
2 parents c74fc91 + a4390ae commit a8ca096
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 64 deletions.
178 changes: 178 additions & 0 deletions dwarf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// This file is part of GoRE.
//
// Copyright (C) 2019-2024 GoRE Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package gore

import (
"bytes"
"debug/dwarf"
"encoding/binary"
)

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 getGoRootFromDwarf(fh fileHandler) (string, bool) {
return getDwarfString(fh, getDwarfStringCheck("runtime.defaultGOROOT"))
}

func getBuildVersionFromDwarf(fh fileHandler) (string, bool) {
return getDwarfString(fh, 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 getDwarfString(fh fileHandler, check func(fh fileHandler, entry *dwarfEntryPlus) (string, dwarfwalkStatus)) (string, bool) {
data, err := fh.getDwarf()
if err != nil {
return "", false
}

r := data.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(fh, 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(fh fileHandler, entry *dwarfEntryPlus) (string, dwarfwalkStatus) {
return func(fh fileHandler, 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(fh, entry)
}
}

func commonStringCheck(fh fileHandler, 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 fh.getFileInfo().WordSize == intSize32 {
addr = uint64(fh.getFileInfo().ByteOrder.Uint32(location[1:]))
} else {
addr = fh.getFileInfo().ByteOrder.Uint64(location[1:])
}

sectionBase, data, err := fh.getSectionDataFromAddress(addr)
if err != nil {
return "", dwStop
}
off := addr - sectionBase
r := bytes.NewReader(data[off:])
var stringData [2]uint64
if fh.getFileInfo().WordSize == intSize32 {
var stringData32 [2]uint32
err = binary.Read(r, fh.getFileInfo().ByteOrder, &stringData32)
if err != nil {
return "", dwStop
}
stringData[0] = uint64(stringData32[0])
stringData[1] = uint64(stringData32[1])
} else {
err = binary.Read(r, fh.getFileInfo().ByteOrder, &stringData)
if err != nil {
return "", dwStop
}
}
addr = stringData[0]
stringLen := stringData[1]
sectionBase, data, err = fh.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)
}
}
9 changes: 7 additions & 2 deletions elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package gore

import (
"debug/dwarf"
"debug/elf"
"debug/gosym"
"errors"
Expand Down Expand Up @@ -113,14 +114,14 @@ func (e *elfFile) moduledataSection() string {
return ".noptrdata"
}

func (e *elfFile) getSectionDataFromOffset(off uint64) (uint64, []byte, error) {
func (e *elfFile) getSectionDataFromAddress(address uint64) (uint64, []byte, error) {
for _, section := range e.file.Sections {
if section.Offset == 0 {
// Only exist in memory
continue
}

if section.Addr <= off && off < (section.Addr+section.Size) {
if section.Addr <= address && address < (section.Addr+section.Size) {
data, err := section.Data()
return section.Addr, data, err
}
Expand Down Expand Up @@ -178,3 +179,7 @@ func (e *elfFile) getBuildID() (string, error) {
}
return parseBuildIDFromElf(data, e.file.ByteOrder)
}

func (e *elfFile) getDwarf() (*dwarf.Data, error) {
return e.file.DWARF()
}
8 changes: 5 additions & 3 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package gore

import (
"bytes"
"debug/dwarf"
"debug/gosym"
"encoding/binary"
"errors"
Expand Down Expand Up @@ -47,7 +48,7 @@ func Open(filePath string) (*GoFile, error) {
return nil, err
}

_, err = f.Seek(0, 0)
_, err = f.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -404,7 +405,7 @@ func (f *GoFile) GetTypes() ([]*GoType, error) {

// Bytes return a slice of raw bytes with the length in the file from the address.
func (f *GoFile) Bytes(address uint64, length uint64) ([]byte, error) {
base, section, err := f.fh.getSectionDataFromOffset(address)
base, section, err := f.fh.getSectionDataFromAddress(address)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -438,14 +439,15 @@ type fileHandler interface {
getPCLNTab() (*gosym.Table, error)
getRData() ([]byte, error)
getCodeSection() (uint64, []byte, error)
getSectionDataFromOffset(uint64) (uint64, []byte, error)
getSectionDataFromAddress(uint64) (uint64, []byte, error)
getSectionData(string) (uint64, []byte, error)
getFileInfo() *FileInfo
getPCLNTABData() (uint64, []byte, error)
moduledataSection() string
getBuildID() (string, error)
getFile() *os.File
getParsedFile() any
getDwarf() (*dwarf.Data, error)
}

func fileMagicMatch(buf, magic []byte) bool {
Expand Down
26 changes: 22 additions & 4 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package gore

import (
"debug/dwarf"
"debug/elf"
"debug/gosym"
"debug/macho"
Expand Down Expand Up @@ -171,7 +172,7 @@ func TestSetGoVersion(t *testing.T) {
}

type mockFileHandler struct {
mGetSectionDataFromOffset func(uint64) (uint64, []byte, error)
mGetSectionDataFromAddress func(uint64) (uint64, []byte, error)
}

func (m *mockFileHandler) getFile() *os.File {
Expand All @@ -198,8 +199,8 @@ func (m *mockFileHandler) getCodeSection() (uint64, []byte, error) {
panic("not implemented")
}

func (m *mockFileHandler) getSectionDataFromOffset(o uint64) (uint64, []byte, error) {
return m.mGetSectionDataFromOffset(o)
func (m *mockFileHandler) getSectionDataFromAddress(a uint64) (uint64, []byte, error) {
return m.mGetSectionDataFromAddress(a)
}

func (m *mockFileHandler) getSectionData(string) (uint64, []byte, error) {
Expand All @@ -222,6 +223,10 @@ func (m *mockFileHandler) getBuildID() (string, error) {
panic("not implemented")
}

func (m *mockFileHandler) getDwarf() (*dwarf.Data, error) {
panic("not implemented")
}

func TestBytes(t *testing.T) {
assert := assert.New(t)
expectedBase := uint64(0x40000)
Expand All @@ -230,7 +235,7 @@ func TestBytes(t *testing.T) {
address := uint64(expectedBase + 2)
length := uint64(len(expectedBytes))
fh := &mockFileHandler{
mGetSectionDataFromOffset: func(a uint64) (uint64, []byte, error) {
mGetSectionDataFromAddress: func(a uint64) (uint64, []byte, error) {
if a > expectedBase+uint64(len(expectedSection)) || a < expectedBase {
return 0, nil, errors.New("out of bound")
}
Expand Down Expand Up @@ -284,3 +289,16 @@ func main() {
data += " | Test"
}
`

const nostripSrc = `
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.GOROOT())
}
`
6 changes: 5 additions & 1 deletion goroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,11 @@ pkgLoop:
}

func findGoRootPath(f *GoFile) (string, error) {
var goroot string
// if DWARF debug info exists, then this can simply be obtained from there
if goroot, ok := getGoRootFromDwarf(f.fh); 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
14 changes: 11 additions & 3 deletions goversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ package gore
import (
"bytes"
"errors"
"regexp"

"github.com/goretk/gore/extern"
"github.com/goretk/gore/extern/gover"
"golang.org/x/arch/x86/x86asm"
"regexp"
)

var goVersionMatcher = regexp.MustCompile(`(go[\d+.]*(beta|rc)?[\d*])`)
Expand Down Expand Up @@ -65,6 +66,13 @@ func GoVersionCompare(a, b string) int {
}

func findGoCompilerVersion(f *GoFile) (*GoVersion, error) {
// if DWARF debug info exists, then this can simply be obtained from there
if gover, ok := getBuildVersionFromDwarf(f.fh); ok {
if ver := ResolveGoVersion(gover); 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 Expand Up @@ -119,7 +127,7 @@ func tryFromSchedInit(f *GoFile) *GoVersion {
is32 = true
}

// Find shedinit function.
// Find schedinit function.
var fcn *Function
std, err := f.GetSTDLib()
if err != nil {
Expand All @@ -140,7 +148,7 @@ pkgLoop:
}
}

// Check if the functions was found
// Check if the function was found
if fcn == nil {
// If we can't find the function there is nothing to do.
return nil
Expand Down
Loading

0 comments on commit a8ca096

Please sign in to comment.