pkg/prog: Improve support for external debug info
Adds a config file option to allow specifying a list of directories to search in when looking for seperate external debug info files. Fixes #1353
This commit is contained in:
parent
a2346ef69a
commit
51c342c6b7
@ -563,15 +563,16 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
|
||||
switch APIVersion {
|
||||
case 1, 2:
|
||||
server = rpccommon.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
ProcessArgs: processArgs,
|
||||
AttachPid: attachPid,
|
||||
AcceptMulti: AcceptMulti,
|
||||
APIVersion: APIVersion,
|
||||
WorkingDir: WorkingDir,
|
||||
Backend: Backend,
|
||||
CoreFile: coreFile,
|
||||
Foreground: Headless,
|
||||
Listener: listener,
|
||||
ProcessArgs: processArgs,
|
||||
AttachPid: attachPid,
|
||||
AcceptMulti: AcceptMulti,
|
||||
APIVersion: APIVersion,
|
||||
WorkingDir: WorkingDir,
|
||||
Backend: Backend,
|
||||
CoreFile: coreFile,
|
||||
Foreground: Headless,
|
||||
DebugInfoDirectories: conf.DebugInfoDirectories,
|
||||
|
||||
DisconnectChan: disconnectChan,
|
||||
})
|
||||
|
@ -43,10 +43,14 @@ type Config struct {
|
||||
// If ShowLocationExpr is true whatis will print the DWARF location
|
||||
// expression for its argument.
|
||||
ShowLocationExpr bool `yaml:"show-location-expr"`
|
||||
|
||||
|
||||
// Source list line-number color (3/4 bit color codes as defined
|
||||
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
|
||||
SourceListLineColor int `yaml:"source-list-line-color"`
|
||||
|
||||
// DebugFileDirectories is the list of directories Delve will use
|
||||
// in order to resolve external debug info files.
|
||||
DebugInfoDirectories []string `yaml:"debug-info-directories"`
|
||||
}
|
||||
|
||||
// LoadConfig attempts to populate a Config object from the config.yml file.
|
||||
@ -160,6 +164,9 @@ substitute-path:
|
||||
|
||||
# Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
|
||||
# show-location-expr: true
|
||||
|
||||
# List of directories to use when searching for separate debug info files.
|
||||
debug-info-directories: ["/usr/lib/debug/.build-id"]
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -27,6 +28,8 @@ import (
|
||||
|
||||
// BinaryInfo holds information on the binary being executed.
|
||||
type BinaryInfo struct {
|
||||
// Path on disk of the binary being executed.
|
||||
Path string
|
||||
// Architecture of this binary.
|
||||
Arch Arch
|
||||
|
||||
@ -91,6 +94,9 @@ var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwi
|
||||
// position independant executable.
|
||||
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
|
||||
|
||||
// ErrNoDebugInfoFound is returned when Delve cannot find the external debug information file.
|
||||
var ErrNoDebugInfoFound = errors.New("could not find external debug info file")
|
||||
|
||||
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
|
||||
|
||||
type compileUnit struct {
|
||||
@ -298,22 +304,22 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo {
|
||||
// LoadBinaryInfo will load and store the information from the binary at 'path'.
|
||||
// It is expected this will be called in parallel with other initialization steps
|
||||
// so a sync.WaitGroup must be provided.
|
||||
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, wg *sync.WaitGroup) error {
|
||||
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string, wg *sync.WaitGroup) error {
|
||||
fi, err := os.Stat(path)
|
||||
if err == nil {
|
||||
bi.lastModified = fi.ModTime()
|
||||
}
|
||||
|
||||
bi.Path = path
|
||||
switch bi.GOOS {
|
||||
case "linux":
|
||||
return bi.LoadBinaryInfoElf(path, entryPoint, wg)
|
||||
return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, wg)
|
||||
case "windows":
|
||||
return bi.LoadBinaryInfoPE(path, entryPoint, wg)
|
||||
case "darwin":
|
||||
return bi.LoadBinaryInfoMacho(path, entryPoint, wg)
|
||||
}
|
||||
return errors.New("unsupported operating system")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GStructOffset returns the offset of the G
|
||||
@ -563,35 +569,32 @@ func (e *ErrNoBuildIDNote) Error() string {
|
||||
// in GDB's documentation [1], and if found returns two handles, one
|
||||
// for the bare file, and another for its corresponding elf.File.
|
||||
// [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
|
||||
func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, error) {
|
||||
buildid := exe.Section(".note.gnu.build-id")
|
||||
if buildid == nil {
|
||||
return nil, nil, &ErrNoBuildIDNote{}
|
||||
//
|
||||
// Alternatively, if the debug file cannot be found be the build-id, Delve
|
||||
// will look in directories specified by the debug-info-directories config value.
|
||||
func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) {
|
||||
var debugFilePath string
|
||||
for _, dir := range debugInfoDirectories {
|
||||
var potentialDebugFilePath string
|
||||
if strings.Contains(dir, "build-id") {
|
||||
desc1, desc2, err := parseBuildID(exe)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
potentialDebugFilePath = fmt.Sprintf("%s/%s/%s.debug", dir, desc1, desc2)
|
||||
} else {
|
||||
potentialDebugFilePath = fmt.Sprintf("%s/%s.debug", dir, filepath.Base(bi.Path))
|
||||
}
|
||||
_, err := os.Stat(potentialDebugFilePath)
|
||||
if err == nil {
|
||||
debugFilePath = potentialDebugFilePath
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
br := buildid.Open()
|
||||
bh := new(buildIDHeader)
|
||||
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
|
||||
return nil, nil, errors.New("can't read build-id header: " + err.Error())
|
||||
if debugFilePath == "" {
|
||||
return nil, nil, ErrNoDebugInfoFound
|
||||
}
|
||||
|
||||
name := make([]byte, bh.Namesz)
|
||||
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
|
||||
return nil, nil, errors.New("can't read build-id name: " + err.Error())
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(name)) != "GNU\x00" {
|
||||
return nil, nil, errors.New("invalid build-id signature")
|
||||
}
|
||||
|
||||
descBinary := make([]byte, bh.Descsz)
|
||||
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
|
||||
return nil, nil, errors.New("can't read build-id desc: " + err.Error())
|
||||
}
|
||||
desc := hex.EncodeToString(descBinary)
|
||||
|
||||
debugPath := fmt.Sprintf("/usr/lib/debug/.build-id/%s/%s.debug", desc[:2], desc[2:])
|
||||
sepFile, err := os.OpenFile(debugPath, 0, os.ModePerm)
|
||||
sepFile, err := os.OpenFile(debugFilePath, 0, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("can't open separate debug file: " + err.Error())
|
||||
}
|
||||
@ -599,19 +602,48 @@ func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File,
|
||||
elfFile, err := elf.NewFile(sepFile)
|
||||
if err != nil {
|
||||
sepFile.Close()
|
||||
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, err.Error())
|
||||
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, err.Error())
|
||||
}
|
||||
|
||||
if elfFile.Machine != elf.EM_X86_64 {
|
||||
sepFile.Close()
|
||||
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, ErrUnsupportedLinuxArch.Error())
|
||||
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, ErrUnsupportedLinuxArch.Error())
|
||||
}
|
||||
|
||||
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 (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync.WaitGroup) error {
|
||||
func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, debugInfoDirectories []string, wg *sync.WaitGroup) error {
|
||||
exe, err := os.OpenFile(path, 0, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -639,7 +671,7 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync
|
||||
if err != nil {
|
||||
var sepFile *os.File
|
||||
var serr error
|
||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile)
|
||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile, debugInfoDirectories)
|
||||
if serr != nil {
|
||||
if _, ok := serr.(*ErrNoBuildIDNote); ok {
|
||||
return err
|
||||
|
@ -185,14 +185,16 @@ var (
|
||||
)
|
||||
|
||||
// OpenCore will open the core file and return a Process struct.
|
||||
func OpenCore(corePath, exePath string) (*Process, error) {
|
||||
// If the DWARF information cannot be found in the binary, Delve will look
|
||||
// for external debug files in the directories passed in.
|
||||
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
|
||||
p, err := readLinuxAMD64Core(corePath, exePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
err = p.bi.LoadBinaryInfo(exePath, p.entryPoint, &wg)
|
||||
err = p.bi.LoadBinaryInfo(exePath, p.entryPoint, debugInfoDirs, &wg)
|
||||
wg.Wait()
|
||||
if err == nil {
|
||||
err = p.bi.LoadError()
|
||||
|
@ -172,7 +172,7 @@ func withCoreFile(t *testing.T, name, args string) *Process {
|
||||
}
|
||||
corePath := cores[0]
|
||||
|
||||
p, err := OpenCore(corePath, fix.Path)
|
||||
p, err := OpenCore(corePath, fix.Path, []string{})
|
||||
if err != nil {
|
||||
t.Errorf("ReadCore(%q) failed: %v", corePath, err)
|
||||
pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern")
|
||||
|
@ -205,7 +205,7 @@ func New(process *os.Process) *Process {
|
||||
}
|
||||
|
||||
// Listen waits for a connection from the stub.
|
||||
func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
||||
func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string) error {
|
||||
acceptChan := make(chan net.Conn)
|
||||
|
||||
go func() {
|
||||
@ -219,7 +219,7 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
||||
if conn == nil {
|
||||
return errors.New("could not connect")
|
||||
}
|
||||
return p.Connect(conn, path, pid)
|
||||
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||
case status := <-p.waitChan:
|
||||
listener.Close()
|
||||
return fmt.Errorf("stub exited while waiting for connection: %v", status)
|
||||
@ -227,11 +227,11 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
||||
}
|
||||
|
||||
// Dial attempts to connect to the stub.
|
||||
func (p *Process) Dial(addr string, path string, pid int) error {
|
||||
func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string) error {
|
||||
for {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
return p.Connect(conn, path, pid)
|
||||
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||
}
|
||||
select {
|
||||
case status := <-p.waitChan:
|
||||
@ -248,7 +248,7 @@ func (p *Process) Dial(addr string, path string, pid int) error {
|
||||
// program and the PID of the target process, both are optional, however
|
||||
// some stubs do not provide ways to determine path and pid automatically
|
||||
// and Connect will be unable to function without knowing them.
|
||||
func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
||||
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
|
||||
p.conn.conn = conn
|
||||
|
||||
p.conn.pid = pid
|
||||
@ -312,7 +312,7 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
err = p.bi.LoadBinaryInfo(path, entryPoint, &wg)
|
||||
err = p.bi.LoadBinaryInfo(path, entryPoint, debugInfoDirs, &wg)
|
||||
wg.Wait()
|
||||
if err == nil {
|
||||
err = p.bi.LoadError()
|
||||
@ -405,7 +405,7 @@ func getLdEnvVars() []string {
|
||||
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
||||
// it to launch the specified target program with the specified arguments
|
||||
// (cmd) on the specified directory wd.
|
||||
func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return nil, ErrUnsupportedOS
|
||||
@ -482,9 +482,9 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
p.conn.isDebugserver = isDebugserver
|
||||
|
||||
if listener != nil {
|
||||
err = p.Listen(listener, cmd[0], 0)
|
||||
err = p.Listen(listener, cmd[0], 0, debugInfoDirs)
|
||||
} else {
|
||||
err = p.Dial(port, cmd[0], 0)
|
||||
err = p.Dial(port, cmd[0], 0, debugInfoDirs)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -497,13 +497,13 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
// Path is path to the target's executable, path only needs to be specified
|
||||
// for some stubs that do not provide an automated way of determining it
|
||||
// (for example debugserver).
|
||||
func LLDBAttach(pid int, path string) (*Process, error) {
|
||||
func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil, ErrUnsupportedOS
|
||||
}
|
||||
|
||||
isDebugserver := false
|
||||
var proc *exec.Cmd
|
||||
var process *exec.Cmd
|
||||
var listener net.Listener
|
||||
var port string
|
||||
if _, err := os.Stat(debugserverExecutable); err == nil {
|
||||
@ -512,32 +512,32 @@ func LLDBAttach(pid int, path string) (*Process, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proc = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid))
|
||||
process = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid))
|
||||
} else {
|
||||
if _, err := exec.LookPath("lldb-server"); err != nil {
|
||||
return nil, &ErrBackendUnavailable{}
|
||||
}
|
||||
port = unusedPort()
|
||||
proc = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port)
|
||||
process = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port)
|
||||
}
|
||||
|
||||
proc.Stdout = os.Stdout
|
||||
proc.Stderr = os.Stderr
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
|
||||
proc.SysProcAttr = sysProcAttr(false)
|
||||
process.SysProcAttr = sysProcAttr(false)
|
||||
|
||||
err := proc.Start()
|
||||
err := process.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := New(proc.Process)
|
||||
p := New(process.Process)
|
||||
p.conn.isDebugserver = isDebugserver
|
||||
|
||||
if listener != nil {
|
||||
err = p.Listen(listener, path, pid)
|
||||
err = p.Listen(listener, path, pid, debugInfoDirs)
|
||||
} else {
|
||||
err = p.Dial(port, path, pid)
|
||||
err = p.Dial(port, path, pid, debugInfoDirs)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -53,7 +53,7 @@ func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
|
||||
|
||||
// Replay starts an instance of rr in replay mode, with the specified trace
|
||||
// directory, and connects to it.
|
||||
func Replay(tracedir string, quiet bool) (*Process, error) {
|
||||
func Replay(tracedir string, quiet bool, debugInfoDirs []string) (*Process, error) {
|
||||
if err := checkRRAvailabe(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -82,7 +82,7 @@ func Replay(tracedir string, quiet bool) (*Process, error) {
|
||||
|
||||
p := New(rrcmd.Process)
|
||||
p.tracedir = tracedir
|
||||
err = p.Dial(init.port, init.exe, 0)
|
||||
err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
|
||||
if err != nil {
|
||||
rrcmd.Process.Kill()
|
||||
return nil, err
|
||||
@ -257,11 +257,11 @@ func splitQuotedFields(in string) []string {
|
||||
}
|
||||
|
||||
// RecordAndReplay acts like calling Record and then Replay.
|
||||
func RecordAndReplay(cmd []string, wd string, quiet bool) (p *Process, tracedir string, err error) {
|
||||
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (p *Process, tracedir string, err error) {
|
||||
tracedir, err = Record(cmd, wd, quiet)
|
||||
if tracedir == "" {
|
||||
return nil, "", err
|
||||
}
|
||||
p, err = Replay(tracedir, quiet)
|
||||
p, err = Replay(tracedir, quiet, debugInfoDirs)
|
||||
return p, tracedir, err
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func withTestRecording(name string, t testing.TB, fn func(p *gdbserial.Process,
|
||||
t.Skip("test skipped, rr not found")
|
||||
}
|
||||
t.Log("recording")
|
||||
p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true)
|
||||
p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{})
|
||||
if err != nil {
|
||||
t.Fatal("Launch():", err)
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ import (
|
||||
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
||||
|
||||
// Launch returns ErrNativeBackendDisabled.
|
||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
|
||||
return nil, ErrNativeBackendDisabled
|
||||
}
|
||||
|
||||
// Attach returns ErrNativeBackendDisabled.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
func Attach(pid int, _ []string) (*Process, error) {
|
||||
return nil, ErrNativeBackendDisabled
|
||||
}
|
||||
|
||||
|
@ -189,7 +189,7 @@ func (dbp *Process) Breakpoints() *proc.BreakpointMap {
|
||||
// * Dwarf .debug_frame section
|
||||
// * Dwarf .debug_line section
|
||||
// * Go symbol table.
|
||||
func (dbp *Process) LoadInformation(path string) error {
|
||||
func (dbp *Process) LoadInformation(path string, debugInfoDirs []string) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
path = findExecutable(path, dbp.pid)
|
||||
@ -201,7 +201,7 @@ func (dbp *Process) LoadInformation(path string) error {
|
||||
|
||||
wg.Add(1)
|
||||
go dbp.loadProcessInformation(&wg)
|
||||
err = dbp.bi.LoadBinaryInfo(path, entryPoint, &wg)
|
||||
err = dbp.bi.LoadBinaryInfo(path, entryPoint, debugInfoDirs, &wg)
|
||||
wg.Wait()
|
||||
if err == nil {
|
||||
err = dbp.bi.LoadError()
|
||||
@ -380,8 +380,8 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
||||
}
|
||||
|
||||
// Returns a new Process struct.
|
||||
func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
|
||||
err := dbp.LoadInformation(path)
|
||||
func initializeDebugProcess(dbp *Process, path string, debugInfoDirs []string) (*Process, error) {
|
||||
err := dbp.LoadInformation(path, debugInfoDirs)
|
||||
if err != nil {
|
||||
return dbp, err
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ type OSProcessDetails struct {
|
||||
// custom fork/exec process in order to take advantage of
|
||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||
// Mach exceptions.
|
||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
|
||||
// check that the argument to Launch is an executable file
|
||||
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
|
||||
return nil, proc.ErrNotExecutable
|
||||
@ -119,7 +119,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
}
|
||||
|
||||
dbp.os.initialized = true
|
||||
dbp, err = initializeDebugProcess(dbp, argv0Go)
|
||||
dbp, err = initializeDebugProcess(dbp, argv0Go, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -132,7 +132,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
}
|
||||
|
||||
// Attach to an existing process with the given PID.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
func Attach(pid int, _ []string) (*Process, error) {
|
||||
dbp := New(pid)
|
||||
|
||||
kret := C.acquire_mach_task(C.int(pid),
|
||||
@ -155,7 +155,7 @@ func Attach(pid int) (*Process, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp, err = initializeDebugProcess(dbp, "")
|
||||
dbp, err = initializeDebugProcess(dbp, "", []string{})
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
return nil, err
|
||||
|
@ -46,7 +46,9 @@ type OSProcessDetails struct {
|
||||
// Launch creates and begins debugging a new process. First entry in
|
||||
// `cmd` is the program to run, and then rest are the arguments
|
||||
// to be supplied to that process. `wd` is working directory of the program.
|
||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
// If the DWARF information cannot be found in the binary, Delve will look
|
||||
// for external debug files in the directories passed in.
|
||||
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) {
|
||||
var (
|
||||
process *exec.Cmd
|
||||
err error
|
||||
@ -88,11 +90,13 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||
}
|
||||
return initializeDebugProcess(dbp, process.Path)
|
||||
return initializeDebugProcess(dbp, process.Path, debugInfoDirs)
|
||||
}
|
||||
|
||||
// Attach to an existing process with the given PID.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
// Attach to an existing process with the given PID. Once attached, if
|
||||
// the DWARF information cannot be found in the binary, Delve will look
|
||||
// for external debug files in the directories passed in.
|
||||
func Attach(pid int, debugInfoDirs []string) (*Process, error) {
|
||||
dbp := New(pid)
|
||||
dbp.common = proc.NewCommonProcess(true)
|
||||
|
||||
@ -106,7 +110,7 @@ func Attach(pid int) (*Process, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp, err = initializeDebugProcess(dbp, "")
|
||||
dbp, err = initializeDebugProcess(dbp, "", debugInfoDirs)
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
return nil, err
|
||||
|
@ -37,7 +37,7 @@ func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
|
||||
}
|
||||
|
||||
// Launch creates and begins debugging a new process.
|
||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
|
||||
argv0Go, err := filepath.Abs(cmd[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -115,7 +115,7 @@ func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return initializeDebugProcess(dbp, exepath)
|
||||
return initializeDebugProcess(dbp, exepath, []string{})
|
||||
}
|
||||
|
||||
// findExePath searches for process pid, and returns its executable path.
|
||||
@ -153,7 +153,7 @@ func findExePath(pid int) (string, error) {
|
||||
}
|
||||
|
||||
// Attach to an existing process with the given PID.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
func Attach(pid int, _ []string) (*Process, error) {
|
||||
// TODO: Probably should have SeDebugPrivilege before starting here.
|
||||
err := _DebugActiveProcess(uint32(pid))
|
||||
if err != nil {
|
||||
|
39
pkg/proc/proc_linux_test.go
Normal file
39
pkg/proc/proc_linux_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package proc_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc/native"
|
||||
protest "github.com/derekparker/delve/pkg/proc/test"
|
||||
)
|
||||
|
||||
func TestLoadingExternalDebugInfo(t *testing.T) {
|
||||
fixture := protest.BuildFixture("locationsprog", 0)
|
||||
defer os.Remove(fixture.Path)
|
||||
stripAndCopyDebugInfo(fixture, t)
|
||||
p, err := native.Launch(append([]string{fixture.Path}, ""), "", false, []string{filepath.Dir(fixture.Path)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.Detach(true)
|
||||
}
|
||||
|
||||
func stripAndCopyDebugInfo(f protest.Fixture, t *testing.T) {
|
||||
name := filepath.Base(f.Path)
|
||||
// Copy the debug information to an external file.
|
||||
copyCmd := exec.Command("objcopy", "--only-keep-debug", name, name+".debug")
|
||||
copyCmd.Dir = filepath.Dir(f.Path)
|
||||
if err := copyCmd.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Strip the original binary of the debug information.
|
||||
stripCmd := exec.Command("strip", "--strip-debug", "--strip-unneeded", name)
|
||||
stripCmd.Dir = filepath.Dir(f.Path)
|
||||
if err := stripCmd.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -66,13 +66,13 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false)
|
||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{})
|
||||
case "lldb":
|
||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false)
|
||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{})
|
||||
case "rr":
|
||||
protest.MustHaveRecordingAllowed(t)
|
||||
t.Log("recording")
|
||||
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true)
|
||||
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{})
|
||||
t.Logf("replaying %q", tracedir)
|
||||
default:
|
||||
t.Fatal("unknown backend")
|
||||
@ -2048,9 +2048,9 @@ func TestIssue509(t *testing.T) {
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
_, err = native.Launch([]string{exepath}, ".", false)
|
||||
_, err = native.Launch([]string{exepath}, ".", false, []string{})
|
||||
case "lldb":
|
||||
_, err = gdbserial.LLDBLaunch([]string{exepath}, ".", false)
|
||||
_, err = gdbserial.LLDBLaunch([]string{exepath}, ".", false, []string{})
|
||||
default:
|
||||
t.Skip("test not valid for this backend")
|
||||
}
|
||||
@ -2090,9 +2090,9 @@ func TestUnsupportedArch(t *testing.T) {
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Launch([]string{outfile}, ".", false)
|
||||
p, err = native.Launch([]string{outfile}, ".", false, []string{})
|
||||
case "lldb":
|
||||
p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false)
|
||||
p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{})
|
||||
default:
|
||||
t.Skip("test not valid for this backend")
|
||||
}
|
||||
@ -2839,13 +2839,13 @@ func TestAttachDetach(t *testing.T) {
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Attach(cmd.Process.Pid)
|
||||
p, err = native.Attach(cmd.Process.Pid, []string{})
|
||||
case "lldb":
|
||||
path := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
path = fixture.Path
|
||||
}
|
||||
p, err = gdbserial.LLDBAttach(cmd.Process.Pid, path)
|
||||
p, err = gdbserial.LLDBAttach(cmd.Process.Pid, path, []string{})
|
||||
default:
|
||||
err = fmt.Errorf("unknown backend %q", testBackend)
|
||||
}
|
||||
@ -3141,13 +3141,13 @@ func TestAttachStripped(t *testing.T) {
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Attach(cmd.Process.Pid)
|
||||
p, err = native.Attach(cmd.Process.Pid, []string{})
|
||||
case "lldb":
|
||||
path := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
path = fixture.Path
|
||||
}
|
||||
p, err = gdbserial.LLDBAttach(cmd.Process.Pid, path)
|
||||
p, err = gdbserial.LLDBAttach(cmd.Process.Pid, path, []string{})
|
||||
default:
|
||||
t.Fatalf("unknown backend %q", testBackend)
|
||||
}
|
||||
|
@ -29,6 +29,10 @@ type Config struct {
|
||||
// CoreFile specifies the path to the core dump to open.
|
||||
CoreFile string
|
||||
|
||||
// DebugInfoDirectories is the list of directories to look for
|
||||
// when resolving external debug info files.
|
||||
DebugInfoDirectories []string
|
||||
|
||||
// Selects server backend.
|
||||
Backend string
|
||||
|
||||
|
@ -65,6 +65,10 @@ type Config struct {
|
||||
|
||||
// Foreground lets target process access stdin.
|
||||
Foreground bool
|
||||
|
||||
// DebugInfoDirectories is the list of directories to look for
|
||||
// when resolving external debug info files.
|
||||
DebugInfoDirectories []string
|
||||
}
|
||||
|
||||
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the
|
||||
@ -102,10 +106,10 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
||||
switch d.config.Backend {
|
||||
case "rr":
|
||||
d.log.Infof("opening trace %s", d.config.CoreFile)
|
||||
p, err = gdbserial.Replay(d.config.CoreFile, false)
|
||||
p, err = gdbserial.Replay(d.config.CoreFile, false, d.config.DebugInfoDirectories)
|
||||
default:
|
||||
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
|
||||
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0])
|
||||
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories)
|
||||
}
|
||||
if err != nil {
|
||||
err = go11DecodeErrorCheck(err)
|
||||
@ -132,17 +136,17 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
||||
func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) {
|
||||
switch d.config.Backend {
|
||||
case "native":
|
||||
return native.Launch(processArgs, wd, d.config.Foreground)
|
||||
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories)
|
||||
case "lldb":
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground))
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories))
|
||||
case "rr":
|
||||
p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false)
|
||||
p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false, d.config.DebugInfoDirectories)
|
||||
return p, err
|
||||
case "default":
|
||||
if runtime.GOOS == "darwin" {
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground))
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories))
|
||||
}
|
||||
return native.Launch(processArgs, wd, d.config.Foreground)
|
||||
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
|
||||
}
|
||||
@ -157,14 +161,14 @@ var ErrNoAttachPath = errors.New("must specify executable path on macOS")
|
||||
func (d *Debugger) Attach(pid int, path string) (proc.Process, error) {
|
||||
switch d.config.Backend {
|
||||
case "native":
|
||||
return native.Attach(pid)
|
||||
return native.Attach(pid, d.config.DebugInfoDirectories)
|
||||
case "lldb":
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path))
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path, d.config.DebugInfoDirectories))
|
||||
case "default":
|
||||
if runtime.GOOS == "darwin" {
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path))
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path, d.config.DebugInfoDirectories))
|
||||
}
|
||||
return native.Attach(pid)
|
||||
return native.Attach(pid, d.config.DebugInfoDirectories)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
|
||||
}
|
||||
|
@ -121,11 +121,12 @@ func (s *ServerImpl) Run() error {
|
||||
|
||||
// Create and start the debugger
|
||||
if s.debugger, err = debugger.New(&debugger.Config{
|
||||
AttachPid: s.config.AttachPid,
|
||||
WorkingDir: s.config.WorkingDir,
|
||||
CoreFile: s.config.CoreFile,
|
||||
Backend: s.config.Backend,
|
||||
Foreground: s.config.Foreground,
|
||||
AttachPid: s.config.AttachPid,
|
||||
WorkingDir: s.config.WorkingDir,
|
||||
CoreFile: s.config.CoreFile,
|
||||
Backend: s.config.Backend,
|
||||
Foreground: s.config.Foreground,
|
||||
DebugInfoDirectories: s.config.DebugInfoDirectories,
|
||||
},
|
||||
s.config.ProcessArgs); err != nil {
|
||||
return err
|
||||
|
@ -117,13 +117,13 @@ func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture
|
||||
var tracedir string
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Launch([]string{fixture.Path}, ".", false)
|
||||
p, err = native.Launch([]string{fixture.Path}, ".", false, []string{})
|
||||
case "lldb":
|
||||
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".", false)
|
||||
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".", false, []string{})
|
||||
case "rr":
|
||||
protest.MustHaveRecordingAllowed(t)
|
||||
t.Log("recording")
|
||||
p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true)
|
||||
p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{})
|
||||
t.Logf("replaying %q", tracedir)
|
||||
default:
|
||||
t.Fatalf("unknown backend %q", testBackend)
|
||||
|
Loading…
Reference in New Issue
Block a user