package core import ( "bytes" "debug/elf" "encoding/binary" "errors" "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, errors.New("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: switch machineType { case _EM_X86_64: note.Desc = &linuxPrStatusAMD64{} case _EM_AARCH64: note.Desc = &linuxPrStatusARM64{} default: return nil, errors.New("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, io.SeekCurrent) if err != nil { return err } if pos%pad == 0 { return nil } if _, err := r.Seek(pad-(pos%pad), io.SeekCurrent); 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 }