Implement source listing from debuginfo (#2885)
* service: Implement BuildID Parse the BuildID of executables and provides it over the RPC service. Signed-off-by: Morten Linderud <morten@linderud.pw> * command: Support debuinfod for file listing Signed-off-by: Morten Linderud <morten@linderud.pw> * debuginfod: create debuginfod package for common code We remove the duplicated code and provide our a new debuginfod package. Signed-off-by: Morten Linderud <morten@linderud.pw> * starlark: Workaround for 'build_i_d' Signed-off-by: Morten Linderud <morten@linderud.pw> * command: Ensure we only overwrite path when one has been found Signed-off-by: Morten Linderud <morten@linderud.pw> * bininfo: Inline parseBuildID Signed-off-by: Morten Linderud <morten@linderud.pw>
This commit is contained in:
parent
5b6f8ec03a
commit
8c392d2fdf
@ -20,6 +20,7 @@ Function | API Call
|
||||
amend_breakpoint(Breakpoint) | Equivalent to API call [AmendBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.AmendBreakpoint)
|
||||
ancestors(GoroutineID, NumAncestors, Depth) | Equivalent to API call [Ancestors](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Ancestors)
|
||||
attached_to_existing_process() | Equivalent to API call [AttachedToExistingProcess](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.AttachedToExistingProcess)
|
||||
build_id() | Equivalent to API call [BuildID](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.BuildID)
|
||||
cancel_next() | Equivalent to API call [CancelNext](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CancelNext)
|
||||
checkpoint(Where) | Equivalent to API call [Checkpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Checkpoint)
|
||||
clear_breakpoint(Id, Name) | Equivalent to API call [ClearBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearBreakpoint)
|
||||
|
@ -111,6 +111,8 @@ func processServerMethods(serverMethods []*types.Func) []binding {
|
||||
name = "set_expr"
|
||||
case "command":
|
||||
name = "raw_command"
|
||||
case "build_i_d":
|
||||
name = "build_id"
|
||||
case "create_e_b_p_f_tracepoint":
|
||||
name = "create_ebpf_tracepoint"
|
||||
default:
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -31,6 +30,7 @@ import (
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc/debuginfod"
|
||||
"github.com/hashicorp/golang-lru/simplelru"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -52,6 +52,9 @@ type BinaryInfo struct {
|
||||
|
||||
debugInfoDirectories []string
|
||||
|
||||
// BuildID of this binary.
|
||||
BuildID string
|
||||
|
||||
// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
|
||||
Functions []Function
|
||||
// Sources is a list of all source files found in debug_line.
|
||||
@ -1193,15 +1196,6 @@ func (bi *BinaryInfo) parseDebugFrameGeneral(image *Image, debugFrameBytes []byt
|
||||
|
||||
// ELF ///////////////////////////////////////////////////////////////
|
||||
|
||||
// ErrNoBuildIDNote is used in openSeparateDebugInfo to signal there's no
|
||||
// build-id note on the binary, so LoadBinaryInfoElf will return
|
||||
// the error message coming from elfFile.DWARF() instead.
|
||||
type ErrNoBuildIDNote struct{}
|
||||
|
||||
func (e *ErrNoBuildIDNote) Error() string {
|
||||
return "can't find build-id note on binary"
|
||||
}
|
||||
|
||||
// openSeparateDebugInfo searches for a file containing the separate
|
||||
// debug info for the binary using the "build ID" method as described
|
||||
// in GDB's documentation [1], and if found returns two handles, one
|
||||
@ -1212,11 +1206,11 @@ func (e *ErrNoBuildIDNote) Error() string {
|
||||
// will look in directories specified by the debug-info-directories config value.
|
||||
func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) {
|
||||
var debugFilePath string
|
||||
desc1, desc2, _ := parseBuildID(exe)
|
||||
var err error
|
||||
for _, dir := range debugInfoDirectories {
|
||||
var potentialDebugFilePath string
|
||||
if strings.Contains(dir, "build-id") {
|
||||
potentialDebugFilePath = fmt.Sprintf("%s/%s/%s.debug", dir, desc1, desc2)
|
||||
potentialDebugFilePath = fmt.Sprintf("%s/%s/%s.debug", dir, bi.BuildID[:2], bi.BuildID[2:])
|
||||
} else if strings.HasPrefix(image.Path, "/proc") {
|
||||
path, err := filepath.EvalSymlinks(image.Path)
|
||||
if err == nil {
|
||||
@ -1234,15 +1228,8 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn
|
||||
// We cannot find the debug information locally on the system. Try and see if we're on a system that
|
||||
// has debuginfod so that we can use that in order to find any relevant debug information.
|
||||
if debugFilePath == "" {
|
||||
const debuginfodFind = "debuginfod-find"
|
||||
if _, err := exec.LookPath(debuginfodFind); err == nil {
|
||||
cmd := exec.Command(debuginfodFind, "debuginfo", desc1+desc2)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, nil, ErrNoDebugInfoFound
|
||||
}
|
||||
debugFilePath = strings.TrimSpace(string(out))
|
||||
} else {
|
||||
debugFilePath, err = debuginfod.GetDebuginfo(bi.BuildID)
|
||||
if err != nil {
|
||||
return nil, nil, ErrNoDebugInfoFound
|
||||
}
|
||||
}
|
||||
@ -1265,35 +1252,6 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn
|
||||
return sepFile, elfFile, nil
|
||||
}
|
||||
|
||||
func parseBuildID(exe *elf.File) (string, string, error) {
|
||||
buildid := exe.Section(".note.gnu.build-id")
|
||||
if buildid == nil {
|
||||
return "", "", &ErrNoBuildIDNote{}
|
||||
}
|
||||
|
||||
br := buildid.Open()
|
||||
bh := new(buildIDHeader)
|
||||
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
|
||||
return "", "", errors.New("can't read build-id header: " + err.Error())
|
||||
}
|
||||
|
||||
name := make([]byte, bh.Namesz)
|
||||
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
|
||||
return "", "", errors.New("can't read build-id name: " + err.Error())
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(name)) != "GNU\x00" {
|
||||
return "", "", errors.New("invalid build-id signature")
|
||||
}
|
||||
|
||||
descBinary := make([]byte, bh.Descsz)
|
||||
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
|
||||
return "", "", errors.New("can't read build-id desc: " + err.Error())
|
||||
}
|
||||
desc := hex.EncodeToString(descBinary)
|
||||
return desc[:2], desc[2:], nil
|
||||
}
|
||||
|
||||
// loadBinaryInfoElf specifically loads information from an ELF binary.
|
||||
func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, wg *sync.WaitGroup) error {
|
||||
exe, err := os.OpenFile(path, 0, os.ModePerm)
|
||||
@ -1330,6 +1288,7 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
|
||||
|
||||
dwarfFile := elfFile
|
||||
|
||||
bi.loadBuildID(image, elfFile)
|
||||
var debugInfoBytes []byte
|
||||
image.dwarf, err = elfFile.DWARF()
|
||||
if err != nil {
|
||||
@ -1395,6 +1354,39 @@ func (bi *BinaryInfo) loadSymbolName(image *Image, file *elf.File, wg *sync.Wait
|
||||
}
|
||||
}
|
||||
|
||||
func (bi *BinaryInfo) loadBuildID(image *Image, file *elf.File) {
|
||||
buildid := file.Section(".note.gnu.build-id")
|
||||
if buildid == nil {
|
||||
bi.logger.Error("can't find build-id note on binary")
|
||||
return
|
||||
}
|
||||
|
||||
br := buildid.Open()
|
||||
bh := new(buildIDHeader)
|
||||
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
|
||||
bi.logger.Errorf("can't read build-id header: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
name := make([]byte, bh.Namesz)
|
||||
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
|
||||
bi.logger.Errorf("can't read build-id name: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(name)) != "GNU\x00" {
|
||||
bi.logger.Error("invalid build-id signature")
|
||||
return
|
||||
}
|
||||
|
||||
descBinary := make([]byte, bh.Descsz)
|
||||
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
|
||||
bi.logger.Errorf("can't read build-id desc: %v", err)
|
||||
return
|
||||
}
|
||||
bi.BuildID = hex.EncodeToString(descBinary)
|
||||
}
|
||||
|
||||
func (bi *BinaryInfo) parseDebugFrameElf(image *Image, dwarfFile, exeFile *elf.File, debugInfoBytes []byte, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
|
28
pkg/proc/debuginfod/debuginfod.go
Normal file
28
pkg/proc/debuginfod/debuginfod.go
Normal file
@ -0,0 +1,28 @@
|
||||
package debuginfod
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const debuginfodFind = "debuginfod-find"
|
||||
|
||||
func execFind(args ...string) (string, error) {
|
||||
if _, err := exec.LookPath(debuginfodFind); err != nil {
|
||||
return "", err
|
||||
}
|
||||
cmd := exec.Command(debuginfodFind, args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), err
|
||||
}
|
||||
|
||||
func GetSource(buildid, filename string) (string, error) {
|
||||
return execFind("source", buildid, filename)
|
||||
}
|
||||
|
||||
func GetDebuginfo(buildid string) (string, error) {
|
||||
return execFind("debuginfo", buildid)
|
||||
}
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/cosiner/argv"
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
"github.com/go-delve/delve/pkg/locspec"
|
||||
"github.com/go-delve/delve/pkg/proc/debuginfod"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
"github.com/go-delve/delve/service/rpc2"
|
||||
@ -2676,7 +2677,15 @@ func printfile(t *Term, filename string, line int, showArrow bool) error {
|
||||
arrowLine = line
|
||||
}
|
||||
|
||||
file, err := os.Open(t.substitutePath(filename))
|
||||
var file *os.File
|
||||
path := t.substitutePath(filename)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
foundPath, err := debuginfod.GetSource(t.client.BuildID(), filename)
|
||||
if err == nil {
|
||||
path = foundPath
|
||||
}
|
||||
}
|
||||
file, err := os.OpenFile(path, 0, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -100,6 +100,18 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["build_id"] = starlark.NewBuiltin("build_id", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
var rpcArgs rpc2.BuildIDIn
|
||||
var rpcRet rpc2.BuildIDOut
|
||||
err := env.ctx.Client().CallAPI("BuildID", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["cancel_next"] = starlark.NewBuiltin("cancel_next", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
|
@ -12,6 +12,9 @@ type Client interface {
|
||||
// Returns the pid of the process we are debugging.
|
||||
ProcessPid() int
|
||||
|
||||
// Returns the BuildID of the process' executable we are debugging.
|
||||
BuildID() string
|
||||
|
||||
// LastModified returns the time that the process' executable was modified.
|
||||
LastModified() time.Time
|
||||
|
||||
|
@ -2199,6 +2199,10 @@ func (d *Debugger) Target() *proc.Target {
|
||||
return d.target
|
||||
}
|
||||
|
||||
func (d *Debugger) BuildID() string {
|
||||
return d.target.BinInfo().BuildID
|
||||
}
|
||||
|
||||
func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult {
|
||||
traces := d.target.GetBufferedTracepoints()
|
||||
if traces == nil {
|
||||
|
@ -48,6 +48,12 @@ func (c *RPCClient) ProcessPid() int {
|
||||
return out.Pid
|
||||
}
|
||||
|
||||
func (c *RPCClient) BuildID() string {
|
||||
out := new(BuildIDOut)
|
||||
c.call("BuildID", BuildIDIn{}, out)
|
||||
return out.BuildID
|
||||
}
|
||||
|
||||
func (c *RPCClient) LastModified() time.Time {
|
||||
out := new(LastModifiedOut)
|
||||
c.call("LastModified", LastModifiedIn{}, out)
|
||||
|
@ -1011,3 +1011,15 @@ func (s *RPCServer) CreateWatchpoint(arg CreateWatchpointIn, out *CreateWatchpoi
|
||||
out.Breakpoint, err = s.debugger.CreateWatchpoint(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Expr, arg.Type)
|
||||
return err
|
||||
}
|
||||
|
||||
type BuildIDIn struct {
|
||||
}
|
||||
|
||||
type BuildIDOut struct {
|
||||
BuildID string
|
||||
}
|
||||
|
||||
func (s *RPCServer) BuildID(arg BuildIDIn, out *BuildIDOut) error {
|
||||
out.BuildID = s.debugger.BuildID()
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user