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:
Alessandro Arzilli 2021-01-29 22:39:33 +01:00 committed by GitHub
parent 11e4ed2bf9
commit 2c1a822632
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2010 additions and 66 deletions

@ -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")

@ -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

@ -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 := &notes[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

@ -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

@ -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.

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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},