Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add brute force search fallback for elf #89

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 123 additions & 19 deletions elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package gore
import (
"debug/dwarf"
"debug/elf"
"debug/gosym"
"errors"
"fmt"
"os"
Expand All @@ -36,14 +35,62 @@ func openELF(fp string) (*elfFile, error) {
if err != nil {
return nil, fmt.Errorf("error when parsing the ELF file: %w", err)
}
return &elfFile{file: f, osFile: osFile}, nil
return &elfFile{
file: f,
osFile: osFile,
pcln: newPclnTabOnce(),
symtab: newSymbolTableOnce(),
}, nil
}

var _ fileHandler = (*elfFile)(nil)

type elfFile struct {
file *elf.File
osFile *os.File
pcln *pclntabOnce
symtab *symbolTableOnce
}

func (e *elfFile) initSymTab() error {
e.symtab.Do(func() {
syms, err := e.file.Symbols()
if err != nil {
// If the error is ErrNoSymbols, we just ignore it.
if !errors.Is(err, elf.ErrNoSymbols) {
e.symtab.err = fmt.Errorf("error when getting the symbols: %w", err)
}
return
}
for _, sym := range syms {
e.symtab.table[sym.Name] = symbol{
Name: sym.Name,
Value: sym.Value,
Size: sym.Size,
}
}
})
return e.symtab.err
}

func (e *elfFile) hasSymbolTable() (bool, error) {
err := e.initSymTab()
if err != nil {
return false, err
}
return len(e.symtab.table) > 0, nil
}

func (e *elfFile) getSymbol(name string) (uint64, uint64, error) {
err := e.initSymTab()
if err != nil {
return 0, 0, err
}
sym, ok := e.symtab.table[name]
if !ok {
return 0, 0, ErrSymbolNotFound
}
return sym.Value, sym.Size, nil
}

func (e *elfFile) getParsedFile() any {
Expand All @@ -54,23 +101,57 @@ func (e *elfFile) getFile() *os.File {
return e.osFile
}

func (e *elfFile) getPCLNTab() (*gosym.Table, error) {
pclnSection := e.file.Section(".gopclntab")
if pclnSection == nil {
// No section found. Check if the PIE section exist instead.
pclnSection = e.file.Section(".data.rel.ro.gopclntab")
func (e *elfFile) searchForPclnTab() (uint64, []byte, error) {
// Only use linkmode=external and buildmode=pie will lead to this
// Since the external linker merged all .data.rel.ro.* sections into .data.rel.ro
// So we have to find .data.rel.ro.gopclntab manually
sec := e.file.Section(".data.rel.ro")
if sec == nil {
return 0, nil, ErrNoPCLNTab
}
if pclnSection == nil {
return nil, fmt.Errorf("no gopclntab section found")
data, err := sec.Data()
if err != nil {
return 0, nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
}

pclndat, err := pclnSection.Data()
tab, err := searchSectionForTab(data, e.getFileInfo().ByteOrder)
if err != nil {
return nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
return 0, nil, fmt.Errorf("could not find the pclntab: %w", err)
}
addr := sec.Addr + sec.FileSize - uint64(len(tab))
return addr, tab, nil
}

pcln := gosym.NewLineTable(pclndat, e.file.Section(".text").Addr)
return gosym.NewTable(make([]byte, 0), pcln)
func (e *elfFile) symbolData(start, end string) (uint64, uint64, []byte) {
elfSyms, err := e.file.Symbols()
if err != nil {
return 0, 0, nil
}
var addr, eaddr uint64
for _, s := range elfSyms {
if s.Name == start {
addr = s.Value
} else if s.Name == end {
eaddr = s.Value
}
if addr != 0 && eaddr != 0 {
break
}
}
if addr == 0 || eaddr < addr {
return 0, 0, nil
}
size := eaddr - addr
data := make([]byte, size)
for _, prog := range e.file.Progs {
if prog.Vaddr <= addr && addr+size-1 <= prog.Vaddr+prog.Filesz-1 {
if _, err := prog.ReadAt(data, int64(addr-prog.Vaddr)); err != nil {
return 0, 0, nil
}
return addr, eaddr, data
}
}
return 0, 0, nil
}

func (e *elfFile) Close() error {
Expand Down Expand Up @@ -101,13 +182,36 @@ func (e *elfFile) getCodeSection() (uint64, []byte, error) {
return section.Addr, data, nil
}

func (e *elfFile) getPCLNTABData() (uint64, []byte, error) {
start, data, err := e.getSectionData(".gopclntab")
if errors.Is(err, ErrSectionDoesNotExist) {
// Try PIE location
return e.getSectionData(".data.rel.ro.gopclntab")
func (e *elfFile) getPCLNTABData() (start uint64, data []byte, err error) {
return e.pcln.load(e.getPCLNTABDataImpl)
}

func (e *elfFile) getPCLNTABDataImpl() (start uint64, data []byte, err error) {
pclnSection := e.file.Section(".gopclntab")
if pclnSection == nil {
// No section found. Check if the PIE section exists instead.
pclnSection = e.file.Section(".data.rel.ro.gopclntab")
}
if pclnSection != nil {
data, err = pclnSection.Data()
if err != nil {
return 0, nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
}
return pclnSection.Addr, data, nil
}

// try to get data from symbol
start, _, data = e.symbolData("runtime.pclntab", "runtime.epclntab")
if data != nil {
return start, data, nil
}

// try brute force searching for the pclntab
start, data, err = e.searchForPclnTab()
if err != nil {
return 0, nil, fmt.Errorf("could not find the pclntab: %w", err)
}
return start, data, err
return
}

func (e *elfFile) moduledataSection() string {
Expand Down
28 changes: 20 additions & 8 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,8 @@ type GoFile struct {

func (f *GoFile) initModuleData() error {
f.initModuleDataOnce.Do(func() {
err := f.ensureCompilerVersion()
if err != nil {
f.initModuleDataError = err
return
}
// since we can traverse moduledata now, no goversion no longer an unrecoverable error
_ = f.ensureCompilerVersion()
f.moduledata, f.initModuleDataError = extractModuledata(f.FileInfo, f.fh)
})
return f.initModuleDataError
Expand All @@ -159,6 +156,10 @@ func (f *GoFile) Moduledata() (Moduledata, error) {
return f.moduledata, nil
}

// initPackages initializes the packages in the binary.
// Note: this init depends on the moduledata initialization internal since PCLNTab() is called.
// any further modifications to moduledata initialization should consider this
// or a cycle dependency will be created.
func (f *GoFile) initPackages() error {
f.initPackagesOnce.Do(func() {
tab, err := f.PCLNTab()
Expand Down Expand Up @@ -378,7 +379,16 @@ func (f *GoFile) Close() error {

// PCLNTab returns the PCLN table.
func (f *GoFile) PCLNTab() (*gosym.Table, error) {
return f.fh.getPCLNTab()
err := f.initModuleData()
if err != nil {
return nil, err
}
_, data, err := f.fh.getPCLNTABData()
if err != nil {
return nil, err
}
pcln := gosym.NewLineTable(data, f.moduledata.TextAddr)
return gosym.NewTable(make([]byte, 0), pcln)
}

// GetTypes returns a map of all types found in the binary file.
Expand Down Expand Up @@ -436,13 +446,15 @@ func sortTypes(types map[uint64]*GoType) []*GoType {

type fileHandler interface {
io.Closer
getPCLNTab() (*gosym.Table, error)
// returns the size, value and error
getSymbol(name string) (uint64, uint64, error)
hasSymbolTable() (bool, error)
getPCLNTABData() (uint64, []byte, error)
getRData() ([]byte, error)
getCodeSection() (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
Expand Down
32 changes: 15 additions & 17 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package gore
import (
"debug/dwarf"
"debug/elf"
"debug/gosym"
"debug/macho"
"debug/pe"
"errors"
Expand Down Expand Up @@ -175,6 +174,15 @@ type mockFileHandler struct {
mGetSectionDataFromAddress func(uint64) (uint64, []byte, error)
}

func (m *mockFileHandler) hasSymbolTable() (bool, error) {
//TODO implement me
panic("implement me")
}

func (m *mockFileHandler) getSymbol(name string) (uint64, uint64, error) {
panic("implement me")
}

func (m *mockFileHandler) getFile() *os.File {
panic("not implemented")
}
Expand All @@ -187,10 +195,6 @@ func (m *mockFileHandler) Close() error {
panic("not implemented")
}

func (m *mockFileHandler) getPCLNTab() (*gosym.Table, error) {
panic("not implemented")
}

func (m *mockFileHandler) getRData() ([]byte, error) {
panic("not implemented")
}
Expand Down Expand Up @@ -279,6 +283,11 @@ func getGoldenResources() ([]string, error) {
const testresourcesrc = `
package main

import (
"fmt"
"runtime"
)

//go:noinline
func getData() string {
return "Name: GoRE"
Expand All @@ -287,18 +296,7 @@ func getData() string {
func main() {
data := getData()
data += " | Test"
}
`

const nostripSrc = `
package main

import (
"fmt"
"runtime"
)

func main() {
fmt.Println(runtime.GOROOT())
fmt.Println(data)
}
`
12 changes: 8 additions & 4 deletions gen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@
package main

import (
"flag"
"fmt"
"os"
)

var forceUpdate = flag.Bool("force", false, "force update")

func main() {
if len(os.Args) != 2 {
fmt.Println("go run ./gen [stdpkgs|goversion|moduledata]")
flag.Parse()

if flag.NArg() < 1 {
fmt.Println("go run ./gen [--force] [stdpkgs|goversion|moduledata]")
return
}

switch os.Args[1] {
switch flag.Arg(0) {
case "stdpkgs":
generateStdPkgs()
case "goversion":
Expand Down
Loading
Loading