delve/pkg/proc/core_linux_amd64.go
Heschi Kreinick 423bcaa83a pkg/proc: add initial data structures for core support
Core files contain a variety of memory mappings either to files or
anonymous regions stored in the core file. These regions can overlap, so
figuring out what exactly to read can be tricky. This commit contains
a data structure, SplicedMemory, which accumulates mappings and reads
from the correct sources.
2017-04-18 13:25:11 -07:00

270 lines
6.9 KiB
Go

package proc
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"os"
"golang.org/x/debug/elf"
"golang.org/x/sys/unix"
)
const NT_FILE elf.NType = 0x46494c45 // "FILE".
// readCore 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 readCore(corePath, exePath string) (*Core, error) {
core, err := elf.Open(corePath)
if err != nil {
return nil, err
}
exe, err := os.Open(exePath)
if err != nil {
return nil, err
}
if core.Type != elf.ET_CORE {
return nil, fmt.Errorf("%v is not a core file", core)
}
notes, err := readNotes(core)
if err != nil {
return nil, err
}
memory := buildMemory(core, exe, notes)
threads := map[int]*LinuxPrStatus{}
pid := 0
for _, note := range notes {
switch note.Type {
case elf.NT_PRSTATUS:
t := note.Desc.(*LinuxPrStatus)
threads[int(t.Pid)] = t
case elf.NT_PRPSINFO:
pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
}
}
return &Core{
MemoryReader: memory,
Threads: threads,
Pid: pid,
}, nil
}
type Core struct {
MemoryReader
Threads map[int]*LinuxPrStatus
Pid int
}
// 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 (Not implemented): 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) ([]*Note, error) {
var notesProg *elf.Prog
for _, prog := range core.Progs {
if prog.Type == elf.PT_NOTE {
notesProg = prog
break
}
}
r := notesProg.Open()
notes := []*Note{}
for {
note, err := readNote(r)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
notes = append(notes, note)
}
return notes, nil
}
// readNote reads a single note from r, decoding the descriptor if possible.
func readNote(r io.ReadSeeker) (*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 := &Note{}
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:
note.Desc = &LinuxPrStatus{}
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_PRPSINFO entry %v: %v", i, err)
}
data.entries = append(data.entries, entry)
}
note.Desc = data
}
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 *elf.File, exe io.ReaderAt, notes []*Note) 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: uintptr(entry.Start - (entry.FileOfs * fileNote.PageSize)),
}
memory.Add(r, uintptr(entry.Start), uintptr(entry.End-entry.Start))
}
}
}
for _, prog := range core.Progs {
if prog.Type == elf.PT_LOAD {
if prog.Filesz == 0 {
continue
}
r := &OffsetReaderAt{
reader: prog.ReaderAt,
offset: uintptr(prog.Vaddr),
}
memory.Add(r, uintptr(prog.Vaddr), uintptr(prog.Filesz))
}
}
return memory
}
// 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
}
type LinuxPrStatus struct {
Siginfo LinuxSiginfo
Cursig uint16
_ [2]uint8
Sigpend uint64
Sighold uint64
Pid, Ppid, Pgrp, Sid int32
Utime, Stime, CUtime, CStime unix.Timeval
Reg unix.PtraceRegs
Fpvalid int32
}
type LinuxSiginfo struct {
Signo int32
Code int32
Errno int32
}
type LinuxNTFile struct {
LinuxNTFileHdr
entries []*LinuxNTFileEntry
}
type LinuxNTFileHdr struct {
Count uint64
PageSize uint64
}
type LinuxNTFileEntry struct {
Start uint64
End uint64
FileOfs uint64
}
// ELF Notes header. Same size on 64 and 32-bit machines.
type ELFNotesHdr struct {
Namesz uint32
Descsz uint32
Type uint32
}