提交 d4364d04 编写于 作者: A Alessandro Arzilli 提交者: Derek Parker

proc/core: support floating point registers (#912)

Updates #794
上级 809bdca1
package main
import "runtime"
import (
"os"
"runtime"
)
func fputestsetup(f64a, f64b, f64c, f64d float64, f32a, f32b, f32c, f32d float32)
......@@ -15,5 +18,9 @@ func main() {
var f32d float32 = 1.8
fputestsetup(f64a, f64b, f64c, f64d, f32a, f32b, f32c, f32d)
runtime.Breakpoint()
if len(os.Args) < 2 || os.Args[1] != "panic" {
runtime.Breakpoint()
} else {
panic("booom!")
}
}
......@@ -145,14 +145,15 @@ type Process struct {
bi proc.BinaryInfo
core *Core
breakpoints map[uint64]*proc.Breakpoint
currentThread *LinuxPrStatus
currentThread *Thread
selectedGoroutine *proc.G
allGCache []*proc.G
}
type Thread struct {
th *LinuxPrStatus
p *Process
th *LinuxPrStatus
fpregs []proc.Register
p *Process
}
var ErrWriteCore = errors.New("can not to core process")
......@@ -169,6 +170,9 @@ func OpenCore(corePath, exePath string) (*Process, error) {
breakpoints: make(map[uint64]*proc.Breakpoint),
bi: proc.NewBinaryInfo("linux", "amd64"),
}
for _, thread := range core.Threads {
thread.p = p
}
var wg sync.WaitGroup
p.bi.LoadBinaryInfo(exePath, &wg)
......@@ -221,8 +225,11 @@ func (t *Thread) ThreadID() int {
}
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
//TODO(aarzilli): handle floating point registers
return &t.th.Reg, nil
r := &Registers{&t.th.Reg, nil}
if floatingPoint {
r.fpregs = t.fpregs
}
return r, nil
}
func (t *Thread) Arch() proc.Arch {
......@@ -274,7 +281,7 @@ func (p *Process) ManualStopRequested() bool {
}
func (p *Process) CurrentThread() proc.Thread {
return &Thread{p.currentThread, p}
return p.currentThread
}
func (p *Process) Detach(bool) error {
......@@ -340,12 +347,62 @@ func (p *Process) SwitchThread(tid int) error {
func (p *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(p.core.Threads))
for _, v := range p.core.Threads {
r = append(r, &Thread{v, p})
r = append(r, v)
}
return r
}
func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
t, ok := p.core.Threads[threadID]
return &Thread{t, p}, ok
return t, ok
}
type Registers struct {
*LinuxCoreRegisters
fpregs []proc.Register
}
func (r *Registers) Slice() []proc.Register {
var regs = []struct {
k string
v uint64
}{
{"Rip", r.Rip},
{"Rsp", r.Rsp},
{"Rax", r.Rax},
{"Rbx", r.Rbx},
{"Rcx", r.Rcx},
{"Rdx", r.Rdx},
{"Rdi", r.Rdi},
{"Rsi", r.Rsi},
{"Rbp", r.Rbp},
{"R8", r.R8},
{"R9", r.R9},
{"R10", r.R10},
{"R11", r.R11},
{"R12", r.R12},
{"R13", r.R13},
{"R14", r.R14},
{"R15", r.R15},
{"Orig_rax", r.Orig_rax},
{"Cs", r.Cs},
{"Eflags", r.Eflags},
{"Ss", r.Ss},
{"Fs_base", r.Fs_base},
{"Gs_base", r.Gs_base},
{"Ds", r.Ds},
{"Es", r.Es},
{"Fs", r.Fs},
{"Gs", r.Gs},
}
out := make([]proc.Register, 0, len(regs))
for _, reg := range regs {
if reg.k == "Eflags" {
out = proc.AppendEflagReg(out, reg.k, reg.v)
} else {
out = proc.AppendQwordReg(out, reg.k, reg.v)
}
}
out = append(out, r.fpregs...)
return out
}
......@@ -125,26 +125,23 @@ func TestSplicedReader(t *testing.T) {
}
}
func TestCore(t *testing.T) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
return
}
func withCoreFile(t *testing.T, name, args string) *Process {
// This is all very fragile and won't work on hosts with non-default core patterns.
// Might be better to check in the binary and core?
tempDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
fix := test.BuildFixture("panic")
bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v", tempDir, fix.Path)
fix := test.BuildFixture(name)
bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args)
exec.Command("bash", "-c", bashCmd).Run()
cores, err := filepath.Glob(path.Join(tempDir, "core*"))
switch {
case err != nil || len(cores) > 1:
t.Fatalf("Got %v, wanted one file named core* in %v", cores, tempDir)
case len(cores) == 0:
t.Logf("core file was not produced, could not run test")
return
t.Skipf("core file was not produced, could not run test")
return nil
}
corePath := cores[0]
......@@ -156,6 +153,15 @@ func TestCore(t *testing.T) {
t.Errorf("read apport log: %q, %v", apport, err)
t.Fatalf("ReadCore() failed: %v", err)
}
return p
}
func TestCore(t *testing.T) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
return
}
p := withCoreFile(t, "panic", "")
gs, err := proc.GoroutinesInfo(p)
if err != nil || len(gs) == 0 {
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
......@@ -207,3 +213,80 @@ func TestCore(t *testing.T) {
t.Logf("%s = %s", reg.Name, reg.Value)
}
}
func TestCoreFpRegisters(t *testing.T) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
return
}
p := withCoreFile(t, "fputest/", "panic")
gs, err := proc.GoroutinesInfo(p)
if err != nil || len(gs) == 0 {
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
}
var regs proc.Registers
for _, thread := range p.ThreadList() {
frames, err := proc.ThreadStacktrace(thread, 10)
if err != nil {
t.Errorf("ThreadStacktrace for %x = %v", thread.ThreadID(), err)
continue
}
for i := range frames {
if frames[i].Current.Fn == nil {
continue
}
if frames[i].Current.Fn.Name == "runtime.crash" {
regs, err = thread.Registers(true)
if err != nil {
t.Fatalf("Could not get registers for thread %x, %v", thread.ThreadID(), err)
}
break
}
}
if regs != nil {
break
}
}
regtests := []struct{ name, value string }{
{"ST(0)", "0x3fffe666660000000000"},
{"ST(1)", "0x3fffd9999a0000000000"},
{"ST(2)", "0x3fffcccccd0000000000"},
{"ST(3)", "0x3fffc000000000000000"},
{"ST(4)", "0x3fffb333333333333000"},
{"ST(5)", "0x3fffa666666666666800"},
{"ST(6)", "0x3fff9999999999999800"},
{"ST(7)", "0x3fff8cccccccccccd000"},
// Unlike TestClientServer_FpRegisters in service/test/integration2_test
// we can not test the value of XMM0, it probably has been reused by
// something between the panic and the time we get the core dump.
{"XMM1", "0x3ff66666666666663ff4cccccccccccd"},
{"XMM2", "0x3fe666663fd9999a3fcccccd3fc00000"},
{"XMM3", "0x3ff199999999999a3ff3333333333333"},
{"XMM4", "0x3ff4cccccccccccd3ff6666666666666"},
{"XMM5", "0x3fcccccd3fc000003fe666663fd9999a"},
{"XMM6", "0x4004cccccccccccc4003333333333334"},
{"XMM7", "0x40026666666666664002666666666666"},
{"XMM8", "0x4059999a404ccccd4059999a404ccccd"},
}
for _, reg := range regs.Slice() {
t.Logf("%s = %s", reg.Name, reg.Value)
}
for _, regtest := range regtests {
found := false
for _, reg := range regs.Slice() {
if reg.Name == regtest.name {
found = true
if !strings.HasPrefix(reg.Value, regtest.value) {
t.Fatalf("register %s expected %q got %q", reg.Name, regtest.value, reg.Value)
}
}
}
if !found {
t.Fatalf("register %s not found: %v", regtest.name, regs)
}
}
}
......@@ -54,7 +54,8 @@ type LinuxCoreTimeval struct {
Usec int64
}
const NT_FILE elf.NType = 0x46494c45 // "FILE".
const NT_FILE elf.NType = 0x46494c45 // "FILE".
const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAVE area.
func (r *LinuxCoreRegisters) PC() uint64 {
return r.Rip
......@@ -241,50 +242,6 @@ func (r *LinuxCoreRegisters) SetPC(proc.Thread, uint64) error {
return errors.New("not supported")
}
func (r *LinuxCoreRegisters) Slice() []proc.Register {
var regs = []struct {
k string
v uint64
}{
{"Rip", r.Rip},
{"Rsp", r.Rsp},
{"Rax", r.Rax},
{"Rbx", r.Rbx},
{"Rcx", r.Rcx},
{"Rdx", r.Rdx},
{"Rdi", r.Rdi},
{"Rsi", r.Rsi},
{"Rbp", r.Rbp},
{"R8", r.R8},
{"R9", r.R9},
{"R10", r.R10},
{"R11", r.R11},
{"R12", r.R12},
{"R13", r.R13},
{"R14", r.R14},
{"R15", r.R15},
{"Orig_rax", r.Orig_rax},
{"Cs", r.Cs},
{"Eflags", r.Eflags},
{"Ss", r.Ss},
{"Fs_base", r.Fs_base},
{"Gs_base", r.Gs_base},
{"Ds", r.Ds},
{"Es", r.Es},
{"Fs", r.Fs},
{"Gs", r.Gs},
}
out := make([]proc.Register, 0, len(regs))
for _, reg := range regs {
if reg.k == "Eflags" {
out = proc.AppendEflagReg(out, reg.k, reg.v)
} else {
out = proc.AppendQwordReg(out, reg.k, reg.v)
}
}
return out
}
// 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/,
......@@ -292,7 +249,7 @@ func (r *LinuxCoreRegisters) Slice() []proc.Register {
// 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)
coreFile, err := elf.Open(corePath)
if err != nil {
return nil, err
}
......@@ -301,37 +258,42 @@ func readCore(corePath, exePath string) (*Core, error) {
return nil, err
}
if core.Type != elf.ET_CORE {
return nil, fmt.Errorf("%v is not a core file", core)
if coreFile.Type != elf.ET_CORE {
return nil, fmt.Errorf("%v is not a core file", coreFile)
}
notes, err := readNotes(core)
notes, err := readNotes(coreFile)
if err != nil {
return nil, err
}
memory := buildMemory(core, exe, notes)
memory := buildMemory(coreFile, exe, notes)
core := &Core{
MemoryReader: memory,
Threads: map[int]*Thread{},
}
threads := map[int]*LinuxPrStatus{}
pid := 0
var lastThread *Thread
for _, note := range notes {
switch note.Type {
case elf.NT_PRSTATUS:
t := note.Desc.(*LinuxPrStatus)
threads[int(t.Pid)] = t
lastThread = &Thread{t, nil, nil}
core.Threads[int(t.Pid)] = lastThread
case NT_X86_XSTATE:
if lastThread != nil {
lastThread.fpregs = note.Desc.(*proc.LinuxX86Xstate).Decode()
}
case elf.NT_PRPSINFO:
pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
core.Pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
}
}
return &Core{
MemoryReader: memory,
Threads: threads,
Pid: pid,
}, nil
return core, nil
}
type Core struct {
proc.MemoryReader
Threads map[int]*LinuxPrStatus
Threads map[int]*Thread
Pid int
}
......@@ -341,7 +303,7 @@ type Core struct {
// - 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.
// - NT_X86_XSTATE: Other registers, including AVX and such.
type Note struct {
Type elf.NType
Name string
......@@ -428,6 +390,12 @@ func readNote(r io.ReadSeeker) (*Note, error) {
data.entries = append(data.entries, entry)
}
note.Desc = data
case NT_X86_XSTATE:
var fpregs proc.LinuxX86Xstate
if err := proc.LinuxX86XstateRead(desc, true, &fpregs); err != nil {
return nil, err
}
note.Desc = &fpregs
}
if err := skipPadding(r, 4); err != nil {
return nil, fmt.Errorf("aligning after desc: %v", err)
......
package native
import (
"encoding/binary"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc"
)
// PtraceAttach executes the sys.PtraceAttach call.
......@@ -56,7 +57,7 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
// and amd64_supply_xsave in gdb/amd64-tdep.c.html
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture
func PtraceGetRegset(tid int) (regset PtraceXsave, err error) {
func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.PtraceFpRegs)), 0, 0)
if err == syscall.Errno(0) {
err = nil
......@@ -71,26 +72,6 @@ func PtraceGetRegset(tid int) (regset PtraceXsave, err error) {
err = nil
}
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= iov.Len {
return
}
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:iov.Len]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return
err = proc.LinuxX86XstateRead(xstateargs[:iov.Len], false, &regset)
return regset, err
}
package native
import (
"fmt"
"golang.org/x/arch/x86/x86asm"
sys "golang.org/x/sys/unix"
......@@ -273,27 +271,6 @@ func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
return r, nil
}
// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
padding [24]uint32
}
type PtraceXsave struct {
PtraceFpRegs
AvxState bool // contains AVX state
YmmSpace [256]byte
}
const (
_X86_XSTATE_MAX_SIZE = 2688
_NT_X86_XSTATE = 0x202
......@@ -305,31 +282,8 @@ const (
)
func (thread *Thread) fpRegisters() (regs []proc.Register, err error) {
var fpregs PtraceXsave
var fpregs proc.LinuxX86Xstate
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
// x87 registers
regs = proc.AppendWordReg(regs, "CW", fpregs.Cwd)
regs = proc.AppendWordReg(regs, "SW", fpregs.Swd)
regs = proc.AppendWordReg(regs, "TW", fpregs.Ftw)
regs = proc.AppendWordReg(regs, "FOP", fpregs.Fop)
regs = proc.AppendQwordReg(regs, "FIP", fpregs.Rip)
regs = proc.AppendQwordReg(regs, "FDP", fpregs.Rdp)
for i := 0; i < len(fpregs.StSpace); i += 4 {
regs = proc.AppendX87Reg(regs, i/4, uint16(fpregs.StSpace[i+2]), uint64(fpregs.StSpace[i+1])<<32|uint64(fpregs.StSpace[i]))
}
// SSE registers
regs = proc.AppendMxcsrReg(regs, "MXCSR", uint64(fpregs.Mxcsr))
regs = proc.AppendDwordReg(regs, "MXCSR_MASK", fpregs.MxcrMask)
for i := 0; i < len(fpregs.XmmSpace); i += 16 {
regs = proc.AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), fpregs.XmmSpace[i:i+16])
if fpregs.AvxState {
regs = proc.AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), fpregs.YmmSpace[i:i+16])
}
}
regs = fpregs.Decode()
return
}
......@@ -232,3 +232,98 @@ func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
}
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
}
// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
Padding [24]uint32
}
// LinuxX86Xstate represents amd64 XSAVE area. See Section 13.1 (and
// following) of Intel® 64 and IA-32 Architectures Software Developer’s
// Manual, Volume 1: Basic Architecture.
type LinuxX86Xstate struct {
PtraceFpRegs
AvxState bool // contains AVX state
YmmSpace [256]byte
}
// Decode decodes an XSAVE area to a list of name/value pairs of registers.
func (xsave *LinuxX86Xstate) Decode() (regs []Register) {
// x87 registers
regs = AppendWordReg(regs, "CW", xsave.Cwd)
regs = AppendWordReg(regs, "SW", xsave.Swd)
regs = AppendWordReg(regs, "TW", xsave.Ftw)
regs = AppendWordReg(regs, "FOP", xsave.Fop)
regs = AppendQwordReg(regs, "FIP", xsave.Rip)
regs = AppendQwordReg(regs, "FDP", xsave.Rdp)
for i := 0; i < len(xsave.StSpace); i += 4 {
regs = AppendX87Reg(regs, i/4, uint16(xsave.StSpace[i+2]), uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i]))
}
// SSE registers
regs = AppendMxcsrReg(regs, "MXCSR", uint64(xsave.Mxcsr))
regs = AppendDwordReg(regs, "MXCSR_MASK", xsave.MxcrMask)
for i := 0; i < len(xsave.XmmSpace); i += 16 {
regs = AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16])
if xsave.AvxState {
regs = AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16])
}
}
return
}
const (
_XSAVE_HEADER_START = 512
_XSAVE_HEADER_LEN = 64
_XSAVE_EXTENDED_REGION_START = 576
_XSAVE_SSE_REGION_LEN = 416
)
// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset.
// If readLegacy is true regset.PtraceFpRegs will be filled with the
// contents of the legacy region of the XSAVE area.
// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures
// Software Developer’s Manual, Volume 1: Basic Architecture.
func LinuxX86XstateRead(xstateargs []byte, readLegacy bool, regset *LinuxX86Xstate) error {
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) {
return nil
}
if readLegacy {
rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START])
if err := binary.Read(rdr, binary.LittleEndian, &regset.PtraceFpRegs); err != nil {
return err
}
}
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return nil
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return nil
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return nil
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册