
- move native backend to pkg/proc/native - move gdbserver backend to pkg/proc/gdbserial - move core dumps backend to pkg/proc/core
210 lines
4.5 KiB
Go
210 lines
4.5 KiB
Go
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/constant"
|
|
"io/ioutil"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/derekparker/delve/pkg/proc"
|
|
"github.com/derekparker/delve/pkg/proc/test"
|
|
)
|
|
|
|
func TestSplicedReader(t *testing.T) {
|
|
data := []byte{}
|
|
data2 := []byte{}
|
|
for i := 0; i < 100; i++ {
|
|
data = append(data, byte(i))
|
|
data2 = append(data2, byte(i+100))
|
|
}
|
|
|
|
type region struct {
|
|
data []byte
|
|
off uintptr
|
|
length uintptr
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
regions []region
|
|
readAddr uintptr
|
|
readLen int
|
|
want []byte
|
|
}{
|
|
{
|
|
"Insert after",
|
|
[]region{
|
|
{data, 0, 1},
|
|
{data2, 1, 1},
|
|
},
|
|
0,
|
|
2,
|
|
[]byte{0, 101},
|
|
},
|
|
{
|
|
"Insert before",
|
|
[]region{
|
|
{data, 1, 1},
|
|
{data2, 0, 1},
|
|
},
|
|
0,
|
|
2,
|
|
[]byte{100, 1},
|
|
},
|
|
{
|
|
"Completely overwrite",
|
|
[]region{
|
|
{data, 1, 1},
|
|
{data2, 0, 3},
|
|
},
|
|
0,
|
|
3,
|
|
[]byte{100, 101, 102},
|
|
},
|
|
{
|
|
"Overwrite end",
|
|
[]region{
|
|
{data, 0, 2},
|
|
{data2, 1, 2},
|
|
},
|
|
0,
|
|
3,
|
|
[]byte{0, 101, 102},
|
|
},
|
|
{
|
|
"Overwrite start",
|
|
[]region{
|
|
{data, 0, 3},
|
|
{data2, 0, 2},
|
|
},
|
|
0,
|
|
3,
|
|
[]byte{100, 101, 2},
|
|
},
|
|
{
|
|
"Punch hole",
|
|
[]region{
|
|
{data, 0, 5},
|
|
{data2, 1, 3},
|
|
},
|
|
0,
|
|
5,
|
|
[]byte{0, 101, 102, 103, 4},
|
|
},
|
|
{
|
|
"Overlap two",
|
|
[]region{
|
|
{data, 10, 4},
|
|
{data, 14, 4},
|
|
{data2, 12, 4},
|
|
},
|
|
10,
|
|
8,
|
|
[]byte{10, 11, 112, 113, 114, 115, 16, 17},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
mem := &SplicedMemory{}
|
|
for _, region := range test.regions {
|
|
r := bytes.NewReader(region.data)
|
|
mem.Add(&OffsetReaderAt{r, 0}, region.off, region.length)
|
|
}
|
|
got := make([]byte, test.readLen)
|
|
n, err := mem.ReadMemory(got, test.readAddr)
|
|
if n != test.readLen || err != nil || !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("ReadAt = %v, %v, %v, want %v, %v, %v", n, err, got, test.readLen, nil, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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.
|
|
// 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)
|
|
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
|
|
}
|
|
corePath := cores[0]
|
|
|
|
p, err := OpenCore(corePath, fix.Path)
|
|
if err != nil {
|
|
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)
|
|
}
|
|
gs, err := proc.GoroutinesInfo(p)
|
|
if err != nil || len(gs) == 0 {
|
|
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
|
|
}
|
|
|
|
var panicking *proc.G
|
|
var panickingStack []proc.Stackframe
|
|
for _, g := range gs {
|
|
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 *proc.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 := proc.FrameToScope(p, *mainFrame).EvalVariable("msg", proc.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)
|
|
}
|
|
}
|