cmd/compile: enhance debug_test for infinite loops

ssa/debug_test.go already had a step limit; this exposes
it to individual tests, and it is then set low for the
infinite loop tests.

That however is not enough; in an infinite loop debuggers
see an unchanging line number, and therefore keep trying
until they see a different one.  To do this, the concept
of a "bogus" line number is introduced, and on output
single-instruction infinite loops are detected and a
hardware nop with correct line number is inserted into
the loop; the branch itself receives a bogus line number.

This breaks up the endless stream of same line number and
causes both gdb and delve to not hang; Delve complains
about the incorrect line number while gdb does
a sort of odd step-to-nowhere that then steps back
to the loop.  Since repeats are suppressed in the reference
file, a single line is shown there.

(The wrong line number mentioned in previous message
was an artifact of debug_test.go, not Delve, and is now
fixed.)

The bogus line number exposed in Delve is less than
wonderful, but compared to hanging, it is better.

Fixes #30664.

Change-Id: I30c927cf8869a84c6c9b84033ee44d7044aab552
Reviewed-on: https://go-review.googlesource.com/c/go/+/168477
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
David Chase 2019-03-20 14:29:28 -04:00
parent 3089d18956
commit 591193b01f
7 changed files with 81 additions and 11 deletions

View file

@ -156,33 +156,34 @@ func TestNexting(t *testing.T) {
subTest(t, debugger+"-dbg-race", "i22600", dbgFlags, append(moreargs, "-race")...)
optSubTest(t, debugger+"-opt", "hist", optFlags, moreargs...)
optSubTest(t, debugger+"-opt", "scopes", optFlags, moreargs...)
optSubTest(t, debugger+"-opt", "hist", optFlags, 1000, moreargs...)
optSubTest(t, debugger+"-opt", "scopes", optFlags, 1000, moreargs...)
optSubTest(t, debugger+"-opt", "infloop", optFlags, 10, moreargs...)
}
// subTest creates a subtest that compiles basename.go with the specified gcflags and additional compiler arguments,
// then runs the debugger on the resulting binary, with any comment-specified actions matching tag triggered.
func subTest(t *testing.T, tag string, basename string, gcflags string, moreargs ...string) {
t.Run(tag+"-"+basename, func(t *testing.T) {
testNexting(t, basename, tag, gcflags, moreargs...)
testNexting(t, basename, tag, gcflags, 1000, moreargs...)
})
}
// optSubTest is the same as subTest except that it skips the test if the runtime and libraries
// were not compiled with optimization turned on. (The skip may not be necessary with Go 1.10 and later)
func optSubTest(t *testing.T, tag string, basename string, gcflags string, moreargs ...string) {
func optSubTest(t *testing.T, tag string, basename string, gcflags string, count int, moreargs ...string) {
// If optimized test is run with unoptimized libraries (compiled with -N -l), it is very likely to fail.
// This occurs in the noopt builders (for example).
t.Run(tag+"-"+basename, func(t *testing.T) {
if *force || optimizedLibs {
testNexting(t, basename, tag, gcflags, moreargs...)
testNexting(t, basename, tag, gcflags, count, moreargs...)
} else {
t.Skip("skipping for unoptimized stdlib/runtime")
}
})
}
func testNexting(t *testing.T, base, tag, gcflags string, moreArgs ...string) {
func testNexting(t *testing.T, base, tag, gcflags string, count int, moreArgs ...string) {
// (1) In testdata, build sample.go into test-sample.<tag>
// (2) Run debugger gathering a history
// (3) Read expected history from testdata/sample.<tag>.nexts
@ -219,7 +220,7 @@ func testNexting(t *testing.T, base, tag, gcflags string, moreArgs ...string) {
} else {
dbg = newGdb(tag, exe)
}
h1 := runDbgr(dbg, 1000)
h1 := runDbgr(dbg, count)
if *dryrun {
fmt.Printf("# Tag for above is %s\n", dbg.tag())
return
@ -261,6 +262,7 @@ func runDbgr(dbg dbgr, maxNext int) *nextHist {
break
}
}
dbg.quit()
h := dbg.hist()
return h
}
@ -298,7 +300,7 @@ func (t tstring) String() string {
}
type pos struct {
line uint16
line uint32
file uint8 // Artifact of plans to implement differencing instead of calling out to diff.
}
@ -386,7 +388,7 @@ func (h *nextHist) add(file, line, text string) bool {
}
}
l := len(h.ps)
p := pos{line: uint16(li), file: fi}
p := pos{line: uint32(li), file: fi}
if l == 0 || *repeats || h.ps[l-1] != p {
h.ps = append(h.ps, p)
@ -721,6 +723,15 @@ func varsToPrint(line, lookfor string) []string {
func (s *gdbState) quit() {
response := s.ioState.writeRead("q\n")
if strings.Contains(response.o, "Quit anyway? (y or n)") {
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); !(ok && strings.Contains(s, "'Y\n'")) {
// Not the panic that was expected.
fmt.Printf("Expected a broken pipe panic, but saw the following panic instead")
panic(r)
}
}
}()
s.ioState.writeRead("Y\n")
}
}