Skip to content

Commit

Permalink
Implement source listing from debuginfo (go-delve#2885)
Browse files Browse the repository at this point in the history
* service: Implement BuildID

Parse the BuildID of executables and provides it over the RPC
service.

Signed-off-by: Morten Linderud <[email protected]>

* command: Support debuinfod for file listing

Signed-off-by: Morten Linderud <[email protected]>

* debuginfod: create debuginfod package for common code

We remove the duplicated code and provide our a new debuginfod package.

Signed-off-by: Morten Linderud <[email protected]>

* starlark: Workaround for 'build_i_d'

Signed-off-by: Morten Linderud <[email protected]>

* command: Ensure we only overwrite path when one has been found

Signed-off-by: Morten Linderud <[email protected]>

* bininfo: Inline parseBuildID

Signed-off-by: Morten Linderud <[email protected]>
  • Loading branch information
Foxboron authored Jan 30, 2022
1 parent 5b6f8ec commit 8c392d2
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 51 deletions.
1 change: 1 addition & 0 deletions Documentation/cli/starlark.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions _scripts/gen-starlark-bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
92 changes: 42 additions & 50 deletions pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"go/token"
"io"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
Expand All @@ -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"
)
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()

Expand Down
28 changes: 28 additions & 0 deletions pkg/proc/debuginfod/debuginfod.go
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 10 additions & 1 deletion pkg/terminal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/terminal/starbind/starlark_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions service/debugger/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions service/rpc2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions service/rpc2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 8c392d2

Please sign in to comment.