terminal,service,proc/*: adds dump command (gcore equivalent) (#2173)
* proc/core: off-by-one error reading ELF core files core.(*splicedMemory).ReadMemory checked the entry interval erroneously when dealing with contiguous entries. * terminal,service,proc/*: adds dump command (gcore equivalent) Adds the `dump` command that creates a core file from the target process. Backends will need to implement a new, optional, method `MemoryMap` that returns a list of mapped memory regions. Additionally the method `DumpProcessNotes` can be implemented to write out to the core file notes describing the target process and its threads. If DumpProcessNotes is not implemented `proc.Dump` will write a description of the process and its threads in a OS/arch-independent format (that only Delve understands). Currently only linux/amd64 implements `DumpProcessNotes`. Core files are only written in ELF, there is no minidump or macho-o writers. # Conflicts: # pkg/proc/proc_test.go
This commit is contained in:
parent
11e4ed2bf9
commit
2c1a822632
@ -1,19 +1,19 @@
|
||||
Tests skipped by each supported backend:
|
||||
|
||||
* 386 skipped = 2.1% (3/146)
|
||||
* 386 skipped = 2% (3/147)
|
||||
* 1 broken
|
||||
* 2 broken - cgo stacktraces
|
||||
* arm64 skipped = 2.1% (3/146)
|
||||
* arm64 skipped = 2% (3/147)
|
||||
* 2 broken
|
||||
* 1 broken - global variable symbolication
|
||||
* darwin/lldb skipped = 0.68% (1/146)
|
||||
* darwin/lldb skipped = 0.68% (1/147)
|
||||
* 1 upstream issue
|
||||
* freebsd skipped = 7.5% (11/146)
|
||||
* freebsd skipped = 7.5% (11/147)
|
||||
* 11 broken
|
||||
* linux/386/pie skipped = 0.68% (1/146)
|
||||
* linux/386/pie skipped = 0.68% (1/147)
|
||||
* 1 broken
|
||||
* pie skipped = 0.68% (1/146)
|
||||
* pie skipped = 0.68% (1/147)
|
||||
* 1 upstream issue - https://github.com/golang/go/issues/29322
|
||||
* windows skipped = 1.4% (2/146)
|
||||
* windows skipped = 1.4% (2/147)
|
||||
* 1 broken
|
||||
* 1 upstream issue
|
||||
|
@ -80,6 +80,7 @@ Command | Description
|
||||
[clear-checkpoint](#clear-checkpoint) | Deletes checkpoint.
|
||||
[config](#config) | Changes configuration parameters.
|
||||
[disassemble](#disassemble) | Disassembler.
|
||||
[dump](#dump) | Creates a core dump from the current process state
|
||||
[edit](#edit) | Open where you are in $DELVE_EDITOR or $EDITOR
|
||||
[exit](#exit) | Exit the debugger.
|
||||
[funcs](#funcs) | Print list of functions.
|
||||
@ -247,6 +248,14 @@ Move the current frame down.
|
||||
Move the current frame down by <m>. The second form runs the command on the given frame.
|
||||
|
||||
|
||||
## dump
|
||||
Creates a core dump from the current process state
|
||||
|
||||
dump <output file>
|
||||
|
||||
The core dump is always written in ELF, even on systems (windows, macOS) where this is not customary. For environments other than linux/amd64 threads and registers are dumped in a format that only Delve can read back.
|
||||
|
||||
|
||||
## edit
|
||||
Open where you are in $DELVE_EDITOR or $EDITOR
|
||||
|
||||
|
@ -28,6 +28,9 @@ raw_command(Name, ThreadID, GoroutineID, ReturnInfoLoadConfig, Expr, UnsafeCall)
|
||||
create_breakpoint(Breakpoint) | Equivalent to API call [CreateBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateBreakpoint)
|
||||
detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach)
|
||||
disassemble(Scope, StartPC, EndPC, Flavour) | Equivalent to API call [Disassemble](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Disassemble)
|
||||
dump_cancel() | Equivalent to API call [DumpCancel](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.DumpCancel)
|
||||
dump_start(Destination) | Equivalent to API call [DumpStart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.DumpStart)
|
||||
dump_wait(Wait) | Equivalent to API call [DumpWait](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.DumpWait)
|
||||
eval(Scope, Expr, Cfg) | Equivalent to API call [Eval](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Eval)
|
||||
examine_memory(Address, Length) | Equivalent to API call [ExamineMemory](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ExamineMemory)
|
||||
find_location(Scope, Loc, IncludeNonExecutableLines, SubstitutePathRules) | Equivalent to API call [FindLocation](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FindLocation)
|
||||
|
@ -11,7 +11,7 @@ The core command will open the specified core file and the associated
|
||||
executable and let you examine the state of the process when the
|
||||
core dump was taken.
|
||||
|
||||
Currently supports linux/amd64 and linux/arm64 core files and windows/amd64 minidumps.
|
||||
Currently supports linux/amd64 and linux/arm64 core files, windows/amd64 minidumps and core files generated by Delve's 'dump' command.
|
||||
|
||||
```
|
||||
dlv core <executable> <core>
|
||||
|
@ -290,7 +290,7 @@ The core command will open the specified core file and the associated
|
||||
executable and let you examine the state of the process when the
|
||||
core dump was taken.
|
||||
|
||||
Currently supports linux/amd64 and linux/arm64 core files and windows/amd64 minidumps.`,
|
||||
Currently supports linux/amd64 and linux/arm64 core files, windows/amd64 minidumps and core files generated by Delve's 'dump' command.`,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 2 {
|
||||
return errors.New("you must provide a core file and an executable")
|
||||
|
11
pkg/elfwriter/delve_core_notes.go
Normal file
11
pkg/elfwriter/delve_core_notes.go
Normal file
@ -0,0 +1,11 @@
|
||||
package elfwriter
|
||||
|
||||
const (
|
||||
DelveHeaderNoteType = 0x444C5645 // DLVE
|
||||
DelveThreadNodeType = 0x444C5654 // DLVT
|
||||
|
||||
DelveHeaderTargetPidPrefix = "Target Pid: "
|
||||
DelveHeaderEntryPointPrefix = "Entry Point: "
|
||||
)
|
||||
|
||||
//TODO(aarzilli): these constants probably need to be in a better place.
|
183
pkg/elfwriter/writer.go
Normal file
183
pkg/elfwriter/writer.go
Normal file
@ -0,0 +1,183 @@
|
||||
// elfwriter is a package to write ELF files without having their entire
|
||||
// contents in memory at any one time.
|
||||
// This package is incomplete, only features needed to write core files are
|
||||
// implemented, notably missing:
|
||||
// - section headers
|
||||
// - program headers at the beginning of the file
|
||||
|
||||
package elfwriter
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriteCloserSeeker is the union of io.Writer, io.Closer and io.Seeker.
|
||||
type WriteCloserSeeker interface {
|
||||
io.Writer
|
||||
io.Seeker
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// Writer writes ELF files.
|
||||
type Writer struct {
|
||||
w WriteCloserSeeker
|
||||
Err error
|
||||
Progs []*elf.ProgHeader
|
||||
|
||||
seekProgHeader int64
|
||||
seekProgNum int64
|
||||
}
|
||||
|
||||
type Note struct {
|
||||
Type elf.NType
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// New creates a new Writer.
|
||||
func New(w WriteCloserSeeker, fhdr *elf.FileHeader) *Writer {
|
||||
const (
|
||||
ehsize = 64
|
||||
phentsize = 56
|
||||
)
|
||||
|
||||
if seek, _ := w.Seek(0, io.SeekCurrent); seek != 0 {
|
||||
panic("can't write halfway through a file")
|
||||
}
|
||||
|
||||
r := &Writer{w: w}
|
||||
|
||||
if fhdr.Class != elf.ELFCLASS64 {
|
||||
panic("unsupported")
|
||||
}
|
||||
|
||||
if fhdr.Data != elf.ELFDATA2LSB {
|
||||
panic("unsupported")
|
||||
}
|
||||
|
||||
// e_ident
|
||||
r.Write([]byte{0x7f, 'E', 'L', 'F', byte(fhdr.Class), byte(fhdr.Data), byte(fhdr.Version), byte(fhdr.OSABI), byte(fhdr.ABIVersion), 0, 0, 0, 0, 0, 0, 0})
|
||||
|
||||
r.u16(uint16(fhdr.Type)) // e_type
|
||||
r.u16(uint16(fhdr.Machine)) // e_machine
|
||||
r.u32(uint32(fhdr.Version)) // e_version
|
||||
r.u64(0) // e_entry
|
||||
r.seekProgHeader = r.Here()
|
||||
r.u64(0) // e_phoff
|
||||
r.u64(0) // e_shoff
|
||||
r.u32(0) // e_flags
|
||||
r.u16(ehsize) // e_ehsize
|
||||
r.u16(phentsize) // e_phentsize
|
||||
r.seekProgNum = r.Here()
|
||||
r.u16(0) // e_phnum
|
||||
r.u16(0) // e_shentsize
|
||||
r.u16(0) // e_shnum
|
||||
r.u16(uint16(elf.SHN_UNDEF)) // e_shstrndx
|
||||
|
||||
// Sanity check, size of file header should be the same as ehsize
|
||||
if sz, _ := w.Seek(0, io.SeekCurrent); sz != ehsize {
|
||||
panic("internal error, ELF header size")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// WriteNotes writes notes to the current location, returns a ProgHeader describing the
|
||||
// notes.
|
||||
func (w *Writer) WriteNotes(notes []Note) *elf.ProgHeader {
|
||||
if len(notes) == 0 {
|
||||
return nil
|
||||
}
|
||||
h := &elf.ProgHeader{
|
||||
Type: elf.PT_NOTE,
|
||||
Align: 4,
|
||||
}
|
||||
for i := range notes {
|
||||
note := ¬es[i]
|
||||
w.Align(4)
|
||||
if h.Off == 0 {
|
||||
h.Off = uint64(w.Here())
|
||||
}
|
||||
w.u32(uint32(len(note.Name)))
|
||||
w.u32(uint32(len(note.Data)))
|
||||
w.u32(uint32(note.Type))
|
||||
w.Write([]byte(note.Name))
|
||||
w.Align(4)
|
||||
w.Write(note.Data)
|
||||
}
|
||||
h.Filesz = uint64(w.Here()) - h.Off
|
||||
return h
|
||||
}
|
||||
|
||||
// WriteProgramHeaders writes the program headers at the current location
|
||||
// and patches the file header accordingly.
|
||||
func (w *Writer) WriteProgramHeaders() {
|
||||
phoff := w.Here()
|
||||
|
||||
// Patch File Header
|
||||
w.w.Seek(w.seekProgHeader, io.SeekStart)
|
||||
w.u64(uint64(phoff))
|
||||
w.w.Seek(w.seekProgNum, io.SeekStart)
|
||||
w.u64(uint64(len(w.Progs)))
|
||||
w.w.Seek(0, io.SeekEnd)
|
||||
|
||||
for _, prog := range w.Progs {
|
||||
w.u32(uint32(prog.Type))
|
||||
w.u32(uint32(prog.Flags))
|
||||
w.u64(prog.Off)
|
||||
w.u64(prog.Vaddr)
|
||||
w.u64(prog.Paddr)
|
||||
w.u64(prog.Filesz)
|
||||
w.u64(prog.Memsz)
|
||||
w.u64(prog.Align)
|
||||
}
|
||||
}
|
||||
|
||||
// Here returns the current seek offset from the start of the file.
|
||||
func (w *Writer) Here() int64 {
|
||||
r, err := w.w.Seek(0, io.SeekCurrent)
|
||||
if err != nil && w.Err == nil {
|
||||
w.Err = err
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Align writes as many padding bytes as needed to make the current file
|
||||
// offset a multiple of align.
|
||||
func (w *Writer) Align(align int64) {
|
||||
off := w.Here()
|
||||
alignOff := (off + (align - 1)) &^ (align - 1)
|
||||
if alignOff-off > 0 {
|
||||
w.Write(make([]byte, alignOff-off))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(buf []byte) {
|
||||
_, err := w.w.Write(buf)
|
||||
if err != nil && w.Err == nil {
|
||||
w.Err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) u16(n uint16) {
|
||||
err := binary.Write(w.w, binary.LittleEndian, n)
|
||||
if err != nil && w.Err == nil {
|
||||
w.Err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) u32(n uint32) {
|
||||
err := binary.Write(w.w, binary.LittleEndian, n)
|
||||
if err != nil && w.Err == nil {
|
||||
w.Err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) u64(n uint64) {
|
||||
err := binary.Write(w.w, binary.LittleEndian, n)
|
||||
if err != nil && w.Err == nil {
|
||||
w.Err = err
|
||||
}
|
||||
}
|
@ -5,9 +5,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// ErrNoThreads core file did not contain any threads.
|
||||
var ErrNoThreads = errors.New("no threads found in core file")
|
||||
|
||||
// A splicedMemory represents a memory space formed from multiple regions,
|
||||
// each of which may override previously regions. For example, in the following
|
||||
// core, the program text was loaded at 0x400000:
|
||||
@ -189,7 +193,7 @@ var (
|
||||
|
||||
type openFn func(string, string) (*process, proc.Thread, error)
|
||||
|
||||
var openFns = []openFn{readLinuxCore, readAMD64Minidump}
|
||||
var openFns = []openFn{readLinuxOrPlatformIndependentCore, readAMD64Minidump}
|
||||
|
||||
// ErrUnrecognizedFormat is returned when the core file is not recognized as
|
||||
// any of the supported formats.
|
||||
@ -212,11 +216,16 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if currentThread == nil {
|
||||
return nil, ErrNoThreads
|
||||
}
|
||||
|
||||
return proc.NewTarget(p, currentThread, proc.NewTargetConfig{
|
||||
Path: exePath,
|
||||
DebugInfoDirs: debugInfoDirs,
|
||||
DisableAsyncPreempt: false,
|
||||
StopReason: proc.StopAttached})
|
||||
StopReason: proc.StopAttached,
|
||||
CanDump: false})
|
||||
}
|
||||
|
||||
// BinInfo will return the binary info.
|
||||
@ -447,3 +456,11 @@ func (p *process) FindThread(threadID int) (proc.Thread, bool) {
|
||||
t, ok := p.Threads[threadID]
|
||||
return t, ok
|
||||
}
|
||||
|
||||
func (p *process) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
||||
return nil, proc.ErrMemoryMapNotSupported
|
||||
}
|
||||
|
||||
func (p *process) DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (threadsDone bool, out []elfwriter.Note, err error) {
|
||||
return false, notes, nil
|
||||
}
|
||||
|
165
pkg/proc/core/delve_core.go
Normal file
165
pkg/proc/core/delve_core.go
Normal file
@ -0,0 +1,165 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
func platformFromNotes(notes []*note) (goos, goarch string, err error) {
|
||||
for _, note := range notes {
|
||||
if note.Type != elfwriter.DelveHeaderNoteType {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(string(note.Desc.([]byte)), "\n")
|
||||
v := strings.Split(lines[0], "/")
|
||||
if len(v) != 2 {
|
||||
return "", "", fmt.Errorf("malformed delve header note: %q", string(note.Desc.([]byte)))
|
||||
}
|
||||
return v[0], v[1], nil
|
||||
}
|
||||
panic("internal error")
|
||||
}
|
||||
|
||||
func threadsFromDelveNotes(p *process, notes []*note) (proc.Thread, error) {
|
||||
var currentThread proc.Thread
|
||||
for _, note := range notes {
|
||||
if note.Type == elfwriter.DelveHeaderNoteType {
|
||||
buf := bytes.NewBuffer(note.Desc.([]byte))
|
||||
for {
|
||||
line, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 && line[len(line)-1] == '\n' {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(line, elfwriter.DelveHeaderTargetPidPrefix):
|
||||
pid, err := strconv.ParseUint(line[len(elfwriter.DelveHeaderTargetPidPrefix):], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed delve header note (bad pid): %v", err)
|
||||
}
|
||||
p.pid = int(pid)
|
||||
case strings.HasPrefix(line, elfwriter.DelveHeaderEntryPointPrefix):
|
||||
entry, err := strconv.ParseUint(line[len(elfwriter.DelveHeaderEntryPointPrefix):], 0, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed delve header note (bad entry point): %v", err)
|
||||
}
|
||||
p.entryPoint = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if note.Type != elfwriter.DelveThreadNodeType {
|
||||
continue
|
||||
}
|
||||
body := bytes.NewReader(note.Desc.([]byte))
|
||||
th := new(delveThread)
|
||||
th.regs = new(delveRegisters)
|
||||
|
||||
var readerr error
|
||||
read := func(out interface{}) {
|
||||
if readerr != nil {
|
||||
return
|
||||
}
|
||||
readerr = binary.Read(body, binary.LittleEndian, out)
|
||||
}
|
||||
|
||||
read(&th.id)
|
||||
|
||||
read(&th.regs.pc)
|
||||
read(&th.regs.sp)
|
||||
read(&th.regs.bp)
|
||||
read(&th.regs.tls)
|
||||
read(&th.regs.hasGAddr)
|
||||
read(&th.regs.gaddr)
|
||||
|
||||
var n uint32
|
||||
read(&n)
|
||||
|
||||
if readerr != nil {
|
||||
return nil, fmt.Errorf("error reading thread note header for thread %d: %v", th.id, readerr)
|
||||
}
|
||||
|
||||
th.regs.slice = make([]proc.Register, n)
|
||||
|
||||
readBytes := func(maxlen uint16, kind string) []byte {
|
||||
if readerr != nil {
|
||||
return nil
|
||||
}
|
||||
var len uint16
|
||||
read(&len)
|
||||
if maxlen > 0 && len > maxlen {
|
||||
readerr = fmt.Errorf("maximum len exceeded (%d) reading %s", len, kind)
|
||||
return nil
|
||||
}
|
||||
buf := make([]byte, len)
|
||||
if readerr != nil {
|
||||
return nil
|
||||
}
|
||||
_, readerr = body.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
for i := 0; i < int(n); i++ {
|
||||
name := string(readBytes(20, "register name"))
|
||||
value := readBytes(2048, "register value")
|
||||
th.regs.slice[i] = proc.Register{Name: name, Reg: op.DwarfRegisterFromBytes(value)}
|
||||
if readerr != nil {
|
||||
return nil, fmt.Errorf("error reading thread note registers for thread %d: %v", th.id, readerr)
|
||||
}
|
||||
}
|
||||
|
||||
p.Threads[int(th.id)] = &thread{th, p, proc.CommonThread{}}
|
||||
if currentThread == nil {
|
||||
currentThread = p.Threads[int(th.id)]
|
||||
}
|
||||
}
|
||||
return currentThread, nil
|
||||
}
|
||||
|
||||
type delveThread struct {
|
||||
id uint64
|
||||
regs *delveRegisters
|
||||
}
|
||||
|
||||
func (th *delveThread) pid() int {
|
||||
return int(th.id)
|
||||
}
|
||||
|
||||
func (th *delveThread) registers() (proc.Registers, error) {
|
||||
return th.regs, nil
|
||||
}
|
||||
|
||||
type delveRegisters struct {
|
||||
pc, sp, bp, tls uint64
|
||||
hasGAddr bool
|
||||
gaddr uint64
|
||||
slice []proc.Register
|
||||
}
|
||||
|
||||
func (regs *delveRegisters) PC() uint64 { return regs.pc }
|
||||
func (regs *delveRegisters) BP() uint64 { return regs.bp }
|
||||
func (regs *delveRegisters) SP() uint64 { return regs.sp }
|
||||
func (regs *delveRegisters) TLS() uint64 { return regs.tls }
|
||||
func (regs *delveRegisters) GAddr() (uint64, bool) { return regs.gaddr, regs.hasGAddr }
|
||||
|
||||
func (regs *delveRegisters) Copy() (proc.Registers, error) {
|
||||
return regs, nil
|
||||
}
|
||||
|
||||
func (regs *delveRegisters) Get(int) (uint64, error) {
|
||||
return 0, errors.New("not supported")
|
||||
}
|
||||
|
||||
func (regs *delveRegisters) Slice(bool) ([]proc.Register, error) {
|
||||
return regs.slice, nil
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/amd64util"
|
||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||
@ -84,13 +85,14 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) p
|
||||
return currentThread
|
||||
}
|
||||
|
||||
// readLinuxCore reads a core file from corePath corresponding to the executable at
|
||||
// exePath. For details on the Linux ELF core format, see:
|
||||
// readLinuxOrPlatformIndependentCore reads a core file from corePath
|
||||
// corresponding to the executable at exePath. For details on the Linux ELF
|
||||
// core format, see:
|
||||
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
||||
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
|
||||
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
|
||||
// and, if absolutely desperate, readelf.c from the binutils source.
|
||||
func readLinuxCore(corePath, exePath string) (*process, proc.Thread, error) {
|
||||
func readLinuxOrPlatformIndependentCore(corePath, exePath string) (*process, proc.Thread, error) {
|
||||
coreFile, err := elf.Open(corePath)
|
||||
if err != nil {
|
||||
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
|
||||
@ -99,38 +101,54 @@ func readLinuxCore(corePath, exePath string) (*process, proc.Thread, error) {
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if coreFile.Type != elf.ET_CORE {
|
||||
return nil, nil, fmt.Errorf("%v is not a core file", coreFile)
|
||||
}
|
||||
|
||||
machineType := coreFile.Machine
|
||||
notes, platformIndependentDelveCore, err := readNotes(coreFile, machineType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
exe, err := os.Open(exePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
exeELF, err := elf.NewFile(exe)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
if !platformIndependentDelveCore {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
if exeELF.Machine != machineType {
|
||||
return nil, nil, fmt.Errorf("architecture mismatch between core file (%#x) and executable file (%#x)", machineType, exeELF.Machine)
|
||||
}
|
||||
if exeELF.Type != elf.ET_EXEC && exeELF.Type != elf.ET_DYN {
|
||||
return nil, nil, fmt.Errorf("%v is not an exe file", exeELF)
|
||||
}
|
||||
}
|
||||
|
||||
if coreFile.Type != elf.ET_CORE {
|
||||
return nil, nil, fmt.Errorf("%v is not a core file", coreFile)
|
||||
}
|
||||
if exeELF.Type != elf.ET_EXEC && exeELF.Type != elf.ET_DYN {
|
||||
return nil, nil, fmt.Errorf("%v is not an exe file", exeELF)
|
||||
}
|
||||
|
||||
machineType := exeELF.Machine
|
||||
notes, err := readNotes(coreFile, machineType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
memory := buildMemory(coreFile, exeELF, exe, notes)
|
||||
|
||||
// TODO support 386
|
||||
var bi *proc.BinaryInfo
|
||||
switch machineType {
|
||||
case _EM_X86_64:
|
||||
bi = proc.NewBinaryInfo("linux", "amd64")
|
||||
case _EM_AARCH64:
|
||||
bi = proc.NewBinaryInfo("linux", "arm64")
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported machine type")
|
||||
if platformIndependentDelveCore {
|
||||
goos, goarch, err := platformFromNotes(notes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bi = proc.NewBinaryInfo(goos, goarch)
|
||||
} else {
|
||||
switch machineType {
|
||||
case _EM_X86_64:
|
||||
bi = proc.NewBinaryInfo("linux", "amd64")
|
||||
case _EM_AARCH64:
|
||||
bi = proc.NewBinaryInfo("linux", "arm64")
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported machine type")
|
||||
}
|
||||
}
|
||||
|
||||
entryPoint := findEntryPoint(notes, bi.Arch.PtrSize())
|
||||
@ -143,6 +161,11 @@ func readLinuxCore(corePath, exePath string) (*process, proc.Thread, error) {
|
||||
breakpoints: proc.NewBreakpointMap(),
|
||||
}
|
||||
|
||||
if platformIndependentDelveCore {
|
||||
currentThread, err := threadsFromDelveNotes(p, notes)
|
||||
return p, currentThread, err
|
||||
}
|
||||
|
||||
currentThread := linuxThreadsFromNotes(p, notes, machineType)
|
||||
return p, currentThread, nil
|
||||
}
|
||||
@ -193,7 +216,7 @@ type note struct {
|
||||
}
|
||||
|
||||
// readNotes reads all the notes from the notes prog in core.
|
||||
func readNotes(core *elf.File, machineType elf.Machine) ([]*note, error) {
|
||||
func readNotes(core *elf.File, machineType elf.Machine) ([]*note, bool, error) {
|
||||
var notesProg *elf.Prog
|
||||
for _, prog := range core.Progs {
|
||||
if prog.Type == elf.PT_NOTE {
|
||||
@ -203,6 +226,9 @@ func readNotes(core *elf.File, machineType elf.Machine) ([]*note, error) {
|
||||
}
|
||||
|
||||
r := notesProg.Open()
|
||||
hasDelveThread := false
|
||||
hasDelveHeader := false
|
||||
hasElfPrStatus := false
|
||||
notes := []*note{}
|
||||
for {
|
||||
note, err := readNote(r, machineType)
|
||||
@ -210,12 +236,20 @@ func readNotes(core *elf.File, machineType elf.Machine) ([]*note, error) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
switch note.Type {
|
||||
case elfwriter.DelveHeaderNoteType:
|
||||
hasDelveHeader = true
|
||||
case elfwriter.DelveThreadNodeType:
|
||||
hasDelveThread = true
|
||||
case elf.NT_PRSTATUS:
|
||||
hasElfPrStatus = true
|
||||
}
|
||||
notes = append(notes, note)
|
||||
}
|
||||
|
||||
return notes, nil
|
||||
return notes, hasDelveThread && hasDelveHeader && !hasElfPrStatus, nil
|
||||
}
|
||||
|
||||
// readNote reads a single note from r, decoding the descriptor if possible.
|
||||
@ -286,7 +320,7 @@ func readNote(r io.ReadSeeker, machineType elf.Machine) (*note, error) {
|
||||
}
|
||||
note.Desc = &fpregs
|
||||
}
|
||||
case _NT_AUXV:
|
||||
case _NT_AUXV, elfwriter.DelveHeaderNoteType, elfwriter.DelveThreadNodeType:
|
||||
note.Desc = desc
|
||||
case _NT_FPREGSET:
|
||||
if machineType == _EM_AARCH64 {
|
||||
@ -340,6 +374,9 @@ func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*note) proc.Me
|
||||
// Load memory segments from exe and then from the core file,
|
||||
// allowing the corefile to overwrite previously loaded segments
|
||||
for _, elfFile := range []*elf.File{exeELF, core} {
|
||||
if elfFile == nil {
|
||||
continue
|
||||
}
|
||||
for _, prog := range elfFile.Progs {
|
||||
if prog.Type == elf.PT_LOAD {
|
||||
if prog.Filesz == 0 {
|
||||
|
420
pkg/proc/dump.go
Normal file
420
pkg/proc/dump.go
Normal file
@ -0,0 +1,420 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMemoryMapNotSupported = errors.New("MemoryMap not supported")
|
||||
)
|
||||
|
||||
// DumpState represents the current state of a core dump in progress.
|
||||
type DumpState struct {
|
||||
Mutex sync.Mutex
|
||||
|
||||
Dumping bool
|
||||
AllDone bool
|
||||
Canceled bool
|
||||
DoneChan chan struct{}
|
||||
|
||||
ThreadsDone, ThreadsTotal int
|
||||
MemDone, MemTotal uint64
|
||||
|
||||
Err error
|
||||
}
|
||||
|
||||
// DumpFlags is used to configure (*Target).Dump
|
||||
type DumpFlags uint16
|
||||
|
||||
const (
|
||||
DumpPlatformIndependent DumpFlags = 1 << iota // always use platfrom-independent notes format
|
||||
)
|
||||
|
||||
// MemoryMapEntry represent a memory mapping in the target process.
|
||||
type MemoryMapEntry struct {
|
||||
Addr uint64
|
||||
Size uint64
|
||||
|
||||
Read, Write, Exec bool
|
||||
|
||||
Filename string
|
||||
Offset uint64
|
||||
}
|
||||
|
||||
func (state *DumpState) setErr(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
state.Mutex.Lock()
|
||||
if state.Err == nil {
|
||||
state.Err = err
|
||||
}
|
||||
state.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (state *DumpState) setThreadsTotal(n int) {
|
||||
state.Mutex.Lock()
|
||||
state.ThreadsTotal = n
|
||||
state.ThreadsDone = 0
|
||||
state.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (state *DumpState) threadDone() {
|
||||
state.Mutex.Lock()
|
||||
state.ThreadsDone++
|
||||
state.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (state *DumpState) setMemTotal(n uint64) {
|
||||
state.Mutex.Lock()
|
||||
state.MemTotal = n
|
||||
state.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (state *DumpState) memDone(delta uint64) {
|
||||
state.Mutex.Lock()
|
||||
state.MemDone += delta
|
||||
state.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (state *DumpState) isCanceled() bool {
|
||||
state.Mutex.Lock()
|
||||
defer state.Mutex.Unlock()
|
||||
return state.Canceled
|
||||
}
|
||||
|
||||
// Dump writes a core dump to out. State is updated as the core dump is written.
|
||||
func (t *Target) Dump(out elfwriter.WriteCloserSeeker, flags DumpFlags, state *DumpState) {
|
||||
defer func() {
|
||||
state.Mutex.Lock()
|
||||
if ierr := recover(); ierr != nil {
|
||||
state.Err = newInternalError(ierr, 2)
|
||||
}
|
||||
err := out.Close()
|
||||
if state.Err == nil && err != nil {
|
||||
state.Err = fmt.Errorf("error writing output file: %v", err)
|
||||
}
|
||||
state.Dumping = false
|
||||
state.Mutex.Unlock()
|
||||
if state.DoneChan != nil {
|
||||
close(state.DoneChan)
|
||||
}
|
||||
}()
|
||||
|
||||
bi := t.BinInfo()
|
||||
|
||||
var fhdr elf.FileHeader
|
||||
fhdr.Class = elf.ELFCLASS64
|
||||
fhdr.Data = elf.ELFDATA2LSB
|
||||
fhdr.Version = elf.EV_CURRENT
|
||||
|
||||
switch bi.GOOS {
|
||||
case "linux":
|
||||
fhdr.OSABI = elf.ELFOSABI_LINUX
|
||||
case "freebsd":
|
||||
fhdr.OSABI = elf.ELFOSABI_FREEBSD
|
||||
default:
|
||||
// There is no OSABI value for windows or macOS because nobody generates ELF core dumps on those systems.
|
||||
fhdr.OSABI = 0xff
|
||||
}
|
||||
|
||||
fhdr.Type = elf.ET_CORE
|
||||
|
||||
switch bi.Arch.Name {
|
||||
case "amd64":
|
||||
fhdr.Machine = elf.EM_X86_64
|
||||
case "386":
|
||||
fhdr.Machine = elf.EM_386
|
||||
case "arm64":
|
||||
fhdr.Machine = elf.EM_AARCH64
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
fhdr.Entry = 0
|
||||
|
||||
w := elfwriter.New(out, &fhdr)
|
||||
|
||||
notes := []elfwriter.Note{}
|
||||
|
||||
entryPoint, err := t.EntryPoint()
|
||||
if err != nil {
|
||||
state.setErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
notes = append(notes, elfwriter.Note{
|
||||
Type: elfwriter.DelveHeaderNoteType,
|
||||
Name: "Delve Header",
|
||||
Data: []byte(fmt.Sprintf("%s/%s\n%s\n%s%d\n%s%#x\n", bi.GOOS, bi.Arch.Name, version.DelveVersion.String(), elfwriter.DelveHeaderTargetPidPrefix, t.Pid(), elfwriter.DelveHeaderEntryPointPrefix, entryPoint)),
|
||||
})
|
||||
|
||||
threads := t.ThreadList()
|
||||
state.setThreadsTotal(len(threads))
|
||||
|
||||
var threadsDone bool
|
||||
|
||||
if flags&DumpPlatformIndependent == 0 {
|
||||
threadsDone, notes, err = t.proc.DumpProcessNotes(notes, state.threadDone)
|
||||
if err != nil {
|
||||
state.setErr(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !threadsDone {
|
||||
for _, th := range threads {
|
||||
if w.Err != nil {
|
||||
state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
|
||||
return
|
||||
}
|
||||
if state.isCanceled() {
|
||||
return
|
||||
}
|
||||
notes = t.dumpThreadNotes(notes, state, th)
|
||||
state.threadDone()
|
||||
}
|
||||
}
|
||||
|
||||
memmap, err := t.proc.MemoryMap()
|
||||
if err != nil {
|
||||
state.setErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
memmapFilter := make([]MemoryMapEntry, 0, len(memmap))
|
||||
memtot := uint64(0)
|
||||
for i := range memmap {
|
||||
mme := &memmap[i]
|
||||
if t.shouldDumpMemory(mme) {
|
||||
memmapFilter = append(memmapFilter, *mme)
|
||||
memtot += mme.Size
|
||||
}
|
||||
}
|
||||
|
||||
state.setMemTotal(memtot)
|
||||
|
||||
for i := range memmapFilter {
|
||||
mme := &memmapFilter[i]
|
||||
if w.Err != nil {
|
||||
state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
|
||||
return
|
||||
}
|
||||
if state.isCanceled() {
|
||||
return
|
||||
}
|
||||
t.dumpMemory(state, w, mme)
|
||||
}
|
||||
|
||||
notesProg := w.WriteNotes(notes)
|
||||
w.Progs = append(w.Progs, notesProg)
|
||||
w.WriteProgramHeaders()
|
||||
if w.Err != nil {
|
||||
state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
|
||||
}
|
||||
state.Mutex.Lock()
|
||||
state.AllDone = true
|
||||
state.Mutex.Unlock()
|
||||
}
|
||||
|
||||
// dumpThreadNotes appends notes describing a thread (thread id and its
|
||||
// registers) using a platform-independent format.
|
||||
func (t *Target) dumpThreadNotes(notes []elfwriter.Note, state *DumpState, th Thread) []elfwriter.Note {
|
||||
// If the backend doesn't provide a way to dump a thread we use a custom format for the note:
|
||||
// - thread_id (8 bytes)
|
||||
// - pc value (8 bytes)
|
||||
// - sp value (8 bytes)
|
||||
// - bp value (8 bytes)
|
||||
// - tls value (8 bytes)
|
||||
// - has_gaddr (1 byte)
|
||||
// - gaddr value (8 bytes)
|
||||
// - num_registers (4 bytes)
|
||||
// Followed by a list of num_register, each as follows:
|
||||
// - register_name_len (2 bytes)
|
||||
// - register_name (register_name_len bytes)
|
||||
// - register_data_len (2 bytes)
|
||||
// - register_data (regiter_data_len bytes)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, binary.LittleEndian, uint64(th.ThreadID()))
|
||||
|
||||
regs, err := th.Registers()
|
||||
if err != nil {
|
||||
state.setErr(err)
|
||||
return notes
|
||||
}
|
||||
|
||||
for _, specialReg := range []uint64{regs.PC(), regs.SP(), regs.BP(), regs.TLS()} {
|
||||
binary.Write(buf, binary.LittleEndian, specialReg)
|
||||
}
|
||||
|
||||
gaddr, hasGaddr := regs.GAddr()
|
||||
binary.Write(buf, binary.LittleEndian, hasGaddr)
|
||||
binary.Write(buf, binary.LittleEndian, gaddr)
|
||||
|
||||
regsv, err := regs.Slice(true)
|
||||
if err != nil {
|
||||
state.setErr(err)
|
||||
return notes
|
||||
}
|
||||
|
||||
binary.Write(buf, binary.LittleEndian, uint32(len(regsv)))
|
||||
|
||||
for _, reg := range regsv {
|
||||
binary.Write(buf, binary.LittleEndian, uint16(len(reg.Name)))
|
||||
buf.Write([]byte(reg.Name))
|
||||
if reg.Reg.Bytes != nil {
|
||||
binary.Write(buf, binary.LittleEndian, uint16(len(reg.Reg.Bytes)))
|
||||
buf.Write(reg.Reg.Bytes)
|
||||
} else {
|
||||
binary.Write(buf, binary.LittleEndian, uint16(8))
|
||||
binary.Write(buf, binary.LittleEndian, reg.Reg.Uint64Val)
|
||||
}
|
||||
}
|
||||
|
||||
return append(notes, elfwriter.Note{
|
||||
Type: elfwriter.DelveThreadNodeType,
|
||||
Name: "",
|
||||
Data: buf.Bytes(),
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Target) dumpMemory(state *DumpState, w *elfwriter.Writer, mme *MemoryMapEntry) {
|
||||
var flags elf.ProgFlag
|
||||
if mme.Read {
|
||||
flags |= elf.PF_R
|
||||
}
|
||||
if mme.Write {
|
||||
flags |= elf.PF_W
|
||||
}
|
||||
if mme.Exec {
|
||||
flags |= elf.PF_X
|
||||
}
|
||||
|
||||
w.Progs = append(w.Progs, &elf.ProgHeader{
|
||||
Type: elf.PT_LOAD,
|
||||
Flags: flags,
|
||||
Off: uint64(w.Here()),
|
||||
Vaddr: mme.Addr,
|
||||
Paddr: 0,
|
||||
Filesz: mme.Size,
|
||||
Memsz: mme.Size,
|
||||
Align: 0,
|
||||
})
|
||||
|
||||
buf := make([]byte, 1024*1024)
|
||||
addr := mme.Addr
|
||||
sz := mme.Size
|
||||
mem := t.Memory()
|
||||
|
||||
for sz > 0 {
|
||||
if w.Err != nil {
|
||||
state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
|
||||
return
|
||||
}
|
||||
if state.isCanceled() {
|
||||
return
|
||||
}
|
||||
chunk := buf
|
||||
if uint64(len(chunk)) > sz {
|
||||
chunk = chunk[:sz]
|
||||
}
|
||||
n, err := mem.ReadMemory(chunk, addr)
|
||||
for i := n; i < len(chunk); i++ {
|
||||
chunk[i] = 0
|
||||
}
|
||||
// Errors and short reads are ignored, the most likely reason is that
|
||||
// (*ProcessInternal).MemoryMap gave us a bad mapping that can't be read
|
||||
// and the behavior that's maximally useful to the user is to generate an
|
||||
// incomplete dump.
|
||||
w.Write(chunk)
|
||||
addr += uint64(len(chunk))
|
||||
sz -= uint64(len(chunk))
|
||||
if err == nil {
|
||||
state.memDone(uint64(len(chunk)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Target) shouldDumpMemory(mme *MemoryMapEntry) bool {
|
||||
if !mme.Read {
|
||||
return false
|
||||
}
|
||||
exeimg := t.BinInfo().Images[0]
|
||||
if mme.Write || mme.Filename == "" || mme.Filename != exeimg.Path {
|
||||
return true
|
||||
}
|
||||
isgo := false
|
||||
for _, cu := range exeimg.compileUnits {
|
||||
if cu.isgo {
|
||||
isgo = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isgo {
|
||||
return true
|
||||
}
|
||||
|
||||
exe, err := elf.Open(exeimg.Path)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if exe.Type != elf.ET_EXEC {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, prog := range exe.Progs {
|
||||
if prog.Type == elf.PT_LOAD && (prog.Flags&elf.PF_W == 0) && (prog.Flags&elf.PF_R != 0) && (prog.Vaddr == mme.Addr) && (prog.Memsz == mme.Size) && (prog.Off == mme.Offset) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type internalError struct {
|
||||
Err interface{}
|
||||
Stack []internalErrorFrame
|
||||
}
|
||||
|
||||
type internalErrorFrame struct {
|
||||
Pc uintptr
|
||||
Func string
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
func newInternalError(ierr interface{}, skip int) *internalError {
|
||||
r := &internalError{ierr, nil}
|
||||
for i := skip; ; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fname := "<unknown>"
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn != nil {
|
||||
fname = fn.Name()
|
||||
}
|
||||
r.Stack = append(r.Stack, internalErrorFrame{pc, fname, file, line})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (err *internalError) Error() string {
|
||||
var out bytes.Buffer
|
||||
fmt.Fprintf(&out, "Internal debugger error: %v\n", err.Err)
|
||||
for _, frame := range err.Stack {
|
||||
fmt.Fprintf(&out, "%s (%#x)\n\t%s:%d\n", frame.Func, frame.Pc, frame.File, frame.Line)
|
||||
}
|
||||
return out.String()
|
||||
}
|
@ -80,6 +80,7 @@ import (
|
||||
"golang.org/x/arch/arm64/arm64asm"
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||
@ -650,7 +651,8 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
|
||||
Path: path,
|
||||
DebugInfoDirs: debugInfoDirs,
|
||||
DisableAsyncPreempt: runtime.GOOS == "darwin",
|
||||
StopReason: stopReason})
|
||||
StopReason: stopReason,
|
||||
CanDump: runtime.GOOS == "darwin"})
|
||||
if err != nil {
|
||||
p.conn.conn.Close()
|
||||
return nil, err
|
||||
@ -1456,6 +1458,37 @@ func (p *gdbProcess) loadGInstr() []byte {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (p *gdbProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
||||
r := []proc.MemoryMapEntry{}
|
||||
addr := uint64(0)
|
||||
for addr != ^uint64(0) {
|
||||
mri, err := p.conn.memoryRegionInfo(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addr+mri.size <= addr {
|
||||
return nil, errors.New("qMemoryRegionInfo response wrapped around the address space or stuck")
|
||||
}
|
||||
if mri.permissions != "" {
|
||||
var mme proc.MemoryMapEntry
|
||||
|
||||
mme.Addr = addr
|
||||
mme.Size = mri.size
|
||||
mme.Read = strings.Contains(mri.permissions, "r")
|
||||
mme.Write = strings.Contains(mri.permissions, "w")
|
||||
mme.Exec = strings.Contains(mri.permissions, "x")
|
||||
|
||||
r = append(r, mme)
|
||||
}
|
||||
addr += mri.size
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (p *gdbProcess) DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (threadsDone bool, out []elfwriter.Note, err error) {
|
||||
return false, notes, nil
|
||||
}
|
||||
|
||||
func (regs *gdbRegisters) init(regsInfo []gdbRegisterInfo, arch *proc.Arch) {
|
||||
regs.arch = arch
|
||||
regs.regs = make(map[string]gdbRegister)
|
||||
|
@ -44,6 +44,7 @@ type gdbConn struct {
|
||||
maxTransmitAttempts int // maximum number of transmit or receive attempts when bad checksums are read
|
||||
threadSuffixSupported bool // thread suffix supported by stub
|
||||
isDebugserver bool // true if the stub is debugserver
|
||||
xcmdok bool // x command can be used to transfer memory
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
@ -163,6 +164,10 @@ func (conn *gdbConn) handshake() error {
|
||||
}
|
||||
}
|
||||
|
||||
if resp, err := conn.exec([]byte("$x0,0"), "init"); err == nil && string(resp) == "OK" {
|
||||
conn.xcmdok = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -871,8 +876,15 @@ func (conn *gdbConn) appendThreadSelector(threadID string) {
|
||||
fmt.Fprintf(&conn.outbuf, ";thread:%s;", threadID)
|
||||
}
|
||||
|
||||
// executes 'm' (read memory) command
|
||||
func (conn *gdbConn) readMemory(data []byte, addr uint64) error {
|
||||
if conn.xcmdok && len(data) > conn.packetSize {
|
||||
return conn.readMemoryBinary(data, addr)
|
||||
}
|
||||
return conn.readMemoryHex(data, addr)
|
||||
}
|
||||
|
||||
// executes 'm' (read memory) command
|
||||
func (conn *gdbConn) readMemoryHex(data []byte, addr uint64) error {
|
||||
size := len(data)
|
||||
data = data[:0]
|
||||
|
||||
@ -900,6 +912,29 @@ func (conn *gdbConn) readMemory(data []byte, addr uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executes 'x' (binary read memory) command
|
||||
func (conn *gdbConn) readMemoryBinary(data []byte, addr uint64) error {
|
||||
size := len(data)
|
||||
data = data[:0]
|
||||
|
||||
for len(data) < size {
|
||||
conn.outbuf.Reset()
|
||||
|
||||
sz := size - len(data)
|
||||
|
||||
fmt.Fprintf(&conn.outbuf, "$x%x,%x", addr+uint64(len(data)), sz)
|
||||
if err := conn.send(conn.outbuf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := conn.recv(conn.outbuf.Bytes(), "binary memory read", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, resp...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAsciiBytes(w io.Writer, data []byte) {
|
||||
for _, b := range data {
|
||||
fmt.Fprintf(w, "%02x", b)
|
||||
@ -1016,6 +1051,87 @@ func (conn *gdbConn) getLoadedDynamicLibraries() ([]imageDescription, error) {
|
||||
return images.Images, err
|
||||
}
|
||||
|
||||
type memoryRegionInfo struct {
|
||||
start uint64
|
||||
size uint64
|
||||
permissions string
|
||||
name string
|
||||
}
|
||||
|
||||
func decodeHexString(in []byte) (string, bool) {
|
||||
out := make([]byte, 0, len(in)/2)
|
||||
for i := 0; i < len(in); i += 2 {
|
||||
v, err := strconv.ParseUint(string(in[i:i+2]), 16, 8)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
out = append(out, byte(v))
|
||||
}
|
||||
return string(out), true
|
||||
}
|
||||
|
||||
func (conn *gdbConn) memoryRegionInfo(addr uint64) (*memoryRegionInfo, error) {
|
||||
conn.outbuf.Reset()
|
||||
fmt.Fprintf(&conn.outbuf, "$qMemoryRegionInfo:%x", addr)
|
||||
resp, err := conn.exec(conn.outbuf.Bytes(), "qMemoryRegionInfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mri := &memoryRegionInfo{}
|
||||
|
||||
buf := resp
|
||||
for len(buf) > 0 {
|
||||
colon := bytes.Index(buf, []byte{':'})
|
||||
if colon < 0 {
|
||||
break
|
||||
}
|
||||
key := buf[:colon]
|
||||
buf = buf[colon+1:]
|
||||
|
||||
semicolon := bytes.Index(buf, []byte{';'})
|
||||
var value []byte
|
||||
if semicolon < 0 {
|
||||
value = buf
|
||||
buf = nil
|
||||
} else {
|
||||
value = buf[:semicolon]
|
||||
buf = buf[semicolon+1:]
|
||||
}
|
||||
|
||||
switch string(key) {
|
||||
case "start":
|
||||
start, err := strconv.ParseUint(string(value), 16, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (start): %v in %s", err, string(resp))
|
||||
}
|
||||
mri.start = start
|
||||
case "size":
|
||||
size, err := strconv.ParseUint(string(value), 16, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (size): %v in %s", err, string(resp))
|
||||
}
|
||||
mri.size = size
|
||||
case "permissions":
|
||||
mri.permissions = string(value)
|
||||
case "name":
|
||||
namestr, ok := decodeHexString(value)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (name): %s", string(resp))
|
||||
}
|
||||
mri.name = namestr
|
||||
case "error":
|
||||
errstr, ok := decodeHexString(value)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("malformed qMemoryRegionInfo response packet (error): %s", string(resp))
|
||||
}
|
||||
return nil, fmt.Errorf("qMemoryRegionInfo error: %s", errstr)
|
||||
}
|
||||
}
|
||||
|
||||
return mri, nil
|
||||
}
|
||||
|
||||
// exec executes a message to the stub and reads a response.
|
||||
// The details of the wire protocol are described here:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
|
||||
|
@ -1,5 +1,7 @@
|
||||
package proc
|
||||
|
||||
import "github.com/go-delve/delve/pkg/elfwriter"
|
||||
|
||||
// Process represents the target of the debugger. This
|
||||
// target could be a system process, core file, etc.
|
||||
//
|
||||
@ -35,6 +37,12 @@ type ProcessInternal interface {
|
||||
|
||||
WriteBreakpoint(*Breakpoint) error
|
||||
EraseBreakpoint(*Breakpoint) error
|
||||
|
||||
// DumpProcessNotes returns ELF core notes describing the process and its threads.
|
||||
// Implementing this method is optional.
|
||||
DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (bool, []elfwriter.Note, error)
|
||||
// MemoryMap returns the memory map of the target process. This method must be implemented if CanDump is true.
|
||||
MemoryMap() ([]MemoryMapEntry, error)
|
||||
}
|
||||
|
||||
// RecordingManipulation is an interface for manipulating process recordings.
|
||||
|
120
pkg/proc/native/dump_linux.go
Normal file
120
pkg/proc/native/dump_linux.go
Normal file
@ -0,0 +1,120 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
func (p *nativeProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
||||
const VmFlagsPrefix = "VmFlags:"
|
||||
|
||||
smapsbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/smaps", p.Pid()))
|
||||
if err != nil {
|
||||
// Older versions of Linux don't have smaps but have maps which is in a similar format.
|
||||
smapsbuf, err = ioutil.ReadFile(fmt.Sprintf("/proc/%d/maps", p.Pid()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
smapsLines := strings.Split(string(smapsbuf), "\n")
|
||||
r := make([]proc.MemoryMapEntry, 0)
|
||||
|
||||
smapsLinesLoop:
|
||||
for i := 0; i < len(smapsLines); {
|
||||
line := smapsLines[i]
|
||||
if line == "" {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
start, end, perm, offset, dev, filename, err := parseSmapsHeaderLine(i+1, line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vmflags []string
|
||||
for i++; i < len(smapsLines); i++ {
|
||||
line := smapsLines[i]
|
||||
if line == "" || line[0] < 'A' || line[0] > 'Z' {
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(line, VmFlagsPrefix) {
|
||||
vmflags = strings.Split(strings.TrimSpace(line[len(VmFlagsPrefix):]), " ")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range vmflags {
|
||||
switch vmflags[i] {
|
||||
case "dd":
|
||||
// "don't dump"
|
||||
continue smapsLinesLoop
|
||||
case "io":
|
||||
continue smapsLinesLoop
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(dev, "00:") {
|
||||
filename = ""
|
||||
offset = 0
|
||||
}
|
||||
|
||||
r = append(r, proc.MemoryMapEntry{
|
||||
Addr: start,
|
||||
Size: end - start,
|
||||
|
||||
Read: perm[0] == 'r',
|
||||
Write: perm[1] == 'w',
|
||||
Exec: perm[2] == 'x',
|
||||
|
||||
Filename: filename,
|
||||
Offset: offset,
|
||||
})
|
||||
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func parseSmapsHeaderLine(lineno int, in string) (start, end uint64, perm string, offset uint64, dev, filename string, err error) {
|
||||
fields := strings.SplitN(in, " ", 6)
|
||||
if len(fields) != 6 {
|
||||
err = fmt.Errorf("malformed /proc/pid/maps on line %d: %q (wrong number of fields)", lineno, in)
|
||||
return
|
||||
}
|
||||
|
||||
v := strings.Split(fields[0], "-")
|
||||
if len(v) != 2 {
|
||||
err = fmt.Errorf("malformed /proc/pid/maps on line %d: %q (bad first field)", lineno, in)
|
||||
return
|
||||
}
|
||||
start, err = strconv.ParseUint(v[0], 16, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("malformed /proc/pid/maps on line %d: %q (%v)", lineno, in, err)
|
||||
return
|
||||
}
|
||||
end, err = strconv.ParseUint(v[1], 16, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("malformed /proc/pid/maps on line %d: %q (%v)", lineno, in, err)
|
||||
return
|
||||
}
|
||||
|
||||
perm = fields[1]
|
||||
if len(perm) < 4 {
|
||||
err = fmt.Errorf("malformed /proc/pid/maps on line %d: %q (permissions column too short)", lineno, in)
|
||||
return
|
||||
}
|
||||
|
||||
offset, err = strconv.ParseUint(fields[2], 16, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("malformed /proc/pid/maps on line %d: %q (%v)", lineno, in, err)
|
||||
return
|
||||
}
|
||||
|
||||
dev = fields[3]
|
||||
|
||||
// fields[4] -> inode
|
||||
|
||||
filename = strings.TrimLeft(fields[5], " ")
|
||||
return
|
||||
|
||||
}
|
152
pkg/proc/native/dump_linux_amd64.go
Normal file
152
pkg/proc/native/dump_linux_amd64.go
Normal file
@ -0,0 +1,152 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const _NT_AUXV elf.NType = 0x6
|
||||
|
||||
type linuxPrPsInfo struct {
|
||||
State uint8
|
||||
Sname int8
|
||||
Zomb uint8
|
||||
Nice int8
|
||||
_ [4]uint8
|
||||
Flag uint64
|
||||
Uid, Gid uint32
|
||||
Pid, Ppid, Pgrp, Sid int32
|
||||
Fname [16]uint8
|
||||
Args [80]uint8
|
||||
}
|
||||
|
||||
func (p *nativeProcess) DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (threadsDone bool, out []elfwriter.Note, err error) {
|
||||
tobytes := func(x interface{}) []byte {
|
||||
out := new(bytes.Buffer)
|
||||
_ = binary.Write(out, binary.LittleEndian, x)
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
prpsinfo := linuxPrPsInfo{
|
||||
Pid: int32(p.pid),
|
||||
}
|
||||
|
||||
fname := p.os.comm
|
||||
if len(fname) > len(prpsinfo.Fname)-1 {
|
||||
fname = fname[:len(prpsinfo.Fname)-1]
|
||||
}
|
||||
copy(prpsinfo.Fname[:], fname)
|
||||
prpsinfo.Fname[len(fname)] = 0
|
||||
|
||||
if cmdline, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", p.pid)); err == nil {
|
||||
for len(cmdline) > 0 && cmdline[len(cmdline)-1] == '\n' {
|
||||
cmdline = cmdline[:len(cmdline)-1]
|
||||
}
|
||||
if zero := bytes.Index(cmdline, []byte{0}); zero >= 0 {
|
||||
cmdline = cmdline[zero+1:]
|
||||
}
|
||||
path := p.BinInfo().Images[0].Path
|
||||
if abs, err := filepath.Abs(path); err == nil {
|
||||
path = abs
|
||||
}
|
||||
args := make([]byte, 0, len(path)+len(cmdline)+1)
|
||||
args = append(args, []byte(path)...)
|
||||
args = append(args, 0)
|
||||
args = append(args, cmdline...)
|
||||
if len(args) > len(prpsinfo.Args)-1 {
|
||||
args = args[:len(prpsinfo.Args)-1]
|
||||
}
|
||||
copy(prpsinfo.Args[:], args)
|
||||
prpsinfo.Args[len(args)] = 0
|
||||
}
|
||||
notes = append(notes, elfwriter.Note{
|
||||
Type: elf.NT_PRPSINFO,
|
||||
Data: tobytes(prpsinfo),
|
||||
})
|
||||
|
||||
auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", p.pid))
|
||||
if err == nil {
|
||||
notes = append(notes, elfwriter.Note{
|
||||
Type: _NT_AUXV,
|
||||
Data: auxvbuf,
|
||||
})
|
||||
}
|
||||
|
||||
for _, th := range p.threads {
|
||||
regs, err := th.Registers()
|
||||
if err != nil {
|
||||
return false, notes, err
|
||||
}
|
||||
|
||||
regs, err = regs.Copy() // triggers floating point register load
|
||||
if err != nil {
|
||||
return false, notes, err
|
||||
}
|
||||
|
||||
nregs := regs.(*linutil.AMD64Registers)
|
||||
|
||||
var prstatus linuxPrStatusAMD64
|
||||
prstatus.Pid = int32(th.ID)
|
||||
prstatus.Ppid = int32(p.pid)
|
||||
prstatus.Pgrp = int32(p.pid)
|
||||
prstatus.Sid = int32(p.pid)
|
||||
prstatus.Reg = *(nregs.Regs)
|
||||
notes = append(notes, elfwriter.Note{
|
||||
Type: elf.NT_PRSTATUS,
|
||||
Data: tobytes(prstatus),
|
||||
})
|
||||
|
||||
var xsave []byte
|
||||
|
||||
if nregs.Fpregset != nil && nregs.Fpregset.Xsave != nil {
|
||||
xsave = make([]byte, len(nregs.Fpregset.Xsave))
|
||||
copy(xsave, nregs.Fpregset.Xsave)
|
||||
} else {
|
||||
xsave = make([]byte, 512+64) // XSAVE header start + XSAVE header length
|
||||
}
|
||||
|
||||
// Even if we have the XSAVE area on some versions of linux (or some CPU
|
||||
// models?) it won't contain the legacy x87 registers, so copy them over
|
||||
// in case we got them from PTRACE_GETFPREGS.
|
||||
buf := new(bytes.Buffer)
|
||||
binary.Write(buf, binary.LittleEndian, &nregs.Fpregset.AMD64PtraceFpRegs)
|
||||
copy(xsave, buf.Bytes())
|
||||
|
||||
notes = append(notes, elfwriter.Note{
|
||||
Type: _NT_X86_XSTATE,
|
||||
Data: xsave,
|
||||
})
|
||||
|
||||
threadDone()
|
||||
}
|
||||
|
||||
return true, notes, nil
|
||||
}
|
||||
|
||||
type linuxPrStatusAMD64 struct {
|
||||
Siginfo linuxSiginfo
|
||||
Cursig uint16
|
||||
_ [2]uint8
|
||||
Sigpend uint64
|
||||
Sighold uint64
|
||||
Pid, Ppid, Pgrp, Sid int32
|
||||
Utime, Stime, CUtime, CStime unix.Timeval
|
||||
Reg linutil.AMD64PtraceRegs
|
||||
Fpvalid int64
|
||||
}
|
||||
|
||||
// LinuxSiginfo is a copy of the
|
||||
// siginfo kernel struct.
|
||||
type linuxSiginfo struct {
|
||||
Signo int32
|
||||
Code int32
|
||||
Errno int32
|
||||
}
|
11
pkg/proc/native/dump_linux_other.go
Normal file
11
pkg/proc/native/dump_linux_other.go
Normal file
@ -0,0 +1,11 @@
|
||||
//+build linux,!amd64
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
)
|
||||
|
||||
func (p *nativeProcess) DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (threadsDone bool, out []elfwriter.Note, err error) {
|
||||
return false, notes, nil
|
||||
}
|
16
pkg/proc/native/dump_other.go
Normal file
16
pkg/proc/native/dump_other.go
Normal file
@ -0,0 +1,16 @@
|
||||
//+build freebsd,amd64 darwin
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
func (p *nativeProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
||||
return nil, proc.ErrMemoryMapNotSupported
|
||||
}
|
||||
|
||||
func (p *nativeProcess) DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (threadsDone bool, notesout []elfwriter.Note, err error) {
|
||||
return false, notes, nil
|
||||
}
|
91
pkg/proc/native/dump_windows_amd64.go
Normal file
91
pkg/proc/native/dump_windows_amd64.go
Normal file
@ -0,0 +1,91 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-delve/delve/pkg/elfwriter"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
func (p *nativeProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
||||
var memoryMapError error
|
||||
r := []proc.MemoryMapEntry{}
|
||||
|
||||
p.execPtraceFunc(func() {
|
||||
is64 := true
|
||||
if isWow64 := uint32(0); _IsWow64Process(p.os.hProcess, &isWow64) != 0 {
|
||||
if isWow64 != 0 {
|
||||
is64 = false
|
||||
}
|
||||
}
|
||||
|
||||
maxaddr := uint64(1 << 48) // windows64 uses only 48 bit addresses
|
||||
if !is64 {
|
||||
maxaddr = uint64(^uint32(0))
|
||||
}
|
||||
|
||||
var meminfo _MEMORY_BASIC_INFORMATION
|
||||
|
||||
for addr := uint64(0); addr < maxaddr; addr += meminfo.RegionSize {
|
||||
size := _VirtualQueryEx(p.os.hProcess, uintptr(addr), &meminfo, unsafe.Sizeof(meminfo))
|
||||
if size == 0 {
|
||||
// size == 0 is an error and the only error returned by VirtualQueryEx
|
||||
// is when addr is above the highest address allocated for the
|
||||
// application.
|
||||
return
|
||||
}
|
||||
if size != unsafe.Sizeof(meminfo) {
|
||||
memoryMapError = fmt.Errorf("bad size returned by _VirtualQueryEx: %d (expected %d)", size, unsafe.Sizeof(meminfo))
|
||||
return
|
||||
}
|
||||
if addr+meminfo.RegionSize <= addr {
|
||||
// this shouldn't happen
|
||||
memoryMapError = errors.New("VirtualQueryEx wrapped around the address space or stuck")
|
||||
return
|
||||
}
|
||||
if meminfo.State == _MEM_FREE || meminfo.State == _MEM_RESERVE {
|
||||
continue
|
||||
}
|
||||
if meminfo.Protect&_PAGE_GUARD != 0 {
|
||||
// reading from this range will result in an error.
|
||||
continue
|
||||
}
|
||||
|
||||
var mme proc.MemoryMapEntry
|
||||
mme.Addr = addr
|
||||
mme.Size = meminfo.RegionSize
|
||||
|
||||
switch meminfo.Protect & 0xff {
|
||||
case _PAGE_EXECUTE:
|
||||
mme.Exec = true
|
||||
case _PAGE_EXECUTE_READ:
|
||||
mme.Exec = true
|
||||
mme.Read = true
|
||||
case _PAGE_EXECUTE_READWRITE:
|
||||
mme.Exec = true
|
||||
mme.Read = true
|
||||
mme.Write = true
|
||||
case _PAGE_EXECUTE_WRITECOPY:
|
||||
mme.Exec = true
|
||||
mme.Read = true
|
||||
case _PAGE_NOACCESS:
|
||||
case _PAGE_READONLY:
|
||||
mme.Read = true
|
||||
case _PAGE_READWRITE:
|
||||
mme.Read = true
|
||||
mme.Write = true
|
||||
case _PAGE_WRITECOPY:
|
||||
mme.Read = true
|
||||
}
|
||||
r = append(r, mme)
|
||||
}
|
||||
})
|
||||
|
||||
return r, memoryMapError
|
||||
}
|
||||
|
||||
func (p *nativeProcess) DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (threadsDone bool, out []elfwriter.Note, err error) {
|
||||
return false, notes, nil
|
||||
}
|
@ -25,13 +25,13 @@ type nativeProcess struct {
|
||||
// Thread used to read and write memory
|
||||
memthread *nativeThread
|
||||
|
||||
os *osProcessDetails
|
||||
firstStart bool
|
||||
resumeChan chan<- struct{}
|
||||
ptraceChan chan func()
|
||||
ptraceDoneChan chan interface{}
|
||||
childProcess bool // this process was launched, not attached to
|
||||
stopMu sync.Mutex // protects manualStopRequested
|
||||
os *osProcessDetails
|
||||
firstStart bool
|
||||
resumeChan chan<- struct{}
|
||||
ptraceChan chan func()
|
||||
ptraceDoneChan chan interface{}
|
||||
childProcess bool // this process was launched, not attached to
|
||||
stopMu sync.Mutex // protects manualStopRequested
|
||||
// manualStopRequested is set if all the threads in the process were
|
||||
// signalled to stop as a result of a Halt API call. Used to disambiguate
|
||||
// why a thread is found to have stopped.
|
||||
@ -285,7 +285,8 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc
|
||||
Path: path,
|
||||
DebugInfoDirs: debugInfoDirs,
|
||||
DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd",
|
||||
StopReason: stopReason})
|
||||
StopReason: stopReason,
|
||||
CanDump: runtime.GOOS == "linux"})
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) handlePtraceFuncs() {
|
||||
|
@ -28,9 +28,10 @@ func ptraceGetRegset(tid int) (regset amd64util.AMD64Xstate, err error) {
|
||||
iov := sys.Iovec{Base: &xstateargs[0], Len: uint32(len(xstateargs))}
|
||||
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
||||
if err != syscall.Errno(0) {
|
||||
if err == syscall.ENODEV || err == syscall.EIO {
|
||||
if err == syscall.ENODEV || err == syscall.EIO || err == syscall.EINVAL {
|
||||
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/go-delve/delve/issues/1022
|
||||
// also ignore EIO, it means that we are running on an old kernel (pre 2.6.34) and PTRACE_GETREGSET is not implemented
|
||||
// also ignore EINVAL, it means the kernel itself does not support the NT_X86_XSTATE argument (but does support PTRACE_GETREGSET)
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
|
@ -25,9 +25,10 @@ func ptraceGetRegset(tid int) (regset amd64util.AMD64Xstate, err error) {
|
||||
iov := sys.Iovec{Base: &xstateargs[0], Len: uint64(len(xstateargs))}
|
||||
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
||||
if err != syscall.Errno(0) {
|
||||
if err == syscall.ENODEV || err == syscall.EIO {
|
||||
if err == syscall.ENODEV || err == syscall.EIO || err == syscall.EINVAL {
|
||||
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/go-delve/delve/issues/1022
|
||||
// also ignore EIO, it means that we are running on an old kernel (pre 2.6.34) and PTRACE_GETREGSET is not implemented
|
||||
// also ignore EINVAL, it means the kernel itself does not support the NT_X86_XSTATE argument (but does support PTRACE_GETREGSET)
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
|
@ -70,6 +70,17 @@ type _EXCEPTION_RECORD struct {
|
||||
ExceptionInformation [_EXCEPTION_MAXIMUM_PARAMETERS]uintptr
|
||||
}
|
||||
|
||||
type _MEMORY_BASIC_INFORMATION struct {
|
||||
BaseAddress uintptr
|
||||
AllocationBase uintptr
|
||||
AllocationProtect uint32
|
||||
PartitionId uint16
|
||||
RegionSize uint64
|
||||
State uint32
|
||||
Protect uint32
|
||||
Type uint32
|
||||
}
|
||||
|
||||
const (
|
||||
_ThreadBasicInformation = 0
|
||||
|
||||
@ -93,6 +104,20 @@ const (
|
||||
_EXCEPTION_SINGLE_STEP = 0x80000004
|
||||
|
||||
_EXCEPTION_MAXIMUM_PARAMETERS = 15
|
||||
|
||||
_MEM_FREE = 0x10000
|
||||
_MEM_RESERVE = 0x2000
|
||||
|
||||
_PAGE_EXECUTE = 0x10
|
||||
_PAGE_EXECUTE_READ = 0x20
|
||||
_PAGE_EXECUTE_READWRITE = 0x40
|
||||
_PAGE_EXECUTE_WRITECOPY = 0x80
|
||||
_PAGE_NOACCESS = 0x01
|
||||
_PAGE_READONLY = 0x02
|
||||
_PAGE_READWRITE = 0x04
|
||||
_PAGE_WRITECOPY = 0x08
|
||||
|
||||
_PAGE_GUARD = 0x100
|
||||
)
|
||||
|
||||
func _NT_SUCCESS(x _NTSTATUS) bool {
|
||||
@ -117,3 +142,5 @@ type _CONTEXT = winutil.CONTEXT
|
||||
//sys _DebugActiveProcess(processid uint32) (err error) = kernel32.DebugActiveProcess
|
||||
//sys _DebugActiveProcessStop(processid uint32) (err error) = kernel32.DebugActiveProcessStop
|
||||
//sys _QueryFullProcessImageName(process syscall.Handle, flags uint32, exename *uint16, size *uint32) (err error) = kernel32.QueryFullProcessImageNameW
|
||||
//sys _VirtualQueryEx(process syscall.Handle, addr uintptr, buffer *_MEMORY_BASIC_INFORMATION, length uintptr) (lengthOut uintptr) = kernel32.VirtualQueryEx
|
||||
//sys _IsWow64Process(process syscall.Handle, wow64process *uint32) (ok uint32) = kernel32.IsWow64Process
|
||||
|
@ -1,17 +1,44 @@
|
||||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
modntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
|
||||
procNtQueryInformationThread = modntdll.NewProc("NtQueryInformationThread")
|
||||
dbgUiRemoteBreakin = modntdll.NewProc("DbgUiRemoteBreakin")
|
||||
@ -27,6 +54,8 @@ var (
|
||||
procDebugActiveProcess = modkernel32.NewProc("DebugActiveProcess")
|
||||
procDebugActiveProcessStop = modkernel32.NewProc("DebugActiveProcessStop")
|
||||
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
|
||||
procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
|
||||
procIsWow64Process = modkernel32.NewProc("IsWow64Process")
|
||||
)
|
||||
|
||||
func _NtQueryInformationThread(threadHandle syscall.Handle, infoclass int32, info uintptr, infolen uint32, retlen *uint32) (status _NTSTATUS) {
|
||||
@ -39,7 +68,7 @@ func _GetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetThreadContext.Addr(), 2, uintptr(thread), uintptr(unsafe.Pointer(context)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -51,7 +80,7 @@ func _SetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetThreadContext.Addr(), 2, uintptr(thread), uintptr(unsafe.Pointer(context)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -64,7 +93,7 @@ func _SuspendThread(threadid syscall.Handle) (prevsuspcount uint32, err error) {
|
||||
prevsuspcount = uint32(r0)
|
||||
if prevsuspcount == 0xffffffff {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -77,7 +106,7 @@ func _ResumeThread(threadid syscall.Handle) (prevsuspcount uint32, err error) {
|
||||
prevsuspcount = uint32(r0)
|
||||
if prevsuspcount == 0xffffffff {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -89,7 +118,7 @@ func _ContinueDebugEvent(processid uint32, threadid uint32, continuestatus uint3
|
||||
r1, _, e1 := syscall.Syscall(procContinueDebugEvent.Addr(), 3, uintptr(processid), uintptr(threadid), uintptr(continuestatus))
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -101,7 +130,7 @@ func _WriteProcessMemory(process syscall.Handle, baseaddr uintptr, buffer *byte,
|
||||
r1, _, e1 := syscall.Syscall6(procWriteProcessMemory.Addr(), 5, uintptr(process), uintptr(baseaddr), uintptr(unsafe.Pointer(buffer)), uintptr(size), uintptr(unsafe.Pointer(byteswritten)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -113,7 +142,7 @@ func _ReadProcessMemory(process syscall.Handle, baseaddr uintptr, buffer *byte,
|
||||
r1, _, e1 := syscall.Syscall6(procReadProcessMemory.Addr(), 5, uintptr(process), uintptr(baseaddr), uintptr(unsafe.Pointer(buffer)), uintptr(size), uintptr(unsafe.Pointer(bytesread)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -125,7 +154,7 @@ func _DebugBreakProcess(process syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDebugBreakProcess.Addr(), 1, uintptr(process), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -137,7 +166,7 @@ func _WaitForDebugEvent(debugevent *_DEBUG_EVENT, milliseconds uint32) (err erro
|
||||
r1, _, e1 := syscall.Syscall(procWaitForDebugEvent.Addr(), 2, uintptr(unsafe.Pointer(debugevent)), uintptr(milliseconds), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -149,7 +178,7 @@ func _DebugActiveProcess(processid uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDebugActiveProcess.Addr(), 1, uintptr(processid), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -161,7 +190,7 @@ func _DebugActiveProcessStop(processid uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDebugActiveProcessStop.Addr(), 1, uintptr(processid), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
@ -173,10 +202,22 @@ func _QueryFullProcessImageName(process syscall.Handle, flags uint32, exename *u
|
||||
r1, _, e1 := syscall.Syscall6(procQueryFullProcessImageNameW.Addr(), 4, uintptr(process), uintptr(flags), uintptr(unsafe.Pointer(exename)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _VirtualQueryEx(process syscall.Handle, addr uintptr, buffer *_MEMORY_BASIC_INFORMATION, length uintptr) (lengthOut uintptr) {
|
||||
r0, _, _ := syscall.Syscall6(procVirtualQueryEx.Addr(), 4, uintptr(process), uintptr(addr), uintptr(unsafe.Pointer(buffer)), uintptr(length), 0, 0)
|
||||
lengthOut = uintptr(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _IsWow64Process(process syscall.Handle, wow64process *uint32) (ok uint32) {
|
||||
r0, _, _ := syscall.Syscall(procIsWow64Process.Addr(), 2, uintptr(process), uintptr(unsafe.Pointer(wow64process)), 0)
|
||||
ok = uint32(r0)
|
||||
return
|
||||
}
|
||||
|
@ -23,12 +23,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/core"
|
||||
"github.com/go-delve/delve/pkg/proc/gdbserial"
|
||||
"github.com/go-delve/delve/pkg/proc/native"
|
||||
protest "github.com/go-delve/delve/pkg/proc/test"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
)
|
||||
|
||||
var normalLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1, 0}
|
||||
@ -1302,9 +1305,23 @@ func TestFrameEvaluation(t *testing.T) {
|
||||
found[vval] = true
|
||||
}
|
||||
|
||||
firsterr := false
|
||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
||||
// We try to make sure that all goroutines are stopped at a sensible place
|
||||
// before reading their stacktrace, but due to the nature of the test
|
||||
// program there is no guarantee that we always find them in a reasonable
|
||||
// state.
|
||||
// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
|
||||
// unnecessary flakiness allow a single goroutine to be in a bad state.
|
||||
firsterr = true
|
||||
}
|
||||
for i := range found {
|
||||
if !found[i] {
|
||||
t.Fatalf("Goroutine %d not found\n", i)
|
||||
if firsterr {
|
||||
firsterr = false
|
||||
} else {
|
||||
t.Fatalf("Goroutine %d not found\n", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4993,3 +5010,163 @@ func TestIssue2319(t *testing.T) {
|
||||
bi := proc.NewBinaryInfo("linux", "amd64")
|
||||
assertNoError(bi.LoadBinaryInfo(fixture.Path, 0, nil), t, "LoadBinaryInfo")
|
||||
}
|
||||
|
||||
func TestDump(t *testing.T) {
|
||||
if runtime.GOOS == "freebsd" || (runtime.GOOS == "darwin" && testBackend == "native") {
|
||||
t.Skip("not supported")
|
||||
}
|
||||
|
||||
convertRegisters := func(arch *proc.Arch, dregs op.DwarfRegisters) string {
|
||||
dregs.Reg(^uint64(0))
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < dregs.CurrentSize(); i++ {
|
||||
reg := dregs.Reg(uint64(i))
|
||||
if reg == nil {
|
||||
continue
|
||||
}
|
||||
name, _, repr := arch.DwarfRegisterToString(i, reg)
|
||||
fmt.Fprintf(buf, " %s=%s", name, repr)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
convertThread := func(thread proc.Thread) string {
|
||||
regs, err := thread.Registers()
|
||||
assertNoError(err, t, fmt.Sprintf("Thread registers %d", thread.ThreadID()))
|
||||
arch := thread.BinInfo().Arch
|
||||
dregs := arch.RegistersToDwarfRegisters(0, regs)
|
||||
return fmt.Sprintf("%08d %s", thread.ThreadID(), convertRegisters(arch, dregs))
|
||||
}
|
||||
|
||||
convertThreads := func(threads []proc.Thread) []string {
|
||||
r := make([]string, len(threads))
|
||||
for i := range threads {
|
||||
r[i] = convertThread(threads[i])
|
||||
}
|
||||
sort.Strings(r)
|
||||
return r
|
||||
}
|
||||
|
||||
convertGoroutine := func(g *proc.G) string {
|
||||
threadID := 0
|
||||
if g.Thread != nil {
|
||||
threadID = g.Thread.ThreadID()
|
||||
}
|
||||
return fmt.Sprintf("%d pc=%#x sp=%#x bp=%#x lr=%#x gopc=%#x startpc=%#x systemstack=%v thread=%d", g.ID, g.PC, g.SP, g.BP, g.LR, g.GoPC, g.StartPC, g.SystemStack, threadID)
|
||||
}
|
||||
|
||||
convertFrame := func(arch *proc.Arch, frame *proc.Stackframe) string {
|
||||
return fmt.Sprintf("currentPC=%#x callPC=%#x frameOff=%#x\n", frame.Current.PC, frame.Call.PC, frame.FrameOffset())
|
||||
}
|
||||
|
||||
makeDump := func(p *proc.Target, corePath, exePath string, flags proc.DumpFlags) *proc.Target {
|
||||
fh, err := os.Create(corePath)
|
||||
assertNoError(err, t, "Create()")
|
||||
var state proc.DumpState
|
||||
p.Dump(fh, flags, &state)
|
||||
assertNoError(state.Err, t, "Dump()")
|
||||
if state.ThreadsDone != state.ThreadsTotal || state.MemDone != state.MemTotal || !state.AllDone || state.Dumping || state.Canceled {
|
||||
t.Fatalf("bad DumpState %#v", &state)
|
||||
}
|
||||
c, err := core.OpenCore(corePath, exePath, nil)
|
||||
assertNoError(err, t, "OpenCore()")
|
||||
return c
|
||||
}
|
||||
|
||||
testDump := func(p, c *proc.Target) {
|
||||
if p.Pid() != c.Pid() {
|
||||
t.Errorf("Pid mismatch %x %x", p.Pid(), c.Pid())
|
||||
}
|
||||
|
||||
threads := convertThreads(p.ThreadList())
|
||||
cthreads := convertThreads(c.ThreadList())
|
||||
|
||||
if len(threads) != len(cthreads) {
|
||||
t.Errorf("Thread number mismatch %d %d", len(threads), len(cthreads))
|
||||
}
|
||||
|
||||
for i := range threads {
|
||||
if threads[i] != cthreads[i] {
|
||||
t.Errorf("Thread mismatch\nlive:\t%s\ncore:\t%s", threads[i], cthreads[i])
|
||||
}
|
||||
}
|
||||
|
||||
gos, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo() - live process")
|
||||
cgos, _, err := proc.GoroutinesInfo(c, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo() - core dump")
|
||||
|
||||
if len(gos) != len(cgos) {
|
||||
t.Errorf("Goroutine number mismatch %d %d", len(gos), len(cgos))
|
||||
}
|
||||
|
||||
var scope, cscope *proc.EvalScope
|
||||
|
||||
for i := range gos {
|
||||
if convertGoroutine(gos[i]) != convertGoroutine(cgos[i]) {
|
||||
t.Errorf("Goroutine mismatch\nlive:\t%s\ncore:\t%s", convertGoroutine(gos[i]), convertGoroutine(cgos[i]))
|
||||
}
|
||||
|
||||
frames, err := gos[i].Stacktrace(20, 0)
|
||||
assertNoError(err, t, fmt.Sprintf("Stacktrace for goroutine %d - live process", gos[i].ID))
|
||||
cframes, err := cgos[i].Stacktrace(20, 0)
|
||||
assertNoError(err, t, fmt.Sprintf("Stacktrace for goroutine %d - core dump", gos[i].ID))
|
||||
|
||||
if len(frames) != len(cframes) {
|
||||
t.Errorf("Frame number mismatch for goroutine %d: %d %d", gos[i].ID, len(frames), len(cframes))
|
||||
}
|
||||
|
||||
for j := range frames {
|
||||
if convertFrame(p.BinInfo().Arch, &frames[j]) != convertFrame(p.BinInfo().Arch, &cframes[j]) {
|
||||
t.Errorf("Frame mismatch %d.%d\nlive:\t%s\ncore:\t%s", gos[i].ID, j, convertFrame(p.BinInfo().Arch, &frames[j]), convertFrame(p.BinInfo().Arch, &cframes[j]))
|
||||
}
|
||||
if frames[j].Call.Fn != nil && frames[j].Call.Fn.Name == "main.main" {
|
||||
scope = proc.FrameToScope(p.BinInfo(), p.Memory(), gos[i], frames[j:]...)
|
||||
cscope = proc.FrameToScope(c.BinInfo(), c.Memory(), cgos[i], cframes[j:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vars, err := scope.LocalVariables(normalLoadConfig)
|
||||
assertNoError(err, t, "LocalVariables - live process")
|
||||
cvars, err := cscope.LocalVariables(normalLoadConfig)
|
||||
assertNoError(err, t, "LocalVariables - core dump")
|
||||
|
||||
if len(vars) != len(cvars) {
|
||||
t.Errorf("Variable number mismatch %d %d", len(vars), len(cvars))
|
||||
}
|
||||
|
||||
for i := range vars {
|
||||
varstr := vars[i].Name + "=" + api.ConvertVar(vars[i]).SinglelineString()
|
||||
cvarstr := cvars[i].Name + "=" + api.ConvertVar(cvars[i]).SinglelineString()
|
||||
if strings.Contains(varstr, "(unreadable") {
|
||||
// errors reading from unmapped memory differ between live process and core
|
||||
continue
|
||||
}
|
||||
if varstr != cvarstr {
|
||||
t.Errorf("Variable mismatch %s %s", varstr, cvarstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
corePath := filepath.Join(fixture.BuildDir, "coredump")
|
||||
corePathPlatIndep := filepath.Join(fixture.BuildDir, "coredump-indep")
|
||||
|
||||
t.Logf("testing normal dump")
|
||||
|
||||
c := makeDump(p, corePath, fixture.Path, 0)
|
||||
defer os.Remove(corePath)
|
||||
testDump(p, c)
|
||||
|
||||
if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
|
||||
// No reason to do this test on other goos/goarch because they use the
|
||||
// platform-independent format anyway.
|
||||
t.Logf("testing platform-independent dump")
|
||||
c2 := makeDump(p, corePathPlatIndep, fixture.Path, proc.DumpPlatformIndependent)
|
||||
defer os.Remove(corePathPlatIndep)
|
||||
testDump(p, c2)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -41,6 +41,9 @@ type Target struct {
|
||||
// case only one will be reported.
|
||||
StopReason StopReason
|
||||
|
||||
// CanDump is true if core dumping is supported.
|
||||
CanDump bool
|
||||
|
||||
// currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected.
|
||||
currentThread Thread
|
||||
|
||||
@ -120,6 +123,7 @@ type NewTargetConfig struct {
|
||||
DebugInfoDirs []string // Directories to search for split debug info
|
||||
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
|
||||
StopReason StopReason // Initial stop reason
|
||||
CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap)
|
||||
}
|
||||
|
||||
// DisableAsyncPreemptEnv returns a process environment (like os.Environ)
|
||||
@ -159,6 +163,7 @@ func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, e
|
||||
fncallForG: make(map[int]*callInjection),
|
||||
StopReason: cfg.StopReason,
|
||||
currentThread: currentThread,
|
||||
CanDump: cfg.CanDump,
|
||||
}
|
||||
|
||||
g, _ := GetG(currentThread)
|
||||
|
@ -410,6 +410,12 @@ For example:
|
||||
The '-a' option adds an expression to the list of expression printed every time the program stops. The '-d' option removes the specified expression from the list.
|
||||
|
||||
If display is called without arguments it will print the value of all expression in the list.`},
|
||||
|
||||
{aliases: []string{"dump"}, cmdFn: dump, helpMsg: `Creates a core dump from the current process state
|
||||
|
||||
dump <output file>
|
||||
|
||||
The core dump is always written in ELF, even on systems (windows, macOS) where this is not customary. For environments other than linux/amd64 threads and registers are dumped in a format that only Delve can read back.`},
|
||||
}
|
||||
|
||||
addrecorded := client == nil
|
||||
@ -2633,6 +2639,33 @@ func display(t *Term, ctx callContext, args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dump(t *Term, ctx callContext, args string) error {
|
||||
dumpState, err := t.client.CoreDumpStart(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
if dumpState.ThreadsDone != dumpState.ThreadsTotal {
|
||||
fmt.Printf("\rDumping threads %d / %d...", dumpState.ThreadsDone, dumpState.ThreadsTotal)
|
||||
} else {
|
||||
fmt.Printf("\rDumping memory %d / %d...", dumpState.MemDone, dumpState.MemTotal)
|
||||
}
|
||||
if !dumpState.Dumping {
|
||||
break
|
||||
}
|
||||
dumpState = t.client.CoreDumpWait(1000)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
if dumpState.Err != "" {
|
||||
fmt.Printf("error dumping: %s\n", dumpState.Err)
|
||||
} else if !dumpState.AllDone {
|
||||
fmt.Printf("canceled\n")
|
||||
} else if dumpState.MemDone != dumpState.MemTotal {
|
||||
fmt.Printf("Core dump could be incomplete\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
||||
thing := "breakpoint"
|
||||
if bp.Tracepoint {
|
||||
|
@ -399,6 +399,78 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["dump_cancel"] = starlark.NewBuiltin("dump_cancel", 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.DumpCancelIn
|
||||
var rpcRet rpc2.DumpCancelOut
|
||||
err := env.ctx.Client().CallAPI("DumpCancel", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["dump_start"] = starlark.NewBuiltin("dump_start", 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.DumpStartIn
|
||||
var rpcRet rpc2.DumpStartOut
|
||||
if len(args) > 0 && args[0] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[0], &rpcArgs.Destination, "Destination")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
case "Destination":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Destination, "Destination")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
err := env.ctx.Client().CallAPI("DumpStart", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["dump_wait"] = starlark.NewBuiltin("dump_wait", 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.DumpWaitIn
|
||||
var rpcRet rpc2.DumpWaitOut
|
||||
if len(args) > 0 && args[0] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[0], &rpcArgs.Wait, "Wait")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
case "Wait":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Wait, "Wait")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
err := env.ctx.Client().CallAPI("DumpWait", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["eval"] = starlark.NewBuiltin("eval", 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)
|
||||
|
@ -149,6 +149,14 @@ func (t *Term) sigintGuard(ch <-chan os.Signal, multiClient bool) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == nil && state.CoreDumping {
|
||||
fmt.Printf("received SIGINT, stopping dump\n")
|
||||
err := t.client.CoreDumpCancel()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if multiClient {
|
||||
answer, err := t.line.Prompt("Would you like to [p]ause the target (returning to Delve's prompt) or [q]uit this client (leaving the target running) [p/q]? ")
|
||||
if err != nil {
|
||||
|
@ -421,3 +421,20 @@ func ConvertRegisters(in *op.DwarfRegisters, dwarfRegisterToString func(int, *op
|
||||
func ConvertImage(image *proc.Image) Image {
|
||||
return Image{Path: image.Path, Address: image.StaticBase}
|
||||
}
|
||||
|
||||
func ConvertDumpState(dumpState *proc.DumpState) *DumpState {
|
||||
dumpState.Mutex.Lock()
|
||||
defer dumpState.Mutex.Unlock()
|
||||
r := &DumpState{
|
||||
Dumping: dumpState.Dumping,
|
||||
AllDone: dumpState.AllDone,
|
||||
ThreadsDone: dumpState.ThreadsDone,
|
||||
ThreadsTotal: dumpState.ThreadsTotal,
|
||||
MemDone: dumpState.MemDone,
|
||||
MemTotal: dumpState.MemTotal,
|
||||
}
|
||||
if dumpState.Err != nil {
|
||||
r.Err = dumpState.Err.Error()
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ type DebuggerState struct {
|
||||
// sending a StopRecording request will halt the recording, every other
|
||||
// request will block until the process has been recorded.
|
||||
Recording bool
|
||||
// Core dumping currently in progress.
|
||||
CoreDumping bool
|
||||
// CurrentThread is the currently selected debugger thread.
|
||||
CurrentThread *Thread `json:"currentThread,omitempty"`
|
||||
// SelectedGoroutine is the currently selected goroutine
|
||||
@ -544,3 +546,14 @@ type PackageBuildInfo struct {
|
||||
DirectoryPath string
|
||||
Files []string
|
||||
}
|
||||
|
||||
// DumpState describes the state of a core dump in progress
|
||||
type DumpState struct {
|
||||
Dumping bool
|
||||
AllDone bool
|
||||
|
||||
ThreadsDone, ThreadsTotal int
|
||||
MemDone, MemTotal uint64
|
||||
|
||||
Err string
|
||||
}
|
||||
|
@ -165,6 +165,13 @@ type Client interface {
|
||||
// StopRecording stops a recording if one is in progress.
|
||||
StopRecording() error
|
||||
|
||||
// CoreDumpStart starts creating a core dump to the specified file
|
||||
CoreDumpStart(dest string) (api.DumpState, error)
|
||||
// CoreDumpWait waits for the core dump to finish, or for the specified amount of milliseconds
|
||||
CoreDumpWait(msec int) api.DumpState
|
||||
// CoreDumpCancel cancels a core dump in progress
|
||||
CoreDumpCancel() error
|
||||
|
||||
// Disconnect closes the connection to the server without sending a Detach request first.
|
||||
// If cont is true a continue command will be sent instead.
|
||||
Disconnect(cont bool) error
|
||||
|
@ -37,6 +37,12 @@ var (
|
||||
// ErrNotRecording is returned when StopRecording is called while the
|
||||
// debugger is not recording the target.
|
||||
ErrNotRecording = errors.New("debugger is not recording")
|
||||
|
||||
// ErrCoreDumpInProgress is returned when a core dump is already in progress.
|
||||
ErrCoreDumpInProgress = errors.New("core dump in progress")
|
||||
|
||||
// ErrCoreDumpNotSupported is returned when core dumping is not supported
|
||||
ErrCoreDumpNotSupported = errors.New("core dumping not supported")
|
||||
)
|
||||
|
||||
// Debugger service.
|
||||
@ -62,6 +68,8 @@ type Debugger struct {
|
||||
|
||||
stopRecording func() error
|
||||
recordMutex sync.Mutex
|
||||
|
||||
dumpState proc.DumpState
|
||||
}
|
||||
|
||||
type ExecuteKind int
|
||||
@ -532,6 +540,12 @@ func (d *Debugger) State(nowait bool) (*api.DebuggerState, error) {
|
||||
return &api.DebuggerState{Recording: true}, nil
|
||||
}
|
||||
|
||||
d.dumpState.Mutex.Lock()
|
||||
if d.dumpState.Dumping && nowait {
|
||||
return &api.DebuggerState{CoreDumping: true}, nil
|
||||
}
|
||||
d.dumpState.Mutex.Unlock()
|
||||
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
return d.state(nil)
|
||||
@ -1653,6 +1667,77 @@ func (d *Debugger) UnlockTarget() {
|
||||
d.targetMutex.Unlock()
|
||||
}
|
||||
|
||||
// DumpStart starts a core dump to dest.
|
||||
func (d *Debugger) DumpStart(dest string) error {
|
||||
d.targetMutex.Lock()
|
||||
// targetMutex will only be unlocked when the dump is done
|
||||
|
||||
if !d.target.CanDump {
|
||||
d.targetMutex.Unlock()
|
||||
return ErrCoreDumpNotSupported
|
||||
}
|
||||
|
||||
d.dumpState.Mutex.Lock()
|
||||
defer d.dumpState.Mutex.Unlock()
|
||||
|
||||
if d.dumpState.Dumping {
|
||||
d.targetMutex.Unlock()
|
||||
return ErrCoreDumpInProgress
|
||||
}
|
||||
|
||||
fh, err := os.Create(dest)
|
||||
if err != nil {
|
||||
d.targetMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
d.dumpState.Dumping = true
|
||||
d.dumpState.AllDone = false
|
||||
d.dumpState.Canceled = false
|
||||
d.dumpState.DoneChan = make(chan struct{})
|
||||
d.dumpState.ThreadsDone = 0
|
||||
d.dumpState.ThreadsTotal = 0
|
||||
d.dumpState.MemDone = 0
|
||||
d.dumpState.MemTotal = 0
|
||||
d.dumpState.Err = nil
|
||||
go func() {
|
||||
defer d.targetMutex.Unlock()
|
||||
d.target.Dump(fh, 0, &d.dumpState)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DumpWait waits for the dump to finish, or for the duration of wait.
|
||||
// Returns the state of the dump.
|
||||
// If wait == 0 returns immediately.
|
||||
func (d *Debugger) DumpWait(wait time.Duration) *proc.DumpState {
|
||||
d.dumpState.Mutex.Lock()
|
||||
if !d.dumpState.Dumping {
|
||||
d.dumpState.Mutex.Unlock()
|
||||
return &d.dumpState
|
||||
}
|
||||
d.dumpState.Mutex.Unlock()
|
||||
|
||||
if wait > 0 {
|
||||
alarm := time.After(wait)
|
||||
select {
|
||||
case <-alarm:
|
||||
case <-d.dumpState.DoneChan:
|
||||
}
|
||||
}
|
||||
|
||||
return &d.dumpState
|
||||
}
|
||||
|
||||
// DumpCancel canels a dump in progress
|
||||
func (d *Debugger) DumpCancel() error {
|
||||
d.dumpState.Mutex.Lock()
|
||||
d.dumpState.Canceled = true
|
||||
d.dumpState.Mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func go11DecodeErrorCheck(err error) error {
|
||||
if _, isdecodeerr := err.(dwarf.DecodeError); !isdecodeerr {
|
||||
return err
|
||||
|
@ -455,6 +455,23 @@ func (c *RPCClient) StopRecording() error {
|
||||
return c.call("StopRecording", StopRecordingIn{}, &StopRecordingOut{})
|
||||
}
|
||||
|
||||
func (c *RPCClient) CoreDumpStart(dest string) (api.DumpState, error) {
|
||||
out := &DumpStartOut{}
|
||||
err := c.call("DumpStart", DumpStartIn{Destination: dest}, out)
|
||||
return out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) CoreDumpWait(msec int) api.DumpState {
|
||||
out := &DumpWaitOut{}
|
||||
_ = c.call("DumpWait", DumpWaitIn{Wait: msec}, out)
|
||||
return out.State
|
||||
}
|
||||
|
||||
func (c *RPCClient) CoreDumpCancel() error {
|
||||
out := &DumpCancelOut{}
|
||||
return c.call("DumpCancel", DumpCancelIn{}, out)
|
||||
}
|
||||
|
||||
func (c *RPCClient) call(method string, args, reply interface{}) error {
|
||||
return c.client.Call("RPCServer."+method, args, reply)
|
||||
}
|
||||
|
@ -864,3 +864,48 @@ func (s *RPCServer) StopRecording(arg StopRecordingIn, cb service.RPCCallback) {
|
||||
}
|
||||
cb.Return(out, nil)
|
||||
}
|
||||
|
||||
type DumpStartIn struct {
|
||||
Destination string
|
||||
}
|
||||
|
||||
type DumpStartOut struct {
|
||||
State api.DumpState
|
||||
}
|
||||
|
||||
// DumpStart starts a core dump to arg.Destination.
|
||||
func (s *RPCServer) DumpStart(arg DumpStartIn, out *DumpStartOut) error {
|
||||
err := s.debugger.DumpStart(arg.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.State = *api.ConvertDumpState(s.debugger.DumpWait(0))
|
||||
return nil
|
||||
}
|
||||
|
||||
type DumpWaitIn struct {
|
||||
Wait int
|
||||
}
|
||||
|
||||
type DumpWaitOut struct {
|
||||
State api.DumpState
|
||||
}
|
||||
|
||||
// DumpWait waits for the core dump to finish or for arg.Wait milliseconds.
|
||||
// Wait == 0 means return immediately.
|
||||
// Returns the core dump status
|
||||
func (s *RPCServer) DumpWait(arg DumpWaitIn, out *DumpWaitOut) error {
|
||||
out.State = *api.ConvertDumpState(s.debugger.DumpWait(time.Duration(arg.Wait) * time.Millisecond))
|
||||
return nil
|
||||
}
|
||||
|
||||
type DumpCancelIn struct {
|
||||
}
|
||||
|
||||
type DumpCancelOut struct {
|
||||
}
|
||||
|
||||
// DumpCancel cancels the core dump.
|
||||
func (s *RPCServer) DumpCancel(arg DumpCancelIn, out *DumpCancelOut) error {
|
||||
return s.debugger.DumpCancel()
|
||||
}
|
||||
|
@ -1548,6 +1548,8 @@ func TestPluginVariables(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCgoEval(t *testing.T) {
|
||||
protest.MustHaveCgo(t)
|
||||
|
||||
testcases := []varTest{
|
||||
{"s", true, `"a string"`, `"a string"`, "*char", nil},
|
||||
{"longstring", true, `"averylongstring0123456789a0123456789b0123456789c0123456789d01234...+1 more"`, `"averylongstring0123456789a0123456789b0123456789c0123456789d01234...+1 more"`, "*const char", nil},
|
||||
|
Loading…
Reference in New Issue
Block a user