
Cleans up repetitive code in pkg/proc/core and also adds coredumpctl support to our test suite.
526 lines
15 KiB
Go
526 lines
15 KiB
Go
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 https://man7.org/linux/man-pages/man5/elf.5.html
|
|
const (
|
|
_EM_AARCH64 = 183
|
|
_EM_X86_64 = 62
|
|
_EM_RISCV = 243
|
|
_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 lastThread osThread
|
|
|
|
for _, note := range notes {
|
|
switch note.Type {
|
|
case elf.NT_PRSTATUS:
|
|
switch machineType {
|
|
case _EM_X86_64:
|
|
t := note.Desc.(*linuxPrStatusAMD64)
|
|
lastThread = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
|
|
case _EM_AARCH64:
|
|
t := note.Desc.(*linuxPrStatusARM64)
|
|
lastThread = &linuxARM64Thread{linutil.ARM64Registers{Regs: &t.Reg}, t}
|
|
case _EM_RISCV:
|
|
t := note.Desc.(*linuxPrStatusRISCV64)
|
|
lastThread = &linuxRISCV64Thread{linutil.RISCV64Registers{Regs: &t.Reg}, t}
|
|
default:
|
|
continue
|
|
}
|
|
p.Threads[lastThread.ThreadID()] = &thread{lastThread, p, proc.CommonThread{}}
|
|
if currentThread == nil {
|
|
currentThread = p.Threads[lastThread.ThreadID()]
|
|
}
|
|
case _NT_FPREGSET:
|
|
switch th := lastThread.(type) {
|
|
case *linuxARM64Thread:
|
|
th.regs.Fpregs = note.Desc.(*linutil.ARM64PtraceFpRegs).Decode()
|
|
case *linuxRISCV64Thread:
|
|
th.regs.Fpregs = note.Desc.(*linutil.RISCV64PtraceFpRegs).Decode()
|
|
}
|
|
case _NT_X86_XSTATE:
|
|
if lastThread != nil {
|
|
lastThread.(*linuxAMD64Thread).regs.Fpregs = note.Desc.(*amd64util.AMD64Xstate).Decode()
|
|
}
|
|
case elf.NT_PRPSINFO:
|
|
p.pid = int(note.Desc.(*linuxPrPsInfo).Pid)
|
|
}
|
|
}
|
|
return currentThread
|
|
}
|
|
|
|
var supportedLinuxMachines = map[elf.Machine]string{
|
|
_EM_X86_64: "amd64",
|
|
_EM_AARCH64: "arm64",
|
|
_EM_RISCV: "riscv64",
|
|
}
|
|
|
|
// readLinuxOrPlatformIndependentCore reads a core file from corePath
|
|
// corresponding to the executable at exePath. For details on the Linux ELF
|
|
// core format, see:
|
|
// https://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
|
// https://uhlo.blogspot.com/2012/05/brief-look-into-core-dumps.html,
|
|
// elf_core_dump in https://elixir.bootlin.com/linux/v4.20.17/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 if goarch, ok := supportedLinuxMachines[machineType]; ok {
|
|
bi = proc.NewBinaryInfo("linux", goarch)
|
|
} else {
|
|
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
|
|
}
|
|
|
|
type linuxRISCV64Thread struct {
|
|
regs linutil.RISCV64Registers
|
|
t *linuxPrStatusRISCV64
|
|
}
|
|
|
|
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 *linuxRISCV64Thread) Registers() (proc.Registers, error) {
|
|
var r linutil.RISCV64Registers
|
|
r.Regs = t.regs.Regs
|
|
r.Fpregs = t.regs.Fpregs
|
|
return &r, nil
|
|
}
|
|
|
|
func (t *linuxAMD64Thread) ThreadID() int {
|
|
return int(t.t.Pid)
|
|
}
|
|
|
|
func (t *linuxARM64Thread) ThreadID() int {
|
|
return int(t.t.Pid)
|
|
}
|
|
|
|
func (t *linuxRISCV64Thread) ThreadID() 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:
|
|
// https://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{}
|
|
case _EM_RISCV:
|
|
note.Desc = &linuxPrStatusRISCV64{}
|
|
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 {
|
|
err = readFpregsetNote(note, &linutil.ARM64PtraceFpRegs{}, desc[:_ARM_FP_HEADER_START])
|
|
} else if machineType == _EM_RISCV {
|
|
err = readFpregsetNote(note, &linutil.RISCV64PtraceFpRegs{}, desc)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := skipPadding(r, 4); err != nil {
|
|
return nil, fmt.Errorf("aligning after desc: %v", err)
|
|
}
|
|
return note, nil
|
|
}
|
|
|
|
func readFpregsetNote(note *note, fpregs interface{ Byte() []byte }, desc []byte) error {
|
|
rdr := bytes.NewReader(desc)
|
|
if err := binary.Read(rdr, binary.LittleEndian, fpregs.Byte()); err != nil {
|
|
return err
|
|
}
|
|
note.Desc = fpregs
|
|
return 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 https://elixir.bootlin.com/linux/v4.20.17/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
|
|
}
|
|
|
|
// LinuxPrStatusRISCV64 is a copy of the prstatus kernel struct.
|
|
type linuxPrStatusRISCV64 struct {
|
|
Siginfo linuxSiginfo
|
|
Cursig uint16
|
|
_ [2]uint8
|
|
Sigpend uint64
|
|
Sighold uint64
|
|
Pid, Ppid, Pgrp, Sid int32
|
|
Utime, Stime, CUtime, CStime linuxCoreTimeval
|
|
Reg linutil.RISCV64PtraceRegs
|
|
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
|
|
}
|