proc: implement target.Interface for core files
This commit is contained in:
parent
3dacc25d2e
commit
c1879472a1
@ -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)
|
||||||
|
|||||||
215
pkg/proc/core.go
215
pkg/proc/core.go
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user