summaryrefslogtreecommitdiffstats
path: root/src/runtime/debug
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/debug')
-rw-r--r--src/runtime/debug/debug.s9
-rw-r--r--src/runtime/debug/garbage.go238
-rw-r--r--src/runtime/debug/garbage_test.go238
-rw-r--r--src/runtime/debug/heapdump_test.go95
-rw-r--r--src/runtime/debug/mod.go287
-rw-r--r--src/runtime/debug/mod_test.go75
-rw-r--r--src/runtime/debug/panic_test.go56
-rw-r--r--src/runtime/debug/stack.go30
-rw-r--r--src/runtime/debug/stack_test.go121
-rw-r--r--src/runtime/debug/stubs.go18
-rw-r--r--src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/5501685e611fa7642
-rw-r--r--src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/71634114e78567cf2
-rw-r--r--src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/c73dce23c1f2494c2
13 files changed, 1173 insertions, 0 deletions
diff --git a/src/runtime/debug/debug.s b/src/runtime/debug/debug.s
new file mode 100644
index 0000000..6aae33a
--- /dev/null
+++ b/src/runtime/debug/debug.s
@@ -0,0 +1,9 @@
+// Copyright 2013 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.
+
+// Nothing to see here.
+// This file exists so that the go command knows that parts of the
+// package are implemented in C, so that it does not instruct the
+// Go compiler to complain about extern declarations.
+// The actual implementations are in package runtime.
diff --git a/src/runtime/debug/garbage.go b/src/runtime/debug/garbage.go
new file mode 100644
index 0000000..cb3248d
--- /dev/null
+++ b/src/runtime/debug/garbage.go
@@ -0,0 +1,238 @@
+// Copyright 2013 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 debug
+
+import (
+ "runtime"
+ "sort"
+ "time"
+)
+
+// GCStats collect information about recent garbage collections.
+type GCStats struct {
+ LastGC time.Time // time of last collection
+ 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
+}
+
+// ReadGCStats reads statistics about garbage collection into stats.
+// The number of entries in the pause history is system-dependent;
+// stats.Pause slice will be reused if large enough, reallocated otherwise.
+// ReadGCStats may use the full capacity of the stats.Pause slice.
+// If stats.PauseQuantiles is non-empty, ReadGCStats fills it with quantiles
+// summarizing the distribution of pause time. For example, if
+// len(stats.PauseQuantiles) is 5, it will be filled with the minimum,
+// 25%, 50%, 75%, and maximum pause times.
+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 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+3 {
+ stats.Pause = make([]time.Duration, 2*maxPause+3)
+ }
+
+ // 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 {
+ stats.PauseQuantiles[i] = 0
+ }
+ } else {
+ // There's room for a second copy of the data in stats.Pause.
+ // See the allocation at the top of the function.
+ sorted := stats.Pause[n : n+n]
+ copy(sorted, stats.Pause)
+ sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
+ nq := len(stats.PauseQuantiles) - 1
+ for i := 0; i < nq; i++ {
+ stats.PauseQuantiles[i] = sorted[len(sorted)*i/nq]
+ }
+ stats.PauseQuantiles[nq] = sorted[len(sorted)-1]
+ }
+ }
+}
+
+// SetGCPercent sets the garbage collection target percentage:
+// a collection is triggered when the ratio of freshly allocated data
+// to live data remaining after the previous collection reaches this percentage.
+// SetGCPercent returns the previous setting.
+// The initial setting is the value of the GOGC environment variable
+// at startup, or 100 if the variable is not set.
+// This setting may be effectively reduced in order to maintain a memory
+// limit.
+// A negative percentage effectively disables garbage collection, unless
+// the memory limit is reached.
+// See SetMemoryLimit for more details.
+func SetGCPercent(percent int) int {
+ return int(setGCPercent(int32(percent)))
+}
+
+// FreeOSMemory forces a garbage collection followed by an
+// attempt to return as much memory to the operating system
+// as possible. (Even if this is not called, the runtime gradually
+// returns memory to the operating system in a background task.)
+func FreeOSMemory() {
+ freeOSMemory()
+}
+
+// SetMaxStack sets the maximum amount of memory that
+// can be used by a single goroutine stack.
+// If any goroutine exceeds this limit while growing its stack,
+// the program crashes.
+// SetMaxStack returns the previous setting.
+// The initial setting is 1 GB on 64-bit systems, 250 MB on 32-bit systems.
+// There may be a system-imposed maximum stack limit regardless
+// of the value provided to SetMaxStack.
+//
+// SetMaxStack is useful mainly for limiting the damage done by
+// goroutines that enter an infinite recursion. It only limits future
+// stack growth.
+func SetMaxStack(bytes int) int {
+ return setMaxStack(bytes)
+}
+
+// SetMaxThreads sets the maximum number of operating system
+// threads that the Go program can use. If it attempts to use more than
+// this many, the program crashes.
+// SetMaxThreads returns the previous setting.
+// The initial setting is 10,000 threads.
+//
+// The limit controls the number of operating system threads, not the number
+// of goroutines. A Go program creates a new thread only when a goroutine
+// is ready to run but all the existing threads are blocked in system calls, cgo calls,
+// or are locked to other goroutines due to use of runtime.LockOSThread.
+//
+// SetMaxThreads is useful mainly for limiting the damage done by
+// programs that create an unbounded number of threads. The idea is
+// to take down the program before it takes down the operating system.
+func SetMaxThreads(threads int) int {
+ return setMaxThreads(threads)
+}
+
+// SetPanicOnFault controls the runtime's behavior when a program faults
+// at an unexpected (non-nil) address. Such faults are typically caused by
+// bugs such as runtime memory corruption, so the default response is to crash
+// the program. Programs working with memory-mapped files or unsafe
+// manipulation of memory may cause faults at non-nil addresses in less
+// dramatic situations; SetPanicOnFault allows such programs to request
+// that the runtime trigger only a panic, not a crash.
+// The runtime.Error that the runtime panics with may have an additional method:
+//
+// Addr() uintptr
+//
+// If that method exists, it returns the memory address which triggered the fault.
+// The results of Addr are best-effort and the veracity of the result
+// may depend on the platform.
+// SetPanicOnFault applies only to the current goroutine.
+// It returns the previous setting.
+func SetPanicOnFault(enabled bool) bool {
+ return setPanicOnFault(enabled)
+}
+
+// WriteHeapDump writes a description of the heap and the objects in
+// it to the given file descriptor.
+//
+// WriteHeapDump suspends the execution of all goroutines until the heap
+// dump is completely written. Thus, the file descriptor must not be
+// connected to a pipe or socket whose other end is in the same Go
+// process; instead, use a temporary file or network socket.
+//
+// The heap dump format is defined at https://golang.org/s/go15heapdump.
+func WriteHeapDump(fd uintptr)
+
+// SetTraceback sets the amount of detail printed by the runtime in
+// the traceback it prints before exiting due to an unrecovered panic
+// or an internal runtime error.
+// The level argument takes the same values as the GOTRACEBACK
+// environment variable. For example, SetTraceback("all") ensure
+// that the program prints all goroutines when it crashes.
+// See the package runtime documentation for details.
+// If SetTraceback is called with a level lower than that of the
+// environment variable, the call is ignored.
+func SetTraceback(level string)
+
+// SetMemoryLimit provides the runtime with a soft memory limit.
+//
+// The runtime undertakes several processes to try to respect this
+// memory limit, including adjustments to the frequency of garbage
+// collections and returning memory to the underlying system more
+// aggressively. This limit will be respected even if GOGC=off (or,
+// if SetGCPercent(-1) is executed).
+//
+// The input limit is provided as bytes, and includes all memory
+// mapped, managed, and not released by the Go runtime. Notably, it
+// does not account for space used by the Go binary and memory
+// external to Go, such as memory managed by the underlying system
+// on behalf of the process, or memory managed by non-Go code inside
+// the same process. Examples of excluded memory sources include: OS
+// kernel memory held on behalf of the process, memory allocated by
+// C code, and memory mapped by syscall.Mmap (because it is not
+// managed by the Go runtime).
+//
+// More specifically, the following expression accurately reflects
+// the value the runtime attempts to maintain as the limit:
+//
+// runtime.MemStats.Sys - runtime.MemStats.HeapReleased
+//
+// or in terms of the runtime/metrics package:
+//
+// /memory/classes/total:bytes - /memory/classes/heap/released:bytes
+//
+// A zero limit or a limit that's lower than the amount of memory
+// used by the Go runtime may cause the garbage collector to run
+// nearly continuously. However, the application may still make
+// progress.
+//
+// The memory limit is always respected by the Go runtime, so to
+// effectively disable this behavior, set the limit very high.
+// [math.MaxInt64] is the canonical value for disabling the limit,
+// but values much greater than the available memory on the underlying
+// system work just as well.
+//
+// See https://go.dev/doc/gc-guide for a detailed guide explaining
+// the soft memory limit in more detail, as well as a variety of common
+// use-cases and scenarios.
+//
+// The initial setting is math.MaxInt64 unless the GOMEMLIMIT
+// environment variable is set, in which case it provides the initial
+// setting. GOMEMLIMIT is a numeric value in bytes with an optional
+// unit suffix. The supported suffixes include B, KiB, MiB, GiB, and
+// TiB. These suffixes represent quantities of bytes as defined by
+// the IEC 80000-13 standard. That is, they are based on powers of
+// two: KiB means 2^10 bytes, MiB means 2^20 bytes, and so on.
+//
+// SetMemoryLimit returns the previously set memory limit.
+// A negative input does not adjust the limit, and allows for
+// retrieval of the currently set memory limit.
+func SetMemoryLimit(limit int64) int64 {
+ return setMemoryLimit(limit)
+}
diff --git a/src/runtime/debug/garbage_test.go b/src/runtime/debug/garbage_test.go
new file mode 100644
index 0000000..cd91782
--- /dev/null
+++ b/src/runtime/debug/garbage_test.go
@@ -0,0 +1,238 @@
+// Copyright 2013 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 debug_test
+
+import (
+ "internal/testenv"
+ "os"
+ "runtime"
+ . "runtime/debug"
+ "testing"
+ "time"
+)
+
+func TestReadGCStats(t *testing.T) {
+ defer SetGCPercent(SetGCPercent(-1))
+
+ var stats GCStats
+ var mstats runtime.MemStats
+ var min, max time.Duration
+
+ // First ReadGCStats will allocate, second should not,
+ // especially if we follow up with an explicit garbage collection.
+ stats.PauseQuantiles = make([]time.Duration, 10)
+ ReadGCStats(&stats)
+ runtime.GC()
+
+ // Assume these will return same data: no GC during ReadGCStats.
+ ReadGCStats(&stats)
+ runtime.ReadMemStats(&mstats)
+
+ if stats.NumGC != int64(mstats.NumGC) {
+ t.Errorf("stats.NumGC = %d, but mstats.NumGC = %d", stats.NumGC, mstats.NumGC)
+ }
+ if stats.PauseTotal != time.Duration(mstats.PauseTotalNs) {
+ t.Errorf("stats.PauseTotal = %d, but mstats.PauseTotalNs = %d", stats.PauseTotal, mstats.PauseTotalNs)
+ }
+ if stats.LastGC.UnixNano() != int64(mstats.LastGC) {
+ t.Errorf("stats.LastGC.UnixNano = %d, but mstats.LastGC = %d", stats.LastGC.UnixNano(), mstats.LastGC)
+ }
+ n := int(mstats.NumGC)
+ if n > len(mstats.PauseNs) {
+ n = len(mstats.PauseNs)
+ }
+ if len(stats.Pause) != n {
+ t.Errorf("len(stats.Pause) = %d, want %d", len(stats.Pause), n)
+ } else {
+ off := (int(mstats.NumGC) + len(mstats.PauseNs) - 1) % len(mstats.PauseNs)
+ for i := 0; i < n; i++ {
+ dt := stats.Pause[i]
+ if dt != time.Duration(mstats.PauseNs[off]) {
+ t.Errorf("stats.Pause[%d] = %d, want %d", i, dt, mstats.PauseNs[off])
+ }
+ if max < dt {
+ max = dt
+ }
+ if min > dt || i == 0 {
+ min = dt
+ }
+ off = (off + len(mstats.PauseNs) - 1) % len(mstats.PauseNs)
+ }
+ }
+
+ q := stats.PauseQuantiles
+ nq := len(q)
+ if q[0] != min || q[nq-1] != max {
+ t.Errorf("stats.PauseQuantiles = [%d, ..., %d], want [%d, ..., %d]", q[0], q[nq-1], min, max)
+ }
+
+ for i := 0; i < nq-1; i++ {
+ if q[i] > q[i+1] {
+ 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.UnixNano(), mstats.PauseEnd[off])
+ }
+ off = (off + len(mstats.PauseEnd) - 1) % len(mstats.PauseEnd)
+ }
+}
+
+var big []byte
+
+func TestFreeOSMemory(t *testing.T) {
+ // Tests FreeOSMemory by making big susceptible to collection
+ // and checking that at least that much memory is returned to
+ // the OS after.
+
+ const bigBytes = 32 << 20
+ big = make([]byte, bigBytes)
+
+ // Make sure any in-progress GCs are complete.
+ runtime.GC()
+
+ var before runtime.MemStats
+ runtime.ReadMemStats(&before)
+
+ // Clear the last reference to the big allocation, making it
+ // susceptible to collection.
+ big = nil
+
+ // FreeOSMemory runs a GC cycle before releasing memory,
+ // so it's fine to skip a GC here.
+ //
+ // It's possible the background scavenger runs concurrently
+ // with this function and does most of the work for it.
+ // If that happens, it's OK. What we want is a test that fails
+ // often if FreeOSMemory does not work correctly, and a test
+ // that passes every time if it does.
+ FreeOSMemory()
+
+ var after runtime.MemStats
+ runtime.ReadMemStats(&after)
+
+ // Check to make sure that the big allocation (now freed)
+ // had its memory shift into HeapReleased as a result of that
+ // FreeOSMemory.
+ if after.HeapReleased <= before.HeapReleased {
+ t.Fatalf("no memory released: %d -> %d", before.HeapReleased, after.HeapReleased)
+ }
+
+ // Check to make sure bigBytes was released, plus some slack. Pages may get
+ // allocated in between the two measurements above for a variety for reasons,
+ // most commonly for GC work bufs. Since this can get fairly high, depending
+ // on scheduling and what GOMAXPROCS is, give a lot of slack up-front.
+ //
+ // Add a little more slack too if the page size is bigger than the runtime page size.
+ // "big" could end up unaligned on its ends, forcing the scavenger to skip at worst
+ // 2x pages.
+ slack := uint64(bigBytes / 2)
+ pageSize := uint64(os.Getpagesize())
+ if pageSize > 8<<10 {
+ slack += pageSize * 2
+ }
+ if slack > bigBytes {
+ // We basically already checked this.
+ return
+ }
+ if after.HeapReleased-before.HeapReleased < bigBytes-slack {
+ t.Fatalf("less than %d released: %d -> %d", bigBytes-slack, before.HeapReleased, after.HeapReleased)
+ }
+}
+
+var (
+ setGCPercentBallast any
+ setGCPercentSink any
+)
+
+func TestSetGCPercent(t *testing.T) {
+ testenv.SkipFlaky(t, 20076)
+
+ // Test that the variable is being set and returned correctly.
+ old := SetGCPercent(123)
+ new := SetGCPercent(old)
+ if new != 123 {
+ t.Errorf("SetGCPercent(123); SetGCPercent(x) = %d, want 123", new)
+ }
+
+ // Test that the percentage is implemented correctly.
+ defer func() {
+ SetGCPercent(old)
+ setGCPercentBallast, setGCPercentSink = nil, nil
+ }()
+ SetGCPercent(100)
+ runtime.GC()
+ // Create 100 MB of live heap as a baseline.
+ const baseline = 100 << 20
+ var ms runtime.MemStats
+ runtime.ReadMemStats(&ms)
+ setGCPercentBallast = make([]byte, baseline-ms.Alloc)
+ runtime.GC()
+ runtime.ReadMemStats(&ms)
+ if abs64(baseline-int64(ms.Alloc)) > 10<<20 {
+ t.Fatalf("failed to set up baseline live heap; got %d MB, want %d MB", ms.Alloc>>20, baseline>>20)
+ }
+ // NextGC should be ~200 MB.
+ const thresh = 20 << 20 // TODO: Figure out why this is so noisy on some builders
+ if want := int64(2 * baseline); abs64(want-int64(ms.NextGC)) > thresh {
+ t.Errorf("NextGC = %d MB, want %d±%d MB", ms.NextGC>>20, want>>20, thresh>>20)
+ }
+ // Create some garbage, but not enough to trigger another GC.
+ for i := 0; i < int(1.2*baseline); i += 1 << 10 {
+ setGCPercentSink = make([]byte, 1<<10)
+ }
+ setGCPercentSink = nil
+ // Adjust GOGC to 50. NextGC should be ~150 MB.
+ SetGCPercent(50)
+ runtime.ReadMemStats(&ms)
+ if want := int64(1.5 * baseline); abs64(want-int64(ms.NextGC)) > thresh {
+ t.Errorf("NextGC = %d MB, want %d±%d MB", ms.NextGC>>20, want>>20, thresh>>20)
+ }
+
+ // Trigger a GC and get back to 100 MB live with GOGC=100.
+ SetGCPercent(100)
+ runtime.GC()
+ // Raise live to 120 MB.
+ setGCPercentSink = make([]byte, int(0.2*baseline))
+ // Lower GOGC to 10. This must force a GC.
+ runtime.ReadMemStats(&ms)
+ ngc1 := ms.NumGC
+ SetGCPercent(10)
+ // It may require an allocation to actually force the GC.
+ setGCPercentSink = make([]byte, 1<<20)
+ runtime.ReadMemStats(&ms)
+ ngc2 := ms.NumGC
+ if ngc1 == ngc2 {
+ t.Errorf("expected GC to run but it did not")
+ }
+}
+
+func abs64(a int64) int64 {
+ if a < 0 {
+ return -a
+ }
+ return a
+}
+
+func TestSetMaxThreadsOvf(t *testing.T) {
+ // Verify that a big threads count will not overflow the int32
+ // maxmcount variable, causing a panic (see Issue 16076).
+ //
+ // This can only happen when ints are 64 bits, since on platforms
+ // with 32 bit ints SetMaxThreads (which takes an int parameter)
+ // cannot be given anything that will overflow an int32.
+ //
+ // Call SetMaxThreads with 1<<31, but only on 64 bit systems.
+ nt := SetMaxThreads(1 << (30 + ^uint(0)>>63))
+ SetMaxThreads(nt) // restore previous value
+}
diff --git a/src/runtime/debug/heapdump_test.go b/src/runtime/debug/heapdump_test.go
new file mode 100644
index 0000000..ee6b054
--- /dev/null
+++ b/src/runtime/debug/heapdump_test.go
@@ -0,0 +1,95 @@
+// 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 debug_test
+
+import (
+ "os"
+ "runtime"
+ . "runtime/debug"
+ "testing"
+)
+
+func TestWriteHeapDumpNonempty(t *testing.T) {
+ if runtime.GOOS == "js" {
+ t.Skipf("WriteHeapDump is not available on %s.", runtime.GOOS)
+ }
+ f, err := os.CreateTemp("", "heapdumptest")
+ if err != nil {
+ t.Fatalf("TempFile failed: %v", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ WriteHeapDump(f.Fd())
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat failed: %v", err)
+ }
+ const minSize = 1
+ if size := fi.Size(); size < minSize {
+ t.Fatalf("Heap dump size %d bytes, expected at least %d bytes", size, minSize)
+ }
+}
+
+type Obj struct {
+ x, y int
+}
+
+func objfin(x *Obj) {
+ //println("finalized", x)
+}
+
+func TestWriteHeapDumpFinalizers(t *testing.T) {
+ if runtime.GOOS == "js" {
+ t.Skipf("WriteHeapDump is not available on %s.", runtime.GOOS)
+ }
+ f, err := os.CreateTemp("", "heapdumptest")
+ if err != nil {
+ t.Fatalf("TempFile failed: %v", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+
+ // bug 9172: WriteHeapDump couldn't handle more than one finalizer
+ println("allocating objects")
+ x := &Obj{}
+ runtime.SetFinalizer(x, objfin)
+ y := &Obj{}
+ runtime.SetFinalizer(y, objfin)
+
+ // Trigger collection of x and y, queueing of their finalizers.
+ println("starting gc")
+ runtime.GC()
+
+ // Make sure WriteHeapDump doesn't fail with multiple queued finalizers.
+ println("starting dump")
+ WriteHeapDump(f.Fd())
+ println("done dump")
+}
+
+type G[T any] struct{}
+type I interface {
+ M()
+}
+
+//go:noinline
+func (g G[T]) M() {}
+
+var dummy I = G[int]{}
+var dummy2 I = G[G[int]]{}
+
+func TestWriteHeapDumpTypeName(t *testing.T) {
+ if runtime.GOOS == "js" {
+ t.Skipf("WriteHeapDump is not available on %s.", runtime.GOOS)
+ }
+ f, err := os.CreateTemp("", "heapdumptest")
+ if err != nil {
+ t.Fatalf("TempFile failed: %v", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ WriteHeapDump(f.Fd())
+ dummy.M()
+ dummy2.M()
+}
diff --git a/src/runtime/debug/mod.go b/src/runtime/debug/mod.go
new file mode 100644
index 0000000..a470560
--- /dev/null
+++ b/src/runtime/debug/mod.go
@@ -0,0 +1,287 @@
+// Copyright 2018 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 debug
+
+import (
+ "fmt"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+// exported from runtime.
+func modinfo() string
+
+// ReadBuildInfo returns the build information embedded
+// in the running binary. The information is available only
+// in binaries built with module support.
+func ReadBuildInfo() (info *BuildInfo, ok bool) {
+ data := modinfo()
+ if len(data) < 32 {
+ return nil, false
+ }
+ data = data[16 : len(data)-16]
+ bi, err := ParseBuildInfo(data)
+ if err != nil {
+ return nil, false
+ }
+
+ // The go version is stored separately from other build info, mostly for
+ // historical reasons. It is not part of the modinfo() string, and
+ // ParseBuildInfo does not recognize it. We inject it here to hide this
+ // awkwardness from the user.
+ bi.GoVersion = runtime.Version()
+
+ return bi, true
+}
+
+// BuildInfo represents the build information read from a Go binary.
+type BuildInfo struct {
+ // GoVersion is the version of the Go toolchain that built the binary
+ // (for example, "go1.19.2").
+ GoVersion string
+
+ // Path is the package path of the main package for the binary
+ // (for example, "golang.org/x/tools/cmd/stringer").
+ Path string
+
+ // Main describes the module that contains the main package for the binary.
+ Main Module
+
+ // Deps describes all the dependency modules, both direct and indirect,
+ // that contributed packages to the build of this binary.
+ Deps []*Module
+
+ // Settings describes the build settings used to build the binary.
+ Settings []BuildSetting
+}
+
+// A Module describes a single module included in a build.
+type Module struct {
+ Path string // module path
+ Version string // module version
+ Sum string // checksum
+ Replace *Module // replaced by this module
+}
+
+// A BuildSetting is a key-value pair describing one setting that influenced a build.
+//
+// Defined keys include:
+//
+// - -buildmode: the buildmode flag used (typically "exe")
+// - -compiler: the compiler toolchain flag used (typically "gc")
+// - CGO_ENABLED: the effective CGO_ENABLED environment variable
+// - CGO_CFLAGS: the effective CGO_CFLAGS environment variable
+// - CGO_CPPFLAGS: the effective CGO_CPPFLAGS environment variable
+// - CGO_CXXFLAGS: the effective CGO_CXXFLAGS environment variable
+// - CGO_LDFLAGS: the effective CGO_LDFLAGS environment variable
+// - GOARCH: the architecture target
+// - GOAMD64/GOARM/GO386/etc: the architecture feature level for GOARCH
+// - GOOS: the operating system target
+// - vcs: the version control system for the source tree where the build ran
+// - vcs.revision: the revision identifier for the current commit or checkout
+// - vcs.time: the modification time associated with vcs.revision, in RFC3339 format
+// - vcs.modified: true or false indicating whether the source tree had local modifications
+type BuildSetting struct {
+ // Key and Value describe the build setting.
+ // Key must not contain an equals sign, space, tab, or newline.
+ // Value must not contain newlines ('\n').
+ Key, Value string
+}
+
+// quoteKey reports whether key is required to be quoted.
+func quoteKey(key string) bool {
+ return len(key) == 0 || strings.ContainsAny(key, "= \t\r\n\"`")
+}
+
+// quoteValue reports whether value is required to be quoted.
+func quoteValue(value string) bool {
+ return strings.ContainsAny(value, " \t\r\n\"`")
+}
+
+func (bi *BuildInfo) String() string {
+ buf := new(strings.Builder)
+ if bi.GoVersion != "" {
+ fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion)
+ }
+ if bi.Path != "" {
+ fmt.Fprintf(buf, "path\t%s\n", bi.Path)
+ }
+ var formatMod func(string, Module)
+ formatMod = func(word string, m Module) {
+ buf.WriteString(word)
+ buf.WriteByte('\t')
+ buf.WriteString(m.Path)
+ buf.WriteByte('\t')
+ buf.WriteString(m.Version)
+ if m.Replace == nil {
+ buf.WriteByte('\t')
+ buf.WriteString(m.Sum)
+ } else {
+ buf.WriteByte('\n')
+ formatMod("=>", *m.Replace)
+ }
+ buf.WriteByte('\n')
+ }
+ if bi.Main != (Module{}) {
+ formatMod("mod", bi.Main)
+ }
+ for _, dep := range bi.Deps {
+ formatMod("dep", *dep)
+ }
+ for _, s := range bi.Settings {
+ key := s.Key
+ if quoteKey(key) {
+ key = strconv.Quote(key)
+ }
+ value := s.Value
+ if quoteValue(value) {
+ value = strconv.Quote(value)
+ }
+ fmt.Fprintf(buf, "build\t%s=%s\n", key, value)
+ }
+
+ return buf.String()
+}
+
+func ParseBuildInfo(data string) (bi *BuildInfo, err error) {
+ lineNum := 1
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not parse Go build info: line %d: %w", lineNum, err)
+ }
+ }()
+
+ var (
+ pathLine = "path\t"
+ modLine = "mod\t"
+ depLine = "dep\t"
+ repLine = "=>\t"
+ buildLine = "build\t"
+ newline = "\n"
+ tab = "\t"
+ )
+
+ readModuleLine := func(elem []string) (Module, error) {
+ if len(elem) != 2 && len(elem) != 3 {
+ return Module{}, fmt.Errorf("expected 2 or 3 columns; got %d", len(elem))
+ }
+ version := elem[1]
+ sum := ""
+ if len(elem) == 3 {
+ sum = elem[2]
+ }
+ return Module{
+ Path: elem[0],
+ Version: version,
+ Sum: sum,
+ }, nil
+ }
+
+ bi = new(BuildInfo)
+ var (
+ last *Module
+ line string
+ ok bool
+ )
+ // Reverse of BuildInfo.String(), except for go version.
+ for len(data) > 0 {
+ line, data, ok = strings.Cut(data, newline)
+ if !ok {
+ break
+ }
+ switch {
+ case strings.HasPrefix(line, pathLine):
+ elem := line[len(pathLine):]
+ bi.Path = string(elem)
+ case strings.HasPrefix(line, modLine):
+ elem := strings.Split(line[len(modLine):], tab)
+ last = &bi.Main
+ *last, err = readModuleLine(elem)
+ if err != nil {
+ return nil, err
+ }
+ case strings.HasPrefix(line, depLine):
+ elem := strings.Split(line[len(depLine):], tab)
+ last = new(Module)
+ bi.Deps = append(bi.Deps, last)
+ *last, err = readModuleLine(elem)
+ if err != nil {
+ return nil, err
+ }
+ case strings.HasPrefix(line, repLine):
+ elem := strings.Split(line[len(repLine):], tab)
+ if len(elem) != 3 {
+ return nil, fmt.Errorf("expected 3 columns for replacement; got %d", len(elem))
+ }
+ if last == nil {
+ return nil, fmt.Errorf("replacement with no module on previous line")
+ }
+ last.Replace = &Module{
+ Path: string(elem[0]),
+ Version: string(elem[1]),
+ Sum: string(elem[2]),
+ }
+ last = nil
+ case strings.HasPrefix(line, buildLine):
+ kv := line[len(buildLine):]
+ if len(kv) < 1 {
+ return nil, fmt.Errorf("build line missing '='")
+ }
+
+ var key, rawValue string
+ switch kv[0] {
+ case '=':
+ return nil, fmt.Errorf("build line with missing key")
+
+ case '`', '"':
+ rawKey, err := strconv.QuotedPrefix(kv)
+ if err != nil {
+ return nil, fmt.Errorf("invalid quoted key in build line")
+ }
+ if len(kv) == len(rawKey) {
+ return nil, fmt.Errorf("build line missing '=' after quoted key")
+ }
+ if c := kv[len(rawKey)]; c != '=' {
+ return nil, fmt.Errorf("unexpected character after quoted key: %q", c)
+ }
+ key, _ = strconv.Unquote(rawKey)
+ rawValue = kv[len(rawKey)+1:]
+
+ default:
+ var ok bool
+ key, rawValue, ok = strings.Cut(kv, "=")
+ if !ok {
+ return nil, fmt.Errorf("build line missing '=' after key")
+ }
+ if quoteKey(key) {
+ return nil, fmt.Errorf("unquoted key %q must be quoted", key)
+ }
+ }
+
+ var value string
+ if len(rawValue) > 0 {
+ switch rawValue[0] {
+ case '`', '"':
+ var err error
+ value, err = strconv.Unquote(rawValue)
+ if err != nil {
+ return nil, fmt.Errorf("invalid quoted value in build line")
+ }
+
+ default:
+ value = rawValue
+ if quoteValue(value) {
+ return nil, fmt.Errorf("unquoted value %q must be quoted", value)
+ }
+ }
+ }
+
+ bi.Settings = append(bi.Settings, BuildSetting{Key: key, Value: value})
+ }
+ lineNum++
+ }
+ return bi, nil
+}
diff --git a/src/runtime/debug/mod_test.go b/src/runtime/debug/mod_test.go
new file mode 100644
index 0000000..b291769
--- /dev/null
+++ b/src/runtime/debug/mod_test.go
@@ -0,0 +1,75 @@
+// Copyright 2022 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 debug_test
+
+import (
+ "reflect"
+ "runtime/debug"
+ "strings"
+ "testing"
+)
+
+// strip removes two leading tabs after each newline of s.
+func strip(s string) string {
+ replaced := strings.ReplaceAll(s, "\n\t\t", "\n")
+ if len(replaced) > 0 && replaced[0] == '\n' {
+ replaced = replaced[1:]
+ }
+ return replaced
+}
+
+func FuzzParseBuildInfoRoundTrip(f *testing.F) {
+ // Package built from outside a module, missing some fields..
+ f.Add(strip(`
+ path rsc.io/fortune
+ mod rsc.io/fortune v1.0.0
+ `))
+
+ // Package built from the standard library, missing some fields..
+ f.Add(`path cmd/test2json`)
+
+ // Package built from inside a module.
+ f.Add(strip(`
+ go 1.18
+ path example.com/m
+ mod example.com/m (devel)
+ build -compiler=gc
+ `))
+
+ // Package built in GOPATH mode.
+ f.Add(strip(`
+ go 1.18
+ path example.com/m
+ build -compiler=gc
+ `))
+
+ // Escaped build info.
+ f.Add(strip(`
+ go 1.18
+ path example.com/m
+ build CRAZY_ENV="requires\nescaping"
+ `))
+
+ f.Fuzz(func(t *testing.T, s string) {
+ bi, err := debug.ParseBuildInfo(s)
+ if err != nil {
+ // Not a round-trippable BuildInfo string.
+ t.Log(err)
+ return
+ }
+
+ // s2 could have different escaping from s.
+ // However, it should parse to exactly the same contents.
+ s2 := bi.String()
+ bi2, err := debug.ParseBuildInfo(s2)
+ if err != nil {
+ t.Fatalf("%v:\n%s", err, s2)
+ }
+
+ if !reflect.DeepEqual(bi2, bi) {
+ t.Fatalf("Parsed representation differs.\ninput:\n%s\noutput:\n%s", s, s2)
+ }
+ })
+}
diff --git a/src/runtime/debug/panic_test.go b/src/runtime/debug/panic_test.go
new file mode 100644
index 0000000..ec5294c
--- /dev/null
+++ b/src/runtime/debug/panic_test.go
@@ -0,0 +1,56 @@
+// Copyright 2020 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.
+
+//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
+
+// TODO: test on Windows?
+
+package debug_test
+
+import (
+ "runtime"
+ "runtime/debug"
+ "syscall"
+ "testing"
+ "unsafe"
+)
+
+func TestPanicOnFault(t *testing.T) {
+ if runtime.GOARCH == "s390x" {
+ t.Skip("s390x fault addresses are missing the low order bits")
+ }
+ if runtime.GOOS == "ios" {
+ t.Skip("iOS doesn't provide fault addresses")
+ }
+ if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm" {
+ t.Skip("netbsd-arm doesn't provide fault address (golang.org/issue/45026)")
+ }
+ m, err := syscall.Mmap(-1, 0, 0x1000, syscall.PROT_READ /* Note: no PROT_WRITE */, syscall.MAP_SHARED|syscall.MAP_ANON)
+ if err != nil {
+ t.Fatalf("can't map anonymous memory: %s", err)
+ }
+ defer syscall.Munmap(m)
+ old := debug.SetPanicOnFault(true)
+ defer debug.SetPanicOnFault(old)
+ const lowBits = 0x3e7
+ defer func() {
+ r := recover()
+ if r == nil {
+ t.Fatalf("write did not fault")
+ }
+ type addressable interface {
+ Addr() uintptr
+ }
+ a, ok := r.(addressable)
+ if !ok {
+ t.Fatalf("fault does not contain address")
+ }
+ want := uintptr(unsafe.Pointer(&m[lowBits]))
+ got := a.Addr()
+ if got != want {
+ t.Fatalf("fault address %x, want %x", got, want)
+ }
+ }()
+ m[lowBits] = 1 // will fault
+}
diff --git a/src/runtime/debug/stack.go b/src/runtime/debug/stack.go
new file mode 100644
index 0000000..3999840
--- /dev/null
+++ b/src/runtime/debug/stack.go
@@ -0,0 +1,30 @@
+// Copyright 2011 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 debug contains facilities for programs to debug themselves while
+// they are running.
+package debug
+
+import (
+ "os"
+ "runtime"
+)
+
+// PrintStack prints to standard error the stack trace returned by runtime.Stack.
+func PrintStack() {
+ os.Stderr.Write(Stack())
+}
+
+// Stack returns a formatted stack trace of the goroutine that calls it.
+// It calls [runtime.Stack] with a large enough buffer to capture the entire trace.
+func Stack() []byte {
+ buf := make([]byte, 1024)
+ for {
+ n := runtime.Stack(buf, false)
+ if n < len(buf) {
+ return buf[:n]
+ }
+ buf = make([]byte, 2*len(buf))
+ }
+}
diff --git a/src/runtime/debug/stack_test.go b/src/runtime/debug/stack_test.go
new file mode 100644
index 0000000..671057c
--- /dev/null
+++ b/src/runtime/debug/stack_test.go
@@ -0,0 +1,121 @@
+// Copyright 2011 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 debug_test
+
+import (
+ "bytes"
+ "fmt"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ . "runtime/debug"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ if os.Getenv("GO_RUNTIME_DEBUG_TEST_DUMP_GOROOT") != "" {
+ fmt.Println(runtime.GOROOT())
+ os.Exit(0)
+ }
+ os.Exit(m.Run())
+}
+
+type T int
+
+func (t *T) ptrmethod() []byte {
+ return Stack()
+}
+func (t T) method() []byte {
+ return t.ptrmethod()
+}
+
+/*
+The traceback should look something like this, modulo line numbers and hex constants.
+Don't worry much about the base levels, but check the ones in our own package.
+
+ goroutine 10 [running]:
+ runtime/debug.Stack(0x0, 0x0, 0x0)
+ /Users/r/go/src/runtime/debug/stack.go:28 +0x80
+ runtime/debug.(*T).ptrmethod(0xc82005ee70, 0x0, 0x0, 0x0)
+ /Users/r/go/src/runtime/debug/stack_test.go:15 +0x29
+ runtime/debug.T.method(0x0, 0x0, 0x0, 0x0)
+ /Users/r/go/src/runtime/debug/stack_test.go:18 +0x32
+ runtime/debug.TestStack(0xc8201ce000)
+ /Users/r/go/src/runtime/debug/stack_test.go:37 +0x38
+ testing.tRunner(0xc8201ce000, 0x664b58)
+ /Users/r/go/src/testing/testing.go:456 +0x98
+ created by testing.RunTests
+ /Users/r/go/src/testing/testing.go:561 +0x86d
+*/
+func TestStack(t *testing.T) {
+ b := T(0).method()
+ lines := strings.Split(string(b), "\n")
+ if len(lines) < 6 {
+ t.Fatal("too few lines")
+ }
+
+ // If built with -trimpath, file locations should start with package paths.
+ // Otherwise, file locations should start with a GOROOT/src prefix
+ // (for whatever value of GOROOT is baked into the binary, not the one
+ // that may be set in the environment).
+ fileGoroot := ""
+ if envGoroot := os.Getenv("GOROOT"); envGoroot != "" {
+ // Since GOROOT is set explicitly in the environment, we can't be certain
+ // that it is the same GOROOT value baked into the binary, and we can't
+ // change the value in-process because runtime.GOROOT uses the value from
+ // initial (not current) environment. Spawn a subprocess to determine the
+ // real baked-in GOROOT.
+ t.Logf("found GOROOT %q from environment; checking embedded GOROOT value", envGoroot)
+ testenv.MustHaveExec(t)
+ exe, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ }
+ cmd := exec.Command(exe)
+ cmd.Env = append(os.Environ(), "GOROOT=", "GO_RUNTIME_DEBUG_TEST_DUMP_GOROOT=1")
+ out, err := cmd.Output()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fileGoroot = string(bytes.TrimSpace(out))
+ } else {
+ // Since GOROOT is not set in the environment, its value (if any) must come
+ // from the path embedded in the binary.
+ fileGoroot = runtime.GOROOT()
+ }
+ filePrefix := ""
+ if fileGoroot != "" {
+ filePrefix = filepath.ToSlash(fileGoroot) + "/src/"
+ }
+
+ n := 0
+ frame := func(file, code string) {
+ t.Helper()
+
+ line := lines[n]
+ if !strings.Contains(line, code) {
+ t.Errorf("expected %q in %q", code, line)
+ }
+ n++
+
+ line = lines[n]
+
+ wantPrefix := "\t" + filePrefix + file
+ if !strings.HasPrefix(line, wantPrefix) {
+ t.Errorf("in line %q, expected prefix %q", line, wantPrefix)
+ }
+ n++
+ }
+ n++
+
+ frame("runtime/debug/stack.go", "runtime/debug.Stack")
+ frame("runtime/debug/stack_test.go", "runtime/debug_test.(*T).ptrmethod")
+ frame("runtime/debug/stack_test.go", "runtime/debug_test.T.method")
+ frame("runtime/debug/stack_test.go", "runtime/debug_test.TestStack")
+ frame("testing/testing.go", "")
+}
diff --git a/src/runtime/debug/stubs.go b/src/runtime/debug/stubs.go
new file mode 100644
index 0000000..913d4b9
--- /dev/null
+++ b/src/runtime/debug/stubs.go
@@ -0,0 +1,18 @@
+// 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 debug
+
+import (
+ "time"
+)
+
+// Implemented in package runtime.
+func readGCStats(*[]time.Duration)
+func freeOSMemory()
+func setMaxStack(int) int
+func setGCPercent(int32) int32
+func setPanicOnFault(bool) bool
+func setMaxThreads(int) int
+func setMemoryLimit(int64) int64
diff --git a/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/5501685e611fa764 b/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/5501685e611fa764
new file mode 100644
index 0000000..4ab5d92
--- /dev/null
+++ b/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/5501685e611fa764
@@ -0,0 +1,2 @@
+go test fuzz v1
+string("mod\t\t0\n")
diff --git a/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/71634114e78567cf b/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/71634114e78567cf
new file mode 100644
index 0000000..741c4df
--- /dev/null
+++ b/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/71634114e78567cf
@@ -0,0 +1,2 @@
+go test fuzz v1
+string("mod\t0\t\n")
diff --git a/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/c73dce23c1f2494c b/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/c73dce23c1f2494c
new file mode 100644
index 0000000..60f9338
--- /dev/null
+++ b/src/runtime/debug/testdata/fuzz/FuzzParseBuildInfoRoundTrip/c73dce23c1f2494c
@@ -0,0 +1,2 @@
+go test fuzz v1
+string("build\t0=\" 0\"\n")