
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.
365 lines
8.2 KiB
Go
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
|
|
}
|