mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-01 03:42:59 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			423 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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 platform-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
 | |
| 	case "ppc64le":
 | |
| 		fhdr.Machine = elf.EM_PPC64
 | |
| 	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 (register_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()
 | |
| }
 | 
