delve/vendor/github.com/goretk/gore/goroot.go
Derek Parker 6e8e1cee9b
pkg/proc: use gore to obtain info from stripped binaries (#3577)
This patch switches from using a forked version of one of the libraries
of goretk/gore to using the module directly. This is possible now that
certain functionality has been exposed / fixed within that module making
it usable for Delve.
2023-11-23 09:12:10 +01:00

365 lines
8.2 KiB
Go

// This file is part of GoRE.
//
// Copyright (C) 2019-2021 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"
"fmt"
"reflect"
"strings"
"unicode/utf8"
"golang.org/x/arch/x86/x86asm"
)
func tryFromGOROOT(f *GoFile) (string, error) {
// Check for non supported architectures.
if f.FileInfo.Arch != Arch386 && f.FileInfo.Arch != ArchAMD64 {
return "", nil
}
is32 := false
if f.FileInfo.Arch == Arch386 {
is32 = true
}
// Find runtime.GOROOT function.
var fcn *Function
std, err := f.GetSTDLib()
if err != nil {
return "", nil
}
pkgLoop:
for _, v := range std {
if v.Name != "runtime" {
continue
}
for _, vv := range v.Functions {
if vv.Name != "GOROOT" {
continue
}
fcn = vv
break pkgLoop
}
}
// Check if the functions was found
if fcn == nil {
// If we can't find the function there is nothing to do.
return "", ErrNoGoRootFound
}
// Get the raw hex.
buf, err := f.Bytes(fcn.Offset, fcn.End-fcn.Offset)
if err != nil {
return "", nil
}
s := 0
mode := f.FileInfo.WordSize * 8
for s < len(buf) {
inst, err := x86asm.Decode(buf[s:], mode)
if err != nil {
// If we fail to decode the instruction, something is wrong so
// bailout.
return "", nil
}
// Update next instruction location.
s = s + inst.Len
// Check if it's a "mov" instruction.
if inst.Op != x86asm.MOV {
continue
}
if inst.Args[0] != x86asm.RAX && inst.Args[0] != x86asm.EAX {
continue
}
arg := inst.Args[1].(x86asm.Mem)
// First assume that the address is a direct addressing.
addr := arg.Disp
if arg.Base == x86asm.EIP || arg.Base == x86asm.RIP {
// If the addressing is based on the instruction pointer, fix the address.
addr = addr + int64(fcn.Offset) + int64(s)
} else if arg.Base == 0 && arg.Disp > 0 {
// In order to support x32 direct addressing
} else {
continue
}
// If the addressing is based on the stack pointer, this is not the right
// instruction.
if arg.Base == x86asm.ESP || arg.Base == x86asm.RSP {
continue
}
// Resolve the pointer to the string. If we get no data, this is not the
// right instruction.
b, _ := f.Bytes(uint64(addr), uint64(0x20))
if b == nil {
continue
}
r := bytes.NewReader(b)
ptr, err := readUIntTo64(r, f.FileInfo.ByteOrder, is32)
if err != nil {
// Probably not the right instruction, so go to next.
continue
}
l, err := readUIntTo64(r, f.FileInfo.ByteOrder, is32)
if err != nil {
// Probably not the right instruction, so go to next.
continue
}
bstr, _ := f.Bytes(ptr, l)
if bstr == nil {
continue
}
ver := string(bstr)
if !utf8.ValidString(ver) {
return "", ErrNoGoRootFound
}
return ver, nil
}
// for go version vary from 1.5 to 1.9
s = 0
var insts []x86asm.Inst
for s < len(buf) {
inst, err := x86asm.Decode(buf[s:], mode)
if err != nil {
return "", nil
}
s = s + inst.Len
insts = append(insts, inst)
}
var length, addr uint64
// Look up the address from the end of the assembly instruction
for i := len(insts) - 1; i >= 0; i-- {
inst := insts[i]
if inst.Op == x86asm.MOV && length == 0 {
switch v := inst.Args[1].(type) {
case x86asm.Imm:
length = uint64(v)
continue
default:
continue
}
}
if (inst.Op == x86asm.LEA || inst.Op == x86asm.MOV) && addr == 0 {
switch v := inst.Args[1].(type) {
case x86asm.Mem:
arg := v
if arg.Base == x86asm.ESP || arg.Base == x86asm.RSP {
continue
}
addr = uint64(arg.Disp)
if arg.Base == x86asm.EIP || arg.Base == x86asm.RIP {
// If the addressing is based on the instruction pointer, fix the address.
s = 0
for i2, inst2 := range insts {
if i2 > i {
break
}
s += inst2.Len
}
addr = addr + fcn.Offset + uint64(s)
} else if arg.Base == 0 && arg.Disp > 0 {
// In order to support x32 direct addressing
} else {
continue
}
case x86asm.Imm:
addr = uint64(v)
default:
continue
}
}
if length > 0 && addr > 0 {
bstr, _ := f.Bytes(addr, length)
if bstr == nil {
continue
}
ver := string(bstr)
if !utf8.ValidString(ver) {
return "", ErrNoGoRootFound
}
return ver, nil
}
}
return "", ErrNoGoRootFound
}
func tryFromTimeInit(f *GoFile) (string, error) {
// Check for non supported architectures.
if f.FileInfo.Arch != Arch386 && f.FileInfo.Arch != ArchAMD64 {
return "", nil
}
is32 := false
if f.FileInfo.Arch == Arch386 {
is32 = true
}
// Find time.init function.
var fcn *Function
std, err := f.GetSTDLib()
if err != nil {
return "", nil
}
pkgLoop:
for _, v := range std {
if v.Name != "time" {
continue
}
for _, vv := range v.Functions {
if vv.Name != "init" {
continue
}
fcn = vv
break pkgLoop
}
}
// Check if the functions was found
if fcn == nil {
// If we can't find the function there is nothing to do.
return "", ErrNoGoRootFound
}
// Get the raw hex.
buf, err := f.Bytes(fcn.Offset, fcn.End-fcn.Offset)
if err != nil {
return "", nil
}
s := 0
mode := f.FileInfo.WordSize * 8
for s < len(buf) {
inst, err := x86asm.Decode(buf[s:], mode)
if err != nil {
// If we fail to decode the instruction, something is wrong so
// bailout.
return "", nil
}
// Update next instruction location.
s = s + inst.Len
// Check if it's a "mov" instruction.
if inst.Op != x86asm.MOV {
continue
}
if inst.Args[0] != x86asm.RAX && inst.Args[0] != x86asm.ECX {
continue
}
kindof := reflect.TypeOf(inst.Args[1])
if kindof.String() != "x86asm.Mem" {
continue
}
arg := inst.Args[1].(x86asm.Mem)
// First assume that the address is a direct addressing.
addr := arg.Disp
if arg.Base == x86asm.EIP || arg.Base == x86asm.RIP {
// If the addressing is based on the instruction pointer, fix the address.
addr = addr + int64(fcn.Offset) + int64(s)
} else if arg.Base == 0 && arg.Disp > 0 {
// In order to support x32 direct addressing
} else {
continue
}
// Resolve the pointer to the string. If we get no data, this is not the
// right instruction.
b, _ := f.Bytes(uint64(addr), uint64(0x20))
if b == nil {
continue
}
r := bytes.NewReader(b)
ptr, err := readUIntTo64(r, f.FileInfo.ByteOrder, is32)
if err != nil {
// Probably not the right instruction, so go to next.
continue
}
// If the pointer is nil, it's not the right instruction
if ptr == 0 {
continue
}
l, err := readUIntTo64(r, f.FileInfo.ByteOrder, is32)
if err != nil {
// Probably not the right instruction, so go to next.
continue
}
bstr, _ := f.Bytes(ptr, l)
if bstr == nil {
continue
}
ver := string(bstr)
if !utf8.ValidString(ver) {
return "", ErrNoGoRootFound
}
return ver, nil
}
return "", ErrNoGoRootFound
}
func findGoRootPath(f *GoFile) (string, error) {
var goroot string
// 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)
if goroot != "" {
return goroot, nil
}
if err != nil && err != ErrNoGoRootFound {
return "", err
}
goroot, err = tryFromTimeInit(f)
if goroot != "" {
return goroot, nil
}
if err != nil && err != ErrNoGoRootFound {
return "", err
}
// Try determine from std lib package paths.
pkg, err := f.GetSTDLib()
if err != nil {
return "", fmt.Errorf("error when getting standard library packages: %w", err)
}
if len(pkg) == 0 {
return "", fmt.Errorf("no standard library packages found")
}
for _, v := range pkg {
subpath := fmt.Sprintf("/src/%s", v.Name)
if strings.HasSuffix(v.Filepath, subpath) {
return strings.TrimSuffix(v.Filepath, subpath), nil
}
}
return "", ErrNoGoRootFound
}