
* proc: separate amd64-arch code separate amd64 code about stacktrace, so we can add arm64 stacktrace code. * proc: implemente stacktrace of arm64 * delve now can use stack, frame commands on arm64-arch debug. Co-authored-by: tykcd996 <tang.yuke@zte.com.cn> Co-authored-by: hengwu0 <wu.heng@zte.com.cn> * test: remove skip-code of stacktrace on arm64 * add LR DWARF register and remove skip-code for fixed tests * proc: fix the Continue command after the hardcoded breakpoint on arm64 Arm64 use hardware breakpoint, and it will not set PC to the next instruction like amd64. We should move PC in both runtime.breakpoints and hardcoded breakpoints(probably cgo). * proc: implement cgo stacktrace on arm64 * proc: combine amd64_stack.go and arm64_stack.go file * proc: reorganize the stacktrace code * move SwitchStack function arch-related * fix Continue command after manual stop on arm64 * add timeout flag to make.go to enable infinite timeouts Co-authored-by: aarzilli <alessandro.arzilli@gmail.com> Co-authored-by: hengwu0 <wu.heng@zte.com.cn> Co-authored-by: tykcd996 <56993522+tykcd996@users.noreply.github.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
1065 lines
31 KiB
Go
1065 lines
31 KiB
Go
package service_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
protest "github.com/go-delve/delve/pkg/proc/test"
|
|
|
|
"github.com/go-delve/delve/pkg/goversion"
|
|
"github.com/go-delve/delve/service"
|
|
"github.com/go-delve/delve/service/api"
|
|
"github.com/go-delve/delve/service/rpc1"
|
|
"github.com/go-delve/delve/service/rpccommon"
|
|
)
|
|
|
|
func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
|
|
withTestClient1Extended(name, t, func(c *rpc1.RPCClient, fixture protest.Fixture) {
|
|
fn(c)
|
|
})
|
|
}
|
|
|
|
func withTestClient1Extended(name string, t *testing.T, fn func(c *rpc1.RPCClient, fixture protest.Fixture)) {
|
|
if testBackend == "rr" {
|
|
protest.MustHaveRecordingAllowed(t)
|
|
}
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("couldn't start listener: %s\n", err)
|
|
}
|
|
defer listener.Close()
|
|
var buildFlags protest.BuildFlags
|
|
if buildMode == "pie" {
|
|
buildFlags = protest.BuildModePIE
|
|
}
|
|
fixture := protest.BuildFixture(name, buildFlags)
|
|
server := rpccommon.NewServer(&service.Config{
|
|
Listener: listener,
|
|
ProcessArgs: []string{fixture.Path},
|
|
Backend: testBackend,
|
|
})
|
|
if err := server.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client := rpc1.NewClient(listener.Addr().String())
|
|
defer func() {
|
|
client.Detach(true)
|
|
}()
|
|
|
|
fn(client, fixture)
|
|
}
|
|
|
|
func Test1RunWithInvalidPath(t *testing.T) {
|
|
if testBackend == "rr" {
|
|
// This test won't work because rr returns an error, after recording, when
|
|
// the recording failed but also when the recording succeeded but the
|
|
// inferior returned an error. Therefore we have to ignore errors from rr.
|
|
return
|
|
}
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("couldn't start listener: %s\n", err)
|
|
}
|
|
defer listener.Close()
|
|
server := rpccommon.NewServer(&service.Config{
|
|
Listener: listener,
|
|
ProcessArgs: []string{"invalid_path"},
|
|
Backend: testBackend,
|
|
})
|
|
if err := server.Run(); err == nil {
|
|
t.Fatal("Expected Run to return error for invalid program path")
|
|
}
|
|
}
|
|
|
|
func Test1Restart_afterExit(t *testing.T) {
|
|
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
|
|
origPid := c.ProcessPid()
|
|
state := <-c.Continue()
|
|
if !state.Exited {
|
|
t.Fatal("expected initial process to have exited")
|
|
}
|
|
if err := c.Restart(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.ProcessPid() == origPid {
|
|
t.Fatal("did not spawn new process, has same PID")
|
|
}
|
|
state = <-c.Continue()
|
|
if !state.Exited {
|
|
t.Fatalf("expected restarted process to have exited %v", state)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1Restart_breakpointPreservation(t *testing.T) {
|
|
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true})
|
|
assertNoError(err, t, "CreateBreakpoint()")
|
|
stateCh := c.Continue()
|
|
|
|
state := <-stateCh
|
|
if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint {
|
|
t.Fatalf("Wrong breakpoint: %#v\n", state.CurrentThread.Breakpoint)
|
|
}
|
|
state = <-stateCh
|
|
if !state.Exited {
|
|
t.Fatal("Did not exit after first tracepoint")
|
|
}
|
|
|
|
t.Log("Restart")
|
|
c.Restart()
|
|
stateCh = c.Continue()
|
|
state = <-stateCh
|
|
if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint {
|
|
t.Fatalf("Wrong breakpoint (after restart): %#v\n", state.CurrentThread.Breakpoint)
|
|
}
|
|
state = <-stateCh
|
|
if !state.Exited {
|
|
t.Fatal("Did not exit after first tracepoint (after restart)")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1Restart_duringStop(t *testing.T) {
|
|
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
|
|
origPid := c.ProcessPid()
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state := <-c.Continue()
|
|
if state.CurrentThread.Breakpoint == nil {
|
|
t.Fatal("did not hit breakpoint")
|
|
}
|
|
if err := c.Restart(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.ProcessPid() == origPid {
|
|
t.Fatal("did not spawn new process, has same PID")
|
|
}
|
|
bps, err := c.ListBreakpoints()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(bps) == 0 {
|
|
t.Fatal("breakpoints not preserved")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1Restart_attachPid(t *testing.T) {
|
|
// Assert it does not work and returns error.
|
|
// We cannot restart a process we did not spawn.
|
|
server := rpccommon.NewServer(&service.Config{
|
|
Listener: nil,
|
|
AttachPid: 999,
|
|
Backend: testBackend,
|
|
})
|
|
if err := server.Restart(); err == nil {
|
|
t.Fatal("expected error on restart after attaching to pid but got none")
|
|
}
|
|
}
|
|
|
|
func Test1ClientServer_exit(t *testing.T) {
|
|
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
|
|
state, err := c.GetState()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if e, a := false, state.Exited; e != a {
|
|
t.Fatalf("Expected exited %v, got %v", e, a)
|
|
}
|
|
state = <-c.Continue()
|
|
if state.Err == nil {
|
|
t.Fatalf("Error expected after continue")
|
|
}
|
|
if !state.Exited {
|
|
t.Fatalf("Expected exit after continue: %v", state)
|
|
}
|
|
_, err = c.GetState()
|
|
if err == nil {
|
|
t.Fatal("Expected error on querying state from exited process")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_step(t *testing.T) {
|
|
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: -1})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
stateBefore := <-c.Continue()
|
|
if stateBefore.Err != nil {
|
|
t.Fatalf("Unexpected error: %v", stateBefore.Err)
|
|
}
|
|
|
|
stateAfter, err := c.Step()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if before, after := stateBefore.CurrentThread.PC, stateAfter.CurrentThread.PC; before >= after {
|
|
t.Fatalf("Expected %#v to be greater than %#v", after, before)
|
|
}
|
|
})
|
|
}
|
|
|
|
func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
|
|
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
|
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
state := <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error: %v", state.Err)
|
|
}
|
|
|
|
_, err = c.ClearBreakpoint(bp.ID)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
if state.CurrentThread.Line != tc.begin {
|
|
t.Fatalf("Program not stopped at correct spot expected %d was %d", tc.begin, state.CurrentThread.Line)
|
|
}
|
|
|
|
t.Logf("Next for scenario %#v", tc)
|
|
state, err = c.Next()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if state.CurrentThread.Line != tc.end {
|
|
t.Fatalf("Program did not continue to correct next location expected %d was %d", tc.end, state.CurrentThread.Line)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1NextGeneral(t *testing.T) {
|
|
var testcases []nextTest
|
|
|
|
ver, _ := goversion.Parse(runtime.Version())
|
|
|
|
if ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{1, 7, -1, 0, 0, ""}) {
|
|
testcases = []nextTest{
|
|
{17, 19},
|
|
{19, 20},
|
|
{20, 23},
|
|
{23, 24},
|
|
{24, 26},
|
|
{26, 31},
|
|
{31, 23},
|
|
{23, 24},
|
|
{24, 26},
|
|
{26, 31},
|
|
{31, 23},
|
|
{23, 24},
|
|
{24, 26},
|
|
{26, 27},
|
|
{27, 28},
|
|
{28, 34},
|
|
}
|
|
} else {
|
|
testcases = []nextTest{
|
|
{17, 19},
|
|
{19, 20},
|
|
{20, 23},
|
|
{23, 24},
|
|
{24, 26},
|
|
{26, 31},
|
|
{31, 23},
|
|
{23, 24},
|
|
{24, 26},
|
|
{26, 31},
|
|
{31, 23},
|
|
{23, 24},
|
|
{24, 26},
|
|
{26, 27},
|
|
{27, 34},
|
|
}
|
|
}
|
|
|
|
testnext(testcases, "main.testnext", t)
|
|
}
|
|
|
|
func Test1NextFunctionReturn(t *testing.T) {
|
|
testcases := []nextTest{
|
|
{13, 14},
|
|
{14, 15},
|
|
{15, 35},
|
|
}
|
|
testnext(testcases, "main.helloworld", t)
|
|
}
|
|
|
|
func Test1ClientServer_breakpointInMainThread(t *testing.T) {
|
|
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
|
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
state := <-c.Continue()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
|
}
|
|
|
|
pc := state.CurrentThread.PC
|
|
|
|
if pc-1 != bp.Addr && pc != bp.Addr {
|
|
f, l := state.CurrentThread.File, state.CurrentThread.Line
|
|
t.Fatalf("Break not respected:\nPC:%#v %s:%d\nFN:%#v \n", pc, f, l, bp.Addr)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_breakpointInSeparateGoroutine(t *testing.T) {
|
|
withTestClient1("testthreads", t, func(c *rpc1.RPCClient) {
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
state := <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
|
}
|
|
|
|
f, l := state.CurrentThread.File, state.CurrentThread.Line
|
|
if f != "testthreads.go" && l != 9 {
|
|
t.Fatal("Program did not hit breakpoint")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_breakAtNonexistentPoint(t *testing.T) {
|
|
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere", Line: 1})
|
|
if err == nil {
|
|
t.Fatal("Should not be able to break at non existent function")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_clearBreakpoint(t *testing.T) {
|
|
withTestClient1("testprog", t, func(c *rpc1.RPCClient) {
|
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime", Line: 1})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if e, a := 1, countBreakpoints(t, c); e != a {
|
|
t.Fatalf("Expected breakpoint count %d, got %d", e, a)
|
|
}
|
|
|
|
deleted, err := c.ClearBreakpoint(bp.ID)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if deleted.ID != bp.ID {
|
|
t.Fatalf("Expected deleted breakpoint ID %v, got %v", bp.ID, deleted.ID)
|
|
}
|
|
|
|
if e, a := 0, countBreakpoints(t, c); e != a {
|
|
t.Fatalf("Expected breakpoint count %d, got %d", e, a)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_switchThread(t *testing.T) {
|
|
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
|
|
// With invalid thread id
|
|
_, err := c.SwitchThread(-1)
|
|
if err == nil {
|
|
t.Fatal("Expected error for invalid thread id")
|
|
}
|
|
|
|
_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
state := <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
|
}
|
|
|
|
var nt int
|
|
ct := state.CurrentThread.ID
|
|
threads, err := c.ListThreads()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
for _, th := range threads {
|
|
if th.ID != ct {
|
|
nt = th.ID
|
|
break
|
|
}
|
|
}
|
|
if nt == 0 {
|
|
t.Fatal("could not find thread to switch to")
|
|
}
|
|
// With valid thread id
|
|
state, err = c.SwitchThread(nt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state.CurrentThread.ID != nt {
|
|
t.Fatal("Did not switch threads")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_infoLocals(t *testing.T) {
|
|
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
|
|
fp := testProgPath(t, "testnextprog")
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
state := <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
|
}
|
|
locals, err := c.ListLocalVariables(api.EvalScope{-1, 0, 0})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(locals) != 3 {
|
|
t.Fatalf("Expected 3 locals, got %d %#v", len(locals), locals)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_infoArgs(t *testing.T) {
|
|
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
|
|
fp := testProgPath(t, "testnextprog")
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
state := <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
|
}
|
|
regs, err := c.ListRegisters()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if regs == "" {
|
|
t.Fatal("Expected string showing registers values, got empty string")
|
|
}
|
|
locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0, 0})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(locals) != 2 {
|
|
t.Fatalf("Expected 2 function args, got %d %#v", len(locals), locals)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_traceContinue(t *testing.T) {
|
|
withTestClient1("integrationprog", t, func(c *rpc1.RPCClient) {
|
|
fp := testProgPath(t, "integrationprog")
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Variables: []string{"i"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
count := 0
|
|
contChan := c.Continue()
|
|
for state := range contChan {
|
|
if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil {
|
|
count++
|
|
|
|
t.Logf("%v", state)
|
|
|
|
bpi := state.CurrentThread.BreakpointInfo
|
|
|
|
if bpi.Goroutine == nil {
|
|
t.Fatalf("No goroutine information")
|
|
}
|
|
|
|
if len(bpi.Stacktrace) <= 0 {
|
|
t.Fatalf("No stacktrace\n")
|
|
}
|
|
|
|
if len(bpi.Variables) != 1 {
|
|
t.Fatalf("Wrong number of variables returned: %d", len(bpi.Variables))
|
|
}
|
|
|
|
if bpi.Variables[0].Name != "i" {
|
|
t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name)
|
|
}
|
|
|
|
t.Logf("Variable i is %v", bpi.Variables[0])
|
|
|
|
n, err := strconv.Atoi(bpi.Variables[0].Value)
|
|
|
|
if err != nil || n != count-1 {
|
|
t.Fatalf("Wrong variable value %q (%v %d)", bpi.Variables[0].Value, err, count)
|
|
}
|
|
}
|
|
if state.Exited {
|
|
continue
|
|
}
|
|
t.Logf("%v", state)
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error during continue: %v\n", state.Err)
|
|
}
|
|
|
|
}
|
|
|
|
if count != 3 {
|
|
t.Fatalf("Wrong number of continues hit: %d\n", count)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_traceContinue2(t *testing.T) {
|
|
withTestClient1("integrationprog", t, func(c *rpc1.RPCClient) {
|
|
bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
bp2, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1, Tracepoint: true})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
countMain := 0
|
|
countSayhi := 0
|
|
contChan := c.Continue()
|
|
for state := range contChan {
|
|
if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil {
|
|
switch state.CurrentThread.Breakpoint.ID {
|
|
case bp1.ID:
|
|
countMain++
|
|
case bp2.ID:
|
|
countSayhi++
|
|
}
|
|
|
|
t.Logf("%v", state)
|
|
}
|
|
if state.Exited {
|
|
continue
|
|
}
|
|
if state.Err != nil {
|
|
t.Fatalf("Unexpected error during continue: %v\n", state.Err)
|
|
}
|
|
|
|
}
|
|
|
|
if countMain != 1 {
|
|
t.Fatalf("Wrong number of continues (main.main) hit: %d\n", countMain)
|
|
}
|
|
|
|
if countSayhi != 3 {
|
|
t.Fatalf("Wrong number of continues (main.sayhi) hit: %d\n", countSayhi)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_FindLocations(t *testing.T) {
|
|
withTestClient1("locationsprog", t, func(c *rpc1.RPCClient) {
|
|
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0]
|
|
someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0]
|
|
findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionLine1)
|
|
findLocationHelper(t, c, "main.anotherFunction:1", false, 1, someFunctionLine1)
|
|
findLocationHelper(t, c, "anotherFunction", false, 1, someFunctionCallAddr)
|
|
findLocationHelper(t, c, "main.anotherFunction", false, 1, someFunctionCallAddr)
|
|
findLocationHelper(t, c, fmt.Sprintf("*0x%x", someFunctionCallAddr), false, 1, someFunctionCallAddr)
|
|
findLocationHelper(t, c, "sprog.go:26", true, 0, 0)
|
|
|
|
findLocationHelper(t, c, "String", true, 0, 0)
|
|
findLocationHelper(t, c, "main.String", true, 0, 0)
|
|
|
|
someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:14", false, 1, 0)[0]
|
|
otherTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:18", false, 1, 0)[0]
|
|
findLocationHelper(t, c, "SomeType.String", false, 1, someTypeStringFuncAddr)
|
|
findLocationHelper(t, c, "(*SomeType).String", false, 1, someTypeStringFuncAddr)
|
|
findLocationHelper(t, c, "main.SomeType.String", false, 1, someTypeStringFuncAddr)
|
|
findLocationHelper(t, c, "main.(*SomeType).String", false, 1, someTypeStringFuncAddr)
|
|
|
|
// Issue #275
|
|
readfile := findLocationHelper(t, c, "io/ioutil.ReadFile", false, 1, 0)[0]
|
|
|
|
// Issue #296
|
|
findLocationHelper(t, c, "/io/ioutil.ReadFile", false, 1, readfile)
|
|
findLocationHelper(t, c, "ioutil.ReadFile", false, 1, readfile)
|
|
|
|
stringAddrs := findLocationHelper(t, c, "/^main.*Type.*String$/", false, 2, 0)
|
|
|
|
if otherTypeStringFuncAddr != stringAddrs[0] && otherTypeStringFuncAddr != stringAddrs[1] {
|
|
t.Fatalf("Wrong locations returned for \"/.*Type.*String/\", got: %v expected: %v and %v\n", stringAddrs, someTypeStringFuncAddr, otherTypeStringFuncAddr)
|
|
}
|
|
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 4, Tracepoint: false})
|
|
if err != nil {
|
|
t.Fatalf("CreateBreakpoint(): %v\n", err)
|
|
}
|
|
|
|
<-c.Continue()
|
|
|
|
locationsprog35Addr := findLocationHelper(t, c, "locationsprog.go:35", false, 1, 0)[0]
|
|
findLocationHelper(t, c, fmt.Sprintf("%s:35", testProgPath(t, "locationsprog")), false, 1, locationsprog35Addr)
|
|
findLocationHelper(t, c, "+1", false, 1, locationsprog35Addr)
|
|
findLocationHelper(t, c, "35", false, 1, locationsprog35Addr)
|
|
findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:33", false, 1, 0)[0])
|
|
})
|
|
|
|
withTestClient1("testnextdefer", t, func(c *rpc1.RPCClient) {
|
|
firstMainLine := findLocationHelper(t, c, "testnextdefer.go:5", false, 1, 0)[0]
|
|
findLocationHelper(t, c, "main.main", false, 1, firstMainLine)
|
|
})
|
|
|
|
withTestClient1("stacktraceprog", t, func(c *rpc1.RPCClient) {
|
|
stacktracemeAddr := findLocationHelper(t, c, "stacktraceprog.go:4", false, 1, 0)[0]
|
|
findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr)
|
|
})
|
|
|
|
withTestClient1Extended("locationsUpperCase", t, func(c *rpc1.RPCClient, fixture protest.Fixture) {
|
|
// Upper case
|
|
findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0)
|
|
|
|
// Fully qualified path
|
|
findLocationHelper(t, c, fixture.Source+":6", false, 1, 0)
|
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{File: fixture.Source, Line: 6})
|
|
if err != nil {
|
|
t.Fatalf("Could not set breakpoint in %s: %v\n", fixture.Source, err)
|
|
}
|
|
c.ClearBreakpoint(bp.ID)
|
|
|
|
// Allow `/` or `\` on Windows
|
|
if runtime.GOOS == "windows" {
|
|
findLocationHelper(t, c, filepath.FromSlash(fixture.Source)+":6", false, 1, 0)
|
|
bp, err = c.CreateBreakpoint(&api.Breakpoint{File: filepath.FromSlash(fixture.Source), Line: 6})
|
|
if err != nil {
|
|
t.Fatalf("Could not set breakpoint in %s: %v\n", filepath.FromSlash(fixture.Source), err)
|
|
}
|
|
c.ClearBreakpoint(bp.ID)
|
|
}
|
|
|
|
// Case-insensitive on Windows, case-sensitive otherwise
|
|
shouldWrongCaseBeError := true
|
|
numExpectedMatches := 0
|
|
if runtime.GOOS == "windows" {
|
|
shouldWrongCaseBeError = false
|
|
numExpectedMatches = 1
|
|
}
|
|
findLocationHelper(t, c, strings.ToLower(fixture.Source)+":6", shouldWrongCaseBeError, numExpectedMatches, 0)
|
|
bp, err = c.CreateBreakpoint(&api.Breakpoint{File: strings.ToLower(fixture.Source), Line: 6})
|
|
if (err == nil) == shouldWrongCaseBeError {
|
|
t.Fatalf("Could not set breakpoint in %s: %v\n", strings.ToLower(fixture.Source), err)
|
|
}
|
|
c.ClearBreakpoint(bp.ID)
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_FindLocationsAddr(t *testing.T) {
|
|
withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) {
|
|
<-c.Continue()
|
|
|
|
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
|
|
anonfunc := findLocationHelper(t, c, "main.main.func1", false, 1, 0)[0]
|
|
|
|
findLocationHelper(t, c, "*fn1", false, 1, afunction)
|
|
findLocationHelper(t, c, "*fn3", false, 1, anonfunc)
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_EvalVariable(t *testing.T) {
|
|
withTestClient1("testvariables", t, func(c *rpc1.RPCClient) {
|
|
state := <-c.Continue()
|
|
|
|
if state.Err != nil {
|
|
t.Fatalf("Continue(): %v\n", state.Err)
|
|
}
|
|
|
|
var1, err := c.EvalVariable(api.EvalScope{-1, 0, 0}, "a1")
|
|
assertNoError(err, t, "EvalVariable")
|
|
|
|
t.Logf("var1: %s", var1.SinglelineString())
|
|
|
|
if var1.Value != "foofoofoofoofoofoo" {
|
|
t.Fatalf("Wrong variable value: %s", var1.Value)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_SetVariable(t *testing.T) {
|
|
withTestClient1("testvariables", t, func(c *rpc1.RPCClient) {
|
|
state := <-c.Continue()
|
|
|
|
if state.Err != nil {
|
|
t.Fatalf("Continue(): %v\n", state.Err)
|
|
}
|
|
|
|
assertNoError(c.SetVariable(api.EvalScope{-1, 0, 0}, "a2", "8"), t, "SetVariable()")
|
|
|
|
a2, err := c.EvalVariable(api.EvalScope{-1, 0, 0}, "a2")
|
|
if err != nil {
|
|
t.Fatalf("Could not evaluate variable: %v", err)
|
|
}
|
|
|
|
t.Logf("a2: %v", a2)
|
|
|
|
n, err := strconv.Atoi(a2.Value)
|
|
|
|
if err != nil && n != 8 {
|
|
t.Fatalf("Wrong variable value: %v", a2)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_FullStacktrace(t *testing.T) {
|
|
withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) {
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
|
|
assertNoError(err, t, "CreateBreakpoint()")
|
|
state := <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Continue(): %v\n", state.Err)
|
|
}
|
|
|
|
gs, err := c.ListGoroutines()
|
|
assertNoError(err, t, "GoroutinesInfo()")
|
|
found := make([]bool, 10)
|
|
for _, g := range gs {
|
|
frames, err := c.Stacktrace(g.ID, 10, true)
|
|
assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID))
|
|
for i, frame := range frames {
|
|
if frame.Function == nil {
|
|
continue
|
|
}
|
|
if frame.Function.Name() != "main.agoroutine" {
|
|
continue
|
|
}
|
|
t.Logf("frame %d: %v", i, frame)
|
|
for _, arg := range frame.Arguments {
|
|
if arg.Name != "i" {
|
|
continue
|
|
}
|
|
t.Logf("frame %d, variable i is %v\n", i, arg)
|
|
argn, err := strconv.Atoi(arg.Value)
|
|
if err == nil {
|
|
found[argn] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := range found {
|
|
if !found[i] {
|
|
t.Fatalf("Goroutine %d not found", i)
|
|
}
|
|
}
|
|
|
|
state = <-c.Continue()
|
|
if state.Err != nil {
|
|
t.Fatalf("Continue(): %v\n", state.Err)
|
|
}
|
|
|
|
frames, err := c.Stacktrace(-1, 10, true)
|
|
assertNoError(err, t, "Stacktrace")
|
|
|
|
cur := 3
|
|
for i, frame := range frames {
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
t.Logf("frame %d: %v", i, frame)
|
|
v := frame.Var("n")
|
|
if v == nil {
|
|
t.Fatalf("Could not find value of variable n in frame %d", i)
|
|
}
|
|
vn, err := strconv.Atoi(v.Value)
|
|
if err != nil || vn != cur {
|
|
t.Fatalf("Expected value %d got %d (error: %v)", cur, vn, err)
|
|
}
|
|
cur--
|
|
if cur < 0 {
|
|
break
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1Issue355(t *testing.T) {
|
|
// After the target process has terminated should return an error but not crash
|
|
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
|
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
|
|
assertNoError(err, t, "CreateBreakpoint()")
|
|
ch := c.Continue()
|
|
state := <-ch
|
|
tid := state.CurrentThread.ID
|
|
gid := state.SelectedGoroutine.ID
|
|
assertNoError(state.Err, t, "First Continue()")
|
|
ch = c.Continue()
|
|
state = <-ch
|
|
if !state.Exited {
|
|
t.Fatalf("Target did not terminate after second continue")
|
|
}
|
|
|
|
ch = c.Continue()
|
|
state = <-ch
|
|
assertError(state.Err, t, "Continue()")
|
|
|
|
_, err = c.Next()
|
|
assertError(err, t, "Next()")
|
|
_, err = c.Step()
|
|
assertError(err, t, "Step()")
|
|
_, err = c.StepInstruction()
|
|
assertError(err, t, "StepInstruction()")
|
|
_, err = c.SwitchThread(tid)
|
|
assertError(err, t, "SwitchThread()")
|
|
_, err = c.SwitchGoroutine(gid)
|
|
assertError(err, t, "SwitchGoroutine()")
|
|
_, err = c.Halt()
|
|
assertError(err, t, "Halt()")
|
|
_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1})
|
|
assertError(err, t, "CreateBreakpoint()")
|
|
_, err = c.ClearBreakpoint(bp.ID)
|
|
assertError(err, t, "ClearBreakpoint()")
|
|
_, err = c.ListThreads()
|
|
assertError(err, t, "ListThreads()")
|
|
_, err = c.GetThread(tid)
|
|
assertError(err, t, "GetThread()")
|
|
assertError(c.SetVariable(api.EvalScope{gid, 0, 0}, "a", "10"), t, "SetVariable()")
|
|
_, err = c.ListLocalVariables(api.EvalScope{gid, 0, 0})
|
|
assertError(err, t, "ListLocalVariables()")
|
|
_, err = c.ListFunctionArgs(api.EvalScope{gid, 0, 0})
|
|
assertError(err, t, "ListFunctionArgs()")
|
|
_, err = c.ListRegisters()
|
|
assertError(err, t, "ListRegisters()")
|
|
_, err = c.ListGoroutines()
|
|
assertError(err, t, "ListGoroutines()")
|
|
_, err = c.Stacktrace(gid, 10, false)
|
|
assertError(err, t, "Stacktrace()")
|
|
_, err = c.FindLocation(api.EvalScope{gid, 0, 0}, "+1")
|
|
assertError(err, t, "FindLocation()")
|
|
_, err = c.DisassemblePC(api.EvalScope{-1, 0, 0}, 0x40100, api.IntelFlavour)
|
|
assertError(err, t, "DisassemblePC()")
|
|
})
|
|
}
|
|
|
|
func Test1Disasm(t *testing.T) {
|
|
// Tests that disassembling by PC, range, and current PC all yeld similar results
|
|
// Tests that disassembly by current PC will return a disassembly containing the instruction at PC
|
|
// Tests that stepping on a calculated CALL instruction will yield a disassembly that contains the
|
|
// effective destination of the CALL instruction
|
|
withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) {
|
|
ch := c.Continue()
|
|
state := <-ch
|
|
assertNoError(state.Err, t, "Continue()")
|
|
|
|
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "main.main")
|
|
assertNoError(err, t, "FindLocation()")
|
|
if len(locs) != 1 {
|
|
t.Fatalf("wrong number of locations for main.main: %d", len(locs))
|
|
}
|
|
d1, err := c.DisassemblePC(api.EvalScope{-1, 0, 0}, locs[0].PC, api.IntelFlavour)
|
|
assertNoError(err, t, "DisassemblePC()")
|
|
if len(d1) < 2 {
|
|
t.Fatalf("wrong size of disassembly: %d", len(d1))
|
|
}
|
|
|
|
pcstart := d1[0].Loc.PC
|
|
pcend := d1[len(d1)-1].Loc.PC + uint64(len(d1[len(d1)-1].Bytes))
|
|
d2, err := c.DisassembleRange(api.EvalScope{-1, 0, 0}, pcstart, pcend, api.IntelFlavour)
|
|
assertNoError(err, t, "DisassembleRange()")
|
|
|
|
if len(d1) != len(d2) {
|
|
t.Logf("d1: %v", d1)
|
|
t.Logf("d2: %v", d2)
|
|
t.Fatal("mismatched length between disassemble pc and disassemble range")
|
|
}
|
|
|
|
d3, err := c.DisassemblePC(api.EvalScope{-1, 0, 0}, state.CurrentThread.PC, api.IntelFlavour)
|
|
assertNoError(err, t, "DisassemblePC() - second call")
|
|
|
|
if len(d1) != len(d3) {
|
|
t.Logf("d1: %v", d1)
|
|
t.Logf("d3: %v", d3)
|
|
t.Fatal("mismatched length between the two calls of disassemble pc")
|
|
}
|
|
|
|
// look for static call to afunction() on line 29
|
|
found := false
|
|
for i := range d3 {
|
|
if d3[i].Loc.Line == 29 && (strings.HasPrefix(d3[i].Text, "call") || strings.HasPrefix(d3[i].Text, "CALL")) && d3[i].DestLoc != nil && d3[i].DestLoc.Function != nil && d3[i].DestLoc.Function.Name() == "main.afunction" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("Could not find call to main.afunction on line 29")
|
|
}
|
|
|
|
haspc := false
|
|
for i := range d3 {
|
|
if d3[i].AtPC {
|
|
haspc = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !haspc {
|
|
t.Logf("d3: %v", d3)
|
|
t.Fatal("PC instruction not found")
|
|
}
|
|
|
|
startinstr := getCurinstr(d3)
|
|
|
|
count := 0
|
|
for {
|
|
if count > 20 {
|
|
t.Fatal("too many step instructions executed without finding a call instruction")
|
|
}
|
|
state, err := c.StepInstruction()
|
|
assertNoError(err, t, fmt.Sprintf("StepInstruction() %d", count))
|
|
|
|
d3, err = c.DisassemblePC(api.EvalScope{-1, 0, 0}, state.CurrentThread.PC, api.IntelFlavour)
|
|
assertNoError(err, t, fmt.Sprintf("StepInstruction() %d", count))
|
|
|
|
curinstr := getCurinstr(d3)
|
|
|
|
if curinstr == nil {
|
|
t.Fatalf("Could not find current instruction %d", count)
|
|
}
|
|
|
|
if curinstr.Loc.Line != startinstr.Loc.Line {
|
|
t.Fatal("Calling StepInstruction() repeatedly did not find the call instruction")
|
|
}
|
|
|
|
if strings.HasPrefix(curinstr.Text, "call") || strings.HasPrefix(curinstr.Text, "CALL") {
|
|
t.Logf("call: %v", curinstr)
|
|
if curinstr.DestLoc == nil || curinstr.DestLoc.Function == nil {
|
|
t.Fatalf("Call instruction does not have destination: %v", curinstr)
|
|
}
|
|
if curinstr.DestLoc.Function.Name() != "main.afunction" {
|
|
t.Fatalf("Call instruction destination not main.afunction: %v", curinstr)
|
|
}
|
|
break
|
|
}
|
|
|
|
count++
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1NegativeStackDepthBug(t *testing.T) {
|
|
// After the target process has terminated should return an error but not crash
|
|
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
|
|
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})
|
|
assertNoError(err, t, "CreateBreakpoint()")
|
|
ch := c.Continue()
|
|
state := <-ch
|
|
assertNoError(state.Err, t, "Continue()")
|
|
_, err = c.Stacktrace(-1, -2, true)
|
|
assertError(err, t, "Stacktrace()")
|
|
})
|
|
}
|
|
|
|
func Test1ClientServer_CondBreakpoint(t *testing.T) {
|
|
if runtime.GOOS == "freebsd" {
|
|
t.Skip("test is not valid on FreeBSD")
|
|
}
|
|
withTestClient1("parallel_next", t, func(c *rpc1.RPCClient) {
|
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1})
|
|
assertNoError(err, t, "CreateBreakpoint()")
|
|
bp.Cond = "n == 7"
|
|
assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 1")
|
|
bp, err = c.GetBreakpoint(bp.ID)
|
|
assertNoError(err, t, "GetBreakpoint() 1")
|
|
bp.Variables = append(bp.Variables, "n")
|
|
assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 2")
|
|
bp, err = c.GetBreakpoint(bp.ID)
|
|
assertNoError(err, t, "GetBreakpoint() 2")
|
|
if bp.Cond == "" {
|
|
t.Fatalf("No condition set on breakpoint %#v", bp)
|
|
}
|
|
if len(bp.Variables) != 1 {
|
|
t.Fatalf("Wrong number of expressions to evaluate on breakpoint %#v", bp)
|
|
}
|
|
state := <-c.Continue()
|
|
assertNoError(state.Err, t, "Continue()")
|
|
|
|
nvar, err := c.EvalVariable(api.EvalScope{-1, 0, 0}, "n")
|
|
assertNoError(err, t, "EvalVariable()")
|
|
|
|
if nvar.SinglelineString() != "7" {
|
|
t.Fatalf("Stopped on wrong goroutine %s\n", nvar.Value)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1Issue419(t *testing.T) {
|
|
// Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously
|
|
// try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe
|
|
withTestClient1("issue419", t, func(c *rpc1.RPCClient) {
|
|
go func() {
|
|
rand.Seed(time.Now().Unix())
|
|
d := time.Duration(rand.Intn(4) + 1)
|
|
time.Sleep(d * time.Second)
|
|
_, err := c.Halt()
|
|
assertNoError(err, t, "RequestManualStop()")
|
|
}()
|
|
statech := c.Continue()
|
|
state := <-statech
|
|
assertNoError(state.Err, t, "Continue()")
|
|
})
|
|
}
|
|
|
|
func Test1TypesCommand(t *testing.T) {
|
|
withTestClient1("testvariables2", t, func(c *rpc1.RPCClient) {
|
|
state := <-c.Continue()
|
|
assertNoError(state.Err, t, "Continue()")
|
|
types, err := c.ListTypes("")
|
|
assertNoError(err, t, "ListTypes()")
|
|
|
|
found := false
|
|
for i := range types {
|
|
if types[i] == "main.astruct" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("Type astruct not found in ListTypes output")
|
|
}
|
|
|
|
types, err = c.ListTypes("^main.astruct$")
|
|
assertNoError(err, t, "ListTypes(\"main.astruct\")")
|
|
if len(types) != 1 {
|
|
t.Fatalf("ListTypes(\"^main.astruct$\") did not filter properly, expected 1 got %d: %v", len(types), types)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test1Issue406(t *testing.T) {
|
|
withTestClient1("issue406", t, func(c *rpc1.RPCClient) {
|
|
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "issue406.go:146")
|
|
assertNoError(err, t, "FindLocation()")
|
|
_, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC})
|
|
assertNoError(err, t, "CreateBreakpoint()")
|
|
ch := c.Continue()
|
|
state := <-ch
|
|
assertNoError(state.Err, t, "Continue()")
|
|
v, err := c.EvalVariable(api.EvalScope{-1, 0, 0}, "cfgtree")
|
|
assertNoError(err, t, "EvalVariable()")
|
|
vs := v.MultilineString("")
|
|
t.Logf("cfgtree formats to: %s\n", vs)
|
|
})
|
|
}
|