mirror of
https://github.com/go-delve/delve.git
synced 2025-10-31 02:36:18 +08:00
proc/core: implementing coredump functionality for ARM64 (#1774)
* proc/native: optimize native.status through buffering (#1865) Benchmark before: BenchmarkConditionalBreakpoints-4 1 15649407130 ns/op Benchmark after: BenchmarkConditionalBreakpoints-4 1 14586710018 ns/op Conditional breakpoint evaluation 1.56ms -> 1.45ms Updates #1549 * proc/core: Review Comments Incorporated Signed-off-by: ossdev07 <ossdev@puresoftware.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
This commit is contained in:
@ -190,7 +190,7 @@ var (
|
|||||||
|
|
||||||
type openFn func(string, string) (*Process, error)
|
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
|
// ErrUnrecognizedFormat is returned when the core file is not recognized as
|
||||||
// any of the supported formats.
|
// any of the supported formats.
|
||||||
|
|||||||
@ -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
|
// NT_AUXV is the note type for notes containing a copy of the Auxv array
|
||||||
const NT_AUXV elf.NType = 0x6
|
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"
|
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:
|
// exePath. For details on the Linux ELF core format, see:
|
||||||
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
||||||
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
|
// 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,
|
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
|
||||||
// and, if absolutely desperate, readelf.c from the binutils source.
|
// 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)
|
coreFile, err := elf.Open(corePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
|
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
|
||||||
@ -62,45 +112,41 @@ func readLinuxAMD64Core(corePath, exePath string) (*Process, error) {
|
|||||||
return nil, fmt.Errorf("%v is not an exe file", exeELF)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
memory := buildMemory(coreFile, exeELF, exe, notes)
|
memory := buildMemory(coreFile, exeELF, exe, notes)
|
||||||
entryPoint := findEntryPoint(notes)
|
entryPoint := findEntryPoint(notes)
|
||||||
|
|
||||||
p := &Process{
|
p := &Process{
|
||||||
mem: memory,
|
mem: memory,
|
||||||
Threads: map[int]*Thread{},
|
Threads: map[int]*Thread{},
|
||||||
entryPoint: entryPoint,
|
entryPoint: entryPoint,
|
||||||
bi: proc.NewBinaryInfo("linux", "amd64"),
|
|
||||||
breakpoints: proc.NewBreakpointMap(),
|
breakpoints: proc.NewBreakpointMap(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastThread *linuxAMD64Thread
|
switch machineType {
|
||||||
for _, note := range notes {
|
case EM_X86_64:
|
||||||
switch note.Type {
|
p.bi = proc.NewBinaryInfo("linux", "amd64")
|
||||||
case elf.NT_PRSTATUS:
|
linuxThreadsFromNotes(p, notes, machineType)
|
||||||
t := note.Desc.(*LinuxPrStatus)
|
case EM_AARCH64:
|
||||||
lastThread = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
|
p.bi = proc.NewBinaryInfo("linux", "arm64")
|
||||||
p.Threads[int(t.Pid)] = &Thread{lastThread, p, proc.CommonThread{}}
|
linuxThreadsFromNotes(p, notes, machineType)
|
||||||
if p.currentThread == nil {
|
default:
|
||||||
p.currentThread = p.Threads[int(t.Pid)]
|
return nil, fmt.Errorf("unsupported machine type")
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type linuxAMD64Thread struct {
|
type linuxAMD64Thread struct {
|
||||||
regs linutil.AMD64Registers
|
regs linutil.AMD64Registers
|
||||||
t *LinuxPrStatus
|
t *LinuxPrStatusAMD64
|
||||||
|
}
|
||||||
|
|
||||||
|
type linuxARM64Thread struct {
|
||||||
|
regs linutil.ARM64Registers
|
||||||
|
t *LinuxPrStatusARM64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error) {
|
func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error) {
|
||||||
@ -112,10 +158,23 @@ func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error)
|
|||||||
return &r, nil
|
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 {
|
func (t *linuxAMD64Thread) pid() int {
|
||||||
return int(t.t.Pid)
|
return int(t.t.Pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *linuxARM64Thread) pid() int {
|
||||||
|
return int(t.t.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
// Note is a note from the PT_NOTE prog.
|
// Note is a note from the PT_NOTE prog.
|
||||||
// Relevant types:
|
// Relevant types:
|
||||||
// - NT_FILE: File mapping information, e.g. program text mappings. Desc is a LinuxNTFile.
|
// - NT_FILE: File mapping information, e.g. program text mappings. Desc is a LinuxNTFile.
|
||||||
@ -130,7 +189,7 @@ type Note struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readNotes reads all the notes from the notes prog in core.
|
// 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
|
var notesProg *elf.Prog
|
||||||
for _, prog := range core.Progs {
|
for _, prog := range core.Progs {
|
||||||
if prog.Type == elf.PT_NOTE {
|
if prog.Type == elf.PT_NOTE {
|
||||||
@ -142,7 +201,7 @@ func readNotes(core *elf.File) ([]*Note, error) {
|
|||||||
r := notesProg.Open()
|
r := notesProg.Open()
|
||||||
notes := []*Note{}
|
notes := []*Note{}
|
||||||
for {
|
for {
|
||||||
note, err := readNote(r)
|
note, err := readNote(r, machineType)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -156,7 +215,7 @@ func readNotes(core *elf.File) ([]*Note, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readNote reads a single note from r, decoding the descriptor if possible.
|
// 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:
|
// Notes are laid out as described in the SysV ABI:
|
||||||
// http://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section
|
// http://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section
|
||||||
note := &Note{}
|
note := &Note{}
|
||||||
@ -183,7 +242,13 @@ func readNote(r io.ReadSeeker) (*Note, error) {
|
|||||||
descReader := bytes.NewReader(desc)
|
descReader := bytes.NewReader(desc)
|
||||||
switch note.Type {
|
switch note.Type {
|
||||||
case elf.NT_PRSTATUS:
|
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 {
|
if err := binary.Read(descReader, binary.LittleEndian, note.Desc); err != nil {
|
||||||
return nil, fmt.Errorf("reading NT_PRSTATUS: %v", err)
|
return nil, fmt.Errorf("reading NT_PRSTATUS: %v", err)
|
||||||
}
|
}
|
||||||
@ -210,13 +275,24 @@ func readNote(r io.ReadSeeker) (*Note, error) {
|
|||||||
}
|
}
|
||||||
note.Desc = data
|
note.Desc = data
|
||||||
case NT_X86_XSTATE:
|
case NT_X86_XSTATE:
|
||||||
var fpregs linutil.AMD64Xstate
|
if machineType == EM_X86_64 {
|
||||||
if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
|
var fpregs linutil.AMD64Xstate
|
||||||
return nil, err
|
if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
note.Desc = &fpregs
|
||||||
}
|
}
|
||||||
note.Desc = &fpregs
|
|
||||||
case NT_AUXV:
|
case NT_AUXV:
|
||||||
note.Desc = desc
|
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 {
|
if err := skipPadding(r, 4); err != nil {
|
||||||
return nil, fmt.Errorf("aligning after desc: %v", err)
|
return nil, fmt.Errorf("aligning after desc: %v", err)
|
||||||
@ -302,8 +378,8 @@ type LinuxPrPsInfo struct {
|
|||||||
Args [80]uint8
|
Args [80]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinuxPrStatus is a copy of the prstatus kernel struct.
|
// LinuxPrStatusAMD64 is a copy of the prstatus kernel struct.
|
||||||
type LinuxPrStatus struct {
|
type LinuxPrStatusAMD64 struct {
|
||||||
Siginfo LinuxSiginfo
|
Siginfo LinuxSiginfo
|
||||||
Cursig uint16
|
Cursig uint16
|
||||||
_ [2]uint8
|
_ [2]uint8
|
||||||
@ -315,6 +391,19 @@ type LinuxPrStatus struct {
|
|||||||
Fpvalid int32
|
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
|
// LinuxSiginfo is a copy of the
|
||||||
// siginfo kernel struct.
|
// siginfo kernel struct.
|
||||||
type LinuxSiginfo struct {
|
type LinuxSiginfo struct {
|
||||||
@ -125,10 +125,22 @@ func (r *ARM64Registers) Copy() proc.Registers {
|
|||||||
return &rr
|
return &rr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode decodes an XSAVE area to a list of name/value pairs of registers.
|
type ARM64PtraceFpRegs struct {
|
||||||
func Decode(fpregs []byte) (regs []proc.Register) {
|
Vregs []byte
|
||||||
for i := 0; i < len(fpregs); i += 16 {
|
Fpsr uint32
|
||||||
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("V%d", i/16), fpregs[i:i+16])
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fpregs *ARM64PtraceFpRegs) Byte() []byte {
|
||||||
|
fpregs.Vregs = make([]byte, _ARM_FP_REGS_LENGTH)
|
||||||
|
return fpregs.Vregs[:]
|
||||||
|
}
|
||||||
|
|||||||
@ -12,13 +12,15 @@ import (
|
|||||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (thread *Thread) fpRegisters() (fpregs []proc.Register, fpregset []byte, err error) {
|
func (thread *Thread) fpRegisters() ([]proc.Register, []byte, error) {
|
||||||
thread.dbp.execPtraceFunc(func() { fpregset, err = PtraceGetFpRegset(thread.ID) })
|
var err error
|
||||||
fpregs = linutil.Decode(fpregset)
|
var arm_fpregs linutil.ARM64PtraceFpRegs
|
||||||
|
thread.dbp.execPtraceFunc(func() { arm_fpregs.Vregs, err = PtraceGetFpRegset(thread.ID) })
|
||||||
|
fpregs := arm_fpregs.Decode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
|
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 {
|
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user