2017-04-21 06:55:53 +00:00
package core
2017-02-08 20:22:04 +00:00
import (
"bytes"
2017-05-29 13:20:01 +00:00
"debug/elf"
2017-02-08 20:22:04 +00:00
"encoding/binary"
"fmt"
"io"
"os"
2018-10-16 10:48:59 +00:00
"strings"
2017-02-08 20:22:04 +00:00
2019-01-04 18:39:25 +00:00
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
2017-02-08 20:22:04 +00:00
)
2017-04-03 09:17:54 +00:00
// Copied from golang.org/x/sys/unix.Timeval since it's not available on all
// systems.
type LinuxCoreTimeval struct {
Sec int64
Usec int64
}
2018-08-31 18:08:18 +00:00
// 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.
2017-07-20 19:04:00 +00:00
const NT_X86_XSTATE elf . NType = 0x202 // Note type for notes containing X86 XSAVE area.
2017-02-08 20:22:04 +00:00
2018-05-29 15:01:51 +00:00
// NT_AUXV is the note type for notes containing a copy of the Auxv array
const NT_AUXV elf . NType = 0x6
2020-02-17 17:29:17 +00:00
// 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
)
2018-10-16 10:48:59 +00:00
const elfErrorBadMagicNumber = "bad magic number"
2020-02-17 17:29:17 +00:00
func linuxThreadsFromNotes ( p * Process , notes [ ] * Note , machineType elf . Machine ) {
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 p . currentThread == nil {
p . 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 p . currentThread == nil {
p . 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 . ( * linutil . AMD64Xstate ) . Decode ( )
}
}
case elf . NT_PRPSINFO :
p . pid = int ( note . Desc . ( * LinuxPrPsInfo ) . Pid )
}
}
}
// readLinuxCore reads a core file from corePath corresponding to the executable at
2017-02-08 20:22:04 +00:00
// 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.
2020-02-17 17:29:17 +00:00
func readLinuxCore ( corePath , exePath string ) ( * Process , error ) {
2017-07-20 19:04:00 +00:00
coreFile , err := elf . Open ( corePath )
2017-02-08 20:22:04 +00:00
if err != nil {
2018-10-16 10:48:59 +00:00
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 , ErrUnrecognizedFormat
}
2017-02-08 20:22:04 +00:00
return nil , err
}
exe , err := os . Open ( exePath )
if err != nil {
return nil , err
}
2018-06-29 22:02:53 +00:00
exeELF , err := elf . NewFile ( exe )
if err != nil {
return nil , err
}
2017-02-08 20:22:04 +00:00
2017-07-20 19:04:00 +00:00
if coreFile . Type != elf . ET_CORE {
return nil , fmt . Errorf ( "%v is not a core file" , coreFile )
2017-02-08 20:22:04 +00:00
}
2018-05-29 15:01:51 +00:00
if exeELF . Type != elf . ET_EXEC && exeELF . Type != elf . ET_DYN {
2018-06-29 22:02:53 +00:00
return nil , fmt . Errorf ( "%v is not an exe file" , exeELF )
}
2017-02-08 20:22:04 +00:00
2020-02-17 17:29:17 +00:00
machineType := exeELF . Machine
notes , err := readNotes ( coreFile , machineType )
2017-02-08 20:22:04 +00:00
if err != nil {
return nil , err
}
2018-06-29 22:02:53 +00:00
memory := buildMemory ( coreFile , exeELF , exe , notes )
2017-02-08 20:22:04 +00:00
2020-03-10 16:34:40 +00:00
// TODO support 386
var bi * proc . BinaryInfo
2020-02-17 17:29:17 +00:00
switch machineType {
case EM_X86_64 :
2020-03-10 16:34:40 +00:00
bi = proc . NewBinaryInfo ( "linux" , "amd64" )
2020-02-17 17:29:17 +00:00
case EM_AARCH64 :
2020-03-10 16:34:40 +00:00
bi = proc . NewBinaryInfo ( "linux" , "arm64" )
2020-02-17 17:29:17 +00:00
default :
return nil , fmt . Errorf ( "unsupported machine type" )
2017-02-08 20:22:04 +00:00
}
2020-03-10 16:34:40 +00:00
entryPoint := findEntryPoint ( notes , bi . Arch . PtrSize ( ) )
p := & Process {
mem : memory ,
Threads : map [ int ] * Thread { } ,
entryPoint : entryPoint ,
bi : bi ,
breakpoints : proc . NewBreakpointMap ( ) ,
}
linuxThreadsFromNotes ( p , notes , machineType )
2018-10-18 07:13:11 +00:00
return p , nil
}
type linuxAMD64Thread struct {
regs linutil . AMD64Registers
2020-02-17 17:29:17 +00:00
t * LinuxPrStatusAMD64
}
type linuxARM64Thread struct {
regs linutil . ARM64Registers
t * LinuxPrStatusARM64
2017-02-08 20:22:04 +00:00
}
2018-10-18 07:13:11 +00:00
func ( t * linuxAMD64Thread ) registers ( floatingPoint bool ) ( proc . Registers , error ) {
var r linutil . AMD64Registers
r . Regs = t . regs . Regs
if floatingPoint {
r . Fpregs = t . regs . Fpregs
}
return & r , nil
}
2018-05-29 15:01:51 +00:00
2020-02-17 17:29:17 +00:00
func ( t * linuxARM64Thread ) registers ( floatingPoint bool ) ( proc . Registers , error ) {
var r linutil . ARM64Registers
r . Regs = t . regs . Regs
if floatingPoint {
r . Fpregs = t . regs . Fpregs
}
return & r , nil
}
2018-10-18 07:13:11 +00:00
func ( t * linuxAMD64Thread ) pid ( ) int {
return int ( t . t . Pid )
2017-02-08 20:22:04 +00:00
}
2020-02-17 17:29:17 +00:00
func ( t * linuxARM64Thread ) pid ( ) int {
return int ( t . t . Pid )
}
2017-02-08 20:22:04 +00:00
// 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.
2017-07-20 19:04:00 +00:00
// - NT_X86_XSTATE: Other registers, including AVX and such.
2017-02-08 20:22:04 +00:00
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.
2020-02-17 17:29:17 +00:00
func readNotes ( core * elf . File , machineType elf . Machine ) ( [ ] * Note , error ) {
2017-02-08 20:22:04 +00:00
var notesProg * elf . Prog
for _ , prog := range core . Progs {
if prog . Type == elf . PT_NOTE {
notesProg = prog
break
}
}
r := notesProg . Open ( )
notes := [ ] * Note { }
for {
2020-02-17 17:29:17 +00:00
note , err := readNote ( r , machineType )
2017-02-08 20:22:04 +00:00
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.
2020-02-17 17:29:17 +00:00
func readNote ( r io . ReadSeeker , machineType elf . Machine ) ( * Note , error ) {
2017-02-08 20:22:04 +00:00
// 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 :
2020-02-17 17:29:17 +00:00
if machineType == EM_X86_64 {
note . Desc = & LinuxPrStatusAMD64 { }
} else if machineType == EM_AARCH64 {
note . Desc = & LinuxPrStatusARM64 { }
} else {
return nil , fmt . Errorf ( "unsupported machine type" )
}
2017-02-08 20:22:04 +00:00
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 {
2018-05-29 15:01:51 +00:00
return nil , fmt . Errorf ( "reading NT_FILE entry %v: %v" , i , err )
2017-02-08 20:22:04 +00:00
}
data . entries = append ( data . entries , entry )
}
note . Desc = data
2017-07-20 19:04:00 +00:00
case NT_X86_XSTATE :
2020-02-17 17:29:17 +00:00
if machineType == EM_X86_64 {
var fpregs linutil . AMD64Xstate
if err := linutil . AMD64XstateRead ( desc , true , & fpregs ) ; err != nil {
return nil , err
}
note . Desc = & fpregs
2017-07-20 19:04:00 +00:00
}
2018-05-29 15:01:51 +00:00
case NT_AUXV :
note . Desc = desc
2020-02-17 17:29:17 +00:00
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
}
2017-02-08 20:22:04 +00:00
}
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 {
2017-04-24 19:44:59 +00:00
pos , err := r . Seek ( 0 , os . SEEK_CUR )
2017-02-08 20:22:04 +00:00
if err != nil {
return err
}
if pos % pad == 0 {
return nil
}
2017-04-24 19:44:59 +00:00
if _ , err := r . Seek ( pad - ( pos % pad ) , os . SEEK_CUR ) ; err != nil {
2017-02-08 20:22:04 +00:00
return err
}
return nil
}
2018-06-29 22:02:53 +00:00
func buildMemory ( core , exeELF * elf . File , exe io . ReaderAt , notes [ ] * Note ) proc . MemoryReader {
2017-02-08 20:22:04 +00:00
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 ) )
}
}
}
2018-06-29 22:02:53 +00:00
// 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 } {
for _ , prog := range elfFile . 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 ) )
2017-02-08 20:22:04 +00:00
}
}
}
return memory
}
2020-03-10 16:34:40 +00:00
func findEntryPoint ( notes [ ] * Note , ptrSize int ) uint64 {
2018-05-29 15:01:51 +00:00
for _ , note := range notes {
if note . Type == NT_AUXV {
2020-03-10 16:34:40 +00:00
return linutil . EntryPointFromAuxv ( note . Desc . ( [ ] byte ) , ptrSize )
2018-05-29 15:01:51 +00:00
}
}
return 0
}
2018-08-31 18:08:18 +00:00
// LinuxPrPsInfo has various structures from the ELF spec and the Linux kernel.
2017-02-08 20:22:04 +00:00
// 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
}
2020-02-17 17:29:17 +00:00
// LinuxPrStatusAMD64 is a copy of the prstatus kernel struct.
type LinuxPrStatusAMD64 struct {
2017-02-08 20:22:04 +00:00
Siginfo LinuxSiginfo
Cursig uint16
_ [ 2 ] uint8
Sigpend uint64
Sighold uint64
Pid , Ppid , Pgrp , Sid int32
2017-04-03 09:17:54 +00:00
Utime , Stime , CUtime , CStime LinuxCoreTimeval
2018-10-17 14:55:53 +00:00
Reg linutil . AMD64PtraceRegs
2017-02-08 20:22:04 +00:00
Fpvalid int32
}
2020-02-17 17:29:17 +00:00
// 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
}
2018-08-31 18:08:18 +00:00
// LinuxSiginfo is a copy of the
// siginfo kernel struct.
2017-02-08 20:22:04 +00:00
type LinuxSiginfo struct {
Signo int32
Code int32
Errno int32
}
2018-08-31 18:08:18 +00:00
// LinuxNTFile contains information on mapped files.
2017-02-08 20:22:04 +00:00
type LinuxNTFile struct {
LinuxNTFileHdr
entries [ ] * LinuxNTFileEntry
}
2018-08-31 18:08:18 +00:00
// LinuxNTFileHdr is a header struct for NTFile.
2017-02-08 20:22:04 +00:00
type LinuxNTFileHdr struct {
Count uint64
PageSize uint64
}
2018-08-31 18:08:18 +00:00
// LinuxNTFileEntry is an entry of an NT_FILE note.
2017-02-08 20:22:04 +00:00
type LinuxNTFileEntry struct {
Start uint64
End uint64
FileOfs uint64
}
2018-08-31 18:08:18 +00:00
// ELFNotesHdr is the ELF Notes header.
// Same size on 64 and 32-bit machines.
2017-02-08 20:22:04 +00:00
type ELFNotesHdr struct {
Namesz uint32
Descsz uint32
Type uint32
}