mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 18:57:18 +08:00 
			
		
		
		
	 2c1a822632
			
		
	
	2c1a822632
	
	
	
		
			
			* 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
		
			
				
	
	
		
			482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package core
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"debug/elf"
 | |
| 	"encoding/binary"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"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"
 | |
| )
 | |
| 
 | |
| // Copied from golang.org/x/sys/unix.Timeval since it's not available on all
 | |
| // systems.
 | |
| type linuxCoreTimeval struct {
 | |
| 	Sec  int64
 | |
| 	Usec int64
 | |
| }
 | |
| 
 | |
| // NT_FILE is file mapping information, e.g. program text mappings. Desc is a LinuxNTFile.
 | |
| const _NT_FILE elf.NType = 0x46494c45 // "FILE".
 | |
| 
 | |
| // NT_X86_XSTATE is other registers, including AVX and such.
 | |
| const _NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAVE area.
 | |
| 
 | |
| // NT_AUXV is the note type for notes containing a copy of the Auxv array
 | |
| const _NT_AUXV elf.NType = 0x6
 | |
| 
 | |
| // NT_FPREGSET is the note type for floating point registers.
 | |
| const _NT_FPREGSET elf.NType = 0x2
 | |
| 
 | |
| // Fetch architecture using exeELF.Machine from core file
 | |
| // Refer http://man7.org/linux/man-pages/man5/elf.5.html
 | |
| const (
 | |
| 	_EM_AARCH64          = 183
 | |
| 	_EM_X86_64           = 62
 | |
| 	_ARM_FP_HEADER_START = 512
 | |
| )
 | |
| 
 | |
| const elfErrorBadMagicNumber = "bad magic number"
 | |
| 
 | |
| func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) proc.Thread {
 | |
| 	var currentThread proc.Thread
 | |
| 	var lastThreadAMD *linuxAMD64Thread
 | |
| 	var lastThreadARM *linuxARM64Thread
 | |
| 	for _, note := range notes {
 | |
| 		switch note.Type {
 | |
| 		case elf.NT_PRSTATUS:
 | |
| 			if machineType == _EM_X86_64 {
 | |
| 				t := note.Desc.(*linuxPrStatusAMD64)
 | |
| 				lastThreadAMD = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
 | |
| 				p.Threads[int(t.Pid)] = &thread{lastThreadAMD, p, proc.CommonThread{}}
 | |
| 				if currentThread == nil {
 | |
| 					currentThread = p.Threads[int(t.Pid)]
 | |
| 				}
 | |
| 			} else if machineType == _EM_AARCH64 {
 | |
| 				t := note.Desc.(*linuxPrStatusARM64)
 | |
| 				lastThreadARM = &linuxARM64Thread{linutil.ARM64Registers{Regs: &t.Reg}, t}
 | |
| 				p.Threads[int(t.Pid)] = &thread{lastThreadARM, p, proc.CommonThread{}}
 | |
| 				if currentThread == nil {
 | |
| 					currentThread = p.Threads[int(t.Pid)]
 | |
| 				}
 | |
| 			}
 | |
| 		case _NT_FPREGSET:
 | |
| 			if machineType == _EM_AARCH64 {
 | |
| 				if lastThreadARM != nil {
 | |
| 					lastThreadARM.regs.Fpregs = note.Desc.(*linutil.ARM64PtraceFpRegs).Decode()
 | |
| 				}
 | |
| 			}
 | |
| 		case _NT_X86_XSTATE:
 | |
| 			if machineType == _EM_X86_64 {
 | |
| 				if lastThreadAMD != nil {
 | |
| 					lastThreadAMD.regs.Fpregs = note.Desc.(*amd64util.AMD64Xstate).Decode()
 | |
| 				}
 | |
| 			}
 | |
| 		case elf.NT_PRPSINFO:
 | |
| 			p.pid = int(note.Desc.(*linuxPrPsInfo).Pid)
 | |
| 		}
 | |
| 	}
 | |
| 	return currentThread
 | |
| }
 | |
| 
 | |
| // 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 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")) {
 | |
| 			// Go >=1.11 and <1.11 produce different errors when reading a non-elf file.
 | |
| 			return nil, nil, ErrUnrecognizedFormat
 | |
| 		}
 | |
| 		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 {
 | |
| 		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)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	memory := buildMemory(coreFile, exeELF, exe, notes)
 | |
| 
 | |
| 	// TODO support 386
 | |
| 	var bi *proc.BinaryInfo
 | |
| 	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())
 | |
| 
 | |
| 	p := &process{
 | |
| 		mem:         memory,
 | |
| 		Threads:     map[int]*thread{},
 | |
| 		entryPoint:  entryPoint,
 | |
| 		bi:          bi,
 | |
| 		breakpoints: proc.NewBreakpointMap(),
 | |
| 	}
 | |
| 
 | |
| 	if platformIndependentDelveCore {
 | |
| 		currentThread, err := threadsFromDelveNotes(p, notes)
 | |
| 		return p, currentThread, err
 | |
| 	}
 | |
| 
 | |
| 	currentThread := linuxThreadsFromNotes(p, notes, machineType)
 | |
| 	return p, currentThread, nil
 | |
| }
 | |
| 
 | |
| type linuxAMD64Thread struct {
 | |
| 	regs linutil.AMD64Registers
 | |
| 	t    *linuxPrStatusAMD64
 | |
| }
 | |
| 
 | |
| type linuxARM64Thread struct {
 | |
| 	regs linutil.ARM64Registers
 | |
| 	t    *linuxPrStatusARM64
 | |
| }
 | |
| 
 | |
| func (t *linuxAMD64Thread) registers() (proc.Registers, error) {
 | |
| 	var r linutil.AMD64Registers
 | |
| 	r.Regs = t.regs.Regs
 | |
| 	r.Fpregs = t.regs.Fpregs
 | |
| 	return &r, nil
 | |
| }
 | |
| 
 | |
| func (t *linuxARM64Thread) registers() (proc.Registers, error) {
 | |
| 	var r linutil.ARM64Registers
 | |
| 	r.Regs = t.regs.Regs
 | |
| 	r.Fpregs = t.regs.Fpregs
 | |
| 	return &r, nil
 | |
| }
 | |
| 
 | |
| func (t *linuxAMD64Thread) pid() int {
 | |
| 	return int(t.t.Pid)
 | |
| }
 | |
| 
 | |
| func (t *linuxARM64Thread) pid() int {
 | |
| 	return int(t.t.Pid)
 | |
| }
 | |
| 
 | |
| // Note is a note from the PT_NOTE prog.
 | |
| // Relevant types:
 | |
| // - NT_FILE: File mapping information, e.g. program text mappings. Desc is a LinuxNTFile.
 | |
| // - NT_PRPSINFO: Information about a process, including PID and signal. Desc is a LinuxPrPsInfo.
 | |
| // - NT_PRSTATUS: Information about a thread, including base registers, state, etc. Desc is a LinuxPrStatus.
 | |
| // - NT_FPREGSET (Not implemented): x87 floating point registers.
 | |
| // - NT_X86_XSTATE: Other registers, including AVX and such.
 | |
| type note struct {
 | |
| 	Type elf.NType
 | |
| 	Name string
 | |
| 	Desc interface{} // Decoded Desc from the
 | |
| }
 | |
| 
 | |
| // readNotes reads all the notes from the notes prog in core.
 | |
| 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 {
 | |
| 			notesProg = prog
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r := notesProg.Open()
 | |
| 	hasDelveThread := false
 | |
| 	hasDelveHeader := false
 | |
| 	hasElfPrStatus := false
 | |
| 	notes := []*note{}
 | |
| 	for {
 | |
| 		note, err := readNote(r, machineType)
 | |
| 		if err == io.EOF {
 | |
| 			break
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			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, hasDelveThread && hasDelveHeader && !hasElfPrStatus, nil
 | |
| }
 | |
| 
 | |
| // readNote reads a single note from r, decoding the descriptor if possible.
 | |
| func readNote(r io.ReadSeeker, machineType elf.Machine) (*note, error) {
 | |
| 	// Notes are laid out as described in the SysV ABI:
 | |
| 	// http://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section
 | |
| 	note := ¬e{}
 | |
| 	hdr := &elfNotesHdr{}
 | |
| 
 | |
| 	err := binary.Read(r, binary.LittleEndian, hdr)
 | |
| 	if err != nil {
 | |
| 		return nil, err // don't wrap so readNotes sees EOF.
 | |
| 	}
 | |
| 	note.Type = elf.NType(hdr.Type)
 | |
| 
 | |
| 	name := make([]byte, hdr.Namesz)
 | |
| 	if _, err := r.Read(name); err != nil {
 | |
| 		return nil, fmt.Errorf("reading name: %v", err)
 | |
| 	}
 | |
| 	note.Name = string(name)
 | |
| 	if err := skipPadding(r, 4); err != nil {
 | |
| 		return nil, fmt.Errorf("aligning after name: %v", err)
 | |
| 	}
 | |
| 	desc := make([]byte, hdr.Descsz)
 | |
| 	if _, err := r.Read(desc); err != nil {
 | |
| 		return nil, fmt.Errorf("reading desc: %v", err)
 | |
| 	}
 | |
| 	descReader := bytes.NewReader(desc)
 | |
| 	switch note.Type {
 | |
| 	case elf.NT_PRSTATUS:
 | |
| 		if machineType == _EM_X86_64 {
 | |
| 			note.Desc = &linuxPrStatusAMD64{}
 | |
| 		} else if machineType == _EM_AARCH64 {
 | |
| 			note.Desc = &linuxPrStatusARM64{}
 | |
| 		} else {
 | |
| 			return nil, fmt.Errorf("unsupported machine type")
 | |
| 		}
 | |
| 		if err := binary.Read(descReader, binary.LittleEndian, note.Desc); err != nil {
 | |
| 			return nil, fmt.Errorf("reading NT_PRSTATUS: %v", err)
 | |
| 		}
 | |
| 	case elf.NT_PRPSINFO:
 | |
| 		note.Desc = &linuxPrPsInfo{}
 | |
| 		if err := binary.Read(descReader, binary.LittleEndian, note.Desc); err != nil {
 | |
| 			return nil, fmt.Errorf("reading NT_PRPSINFO: %v", err)
 | |
| 		}
 | |
| 	case _NT_FILE:
 | |
| 		// No good documentation reference, but the structure is
 | |
| 		// simply a header, including entry count, followed by that
 | |
| 		// many entries, and then the file name of each entry,
 | |
| 		// null-delimited. Not reading the names here.
 | |
| 		data := &linuxNTFile{}
 | |
| 		if err := binary.Read(descReader, binary.LittleEndian, &data.linuxNTFileHdr); err != nil {
 | |
| 			return nil, fmt.Errorf("reading NT_FILE header: %v", err)
 | |
| 		}
 | |
| 		for i := 0; i < int(data.Count); i++ {
 | |
| 			entry := &linuxNTFileEntry{}
 | |
| 			if err := binary.Read(descReader, binary.LittleEndian, entry); err != nil {
 | |
| 				return nil, fmt.Errorf("reading NT_FILE entry %v: %v", i, err)
 | |
| 			}
 | |
| 			data.entries = append(data.entries, entry)
 | |
| 		}
 | |
| 		note.Desc = data
 | |
| 	case _NT_X86_XSTATE:
 | |
| 		if machineType == _EM_X86_64 {
 | |
| 			var fpregs amd64util.AMD64Xstate
 | |
| 			if err := amd64util.AMD64XstateRead(desc, true, &fpregs); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			note.Desc = &fpregs
 | |
| 		}
 | |
| 	case _NT_AUXV, elfwriter.DelveHeaderNoteType, elfwriter.DelveThreadNodeType:
 | |
| 		note.Desc = desc
 | |
| 	case _NT_FPREGSET:
 | |
| 		if machineType == _EM_AARCH64 {
 | |
| 			fpregs := &linutil.ARM64PtraceFpRegs{}
 | |
| 			rdr := bytes.NewReader(desc[:_ARM_FP_HEADER_START])
 | |
| 			if err := binary.Read(rdr, binary.LittleEndian, fpregs.Byte()); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			note.Desc = fpregs
 | |
| 		}
 | |
| 	}
 | |
| 	if err := skipPadding(r, 4); err != nil {
 | |
| 		return nil, fmt.Errorf("aligning after desc: %v", err)
 | |
| 	}
 | |
| 	return note, nil
 | |
| }
 | |
| 
 | |
| // skipPadding moves r to the next multiple of pad.
 | |
| func skipPadding(r io.ReadSeeker, pad int64) error {
 | |
| 	pos, err := r.Seek(0, os.SEEK_CUR)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if pos%pad == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if _, err := r.Seek(pad-(pos%pad), os.SEEK_CUR); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*note) proc.MemoryReader {
 | |
| 	memory := &splicedMemory{}
 | |
| 
 | |
| 	// For now, assume all file mappings are to the exe.
 | |
| 	for _, note := range notes {
 | |
| 		if note.Type == _NT_FILE {
 | |
| 			fileNote := note.Desc.(*linuxNTFile)
 | |
| 			for _, entry := range fileNote.entries {
 | |
| 				r := &offsetReaderAt{
 | |
| 					reader: exe,
 | |
| 					offset: entry.Start - (entry.FileOfs * fileNote.PageSize),
 | |
| 				}
 | |
| 				memory.Add(r, entry.Start, entry.End-entry.Start)
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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 {
 | |
| 					continue
 | |
| 				}
 | |
| 				r := &offsetReaderAt{
 | |
| 					reader: prog.ReaderAt,
 | |
| 					offset: prog.Vaddr,
 | |
| 				}
 | |
| 				memory.Add(r, prog.Vaddr, prog.Filesz)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return memory
 | |
| }
 | |
| 
 | |
| func findEntryPoint(notes []*note, ptrSize int) uint64 {
 | |
| 	for _, note := range notes {
 | |
| 		if note.Type == _NT_AUXV {
 | |
| 			return linutil.EntryPointFromAuxv(note.Desc.([]byte), ptrSize)
 | |
| 		}
 | |
| 	}
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| // LinuxPrPsInfo has various structures from the ELF spec and the Linux kernel.
 | |
| // AMD64 specific primarily because of unix.PtraceRegs, but also
 | |
| // because some of the fields are word sized.
 | |
| // See http://lxr.free-electrons.com/source/include/uapi/linux/elfcore.h
 | |
| 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
 | |
| }
 | |
| 
 | |
| // LinuxPrStatusAMD64 is a copy of the prstatus kernel struct.
 | |
| type linuxPrStatusAMD64 struct {
 | |
| 	Siginfo                      linuxSiginfo
 | |
| 	Cursig                       uint16
 | |
| 	_                            [2]uint8
 | |
| 	Sigpend                      uint64
 | |
| 	Sighold                      uint64
 | |
| 	Pid, Ppid, Pgrp, Sid         int32
 | |
| 	Utime, Stime, CUtime, CStime linuxCoreTimeval
 | |
| 	Reg                          linutil.AMD64PtraceRegs
 | |
| 	Fpvalid                      int32
 | |
| }
 | |
| 
 | |
| // LinuxPrStatusARM64 is a copy of the prstatus kernel struct.
 | |
| type linuxPrStatusARM64 struct {
 | |
| 	Siginfo                      linuxSiginfo
 | |
| 	Cursig                       uint16
 | |
| 	_                            [2]uint8
 | |
| 	Sigpend                      uint64
 | |
| 	Sighold                      uint64
 | |
| 	Pid, Ppid, Pgrp, Sid         int32
 | |
| 	Utime, Stime, CUtime, CStime linuxCoreTimeval
 | |
| 	Reg                          linutil.ARM64PtraceRegs
 | |
| 	Fpvalid                      int32
 | |
| }
 | |
| 
 | |
| // LinuxSiginfo is a copy of the
 | |
| // siginfo kernel struct.
 | |
| type linuxSiginfo struct {
 | |
| 	Signo int32
 | |
| 	Code  int32
 | |
| 	Errno int32
 | |
| }
 | |
| 
 | |
| // LinuxNTFile contains information on mapped files.
 | |
| type linuxNTFile struct {
 | |
| 	linuxNTFileHdr
 | |
| 	entries []*linuxNTFileEntry
 | |
| }
 | |
| 
 | |
| // LinuxNTFileHdr is a header struct for NTFile.
 | |
| type linuxNTFileHdr struct {
 | |
| 	Count    uint64
 | |
| 	PageSize uint64
 | |
| }
 | |
| 
 | |
| // LinuxNTFileEntry is an entry of an NT_FILE note.
 | |
| type linuxNTFileEntry struct {
 | |
| 	Start   uint64
 | |
| 	End     uint64
 | |
| 	FileOfs uint64
 | |
| }
 | |
| 
 | |
| // elfNotesHdr is the ELF Notes header.
 | |
| // Same size on 64 and 32-bit machines.
 | |
| type elfNotesHdr struct {
 | |
| 	Namesz uint32
 | |
| 	Descsz uint32
 | |
| 	Type   uint32
 | |
| }
 |