proctl_test.go 6.7 KB
Newer Older
D
Derek Parker 已提交
1
package proctl_test
D
Derek Parker 已提交
2 3

import (
4
	"bytes"
5 6
	"fmt"
	"os"
7
	"path/filepath"
8
	"syscall"
D
Derek Parker 已提交
9
	"testing"
10
	"time"
D
Derek Parker 已提交
11

D
Derek Parker 已提交
12 13 14
	"github.com/derekparker/dbg/_helper"
	"github.com/derekparker/dbg/proctl"
)
15

16 17 18 19 20 21 22 23 24 25
func dataAtAddr(pid int, addr uint64) ([]byte, error) {
	data := make([]byte, 1)
	_, err := syscall.PtracePeekData(pid, uintptr(addr), data)
	if err != nil {
		return nil, err
	}

	return data, nil
}

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
func assertNoError(err error, t *testing.T, s string) {
	if err != nil {
		t.Fatal(s, ":", err)
	}
}

func currentPC(p *proctl.DebuggedProcess, t *testing.T) uint64 {
	pc, err := p.CurrentPC()
	if err != nil {
		t.Fatal(err)
	}

	return pc
}

func currentLineNumber(p *proctl.DebuggedProcess, t *testing.T) int {
	pc := currentPC(p, t)
	_, l, _ := p.GoSymTable.PCToLine(pc)

	return l
}

48
func TestAttachProcess(t *testing.T) {
49
	helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
50
		if !p.ProcessState.Stopped() {
51 52 53
			t.Errorf("Process was not stopped correctly")
		}
	})
D
Derek Parker 已提交
54
}
55

56
func TestStep(t *testing.T) {
57
	helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
D
Derek Parker 已提交
58 59 60 61
		if p.ProcessState.Exited() {
			t.Fatal("Process already exited")
		}

D
Derek Parker 已提交
62
		regs := helper.GetRegisters(p, t)
63
		rip := regs.PC()
64

65
		err := p.Step()
D
Derek Parker 已提交
66
		assertNoError(err, t, "Step()")
67

D
Derek Parker 已提交
68
		regs = helper.GetRegisters(p, t)
69 70 71 72 73
		if rip >= regs.PC() {
			t.Errorf("Expected %#v to be greater than %#v", regs.PC(), rip)
		}
	})
}
74

75
func TestContinue(t *testing.T) {
76
	helper.WithTestProcess("../_fixtures/continuetestprog", t, func(p *proctl.DebuggedProcess) {
77 78 79 80 81
		if p.ProcessState.Exited() {
			t.Fatal("Process already exited")
		}

		err := p.Continue()
D
Derek Parker 已提交
82
		assertNoError(err, t, "Continue()")
83

84
		if p.ProcessState.ExitStatus() != 0 {
85 86 87
			t.Fatal("Process did not exit successfully")
		}
	})
88
}
89 90

func TestBreakPoint(t *testing.T) {
91
	helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
92 93 94 95
		sleepytimefunc := p.GoSymTable.LookupFunc("main.sleepytime")
		sleepyaddr := sleepytimefunc.Entry

		bp, err := p.Break(uintptr(sleepyaddr))
D
Derek Parker 已提交
96
		assertNoError(err, t, "Break()")
97 98 99

		breakpc := bp.Addr + 1
		err = p.Continue()
D
Derek Parker 已提交
100
		assertNoError(err, t, "Continue()")
101

D
Derek Parker 已提交
102
		regs := helper.GetRegisters(p, t)
103 104 105 106 107 108 109

		pc := regs.PC()
		if pc != breakpc {
			t.Fatalf("Break not respected:\nPC:%d\nFN:%d\n", pc, breakpc)
		}

		err = p.Step()
D
Derek Parker 已提交
110
		assertNoError(err, t, "Step()")
111

D
Derek Parker 已提交
112
		regs = helper.GetRegisters(p, t)
113 114 115 116 117 118

		pc = regs.PC()
		if pc == breakpc {
			t.Fatalf("Step not respected:\nPC:%d\nFN:%d\n", pc, breakpc)
		}
	})
119
}
120 121

func TestBreakPointWithNonExistantFunction(t *testing.T) {
122
	helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
123 124 125 126 127
		_, err := p.Break(uintptr(0))
		if err == nil {
			t.Fatal("Should not be able to break at non existant function")
		}
	})
128
}
129 130

func TestClearBreakPoint(t *testing.T) {
131
	helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
132 133
		fn := p.GoSymTable.LookupFunc("main.sleepytime")
		bp, err := p.Break(uintptr(fn.Entry))
D
Derek Parker 已提交
134
		assertNoError(err, t, "Break()")
135 136 137 138 139 140 141

		int3, err := dataAtAddr(p.Pid, bp.Addr)
		if err != nil {
			t.Fatal(err)
		}

		bp, err = p.Clear(fn.Entry)
D
Derek Parker 已提交
142
		assertNoError(err, t, "Clear()")
143 144 145 146 147 148 149 150 151 152 153 154 155 156

		data, err := dataAtAddr(p.Pid, bp.Addr)
		if err != nil {
			t.Fatal(err)
		}

		if bytes.Equal(data, int3) {
			t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
		}

		if len(p.BreakPoints) != 0 {
			t.Fatal("Breakpoint not removed internally")
		}
	})
157
}
158 159 160

func TestNext(t *testing.T) {
	var (
161 162 163
		ln             int
		err            error
		executablePath = "../_fixtures/testnextprog"
164 165 166 167 168
	)

	testcases := []struct {
		begin, end int
	}{
D
Derek Parker 已提交
169 170
		{17, 19},
		{19, 20},
171
		{20, 22},
172 173
		{22, 19},
		{19, 20},
D
Derek Parker 已提交
174
		{20, 22},
175 176
		{22, 19},
		{19, 25},
D
Derek Parker 已提交
177
		{25, 26},
178 179
		{26, 29},
		{29, 30},
D
Derek Parker 已提交
180
		{30, 31},
181 182 183 184 185 186 187
	}

	fp, err := filepath.Abs("../_fixtures/testnextprog.go")
	if err != nil {
		t.Fatal(err)
	}

188
	helper.WithTestProcess(executablePath, t, func(p *proctl.DebuggedProcess) {
189
		pc, _, _ := p.GoSymTable.LineToPC(fp, testcases[0].begin)
190
		bp, err := p.Break(uintptr(pc))
191 192
		assertNoError(err, t, "Break()")
		assertNoError(p.Continue(), t, "Continue()")
193 194 195 196 197 198 199 200 201 202 203 204 205 206

		for _, tc := range testcases {
			ln = currentLineNumber(p, t)
			if ln != tc.begin {
				t.Fatalf("Program not stopped at correct spot expected %d was %d", tc.begin, ln)
			}

			assertNoError(p.Next(), t, "Next() returned an error")

			ln = currentLineNumber(p, t)
			if ln != tc.end {
				t.Fatalf("Program did not continue to correct next location expected %d was %d", tc.end, ln)
			}
		}
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222

		if len(p.TempBreakPoints) != 0 {
			t.Fatal("Not all breakpoints were cleaned up")
		}

		// Test that next will properly clean up after itself.
		// Since we kind of spray breakpoints all around, we want to
		// make sure here that after next'ing a couple time, we can
		// still continue to execute the program without hitting a
		// rogue breakpoint.
		exited := make(chan interface{})

		_, err = p.Clear(bp.Addr)
		assertNoError(err, t, "Clear()")

		go func() {
D
Derek Parker 已提交
223 224 225 226 227
			fmt.Println("waiting")
			select {
			case <-time.After(2 * time.Second):
				syscall.PtraceDetach(p.Pid)
				p.Process.Kill()
228
				os.Exit(1)
D
Derek Parker 已提交
229 230 231
			case <-exited:
				p.Process.Kill()
				os.Exit(0)
232 233 234 235
			}
		}()

		go func() {
D
Derek Parker 已提交
236 237 238
			_, err := syscall.Wait4(p.Pid, nil, 0, nil)
			if err != nil && err != syscall.ECHILD {
				fmt.Println(err)
239 240
				os.Exit(1)
			}
D
Derek Parker 已提交
241
			exited <- nil
242 243 244 245 246 247
		}()

		// We must call Continue outside of the goroutine
		// because all ptrace commands must be executed
		// from the thread that started the trace.
		assertNoError(p.Continue(), t, "Continue()")
248 249
	})
}
250 251 252 253 254 255 256 257 258 259 260 261 262 263

func TestVariableEvaluation(t *testing.T) {
	executablePath := "../_fixtures/testvariables"

	fp, err := filepath.Abs(executablePath + ".go")
	if err != nil {
		t.Fatal(err)
	}

	testcases := []struct {
		name    string
		value   string
		varType string
	}{
D
Derek Parker 已提交
264
		{"a1", "foo", "struct string"},
265 266
		{"a2", "6", "int"},
		{"a3", "7.23", "float64"},
D
Derek Parker 已提交
267
		{"a4", "[2]int [1 2]", "[97]int"}, // There is a weird bug in the Go dwarf parser that is grabbing the wrong size for an array.
268
		{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"},
269 270 271
	}

	helper.WithTestProcess(executablePath, t, func(p *proctl.DebuggedProcess) {
272
		pc, _, _ := p.GoSymTable.LineToPC(fp, 21)
273 274 275 276 277

		_, err := p.Break(uintptr(pc))
		assertNoError(err, t, "Break() returned an error")

		err = p.Continue()
278
		assertNoError(err, t, "Continue() returned an error")
279 280 281 282 283 284 285 286 287 288 289 290 291 292

		for _, tc := range testcases {
			variable, err := p.EvalSymbol(tc.name)
			assertNoError(err, t, "Variable() returned an error")

			if variable.Name != tc.name {
				t.Fatalf("Expected %s got %s\n", tc.name, variable.Name)
			}

			if variable.Type != tc.varType {
				t.Fatalf("Expected %s got %s\n", tc.varType, variable.Type)
			}

			if variable.Value != tc.value {
D
Derek Parker 已提交
293
				t.Fatalf("Expected %#v got %#v\n", tc.value, variable.Value)
294 295 296 297
			}
		}
	})
}