Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proc/core: implementing coredump functionality for ARM64 #1774

Merged
merged 2 commits into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/proc/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ var (

type openFn func(string, string) (*Process, error)

var openFns = []openFn{readLinuxAMD64Core, readAMD64Minidump}
var openFns = []openFn{readLinuxCore, readAMD64Minidump}

// ErrUnrecognizedFormat is returned when the core file is not recognized as
// any of the supported formats.
Expand Down
155 changes: 122 additions & 33 deletions pkg/proc/core/linux_amd64_core.go → pkg/proc/core/linux_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,65 @@ const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAV
// 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"

// readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
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
// 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 readLinuxAMD64Core(corePath, exePath string) (*Process, error) {
func readLinuxCore(corePath, exePath string) (*Process, 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")) {
Expand All @@ -62,45 +112,41 @@ func readLinuxAMD64Core(corePath, exePath string) (*Process, error) {
return nil, fmt.Errorf("%v is not an exe file", exeELF)
}

notes, err := readNotes(coreFile)
machineType := exeELF.Machine
notes, err := readNotes(coreFile, machineType)
if err != nil {
return nil, err
}
memory := buildMemory(coreFile, exeELF, exe, notes)
entryPoint := findEntryPoint(notes)

p := &Process{
mem: memory,
Threads: map[int]*Thread{},
entryPoint: entryPoint,
bi: proc.NewBinaryInfo("linux", "amd64"),
breakpoints: proc.NewBreakpointMap(),
}

var lastThread *linuxAMD64Thread
for _, note := range notes {
switch note.Type {
case elf.NT_PRSTATUS:
t := note.Desc.(*LinuxPrStatus)
lastThread = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
p.Threads[int(t.Pid)] = &Thread{lastThread, p, proc.CommonThread{}}
if p.currentThread == nil {
p.currentThread = p.Threads[int(t.Pid)]
}
case NT_X86_XSTATE:
if lastThread != nil {
lastThread.regs.Fpregs = note.Desc.(*linutil.AMD64Xstate).Decode()
}
case elf.NT_PRPSINFO:
p.pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
}
switch machineType {
case EM_X86_64:
p.bi = proc.NewBinaryInfo("linux", "amd64")
linuxThreadsFromNotes(p, notes, machineType)
case EM_AARCH64:
p.bi = proc.NewBinaryInfo("linux", "arm64")
linuxThreadsFromNotes(p, notes, machineType)
default:
return nil, fmt.Errorf("unsupported machine type")
}
return p, nil
}

type linuxAMD64Thread struct {
regs linutil.AMD64Registers
t *LinuxPrStatus
t *LinuxPrStatusAMD64
}

type linuxARM64Thread struct {
regs linutil.ARM64Registers
t *LinuxPrStatusARM64
}

func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error) {
Expand All @@ -112,10 +158,23 @@ func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error)
return &r, nil
}

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
}

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.
Expand All @@ -130,7 +189,7 @@ type Note struct {
}

// readNotes reads all the notes from the notes prog in core.
func readNotes(core *elf.File) ([]*Note, error) {
func readNotes(core *elf.File, machineType elf.Machine) ([]*Note, error) {
var notesProg *elf.Prog
for _, prog := range core.Progs {
if prog.Type == elf.PT_NOTE {
Expand All @@ -142,7 +201,7 @@ func readNotes(core *elf.File) ([]*Note, error) {
r := notesProg.Open()
notes := []*Note{}
for {
note, err := readNote(r)
note, err := readNote(r, machineType)
if err == io.EOF {
break
}
Expand All @@ -156,7 +215,7 @@ func readNotes(core *elf.File) ([]*Note, error) {
}

// readNote reads a single note from r, decoding the descriptor if possible.
func readNote(r io.ReadSeeker) (*Note, error) {
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 := &Note{}
Expand All @@ -183,7 +242,13 @@ func readNote(r io.ReadSeeker) (*Note, error) {
descReader := bytes.NewReader(desc)
switch note.Type {
case elf.NT_PRSTATUS:
note.Desc = &LinuxPrStatus{}
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)
}
Expand All @@ -210,13 +275,24 @@ func readNote(r io.ReadSeeker) (*Note, error) {
}
note.Desc = data
case NT_X86_XSTATE:
var fpregs linutil.AMD64Xstate
if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
return nil, err
if machineType == EM_X86_64 {
var fpregs linutil.AMD64Xstate
if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
return nil, err
}
note.Desc = &fpregs
}
note.Desc = &fpregs
case NT_AUXV:
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)
Expand Down Expand Up @@ -302,8 +378,8 @@ type LinuxPrPsInfo struct {
Args [80]uint8
}

// LinuxPrStatus is a copy of the prstatus kernel struct.
type LinuxPrStatus struct {
// LinuxPrStatusAMD64 is a copy of the prstatus kernel struct.
type LinuxPrStatusAMD64 struct {
Siginfo LinuxSiginfo
Cursig uint16
_ [2]uint8
Expand All @@ -315,6 +391,19 @@ type LinuxPrStatus struct {
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 {
Expand Down
20 changes: 16 additions & 4 deletions pkg/proc/linutil/regs_arm64_arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,22 @@ func (r *ARM64Registers) Copy() proc.Registers {
return &rr
}

// Decode decodes an XSAVE area to a list of name/value pairs of registers.
func Decode(fpregs []byte) (regs []proc.Register) {
for i := 0; i < len(fpregs); i += 16 {
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("V%d", i/16), fpregs[i:i+16])
type ARM64PtraceFpRegs struct {
Vregs []byte
Fpsr uint32
Fpcr uint32
}

const _ARM_FP_REGS_LENGTH = 512

func (fpregs *ARM64PtraceFpRegs) Decode() (regs []proc.Register) {
for i := 0; i < len(fpregs.Vregs); i += 16 {
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("V%d", i/16), fpregs.Vregs[i:i+16])
}
return
}

func (fpregs *ARM64PtraceFpRegs) Byte() []byte {
fpregs.Vregs = make([]byte, _ARM_FP_REGS_LENGTH)
return fpregs.Vregs[:]
}
4 changes: 3 additions & 1 deletion pkg/proc/native/proc_linux.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package native

import (
"bufio"
"bytes"
"errors"
"fmt"
Expand Down Expand Up @@ -351,6 +352,7 @@ func status(pid int, comm string) rune {
return '\000'
}
defer f.Close()
rd := bufio.NewReader(f)

var (
p int
Expand All @@ -361,7 +363,7 @@ func status(pid int, comm string) rune {
// The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters
// Since both parenthesis and spaces can appear inside the name of the task and no escaping happens we need to read the name of the executable first
// See: include/linux/sched.c:315 and include/linux/sched.c:1510
fmt.Fscanf(f, "%d ("+comm+") %c", &p, &state)
fmt.Fscanf(rd, "%d ("+comm+") %c", &p, &state)
return state
}

Expand Down
10 changes: 6 additions & 4 deletions pkg/proc/native/threads_linux_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import (
"github.com/go-delve/delve/pkg/proc/linutil"
)

func (thread *Thread) fpRegisters() (fpregs []proc.Register, fpregset []byte, err error) {
thread.dbp.execPtraceFunc(func() { fpregset, err = PtraceGetFpRegset(thread.ID) })
fpregs = linutil.Decode(fpregset)
func (thread *Thread) fpRegisters() ([]proc.Register, []byte, error) {
var err error
var arm_fpregs linutil.ARM64PtraceFpRegs
thread.dbp.execPtraceFunc(func() { arm_fpregs.Vregs, err = PtraceGetFpRegset(thread.ID) })
fpregs := arm_fpregs.Decode()
if err != nil {
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
}
return
return fpregs, arm_fpregs.Vregs, err
}

func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
Expand Down