github.com/undoio/delve@v1.9.0/pkg/proc/dump.go (about)

     1  package proc
     2  
     3  import (
     4  	"bytes"
     5  	"debug/elf"
     6  	"encoding/binary"
     7  	"errors"
     8  	"fmt"
     9  	"runtime"
    10  	"sync"
    11  
    12  	"github.com/undoio/delve/pkg/elfwriter"
    13  	"github.com/undoio/delve/pkg/version"
    14  )
    15  
    16  var (
    17  	ErrMemoryMapNotSupported = errors.New("MemoryMap not supported")
    18  )
    19  
    20  // DumpState represents the current state of a core dump in progress.
    21  type DumpState struct {
    22  	Mutex sync.Mutex
    23  
    24  	Dumping  bool
    25  	AllDone  bool
    26  	Canceled bool
    27  	DoneChan chan struct{}
    28  
    29  	ThreadsDone, ThreadsTotal int
    30  	MemDone, MemTotal         uint64
    31  
    32  	Err error
    33  }
    34  
    35  // DumpFlags is used to configure (*Target).Dump
    36  type DumpFlags uint16
    37  
    38  const (
    39  	DumpPlatformIndependent DumpFlags = 1 << iota // always use platform-independent notes format
    40  )
    41  
    42  // MemoryMapEntry represent a memory mapping in the target process.
    43  type MemoryMapEntry struct {
    44  	Addr uint64
    45  	Size uint64
    46  
    47  	Read, Write, Exec bool
    48  
    49  	Filename string
    50  	Offset   uint64
    51  }
    52  
    53  func (state *DumpState) setErr(err error) {
    54  	if err == nil {
    55  		return
    56  	}
    57  	state.Mutex.Lock()
    58  	if state.Err == nil {
    59  		state.Err = err
    60  	}
    61  	state.Mutex.Unlock()
    62  }
    63  
    64  func (state *DumpState) setThreadsTotal(n int) {
    65  	state.Mutex.Lock()
    66  	state.ThreadsTotal = n
    67  	state.ThreadsDone = 0
    68  	state.Mutex.Unlock()
    69  }
    70  
    71  func (state *DumpState) threadDone() {
    72  	state.Mutex.Lock()
    73  	state.ThreadsDone++
    74  	state.Mutex.Unlock()
    75  }
    76  
    77  func (state *DumpState) setMemTotal(n uint64) {
    78  	state.Mutex.Lock()
    79  	state.MemTotal = n
    80  	state.Mutex.Unlock()
    81  }
    82  
    83  func (state *DumpState) memDone(delta uint64) {
    84  	state.Mutex.Lock()
    85  	state.MemDone += delta
    86  	state.Mutex.Unlock()
    87  }
    88  
    89  func (state *DumpState) isCanceled() bool {
    90  	state.Mutex.Lock()
    91  	defer state.Mutex.Unlock()
    92  	return state.Canceled
    93  }
    94  
    95  // Dump writes a core dump to out. State is updated as the core dump is written.
    96  func (t *Target) Dump(out elfwriter.WriteCloserSeeker, flags DumpFlags, state *DumpState) {
    97  	defer func() {
    98  		state.Mutex.Lock()
    99  		if ierr := recover(); ierr != nil {
   100  			state.Err = newInternalError(ierr, 2)
   101  		}
   102  		err := out.Close()
   103  		if state.Err == nil && err != nil {
   104  			state.Err = fmt.Errorf("error writing output file: %v", err)
   105  		}
   106  		state.Dumping = false
   107  		state.Mutex.Unlock()
   108  		if state.DoneChan != nil {
   109  			close(state.DoneChan)
   110  		}
   111  	}()
   112  
   113  	bi := t.BinInfo()
   114  
   115  	var fhdr elf.FileHeader
   116  	fhdr.Class = elf.ELFCLASS64
   117  	fhdr.Data = elf.ELFDATA2LSB
   118  	fhdr.Version = elf.EV_CURRENT
   119  
   120  	switch bi.GOOS {
   121  	case "linux":
   122  		fhdr.OSABI = elf.ELFOSABI_LINUX
   123  	case "freebsd":
   124  		fhdr.OSABI = elf.ELFOSABI_FREEBSD
   125  	default:
   126  		// There is no OSABI value for windows or macOS because nobody generates ELF core dumps on those systems.
   127  		fhdr.OSABI = 0xff
   128  	}
   129  
   130  	fhdr.Type = elf.ET_CORE
   131  
   132  	switch bi.Arch.Name {
   133  	case "amd64":
   134  		fhdr.Machine = elf.EM_X86_64
   135  	case "386":
   136  		fhdr.Machine = elf.EM_386
   137  	case "arm64":
   138  		fhdr.Machine = elf.EM_AARCH64
   139  	default:
   140  		panic("not implemented")
   141  	}
   142  
   143  	fhdr.Entry = 0
   144  
   145  	w := elfwriter.New(out, &fhdr)
   146  
   147  	notes := []elfwriter.Note{}
   148  
   149  	entryPoint, err := t.EntryPoint()
   150  	if err != nil {
   151  		state.setErr(err)
   152  		return
   153  	}
   154  
   155  	notes = append(notes, elfwriter.Note{
   156  		Type: elfwriter.DelveHeaderNoteType,
   157  		Name: "Delve Header",
   158  		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)),
   159  	})
   160  
   161  	threads := t.ThreadList()
   162  	state.setThreadsTotal(len(threads))
   163  
   164  	var threadsDone bool
   165  
   166  	if flags&DumpPlatformIndependent == 0 {
   167  		threadsDone, notes, err = t.proc.DumpProcessNotes(notes, state.threadDone)
   168  		if err != nil {
   169  			state.setErr(err)
   170  			return
   171  		}
   172  	}
   173  
   174  	if !threadsDone {
   175  		for _, th := range threads {
   176  			if w.Err != nil {
   177  				state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
   178  				return
   179  			}
   180  			if state.isCanceled() {
   181  				return
   182  			}
   183  			notes = t.dumpThreadNotes(notes, state, th)
   184  			state.threadDone()
   185  		}
   186  	}
   187  
   188  	memmap, err := t.proc.MemoryMap()
   189  	if err != nil {
   190  		state.setErr(err)
   191  		return
   192  	}
   193  
   194  	memmapFilter := make([]MemoryMapEntry, 0, len(memmap))
   195  	memtot := uint64(0)
   196  	for i := range memmap {
   197  		mme := &memmap[i]
   198  		if t.shouldDumpMemory(mme) {
   199  			memmapFilter = append(memmapFilter, *mme)
   200  			memtot += mme.Size
   201  		}
   202  	}
   203  
   204  	state.setMemTotal(memtot)
   205  
   206  	for i := range memmapFilter {
   207  		mme := &memmapFilter[i]
   208  		if w.Err != nil {
   209  			state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
   210  			return
   211  		}
   212  		if state.isCanceled() {
   213  			return
   214  		}
   215  		t.dumpMemory(state, w, mme)
   216  	}
   217  
   218  	notesProg := w.WriteNotes(notes)
   219  	w.Progs = append(w.Progs, notesProg)
   220  	w.WriteProgramHeaders()
   221  	if w.Err != nil {
   222  		state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
   223  	}
   224  	state.Mutex.Lock()
   225  	state.AllDone = true
   226  	state.Mutex.Unlock()
   227  }
   228  
   229  // dumpThreadNotes appends notes describing a thread (thread id and its
   230  // registers) using a platform-independent format.
   231  func (t *Target) dumpThreadNotes(notes []elfwriter.Note, state *DumpState, th Thread) []elfwriter.Note {
   232  	// If the backend doesn't provide a way to dump a thread we use a custom format for the note:
   233  	// - thread_id (8 bytes)
   234  	// - pc value (8 bytes)
   235  	// - sp value (8 bytes)
   236  	// - bp value (8 bytes)
   237  	// - tls value (8 bytes)
   238  	// - has_gaddr (1 byte)
   239  	// - gaddr value (8 bytes)
   240  	// - num_registers (4 bytes)
   241  	// Followed by a list of num_register, each as follows:
   242  	// - register_name_len (2 bytes)
   243  	// - register_name (register_name_len bytes)
   244  	// - register_data_len (2 bytes)
   245  	// - register_data (regiter_data_len bytes)
   246  
   247  	buf := new(bytes.Buffer)
   248  	_ = binary.Write(buf, binary.LittleEndian, uint64(th.ThreadID()))
   249  
   250  	regs, err := th.Registers()
   251  	if err != nil {
   252  		state.setErr(err)
   253  		return notes
   254  	}
   255  
   256  	for _, specialReg := range []uint64{regs.PC(), regs.SP(), regs.BP(), regs.TLS()} {
   257  		binary.Write(buf, binary.LittleEndian, specialReg)
   258  	}
   259  
   260  	gaddr, hasGaddr := regs.GAddr()
   261  	binary.Write(buf, binary.LittleEndian, hasGaddr)
   262  	binary.Write(buf, binary.LittleEndian, gaddr)
   263  
   264  	regsv, err := regs.Slice(true)
   265  	if err != nil {
   266  		state.setErr(err)
   267  		return notes
   268  	}
   269  
   270  	binary.Write(buf, binary.LittleEndian, uint32(len(regsv)))
   271  
   272  	for _, reg := range regsv {
   273  		binary.Write(buf, binary.LittleEndian, uint16(len(reg.Name)))
   274  		buf.Write([]byte(reg.Name))
   275  		if reg.Reg.Bytes != nil {
   276  			binary.Write(buf, binary.LittleEndian, uint16(len(reg.Reg.Bytes)))
   277  			buf.Write(reg.Reg.Bytes)
   278  		} else {
   279  			binary.Write(buf, binary.LittleEndian, uint16(8))
   280  			binary.Write(buf, binary.LittleEndian, reg.Reg.Uint64Val)
   281  		}
   282  	}
   283  
   284  	return append(notes, elfwriter.Note{
   285  		Type: elfwriter.DelveThreadNodeType,
   286  		Name: "",
   287  		Data: buf.Bytes(),
   288  	})
   289  }
   290  
   291  func (t *Target) dumpMemory(state *DumpState, w *elfwriter.Writer, mme *MemoryMapEntry) {
   292  	var flags elf.ProgFlag
   293  	if mme.Read {
   294  		flags |= elf.PF_R
   295  	}
   296  	if mme.Write {
   297  		flags |= elf.PF_W
   298  	}
   299  	if mme.Exec {
   300  		flags |= elf.PF_X
   301  	}
   302  
   303  	w.Progs = append(w.Progs, &elf.ProgHeader{
   304  		Type:   elf.PT_LOAD,
   305  		Flags:  flags,
   306  		Off:    uint64(w.Here()),
   307  		Vaddr:  mme.Addr,
   308  		Paddr:  0,
   309  		Filesz: mme.Size,
   310  		Memsz:  mme.Size,
   311  		Align:  0,
   312  	})
   313  
   314  	buf := make([]byte, 1024*1024)
   315  	addr := mme.Addr
   316  	sz := mme.Size
   317  	mem := t.Memory()
   318  
   319  	for sz > 0 {
   320  		if w.Err != nil {
   321  			state.setErr(fmt.Errorf("error writing to output file: %v", w.Err))
   322  			return
   323  		}
   324  		if state.isCanceled() {
   325  			return
   326  		}
   327  		chunk := buf
   328  		if uint64(len(chunk)) > sz {
   329  			chunk = chunk[:sz]
   330  		}
   331  		n, err := mem.ReadMemory(chunk, addr)
   332  		for i := n; i < len(chunk); i++ {
   333  			chunk[i] = 0
   334  		}
   335  		// Errors and short reads are ignored, the most likely reason is that
   336  		// (*ProcessInternal).MemoryMap gave us a bad mapping that can't be read
   337  		// and the behavior that's maximally useful to the user is to generate an
   338  		// incomplete dump.
   339  		w.Write(chunk)
   340  		addr += uint64(len(chunk))
   341  		sz -= uint64(len(chunk))
   342  		if err == nil {
   343  			state.memDone(uint64(len(chunk)))
   344  		}
   345  	}
   346  }
   347  
   348  func (t *Target) shouldDumpMemory(mme *MemoryMapEntry) bool {
   349  	if !mme.Read {
   350  		return false
   351  	}
   352  	exeimg := t.BinInfo().Images[0]
   353  	if mme.Write || mme.Filename == "" || mme.Filename != exeimg.Path {
   354  		return true
   355  	}
   356  	isgo := false
   357  	for _, cu := range exeimg.compileUnits {
   358  		if cu.isgo {
   359  			isgo = true
   360  			break
   361  		}
   362  	}
   363  	if !isgo {
   364  		return true
   365  	}
   366  
   367  	exe, err := elf.Open(exeimg.Path)
   368  	if err != nil {
   369  		return true
   370  	}
   371  
   372  	if exe.Type != elf.ET_EXEC {
   373  		return true
   374  	}
   375  
   376  	for _, prog := range exe.Progs {
   377  		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) {
   378  			return false
   379  		}
   380  	}
   381  	return true
   382  }
   383  
   384  type internalError struct {
   385  	Err   interface{}
   386  	Stack []internalErrorFrame
   387  }
   388  
   389  type internalErrorFrame struct {
   390  	Pc   uintptr
   391  	Func string
   392  	File string
   393  	Line int
   394  }
   395  
   396  func newInternalError(ierr interface{}, skip int) *internalError {
   397  	r := &internalError{ierr, nil}
   398  	for i := skip; ; i++ {
   399  		pc, file, line, ok := runtime.Caller(i)
   400  		if !ok {
   401  			break
   402  		}
   403  		fname := "<unknown>"
   404  		fn := runtime.FuncForPC(pc)
   405  		if fn != nil {
   406  			fname = fn.Name()
   407  		}
   408  		r.Stack = append(r.Stack, internalErrorFrame{pc, fname, file, line})
   409  	}
   410  	return r
   411  }
   412  
   413  func (err *internalError) Error() string {
   414  	var out bytes.Buffer
   415  	fmt.Fprintf(&out, "Internal debugger error: %v\n", err.Err)
   416  	for _, frame := range err.Stack {
   417  		fmt.Fprintf(&out, "%s (%#x)\n\t%s:%d\n", frame.Func, frame.Pc, frame.File, frame.Line)
   418  	}
   419  	return out.String()
   420  }