mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 06:32:16 +08:00 
			
		
		
		
	* delve: support linux-loong64 native debug LoongArch is a new RISC ISA, which is independently designed by Loongson Technology. LoongArch includes a reduced 32-bit version (LA32R), a standard 32-bit version (LA32S) and a 64-bit version (LA64), and loong64 is the 64-bit version of LoongArch. LoongArch documentation: https://github.com/loongson/LoongArch-Documentation.git * *: mark loong64 port as experimental --------- Co-authored-by: Huang Qiqi <huangqiqi@loongson.cn>
		
			
				
	
	
		
			427 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			9.2 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
 | 
						|
	case "riscv64":
 | 
						|
		fhdr.Machine = elf.EM_RISCV
 | 
						|
	case "loong64":
 | 
						|
		fhdr.Machine = elf.EM_LOONGARCH
 | 
						|
	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.WriteString(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()
 | 
						|
}
 |