proc: implement target.Interface for core files

This commit is contained in:
aarzilli 2017-04-03 11:17:54 +02:00 committed by Derek Parker
parent 3dacc25d2e
commit c1879472a1
13 changed files with 624 additions and 33 deletions

@ -1,5 +1,6 @@
package main package main
func main() { func main() {
panic("BOOM!") msg := "BOOM!"
panic(msg)
} }

@ -159,7 +159,7 @@ consider compiling debugging binaries with -gcflags="-N -l".`,
return nil return nil
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
os.Exit(execute(0, args, conf, executingExistingFile)) os.Exit(execute(0, args, conf, "", executingExistingFile))
}, },
} }
RootCommand.AddCommand(execCommand) RootCommand.AddCommand(execCommand)
@ -205,6 +205,24 @@ to know what functions your process is executing.`,
traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.") traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
RootCommand.AddCommand(traceCommand) RootCommand.AddCommand(traceCommand)
coreCommand := &cobra.Command{
Use: "core <executable> <core>",
Short: "Examine a core dump.",
Long: `Examine a core dump.
The core command will open the specified core file and the associated
executable and let you examine the state of the process when the
core dump was taken.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return errors.New("you must provide a core file and an executable")
}
return nil
},
Run: coreCmd,
}
RootCommand.AddCommand(coreCommand)
// 'version' subcommand. // 'version' subcommand.
versionCommand := &cobra.Command{ versionCommand := &cobra.Command{
Use: "version", Use: "version",
@ -243,7 +261,7 @@ func debugCmd(cmd *cobra.Command, args []string) {
return 1 return 1
} }
processArgs := append([]string{abs}, targetArgs...) processArgs := append([]string{abs}, targetArgs...)
return execute(0, processArgs, conf, executingGeneratedFile) return execute(0, processArgs, conf, "", executingGeneratedFile)
}() }()
os.Exit(status) os.Exit(status)
} }
@ -332,7 +350,7 @@ func testCmd(cmd *cobra.Command, args []string) {
defer os.Remove("./" + testdebugname) defer os.Remove("./" + testdebugname)
processArgs := append([]string{"./" + testdebugname}, targetArgs...) processArgs := append([]string{"./" + testdebugname}, targetArgs...)
return execute(0, processArgs, conf, executingOther) return execute(0, processArgs, conf, "", executingOther)
}() }()
os.Exit(status) os.Exit(status)
} }
@ -343,7 +361,11 @@ func attachCmd(cmd *cobra.Command, args []string) {
fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0])
os.Exit(1) os.Exit(1)
} }
os.Exit(execute(pid, nil, conf, executingOther)) os.Exit(execute(pid, nil, conf, "", executingOther))
}
func coreCmd(cmd *cobra.Command, args []string) {
os.Exit(execute(0, []string{args[0]}, conf, args[1], executingOther))
} }
func connectCmd(cmd *cobra.Command, args []string) { func connectCmd(cmd *cobra.Command, args []string) {
@ -382,7 +404,7 @@ const (
executingOther executingOther
) )
func execute(attachPid int, processArgs []string, conf *config.Config, kind executeKind) int { func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind executeKind) int {
// Make a TCP listener // Make a TCP listener
listener, err := net.Listen("tcp", Addr) listener, err := net.Listen("tcp", Addr)
if err != nil { if err != nil {
@ -410,6 +432,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, kind exec
AcceptMulti: AcceptMulti, AcceptMulti: AcceptMulti,
APIVersion: APIVersion, APIVersion: APIVersion,
WorkingDir: WorkingDir, WorkingDir: WorkingDir,
CoreFile: coreFile,
}, Log) }, Log)
default: default:
fmt.Printf("Unknown API version: %d\n", APIVersion) fmt.Printf("Unknown API version: %d\n", APIVersion)

@ -1,8 +1,12 @@
package proc package proc
import ( import (
"debug/gosym"
"errors"
"fmt" "fmt"
"go/ast"
"io" "io"
"sync"
) )
// MemoryReader is like io.ReaderAt, but the offset is a uintptr so that it // MemoryReader is like io.ReaderAt, but the offset is a uintptr so that it
@ -144,3 +148,214 @@ type OffsetReaderAt struct {
func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error) { func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
return r.reader.ReadAt(buf, int64(addr-r.offset)) return r.reader.ReadAt(buf, int64(addr-r.offset))
} }
type CoreProcess struct {
bi BinaryInfo
core *Core
breakpoints map[uint64]*Breakpoint
currentThread *LinuxPrStatus
selectedGoroutine *G
allGCache []*G
}
type CoreThread struct {
th *LinuxPrStatus
p *CoreProcess
}
var ErrWriteCore = errors.New("can not to core process")
var ErrShortRead = errors.New("short read")
var ErrContinueCore = errors.New("can not continue execution of core process")
func OpenCore(corePath, exePath string) (*CoreProcess, error) {
core, err := readCore(corePath, exePath)
if err != nil {
return nil, err
}
p := &CoreProcess{
core: core,
breakpoints: make(map[uint64]*Breakpoint),
bi: NewBinaryInfo("linux", "amd64"),
}
var wg sync.WaitGroup
p.bi.LoadBinaryInfo(exePath, &wg)
wg.Wait()
for _, th := range p.core.Threads {
p.currentThread = th
break
}
scope := &EvalScope{0, 0, p.CurrentThread(), nil, &p.bi}
ver, isextld, err := scope.getGoInformation()
if err != nil {
return nil, err
}
p.bi.arch.SetGStructOffset(ver, isextld)
p.selectedGoroutine, _ = GetG(p.CurrentThread())
return p, nil
}
func (p *CoreProcess) BinInfo() *BinaryInfo {
return &p.bi
}
func (thread *CoreThread) readMemory(addr uintptr, size int) (data []byte, err error) {
data = make([]byte, size)
n, err := thread.p.core.ReadMemory(data, addr)
if err == nil && n != len(data) {
err = ErrShortRead
}
return data, err
}
func (thread *CoreThread) writeMemory(addr uintptr, data []byte) (int, error) {
return 0, ErrWriteCore
}
func (t *CoreThread) Location() (*Location, error) {
f, l, fn := t.p.bi.PCToLine(t.th.Reg.Rip)
return &Location{PC: t.th.Reg.Rip, File: f, Line: l, Fn: fn}, nil
}
func (t *CoreThread) Breakpoint() (*Breakpoint, bool, error) {
return nil, false, nil
}
func (t *CoreThread) ThreadID() int {
return int(t.th.Pid)
}
func (t *CoreThread) Registers(floatingPoint bool) (Registers, error) {
//TODO(aarzilli): handle floating point registers
return &t.th.Reg, nil
}
func (t *CoreThread) Arch() Arch {
return t.p.bi.arch
}
func (t *CoreThread) BinInfo() *BinaryInfo {
return &t.p.bi
}
func (t *CoreThread) StepInstruction() error {
return ErrContinueCore
}
func (p *CoreProcess) Breakpoints() map[uint64]*Breakpoint {
return p.breakpoints
}
func (p *CoreProcess) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
return nil, NoBreakpointError{addr: addr}
}
func (p *CoreProcess) ClearInternalBreakpoints() error {
return nil
}
func (p *CoreProcess) ContinueOnce() (IThread, error) {
return nil, ErrContinueCore
}
func (p *CoreProcess) StepInstruction() error {
return ErrContinueCore
}
func (p *CoreProcess) RequestManualStop() error {
return nil
}
func (p *CoreProcess) CurrentThread() IThread {
return &CoreThread{p.currentThread, p}
}
func (p *CoreProcess) Detach(bool) error {
return nil
}
func (p *CoreProcess) Exited() bool {
return false
}
func (p *CoreProcess) FindFileLocation(fileName string, lineNumber int) (uint64, error) {
return FindFileLocation(p.CurrentThread(), p.breakpoints, &p.bi, fileName, lineNumber)
}
func (p *CoreProcess) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) {
return FirstPCAfterPrologue(p.CurrentThread(), p.breakpoints, &p.bi, fn, sameline)
}
func (p *CoreProcess) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) {
return FindFunctionLocation(p.CurrentThread(), p.breakpoints, &p.bi, funcName, firstLine, lineOffset)
}
func (p *CoreProcess) AllGCache() *[]*G {
return &p.allGCache
}
func (p *CoreProcess) Halt() error {
return nil
}
func (p *CoreProcess) Kill() error {
return nil
}
func (p *CoreProcess) Pid() int {
return p.core.Pid
}
func (p *CoreProcess) Running() bool {
return false
}
func (p *CoreProcess) SelectedGoroutine() *G {
return p.selectedGoroutine
}
func (p *CoreProcess) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) {
return nil, ErrWriteCore
}
func (p *CoreProcess) SwitchGoroutine(gid int) error {
g, err := FindGoroutine(p, gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil
}
if g.thread != nil {
return p.SwitchThread(g.thread.ThreadID())
}
p.selectedGoroutine = g
return nil
}
func (p *CoreProcess) SwitchThread(tid int) error {
if th, ok := p.core.Threads[tid]; ok {
p.currentThread = th
p.selectedGoroutine, _ = GetG(p.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
func (p *CoreProcess) ThreadList() []IThread {
r := make([]IThread, 0, len(p.core.Threads))
for _, v := range p.core.Threads {
r = append(r, &CoreThread{v, p})
}
return r
}
func (p *CoreProcess) FindThread(threadID int) (IThread, bool) {
t, ok := p.core.Threads[threadID]
return &CoreThread{t, p}, ok
}

@ -2,14 +2,16 @@ package proc
import ( import (
"bytes" "bytes"
"fmt"
"go/constant"
"io/ioutil" "io/ioutil"
"os/exec" "os/exec"
"reflect"
"testing"
"fmt"
"path" "path"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/derekparker/delve/pkg/proc/test" "github.com/derekparker/delve/pkg/proc/test"
) )
@ -122,9 +124,12 @@ func TestSplicedReader(t *testing.T) {
} }
} }
func TestReadCore(t *testing.T) { func TestCore(t *testing.T) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
return
}
// This is all very fragile and won't work on hosts with non-default core patterns. // This is all very fragile and won't work on hosts with non-default core patterns.
// Might be better to check in the core? // Might be better to check in the binary and core?
tempDir, err := ioutil.TempDir("", "") tempDir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -132,20 +137,72 @@ func TestReadCore(t *testing.T) {
fix := test.BuildFixture("panic") fix := test.BuildFixture("panic")
bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v", tempDir, fix.Path) bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v", tempDir, fix.Path)
exec.Command("bash", "-c", bashCmd).Run() exec.Command("bash", "-c", bashCmd).Run()
corePath := path.Join(tempDir, "core") 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
}
corePath := cores[0]
core, err := readCore(corePath, fix.Path) p, err := OpenCore(corePath, fix.Path)
if err != nil { if err != nil {
t.Fatal(err) pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern")
t.Errorf("read core_pattern: %q, %v", pat, err)
apport, err := ioutil.ReadFile("/var/log/apport.log")
t.Errorf("read apport log: %q, %v", apport, err)
t.Fatalf("ReadCore() failed: %v", err)
} }
if len(core.Threads) == 0 { gs, err := GoroutinesInfo(p)
t.Error("expected at least one thread") if err != nil || len(gs) == 0 {
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
} }
// Punch through the abstraction to verify that we got some mappings.
spliced := core.MemoryReader.(*SplicedMemory) var panicking *G
// There should be at least an RO section, RW section, RX section, the heap, and a thread stack. var panickingStack []Stackframe
if len(spliced.readers) < 5 { for _, g := range gs {
t.Errorf("expected at least 5 memory regions, got only %v", len(spliced.readers)) stack, err := g.Stacktrace(10)
if err != nil {
t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
}
for _, frame := range stack {
if strings.Contains(frame.Current.Fn.Name, "panic") {
panicking = g
panickingStack = stack
}
}
}
if panicking == nil {
t.Fatalf("Didn't find a call to panic in goroutine stacks: %v", gs)
}
var mainFrame *Stackframe
// Walk backward, because the current function seems to be main.main
// in the actual call to panic().
for i := len(panickingStack) - 1; i >= 0; i-- {
if panickingStack[i].Current.Fn.Name == "main.main" {
mainFrame = &panickingStack[i]
}
}
if mainFrame == nil {
t.Fatalf("Couldn't find main in stack %v", panickingStack)
}
msg, err := FrameToScope(p, *mainFrame).EvalVariable("msg", LoadConfig{MaxStringLen: 64})
if err != nil {
t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err)
}
if constant.StringVal(msg.Value) != "BOOM!" {
t.Errorf("main.msg = %q, want %q", msg.Value, "BOOM!")
}
regs, err := p.CurrentThread().Registers(true)
if err != nil {
t.Fatalf("Couldn't get current thread registers: %v", err)
}
regslice := regs.Slice()
for _, reg := range regslice {
t.Logf("%s = %s", reg.Name, reg.Value)
} }
// Would be good to test more stuff but not sure what without reading debug information, etc.
} }

@ -3,16 +3,286 @@ package proc
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"golang.org/x/debug/elf" "golang.org/x/debug/elf"
"golang.org/x/sys/unix"
"golang.org/x/arch/x86/x86asm"
) )
// Copied from golang.org/x/sys/unix.PtraceRegs since it's not available on
// all systems.
type LinuxCoreRegisters struct {
R15 uint64
R14 uint64
R13 uint64
R12 uint64
Rbp uint64
Rbx uint64
R11 uint64
R10 uint64
R9 uint64
R8 uint64
Rax uint64
Rcx uint64
Rdx uint64
Rsi uint64
Rdi uint64
Orig_rax uint64
Rip uint64
Cs uint64
Eflags uint64
Rsp uint64
Ss uint64
Fs_base uint64
Gs_base uint64
Ds uint64
Es uint64
Fs uint64
Gs uint64
}
// Copied from golang.org/x/sys/unix.Timeval since it's not available on all
// systems.
type LinuxCoreTimeval struct {
Sec int64
Usec int64
}
const NT_FILE elf.NType = 0x46494c45 // "FILE". const NT_FILE elf.NType = 0x46494c45 // "FILE".
func (r *LinuxCoreRegisters) PC() uint64 {
return r.Rip
}
func (r *LinuxCoreRegisters) SP() uint64 {
return r.Rsp
}
func (r *LinuxCoreRegisters) BP() uint64 {
return r.Rbp
}
func (r *LinuxCoreRegisters) CX() uint64 {
return r.Rcx
}
func (r *LinuxCoreRegisters) TLS() uint64 {
return r.Fs_base
}
func (r *LinuxCoreRegisters) GAddr() (uint64, bool) {
return 0, false
}
func (r *LinuxCoreRegisters) Get(n int) (uint64, error) {
reg := x86asm.Reg(n)
const (
mask8 = 0x000f
mask16 = 0x00ff
mask32 = 0xffff
)
switch reg {
// 8-bit
case x86asm.AL:
return r.Rax & mask8, nil
case x86asm.CL:
return r.Rcx & mask8, nil
case x86asm.DL:
return r.Rdx & mask8, nil
case x86asm.BL:
return r.Rbx & mask8, nil
case x86asm.AH:
return (r.Rax >> 8) & mask8, nil
case x86asm.CH:
return (r.Rcx >> 8) & mask8, nil
case x86asm.DH:
return (r.Rdx >> 8) & mask8, nil
case x86asm.BH:
return (r.Rbx >> 8) & mask8, nil
case x86asm.SPB:
return r.Rsp & mask8, nil
case x86asm.BPB:
return r.Rbp & mask8, nil
case x86asm.SIB:
return r.Rsi & mask8, nil
case x86asm.DIB:
return r.Rdi & mask8, nil
case x86asm.R8B:
return r.R8 & mask8, nil
case x86asm.R9B:
return r.R9 & mask8, nil
case x86asm.R10B:
return r.R10 & mask8, nil
case x86asm.R11B:
return r.R11 & mask8, nil
case x86asm.R12B:
return r.R12 & mask8, nil
case x86asm.R13B:
return r.R13 & mask8, nil
case x86asm.R14B:
return r.R14 & mask8, nil
case x86asm.R15B:
return r.R15 & mask8, nil
// 16-bit
case x86asm.AX:
return r.Rax & mask16, nil
case x86asm.CX:
return r.Rcx & mask16, nil
case x86asm.DX:
return r.Rdx & mask16, nil
case x86asm.BX:
return r.Rbx & mask16, nil
case x86asm.SP:
return r.Rsp & mask16, nil
case x86asm.BP:
return r.Rbp & mask16, nil
case x86asm.SI:
return r.Rsi & mask16, nil
case x86asm.DI:
return r.Rdi & mask16, nil
case x86asm.R8W:
return r.R8 & mask16, nil
case x86asm.R9W:
return r.R9 & mask16, nil
case x86asm.R10W:
return r.R10 & mask16, nil
case x86asm.R11W:
return r.R11 & mask16, nil
case x86asm.R12W:
return r.R12 & mask16, nil
case x86asm.R13W:
return r.R13 & mask16, nil
case x86asm.R14W:
return r.R14 & mask16, nil
case x86asm.R15W:
return r.R15 & mask16, nil
// 32-bit
case x86asm.EAX:
return r.Rax & mask32, nil
case x86asm.ECX:
return r.Rcx & mask32, nil
case x86asm.EDX:
return r.Rdx & mask32, nil
case x86asm.EBX:
return r.Rbx & mask32, nil
case x86asm.ESP:
return r.Rsp & mask32, nil
case x86asm.EBP:
return r.Rbp & mask32, nil
case x86asm.ESI:
return r.Rsi & mask32, nil
case x86asm.EDI:
return r.Rdi & mask32, nil
case x86asm.R8L:
return r.R8 & mask32, nil
case x86asm.R9L:
return r.R9 & mask32, nil
case x86asm.R10L:
return r.R10 & mask32, nil
case x86asm.R11L:
return r.R11 & mask32, nil
case x86asm.R12L:
return r.R12 & mask32, nil
case x86asm.R13L:
return r.R13 & mask32, nil
case x86asm.R14L:
return r.R14 & mask32, nil
case x86asm.R15L:
return r.R15 & mask32, nil
// 64-bit
case x86asm.RAX:
return r.Rax, nil
case x86asm.RCX:
return r.Rcx, nil
case x86asm.RDX:
return r.Rdx, nil
case x86asm.RBX:
return r.Rbx, nil
case x86asm.RSP:
return r.Rsp, nil
case x86asm.RBP:
return r.Rbp, nil
case x86asm.RSI:
return r.Rsi, nil
case x86asm.RDI:
return r.Rdi, nil
case x86asm.R8:
return r.R8, nil
case x86asm.R9:
return r.R9, nil
case x86asm.R10:
return r.R10, nil
case x86asm.R11:
return r.R11, nil
case x86asm.R12:
return r.R12, nil
case x86asm.R13:
return r.R13, nil
case x86asm.R14:
return r.R14, nil
case x86asm.R15:
return r.R15, nil
}
return 0, UnknownRegisterError
}
func (r *LinuxCoreRegisters) SetPC(IThread, uint64) error {
return errors.New("not supported")
}
func (r *LinuxCoreRegisters) Slice() []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([]Register, 0, len(regs))
for _, reg := range regs {
if reg.k == "Eflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
} else {
out = appendQwordReg(out, reg.k, reg.v)
}
}
return out
}
// readCore reads a core file from corePath corresponding to the executable at // readCore 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/,
@ -234,8 +504,8 @@ type LinuxPrStatus struct {
Sigpend uint64 Sigpend uint64
Sighold uint64 Sighold uint64
Pid, Ppid, Pgrp, Sid int32 Pid, Ppid, Pgrp, Sid int32
Utime, Stime, CUtime, CStime unix.Timeval Utime, Stime, CUtime, CStime LinuxCoreTimeval
Reg unix.PtraceRegs Reg LinuxCoreRegisters
Fpvalid int32 Fpvalid int32
} }

@ -21,7 +21,7 @@ type Registers interface {
CX() uint64 CX() uint64
TLS() uint64 TLS() uint64
Get(int) (uint64, error) Get(int) (uint64, error)
SetPC(*Thread, uint64) error SetPC(IThread, uint64) error
Slice() []Register Slice() []Register
} }

@ -106,7 +106,8 @@ func (r *Regs) TLS() uint64 {
} }
// SetPC sets the RIP register to the value specified by `pc`. // SetPC sets the RIP register to the value specified by `pc`.
func (r *Regs) SetPC(thread *Thread, pc uint64) error { func (r *Regs) SetPC(t IThread, pc uint64) error {
thread := t.(*Thread)
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc)) kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not set pc") return fmt.Errorf("could not set pc")

@ -84,7 +84,8 @@ func (r *Regs) TLS() uint64 {
} }
// SetPC sets RIP to the value specified by 'pc'. // SetPC sets RIP to the value specified by 'pc'.
func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) { func (r *Regs) SetPC(t IThread, pc uint64) (err error) {
thread := t.(*Thread)
r.regs.SetPC(pc) r.regs.SetPC(pc)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) }) thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
return return

@ -125,7 +125,8 @@ func (r *Regs) TLS() uint64 {
} }
// SetPC sets the RIP register to the value specified by `pc`. // SetPC sets the RIP register to the value specified by `pc`.
func (r *Regs) SetPC(thread *Thread, pc uint64) error { func (r *Regs) SetPC(t IThread, pc uint64) error {
thread := t.(*Thread)
context := newCONTEXT() context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL context.ContextFlags = _CONTEXT_ALL

@ -90,3 +90,4 @@ type VariableEval interface {
} }
var _ Interface = &proc.Process{} var _ Interface = &proc.Process{}
var _ Interface = &proc.CoreProcess{}

@ -25,4 +25,7 @@ type Config struct {
AcceptMulti bool AcceptMulti bool
// APIVersion selects which version of the API to serve (default: 1). // APIVersion selects which version of the API to serve (default: 1).
APIVersion int APIVersion int
// CoreFile specifies the path to the core dump to open
CoreFile string
} }

@ -48,6 +48,9 @@ type Config struct {
// AttachPid is the PID of an existing process to which the debugger should // AttachPid is the PID of an existing process to which the debugger should
// attach. // attach.
AttachPid int AttachPid int
// CoreFile specifies the path to the core dump to open
CoreFile string
} }
// New creates a new Debugger. // New creates a new Debugger.
@ -57,14 +60,24 @@ func New(config *Config) (*Debugger, error) {
} }
// Create the process by either attaching or launching. // Create the process by either attaching or launching.
if d.config.AttachPid > 0 { switch {
case d.config.AttachPid > 0:
log.Printf("attaching to pid %d", d.config.AttachPid) log.Printf("attaching to pid %d", d.config.AttachPid)
p, err := proc.Attach(d.config.AttachPid) p, err := proc.Attach(d.config.AttachPid)
if err != nil { if err != nil {
return nil, attachErrorMessage(d.config.AttachPid, err) return nil, attachErrorMessage(d.config.AttachPid, err)
} }
d.target = p d.target = p
} else {
case d.config.CoreFile != "":
log.Printf("opening core file %s (executable %s)", d.config.CoreFile, d.config.ProcessArgs[0])
p, err := proc.OpenCore(d.config.CoreFile, d.config.ProcessArgs[0])
if err != nil {
return nil, err
}
d.target = p
default:
log.Printf("launching process with args: %v", d.config.ProcessArgs) log.Printf("launching process with args: %v", d.config.ProcessArgs)
p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir)
if err != nil { if err != nil {
@ -113,6 +126,10 @@ func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
d.processMutex.Lock() d.processMutex.Lock()
defer d.processMutex.Unlock() defer d.processMutex.Unlock()
if d.config.CoreFile != "" {
return nil, errors.New("can not restart core dump")
}
if !d.target.Exited() { if !d.target.Exited() {
if d.target.Running() { if d.target.Running() {
d.target.Halt() d.target.Halt()

@ -114,6 +114,7 @@ func (s *ServerImpl) Run() error {
ProcessArgs: s.config.ProcessArgs, ProcessArgs: s.config.ProcessArgs,
AttachPid: s.config.AttachPid, AttachPid: s.config.AttachPid,
WorkingDir: s.config.WorkingDir, WorkingDir: s.config.WorkingDir,
CoreFile: s.config.CoreFile,
}); err != nil { }); err != nil {
return err return err
} }