diff options
| author | Russ Cox <rsc@golang.org> | 2014-10-29 11:54:48 -0400 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2014-10-29 11:54:48 -0400 |
| commit | 8e171e196615bc40a3a804811acf768eee6b2aa8 (patch) | |
| tree | 843fd3e672b94483e5684f537518b544124486d0 /src/runtime | |
| parent | 5550249ad3030e563ba810f3c30a1ba6b01f448c (diff) | |
| parent | 3bbc8638d5dd2564b3c60a603c094c0e570bd301 (diff) | |
| download | go-8e171e196615bc40a3a804811acf768eee6b2aa8.tar.xz | |
[dev.garbage] all: merge default (dd5014ed9b01) into dev.garbage
LGTM=rlh
R=rlh
CC=golang-codereviews
https://golang.org/cl/170730043
Diffstat (limited to 'src/runtime')
30 files changed, 401 insertions, 101 deletions
diff --git a/src/runtime/asm_386.s b/src/runtime/asm_386.s index b0ed2d8ceb..0d46a9eff7 100644 --- a/src/runtime/asm_386.s +++ b/src/runtime/asm_386.s @@ -732,6 +732,20 @@ needm: MOVL g(CX), BP MOVL g_m(BP), BP + // Set m->sched.sp = SP, so that if a panic happens + // during the function we are about to execute, it will + // have a valid SP to run on the g0 stack. + // The next few lines (after the havem label) + // will save this SP onto the stack and then write + // the same SP back to m->sched.sp. That seems redundant, + // but if an unrecovered panic happens, unwindm will + // restore the g->sched.sp from the stack location + // and then onM will try to use it. If we don't set it here, + // that restored SP will be uninitialized (typically 0) and + // will not be usable. + MOVL m_g0(BP), SI + MOVL SP, (g_sched+gobuf_sp)(SI) + havem: // Now there's a valid m, and we're running on its m->g0. // Save current m->g0->sched.sp on stack and then set it to SP. @@ -871,12 +885,6 @@ TEXT runtime·cputicks(SB),NOSPLIT,$0-8 MOVL DX, ret_hi+4(FP) RET -TEXT runtime·gocputicks(SB),NOSPLIT,$0-8 - RDTSC - MOVL AX, ret_lo+0(FP) - MOVL DX, ret_hi+4(FP) - RET - TEXT runtime·ldt0setup(SB),NOSPLIT,$16-0 // set up ldt 7 to point at tls0 // ldt 1 would be fine on Linux, but on OS X, 7 is as low as we can go. diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index 2ee3312086..a9b082beb8 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -717,6 +717,20 @@ needm: get_tls(CX) MOVQ g(CX), BP MOVQ g_m(BP), BP + + // Set m->sched.sp = SP, so that if a panic happens + // during the function we are about to execute, it will + // have a valid SP to run on the g0 stack. + // The next few lines (after the havem label) + // will save this SP onto the stack and then write + // the same SP back to m->sched.sp. That seems redundant, + // but if an unrecovered panic happens, unwindm will + // restore the g->sched.sp from the stack location + // and then onM will try to use it. If we don't set it here, + // that restored SP will be uninitialized (typically 0) and + // will not be usable. + MOVQ m_g0(BP), SI + MOVQ SP, (g_sched+gobuf_sp)(SI) havem: // Now there's a valid m, and we're running on its m->g0. @@ -855,13 +869,6 @@ TEXT runtime·cputicks(SB),NOSPLIT,$0-0 MOVQ AX, ret+0(FP) RET -TEXT runtime·gocputicks(SB),NOSPLIT,$0-8 - RDTSC - SHLQ $32, DX - ADDQ DX, AX - MOVQ AX, ret+0(FP) - RET - // hash function using AES hardware instructions TEXT runtime·aeshash(SB),NOSPLIT,$0-32 MOVQ p+0(FP), AX // ptr to data diff --git a/src/runtime/asm_amd64p32.s b/src/runtime/asm_amd64p32.s index e27f67e1ee..28875bc55a 100644 --- a/src/runtime/asm_amd64p32.s +++ b/src/runtime/asm_amd64p32.s @@ -657,13 +657,6 @@ TEXT runtime·cputicks(SB),NOSPLIT,$0-0 MOVQ AX, ret+0(FP) RET -TEXT runtime·gocputicks(SB),NOSPLIT,$0-8 - RDTSC - SHLQ $32, DX - ADDQ DX, AX - MOVQ AX, ret+0(FP) - RET - // hash function using AES hardware instructions // For now, our one amd64p32 system (NaCl) does not // support using AES instructions, so have not bothered to diff --git a/src/runtime/asm_arm.s b/src/runtime/asm_arm.s index b21441488a..e94b4c1ff6 100644 --- a/src/runtime/asm_arm.s +++ b/src/runtime/asm_arm.s @@ -556,6 +556,21 @@ TEXT ·cgocallback_gofunc(SB),NOSPLIT,$8-12 MOVW $runtime·needm(SB), R0 BL (R0) + // Set m->sched.sp = SP, so that if a panic happens + // during the function we are about to execute, it will + // have a valid SP to run on the g0 stack. + // The next few lines (after the havem label) + // will save this SP onto the stack and then write + // the same SP back to m->sched.sp. That seems redundant, + // but if an unrecovered panic happens, unwindm will + // restore the g->sched.sp from the stack location + // and then onM will try to use it. If we don't set it here, + // that restored SP will be uninitialized (typically 0) and + // will not be usable. + MOVW g_m(g), R8 + MOVW m_g0(R8), R3 + MOVW R13, (g_sched+gobuf_sp)(R3) + havem: MOVW g_m(g), R8 MOVW R8, savedm-4(SP) @@ -1275,9 +1290,6 @@ TEXT runtime·fastrand1(SB),NOSPLIT,$-4-4 MOVW R0, ret+0(FP) RET -TEXT runtime·gocputicks(SB),NOSPLIT,$0 - B runtime·cputicks(SB) - TEXT runtime·return0(SB),NOSPLIT,$0 MOVW $0, R0 RET diff --git a/src/runtime/cgo/gcc_arm.S b/src/runtime/cgo/gcc_arm.S index 2e4b3528ba..d5833bfad0 100644 --- a/src/runtime/cgo/gcc_arm.S +++ b/src/runtime/cgo/gcc_arm.S @@ -12,13 +12,6 @@ #endif /* - * Because the assembler might target an earlier revision of the ISA - * by default, we must explicitly specify the ISA revision to ensure - * BLX is recognized as a valid instruction. - */ -.arch armv5t - -/* * void crosscall_arm1(void (*fn)(void), void (*setg_gcc)(void *g), void *g) * * Calling into the 5c tool chain, where all registers are caller save. @@ -31,8 +24,12 @@ EXT(crosscall_arm1): mov r4, r0 mov r5, r1 mov r0, r2 - blx r5 // setg(g) - blx r4 // fn() + + // Because the assembler might target an earlier revision of the ISA + // by default, we encode BLX as a .word. + .word 0xe12fff35 // blx r5 // setg(g) + .word 0xe12fff34 // blx r4 // fn() + pop {r4, r5, r6, r7, r8, r9, r10, r11, ip, pc} .globl EXT(__stack_chk_fail_local) diff --git a/src/runtime/chan_test.go b/src/runtime/chan_test.go index 01632892ed..e689ceaed1 100644 --- a/src/runtime/chan_test.go +++ b/src/runtime/chan_test.go @@ -482,6 +482,35 @@ func TestShrinkStackDuringBlockedSend(t *testing.T) { <-done } +func TestSelectDuplicateChannel(t *testing.T) { + // This test makes sure we can queue a G on + // the same channel multiple times. + c := make(chan int) + d := make(chan int) + e := make(chan int) + + // goroutine A + go func() { + select { + case <-c: + case <-c: + case <-d: + } + e <- 9 + }() + time.Sleep(time.Millisecond) // make sure goroutine A gets qeueued first on c + + // goroutine B + go func() { + <-c + }() + time.Sleep(time.Millisecond) // make sure goroutine B gets queued on c before continuing + + d <- 7 // wake up A, it dequeues itself from c. This operation used to corrupt c.recvq. + <-e // A tells us it's done + c <- 8 // wake up B. This operation used to fail because c.recvq was corrupted (it tries to wake up an already running G instead of B) +} + func BenchmarkChanNonblocking(b *testing.B) { myc := make(chan int) b.RunParallel(func(pb *testing.PB) { diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go index 4ff0084c22..5958ad8914 100644 --- a/src/runtime/crash_cgo_test.go +++ b/src/runtime/crash_cgo_test.go @@ -8,6 +8,7 @@ package runtime_test import ( "runtime" + "strings" "testing" ) @@ -34,6 +35,17 @@ func TestCgoTraceback(t *testing.T) { } } +func TestCgoExternalThreadPanic(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skipf("no pthreads on %s", runtime.GOOS) + } + got := executeTest(t, cgoExternalThreadPanicSource, nil, "main.c", cgoExternalThreadPanicC) + want := "panic: BOOM" + if !strings.Contains(got, want) { + t.Fatalf("want failure containing %q. output:\n%s\n", want, got) + } +} + const cgoSignalDeadlockSource = ` package main @@ -117,3 +129,43 @@ func main() { fmt.Printf("OK\n") } ` + +const cgoExternalThreadPanicSource = ` +package main + +// void start(void); +import "C" + +func main() { + C.start() + select {} +} + +//export gopanic +func gopanic() { + panic("BOOM") +} +` + +const cgoExternalThreadPanicC = ` +#include <stdlib.h> +#include <stdio.h> +#include <pthread.h> + +void gopanic(void); + +static void* +die(void* x) +{ + gopanic(); + return 0; +} + +void +start(void) +{ + pthread_t t; + if(pthread_create(&t, 0, die, 0) != 0) + printf("pthread_create failed\n"); +} +` diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index 783b4c48f5..211a0476fd 100644 --- a/src/runtime/crash_test.go +++ b/src/runtime/crash_test.go @@ -31,7 +31,7 @@ func testEnv(cmd *exec.Cmd) *exec.Cmd { return cmd } -func executeTest(t *testing.T, templ string, data interface{}) string { +func executeTest(t *testing.T, templ string, data interface{}, extra ...string) string { switch runtime.GOOS { case "android", "nacl": t.Skipf("skipping on %s", runtime.GOOS) @@ -61,7 +61,20 @@ func executeTest(t *testing.T, templ string, data interface{}) string { t.Fatalf("failed to close file: %v", err) } - got, _ := testEnv(exec.Command("go", "run", src)).CombinedOutput() + for i := 0; i < len(extra); i += 2 { + if err := ioutil.WriteFile(filepath.Join(dir, extra[i]), []byte(extra[i+1]), 0666); err != nil { + t.Fatal(err) + } + } + + cmd := exec.Command("go", "build", "-o", "a.exe") + cmd.Dir = dir + out, err := testEnv(cmd).CombinedOutput() + if err != nil { + t.Fatalf("building source: %v\n%s", err, out) + } + + got, _ := testEnv(exec.Command(filepath.Join(dir, "a.exe"))).CombinedOutput() return string(got) } diff --git a/src/runtime/debug/garbage.go b/src/runtime/debug/garbage.go index 30994f2196..4a77dcfcd6 100644 --- a/src/runtime/debug/garbage.go +++ b/src/runtime/debug/garbage.go @@ -16,6 +16,7 @@ type GCStats struct { NumGC int64 // number of garbage collections PauseTotal time.Duration // total pause for all collections Pause []time.Duration // pause history, most recent first + PauseEnd []time.Time // pause end times history, most recent first PauseQuantiles []time.Duration } @@ -30,25 +31,36 @@ type GCStats struct { func ReadGCStats(stats *GCStats) { // Create a buffer with space for at least two copies of the // pause history tracked by the runtime. One will be returned - // to the caller and the other will be used as a temporary buffer - // for computing quantiles. + // to the caller and the other will be used as transfer buffer + // for end times history and as a temporary buffer for + // computing quantiles. const maxPause = len(((*runtime.MemStats)(nil)).PauseNs) - if cap(stats.Pause) < 2*maxPause { - stats.Pause = make([]time.Duration, 2*maxPause) + if cap(stats.Pause) < 2*maxPause+3 { + stats.Pause = make([]time.Duration, 2*maxPause+3) } - // readGCStats fills in the pause history (up to maxPause entries) - // and then three more: Unix ns time of last GC, number of GC, - // and total pause time in nanoseconds. Here we depend on the - // fact that time.Duration's native unit is nanoseconds, so the - // pauses and the total pause time do not need any conversion. + // readGCStats fills in the pause and end times histories (up to + // maxPause entries) and then three more: Unix ns time of last GC, + // number of GC, and total pause time in nanoseconds. Here we + // depend on the fact that time.Duration's native unit is + // nanoseconds, so the pauses and the total pause time do not need + // any conversion. readGCStats(&stats.Pause) n := len(stats.Pause) - 3 stats.LastGC = time.Unix(0, int64(stats.Pause[n])) stats.NumGC = int64(stats.Pause[n+1]) stats.PauseTotal = stats.Pause[n+2] + n /= 2 // buffer holds pauses and end times stats.Pause = stats.Pause[:n] + if cap(stats.PauseEnd) < maxPause { + stats.PauseEnd = make([]time.Time, 0, maxPause) + } + stats.PauseEnd = stats.PauseEnd[:0] + for _, ns := range stats.Pause[n : n+n] { + stats.PauseEnd = append(stats.PauseEnd, time.Unix(0, int64(ns))) + } + if len(stats.PauseQuantiles) > 0 { if n == 0 { for i := range stats.PauseQuantiles { diff --git a/src/runtime/debug/garbage_test.go b/src/runtime/debug/garbage_test.go index 149bafc6f3..54c33bd4f3 100644 --- a/src/runtime/debug/garbage_test.go +++ b/src/runtime/debug/garbage_test.go @@ -70,6 +70,19 @@ func TestReadGCStats(t *testing.T) { t.Errorf("stats.PauseQuantiles[%d]=%d > stats.PauseQuantiles[%d]=%d", i, q[i], i+1, q[i+1]) } } + + // compare memory stats with gc stats: + if len(stats.PauseEnd) != n { + t.Fatalf("len(stats.PauseEnd) = %d, want %d", len(stats.PauseEnd), n) + } + off := (int(mstats.NumGC) + len(mstats.PauseEnd) - 1) % len(mstats.PauseEnd) + for i := 0; i < n; i++ { + dt := stats.PauseEnd[i] + if dt.UnixNano() != int64(mstats.PauseEnd[off]) { + t.Errorf("stats.PauseEnd[%d] = %d, want %d", i, dt, mstats.PauseEnd[off]) + } + off = (off + len(mstats.PauseEnd) - 1) % len(mstats.PauseEnd) + } } var big = make([]byte, 1<<20) diff --git a/src/runtime/env_plan9.go b/src/runtime/env_plan9.go index 76e9867e03..e442c34835 100644 --- a/src/runtime/env_plan9.go +++ b/src/runtime/env_plan9.go @@ -30,7 +30,7 @@ func gogetenv(key string) string { if fd < 0 { return "" } - n := seek(fd, 0, 2) - 1 + n := seek(fd, 0, 2) if n <= 0 { close(fd) return "" @@ -44,6 +44,10 @@ func gogetenv(key string) string { return "" } + if p[r-1] == 0 { + r-- + } + var s string sp := (*_string)(unsafe.Pointer(&s)) sp.str = &p[0] diff --git a/src/runtime/extern.go b/src/runtime/extern.go index b8db5d0c4b..1b8052bb56 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -39,6 +39,12 @@ a comma-separated list of name=val pairs. Supported names are: gcdead: setting gcdead=1 causes the garbage collector to clobber all stack slots that it thinks are dead. + invalidptr: defaults to invalidptr=1, causing the garbage collector and stack + copier to crash the program if an invalid pointer value (for example, 1) + is found in a pointer-typed location. Setting invalidptr=0 disables this check. + This should only be used as a temporary workaround to diagnose buggy code. + The real fix is to not store integers in pointer-typed locations. + scheddetail: setting schedtrace=X and scheddetail=1 causes the scheduler to emit detailed multiline info every X milliseconds, describing state of the scheduler, processors, threads and goroutines. diff --git a/src/runtime/funcdata.h b/src/runtime/funcdata.h index a2667a4c02..d6c14fcb41 100644 --- a/src/runtime/funcdata.h +++ b/src/runtime/funcdata.h @@ -28,6 +28,9 @@ // defines the pointer map for the function's arguments. // GO_ARGS should be the first instruction in a function that uses it. // It can be omitted if there are no arguments at all. +// GO_ARGS is inserted implicitly by the linker for any function +// that also has a Go prototype and therefore is usually not necessary +// to write explicitly. #define GO_ARGS FUNCDATA $FUNCDATA_ArgsPointerMaps, go_args_stackmap(SB) // GO_RESULTS_INITIALIZED indicates that the assembly function diff --git a/src/runtime/malloc.h b/src/runtime/malloc.h index e606b0c7af..522b11bbad 100644 --- a/src/runtime/malloc.h +++ b/src/runtime/malloc.h @@ -268,7 +268,8 @@ struct MStats uint64 next_gc; // next GC (in heap_alloc time) uint64 last_gc; // last GC (in absolute time) uint64 pause_total_ns; - uint64 pause_ns[256]; + uint64 pause_ns[256]; // circular buffer of recent GC pause lengths + uint64 pause_end[256]; // circular buffer of recent GC end times (nanoseconds since 1970) uint32 numgc; bool enablegc; bool debuggc; diff --git a/src/runtime/mem.go b/src/runtime/mem.go index 438f22ec09..e6f1eb0e64 100644 --- a/src/runtime/mem.go +++ b/src/runtime/mem.go @@ -44,7 +44,8 @@ type MemStats struct { NextGC uint64 // next collection will happen when HeapAlloc ≥ this amount LastGC uint64 // end time of last collection (nanoseconds since 1970) PauseTotalNs uint64 - PauseNs [256]uint64 // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] + PauseNs [256]uint64 // circular buffer of recent GC pause durations, most recent at [(NumGC+255)%256] + PauseEnd [256]uint64 // circular buffer of recent GC pause end times NumGC uint32 EnableGC bool DebugGC bool diff --git a/src/runtime/mgc0.c b/src/runtime/mgc0.c index cc1f811234..bcc5a2f39d 100644 --- a/src/runtime/mgc0.c +++ b/src/runtime/mgc0.c @@ -687,7 +687,7 @@ putpartial(Workbuf *b) else if (b->nobj == nelem(b->obj)) runtime·lfstackpush(&runtime·work.full, &b->node); else { - runtime·printf("b=%p, b->nobj=%d, nelem(b->obj)=%d\n", b, b->nobj, (uint32)nelem(b->obj)); + runtime·printf("b=%p, b->nobj=%d, nelem(b->obj)=%d\n", b, (uint32)b->nobj, (uint32)nelem(b->obj)); runtime·throw("putpartial: bad Workbuf b->nobj"); } } @@ -1725,6 +1725,7 @@ gc(struct gc_args *args) t4 = runtime·nanotime(); runtime·atomicstore64(&mstats.last_gc, runtime·unixnanotime()); // must be Unix time to make sense to user mstats.pause_ns[mstats.numgc%nelem(mstats.pause_ns)] = t4 - t0; + mstats.pause_end[mstats.numgc%nelem(mstats.pause_end)] = t4; mstats.pause_total_ns += t4 - t0; mstats.numgc++; if(mstats.debuggc) @@ -1773,7 +1774,6 @@ gc(struct gc_args *args) runtime·sweep.spanidx = 0; runtime·unlock(&runtime·mheap.lock); - // Temporary disable concurrent sweep, because we see failures on builders. if(ConcurrentSweep && !args->eagersweep) { runtime·lock(&runtime·gclock); if(runtime·sweep.g == nil) @@ -1787,6 +1787,8 @@ gc(struct gc_args *args) // Sweep all spans eagerly. while(runtime·sweepone() != -1) runtime·sweep.npausesweep++; + // Do an additional mProf_GC, because all 'free' events are now real as well. + runtime·mProf_GC(); } runtime·mProf_GC(); @@ -1834,7 +1836,7 @@ readgcstats_m(void) { Slice *pauses; uint64 *p; - uint32 i, n; + uint32 i, j, n; pauses = g->m->ptrarg[0]; g->m->ptrarg[0] = nil; @@ -1843,25 +1845,29 @@ readgcstats_m(void) if(pauses->cap < nelem(mstats.pause_ns)+3) runtime·throw("runtime: short slice passed to readGCStats"); - // Pass back: pauses, last GC (absolute time), number of GC, total pause ns. + // Pass back: pauses, pause ends, last gc (absolute time), number of gc, total pause ns. p = (uint64*)pauses->array; runtime·lock(&runtime·mheap.lock); + n = mstats.numgc; if(n > nelem(mstats.pause_ns)) n = nelem(mstats.pause_ns); - + // The pause buffer is circular. The most recent pause is at // pause_ns[(numgc-1)%nelem(pause_ns)], and then backward // from there to go back farther in time. We deliver the times // most recent first (in p[0]). - for(i=0; i<n; i++) - p[i] = mstats.pause_ns[(mstats.numgc-1-i)%nelem(mstats.pause_ns)]; + for(i=0; i<n; i++) { + j = (mstats.numgc-1-i)%nelem(mstats.pause_ns); + p[i] = mstats.pause_ns[j]; + p[n+i] = mstats.pause_end[j]; + } - p[n] = mstats.last_gc; - p[n+1] = mstats.numgc; - p[n+2] = mstats.pause_total_ns; + p[n+n] = mstats.last_gc; + p[n+n+1] = mstats.numgc; + p[n+n+2] = mstats.pause_total_ns; runtime·unlock(&runtime·mheap.lock); - pauses->len = n+3; + pauses->len = n+n+3; } void @@ -2041,7 +2047,7 @@ runtime·unrollgcprog_m(void) Type *typ; byte *mask, *prog; uintptr pos; - uint32 x; + uintptr x; typ = g->m->ptrarg[0]; g->m->ptrarg[0] = nil; @@ -2060,8 +2066,9 @@ runtime·unrollgcprog_m(void) unrollgcprog1(mask, prog, &pos, false, true); } // atomic way to say mask[0] = 1 - x = ((uint32*)mask)[0]; - runtime·atomicstore((uint32*)mask, x|1); + x = *(uintptr*)mask; + ((byte*)&x)[0] = 1; + runtime·atomicstorep((void**)mask, (void*)x); } runtime·unlock(&lock); } diff --git a/src/runtime/mprof.go b/src/runtime/mprof.go index 89e9915236..803da56670 100644 --- a/src/runtime/mprof.go +++ b/src/runtime/mprof.go @@ -234,7 +234,7 @@ func mProf_GC() { // Called by malloc to record a profiled block. func mProf_Malloc(p unsafe.Pointer, size uintptr) { var stk [maxStack]uintptr - nstk := callers(1, &stk[0], len(stk)) + nstk := callers(4, &stk[0], len(stk)) lock(&proflock) b := stkbucket(memProfile, size, stk[:nstk], true) mp := b.mp() @@ -284,6 +284,8 @@ func SetBlockProfileRate(rate int) { var r int64 if rate <= 0 { r = 0 // disable profiling + } else if rate == 1 { + r = 1 // profile everything } else { // convert ns to cycles, use float64 to prevent overflow during multiplication r = int64(float64(rate) * float64(tickspersecond()) / (1000 * 1000 * 1000)) @@ -297,7 +299,7 @@ func SetBlockProfileRate(rate int) { func blockevent(cycles int64, skip int) { if cycles <= 0 { - return + cycles = 1 } rate := int64(atomicload64(&blockprofilerate)) if rate <= 0 || (rate > cycles && int64(fastrand1())%rate > cycles) { diff --git a/src/runtime/pprof/mprof_test.go b/src/runtime/pprof/mprof_test.go new file mode 100644 index 0000000000..ebf53dd66b --- /dev/null +++ b/src/runtime/pprof/mprof_test.go @@ -0,0 +1,99 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pprof_test + +import ( + "bytes" + "fmt" + "regexp" + "runtime" + . "runtime/pprof" + "testing" + "unsafe" +) + +var memSink interface{} + +func allocateTransient1M() { + for i := 0; i < 1024; i++ { + memSink = &struct{ x [1024]byte }{} + } +} + +func allocateTransient2M() { + // prevent inlining + if memSink == nil { + panic("bad") + } + memSink = make([]byte, 2<<20) +} + +type Obj32 struct { + link *Obj32 + pad [32 - unsafe.Sizeof(uintptr(0))]byte +} + +var persistentMemSink *Obj32 + +func allocatePersistent1K() { + for i := 0; i < 32; i++ { + // Can't use slice because that will introduce implicit allocations. + obj := &Obj32{link: persistentMemSink} + persistentMemSink = obj + } +} + +var memoryProfilerRun = 0 + +func TestMemoryProfiler(t *testing.T) { + // Disable sampling, otherwise it's difficult to assert anything. + oldRate := runtime.MemProfileRate + runtime.MemProfileRate = 1 + defer func() { + runtime.MemProfileRate = oldRate + }() + + // Allocate a meg to ensure that mcache.next_sample is updated to 1. + for i := 0; i < 1024; i++ { + memSink = make([]byte, 1024) + } + + // Do the interesting allocations. + allocateTransient1M() + allocateTransient2M() + allocatePersistent1K() + memSink = nil + + runtime.GC() // materialize stats + var buf bytes.Buffer + if err := Lookup("heap").WriteTo(&buf, 1); err != nil { + t.Fatalf("failed to write heap profile: %v", err) + } + + memoryProfilerRun++ + + tests := []string{ + fmt.Sprintf(`%v: %v \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ +# 0x[0-9,a-f]+ runtime/pprof_test\.allocatePersistent1K\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test\.go:43 +# 0x[0-9,a-f]+ runtime/pprof_test\.TestMemoryProfiler\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test\.go:66 +`, 32*memoryProfilerRun, 1024*memoryProfilerRun, 32*memoryProfilerRun, 1024*memoryProfilerRun), + + fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ +# 0x[0-9,a-f]+ runtime/pprof_test\.allocateTransient1M\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:21 +# 0x[0-9,a-f]+ runtime/pprof_test\.TestMemoryProfiler\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:64 +`, (1<<10)*memoryProfilerRun, (1<<20)*memoryProfilerRun), + + fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ +# 0x[0-9,a-f]+ runtime/pprof_test\.allocateTransient2M\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:30 +# 0x[0-9,a-f]+ runtime/pprof_test\.TestMemoryProfiler\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:65 +`, memoryProfilerRun, (2<<20)*memoryProfilerRun), + } + + for _, test := range tests { + if !regexp.MustCompile(test).Match(buf.Bytes()) { + t.Fatalf("The entry did not match:\n%v\n\nProfile:\n%v\n", test, buf.String()) + } + } +} diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index edd471a0c9..8677cb30c5 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -249,7 +249,7 @@ func TestGoroutineSwitch(t *testing.T) { // exists to record a PC without a traceback. Those are okay. if len(stk) == 2 { f := runtime.FuncForPC(stk[1]) - if f != nil && (f.Name() == "System" || f.Name() == "ExternalCode") { + if f != nil && (f.Name() == "System" || f.Name() == "ExternalCode" || f.Name() == "GC") { return } } diff --git a/src/runtime/print1.go b/src/runtime/print1.go index 0fa1fb63c4..8f8268873b 100644 --- a/src/runtime/print1.go +++ b/src/runtime/print1.go @@ -19,32 +19,17 @@ func bytes(s string) (ret []byte) { return } -// goprintf is the function call that is actually deferred when you write -// defer print(...) -// It is otherwise unused. In particular it is not used for ordinary prints. -// Right now a dynamically allocated string that is being passed as an -// argument is invisible to the garbage collector and might be collected -// if that argument list is the only reference. For now we ignore that possibility. -// To fix, we should change to defer a call to vprintf with a pointer to -// an argument list on the stack, stored in an appropriately typed -// struct. golang.org/issue/8614. -//go:nosplit -func goprintf(s string) { - vprintf(s, add(unsafe.Pointer(&s), unsafe.Sizeof(s))) -} - -// printf is only called from C code. It has the same problem as goprintf -// with strings possibly being collected from underneath. -// However, the runtime never prints dynamically allocated -// Go strings using printf. The strings it prints come from the symbol -// and type tables. +// printf is only called from C code. It has no type information for the args, +// but C stacks are ignored by the garbage collector anyway, so having +// type information would not add anything. //go:nosplit func printf(s *byte) { vprintf(gostringnocopy(s), add(unsafe.Pointer(&s), unsafe.Sizeof(s))) } -// sprintf is only called from C code. -// It has the same problem as goprintf. +// sprintf is only called from C code. It has no type information for the args, +// but C stacks are ignored by the garbage collector anyway, so having +// type information would not add anything. //go:nosplit func snprintf(dst *byte, n int32, s *byte) { buf := (*[1 << 30]byte)(unsafe.Pointer(dst))[0:n:n] diff --git a/src/runtime/proc.c b/src/runtime/proc.c index b824f574d9..ab6812329f 100644 --- a/src/runtime/proc.c +++ b/src/runtime/proc.c @@ -2764,6 +2764,8 @@ static void checkdead(void) { G *gp; + P *p; + M *mp; int32 run, grunning, s; uintptr i; @@ -2805,6 +2807,24 @@ checkdead(void) runtime·unlock(&runtime·allglock); if(grunning == 0) // possible if main goroutine calls runtime·Goexit() runtime·throw("no goroutines (main called runtime.Goexit) - deadlock!"); + + // Maybe jump time forward for playground. + if((gp = runtime·timejump()) != nil) { + runtime·casgstatus(gp, Gwaiting, Grunnable); + globrunqput(gp); + p = pidleget(); + if(p == nil) + runtime·throw("checkdead: no p for timer"); + mp = mget(); + if(mp == nil) + newm(nil, p); + else { + mp->nextp = p; + runtime·notewakeup(&mp->park); + } + return; + } + g->m->throwing = -1; // do not dump full stacks runtime·throw("all goroutines are asleep - deadlock!"); } diff --git a/src/runtime/rt0_nacl_amd64p32.s b/src/runtime/rt0_nacl_amd64p32.s index d8703dc0f0..54e4b1de89 100644 --- a/src/runtime/rt0_nacl_amd64p32.s +++ b/src/runtime/rt0_nacl_amd64p32.s @@ -26,5 +26,5 @@ TEXT _rt0_amd64p32_nacl(SB),NOSPLIT,$16 TEXT main(SB),NOSPLIT,$0 // Uncomment for fake time like on Go Playground. //MOVQ $1257894000000000000, AX - //MOVQ AX, runtime·timens(SB) + //MOVQ AX, runtime·faketime(SB) JMP runtime·rt0_go(SB) diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c index b3503fb909..c823691ec5 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -276,9 +276,13 @@ struct DbgVar int32* value; }; +// Do we report invalid pointers found during stack or heap scans? +int32 runtime·invalidptr = 1; + #pragma dataflag NOPTR /* dbgvar has no heap pointers */ static DbgVar dbgvar[] = { {"allocfreetrace", &runtime·debug.allocfreetrace}, + {"invalidptr", &runtime·invalidptr}, {"efence", &runtime·debug.efence}, {"gctrace", &runtime·debug.gctrace}, {"gcdead", &runtime·debug.gcdead}, diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index c1bba423a1..6a02ef1d31 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -672,6 +672,8 @@ enum { byte* runtime·startup_random_data; uint32 runtime·startup_random_data_len; +int32 runtime·invalidptr; + enum { // hashinit wants this many random bytes HashRandomBytes = 32 diff --git a/src/runtime/select.go b/src/runtime/select.go index 2d0787bd96..d703e1d79b 100644 --- a/src/runtime/select.go +++ b/src/runtime/select.go @@ -393,9 +393,9 @@ loop: } else { c = k._chan if k.kind == _CaseSend { - c.sendq.dequeueg(gp) + c.sendq.dequeueSudoG(sglist) } else { - c.recvq.dequeueg(gp) + c.recvq.dequeueSudoG(sglist) } } sgnext = sglist.waitlink @@ -623,7 +623,7 @@ func reflect_rselect(cases []runtimeSelect) (chosen int, recvOK bool) { return } -func (q *waitq) dequeueg(gp *g) { +func (q *waitq) dequeueSudoG(s *sudog) { var prevsgp *sudog l := &q.first for { @@ -631,7 +631,7 @@ func (q *waitq) dequeueg(gp *g) { if sgp == nil { return } - if sgp.g == gp { + if sgp == s { *l = sgp.next if q.last == sgp { q.last = prevsgp diff --git a/src/runtime/stack.c b/src/runtime/stack.c index e06e48a93d..f18171ea5c 100644 --- a/src/runtime/stack.c +++ b/src/runtime/stack.c @@ -401,12 +401,12 @@ adjustpointers(byte **scanp, BitVector *bv, AdjustInfo *adjinfo, Func *f) break; case BitsPointer: p = scanp[i]; - if(f != nil && (byte*)0 < p && (p < (byte*)PageSize || (uintptr)p == PoisonGC || (uintptr)p == PoisonStack)) { + if(f != nil && (byte*)0 < p && (p < (byte*)PageSize && runtime·invalidptr || (uintptr)p == PoisonGC || (uintptr)p == PoisonStack)) { // Looks like a junk value in a pointer slot. // Live analysis wrong? g->m->traceback = 2; runtime·printf("runtime: bad pointer in frame %s at %p: %p\n", runtime·funcname(f), &scanp[i], p); - runtime·throw("bad pointer!"); + runtime·throw("invalid stack pointer"); } if(minp <= p && p < maxp) { if(StackDebug >= 3) diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go index 32dfed7d39..2d5e41c1c5 100644 --- a/src/runtime/stubs.go +++ b/src/runtime/stubs.go @@ -182,7 +182,11 @@ func exit(code int32) func breakpoint() func nanotime() int64 func usleep(usec uint32) + +// careful: cputicks is not guaranteed to be monotonic! In particular, we have +// noticed drift between cpus on certain os/arch combinations. See issue 8976. func cputicks() int64 + func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer func munmap(addr unsafe.Pointer, n uintptr) func madvise(addr unsafe.Pointer, n uintptr, flags int32) diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 48d4023b9a..45d107b777 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -84,10 +84,13 @@ func symtabinit() { } } - // file table follows ftab. + // The ftab ends with a half functab consisting only of + // 'entry', followed by a uint32 giving the pcln-relative + // offset of the file table. sp = (*sliceStruct)(unsafe.Pointer(&filetab)) - p = unsafe.Pointer(add(unsafe.Pointer(pcln), ftab[nftab].funcoff)) - sp.array = unsafe.Pointer(add(unsafe.Pointer(pcln), ftab[nftab].funcoff)) + end := unsafe.Pointer(&ftab[nftab].funcoff) // just beyond ftab + fileoffset := *(*uint32)(end) + sp.array = unsafe.Pointer(&pclntable[fileoffset]) // length is in first element of array. // set len to 1 so we can get first element. sp.len = 1 @@ -224,7 +227,7 @@ func funcline(f *_func, targetpc uintptr, file *string) int32 { func funcspdelta(f *_func, targetpc uintptr) int32 { x := pcvalue(f, f.pcsp, targetpc, true) if x&(ptrSize-1) != 0 { - print("invalid spdelta ", f.pcsp, " ", x, "\n") + print("invalid spdelta ", hex(f.entry), " ", hex(targetpc), " ", hex(f.pcsp), " ", x, "\n") } return x } diff --git a/src/runtime/sys_nacl_amd64p32.s b/src/runtime/sys_nacl_amd64p32.s index c30c2a8933..4eb4aacdd5 100644 --- a/src/runtime/sys_nacl_amd64p32.s +++ b/src/runtime/sys_nacl_amd64p32.s @@ -60,7 +60,7 @@ TEXT syscall·naclWrite(SB), NOSPLIT, $24-20 TEXT runtime·write(SB),NOSPLIT,$16-20 // If using fake time and writing to stdout or stderr, // emit playback header before actual data. - MOVQ runtime·timens(SB), AX + MOVQ runtime·faketime(SB), AX CMPQ AX, $0 JEQ write MOVL fd+0(FP), DI @@ -242,7 +242,7 @@ TEXT runtime·mmap(SB),NOSPLIT,$8 RET TEXT time·now(SB),NOSPLIT,$16 - MOVQ runtime·timens(SB), AX + MOVQ runtime·faketime(SB), AX CMPQ AX, $0 JEQ realtime MOVQ $0, DX @@ -277,7 +277,7 @@ TEXT runtime·nacl_clock_gettime(SB),NOSPLIT,$0 RET TEXT runtime·nanotime(SB),NOSPLIT,$16 - MOVQ runtime·timens(SB), AX + MOVQ runtime·faketime(SB), AX CMPQ AX, $0 JEQ 3(PC) MOVQ AX, ret+0(FP) diff --git a/src/runtime/time.go b/src/runtime/time.go index 8cf9eecf83..11862c7e23 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -35,8 +35,8 @@ var timers struct { t []*timer } -// nacl fake time support. -var timens int64 +// nacl fake time support - time in nanoseconds since 1970 +var faketime int64 // Package time APIs. // Godoc uses the comments in package time, not these. @@ -194,7 +194,7 @@ func timerproc() { f(arg, seq) lock(&timers.lock) } - if delta < 0 { + if delta < 0 || faketime > 0 { // No timers left - put goroutine to sleep. timers.rescheduling = true goparkunlock(&timers.lock, "timer goroutine (idle)") @@ -208,6 +208,29 @@ func timerproc() { } } +func timejump() *g { + if faketime == 0 { + return nil + } + + lock(&timers.lock) + if !timers.created || len(timers.t) == 0 { + unlock(&timers.lock) + return nil + } + + var gp *g + if faketime < timers.t[0].when { + faketime = timers.t[0].when + if timers.rescheduling { + timers.rescheduling = false + gp = timers.gp + } + } + unlock(&timers.lock) + return gp +} + // Heap maintenance algorithms. func siftupTimer(i int) { |
