]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.25] runtime: initialise debug settings much earlier in startup...
authorSteve Muir <sjmuir@google.com>
Thu, 18 Sep 2025 14:54:57 +0000 (07:54 -0700)
committerCarlos Amedee <carlos@golang.org>
Wed, 1 Oct 2025 16:39:42 +0000 (09:39 -0700)
This is necessary specifically to set the value of `debug.decoratemappings`
sufficiently early in the startup sequence that all memory ranges allocated
can be named appropriately using the new Linux-specific naming API
introduced in #71546.

Example output (on ARM64):
https://gist.github.com/9muir/3667654b9c3f52e8be92756219371672

For: #75324
Fixes #75669

Change-Id: Ic0b16233f54a45adef1660c4d0df59af2f5af86a
Reviewed-on: https://go-review.googlesource.com/c/go/+/703476
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
(cherry picked from commit 300d9d2714164e455abc7990d52de9de6b084df1)
Reviewed-on: https://go-review.googlesource.com/c/go/+/708359
Reviewed-by: Cherry Mui <cherryyz@google.com>
src/runtime/decoratemappings_test.go [new file with mode: 0644]
src/runtime/export_test.go
src/runtime/proc.go
src/runtime/runtime1.go
src/runtime/set_vma_name_linux.go
src/runtime/set_vma_name_stub.go

diff --git a/src/runtime/decoratemappings_test.go b/src/runtime/decoratemappings_test.go
new file mode 100644 (file)
index 0000000..7d1121c
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2025 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 runtime_test
+
+import (
+       "os"
+       "regexp"
+       "runtime"
+       "testing"
+)
+
+func validateMapLabels(t *testing.T, labels []string) {
+       // These are the specific region labels that need get added during the
+       // runtime phase. Hence they are the ones that need to be confirmed as
+       // present at the time the test reads its own region labels, which
+       // is sufficient to validate that the default `decoratemappings` value
+       // (enabled) was set early enough in the init process.
+       regions := map[string]bool{
+               "allspans array":    false,
+               "gc bits":           false,
+               "heap":              false,
+               "heap index":        false,
+               "heap reservation":  false,
+               "immortal metadata": false,
+               "page alloc":        false,
+               "page alloc index":  false,
+               "page summary":      false,
+               "scavenge index":    false,
+       }
+       for _, label := range labels {
+               if _, ok := regions[label]; !ok {
+                       t.Logf("unexpected region label found: \"%s\"", label)
+               }
+               regions[label] = true
+       }
+       for label, found := range regions {
+               if !found {
+                       t.Logf("region label missing: \"%s\"", label)
+               }
+       }
+}
+
+func TestDecorateMappings(t *testing.T) {
+       if runtime.GOOS != "linux" {
+               t.Skip("decoratemappings is only supported on Linux")
+               // /proc/self/maps is also Linux-specific
+       }
+
+       var labels []string
+       if rawMaps, err := os.ReadFile("/proc/self/maps"); err != nil {
+               t.Fatalf("failed to read /proc/self/maps: %v", err)
+       } else {
+               t.Logf("maps:%s\n", string(rawMaps))
+               matches := regexp.MustCompile("[^[]+ \\[anon: Go: (.+)\\]\n").FindAllSubmatch(rawMaps, -1)
+               for _, match_pair := range matches {
+                       // match_pair consists of the matching substring and the parenthesized group
+                       labels = append(labels, string(match_pair[1]))
+               }
+       }
+       t.Logf("DebugDecorateMappings: %v", *runtime.DebugDecorateMappings)
+       if *runtime.DebugDecorateMappings != 0 && runtime.SetVMANameSupported() {
+               validateMapLabels(t, labels)
+       } else {
+               if len(labels) > 0 {
+                       t.Errorf("unexpected mapping labels present: %v", labels)
+               } else {
+                       t.Skip("mapping labels absent as expected")
+               }
+       }
+}
index 9a4611e26e52a254ffe134e0548c25a0f71de02d..6559ecec67bbde45a90215ffdca57498f7073cd2 100644 (file)
@@ -1927,3 +1927,7 @@ func (t *TraceStackTable) Reset() {
 func TraceStack(gp *G, tab *TraceStackTable) {
        traceStack(0, gp, (*traceStackTable)(tab))
 }
+
+var DebugDecorateMappings = &debug.decoratemappings
+
+func SetVMANameSupported() bool { return setVMANameSupported() }
index b41bbe93cf57c755274bc4b18c47e875d8635da4..d898c9668b36ddaac19a1000b0b820b0dc4cd1f8 100644 (file)
@@ -789,7 +789,9 @@ func cpuinit(env string) {
 // getGodebugEarly extracts the environment variable GODEBUG from the environment on
 // Unix-like operating systems and returns it. This function exists to extract GODEBUG
 // early before much of the runtime is initialized.
-func getGodebugEarly() string {
+//
+// Returns nil, false if OS doesn't provide env vars early in the init sequence.
+func getGodebugEarly() (string, bool) {
        const prefix = "GODEBUG="
        var env string
        switch GOOS {
@@ -807,12 +809,16 @@ func getGodebugEarly() string {
                        s := unsafe.String(p, findnull(p))
 
                        if stringslite.HasPrefix(s, prefix) {
-                               env = gostring(p)[len(prefix):]
+                               env = gostringnocopy(p)[len(prefix):]
                                break
                        }
                }
+               break
+
+       default:
+               return "", false
        }
-       return env
+       return env, true
 }
 
 // The bootstrap sequence is:
@@ -859,11 +865,14 @@ func schedinit() {
        // The world starts stopped.
        worldStopped()
 
+       godebug, parsedGodebug := getGodebugEarly()
+       if parsedGodebug {
+               parseRuntimeDebugVars(godebug)
+       }
        ticks.init() // run as early as possible
        moduledataverify()
        stackinit()
        mallocinit()
-       godebug := getGodebugEarly()
        cpuinit(godebug) // must run before alginit
        randinit()       // must run before alginit, mcommoninit
        alginit()        // maps, hash, rand must not be used before this call
@@ -880,7 +889,12 @@ func schedinit() {
        goenvs()
        secure()
        checkfds()
-       parsedebugvars()
+       if !parsedGodebug {
+               // Some platforms, e.g., Windows, didn't make env vars available "early",
+               // so try again now.
+               parseRuntimeDebugVars(gogetenv("GODEBUG"))
+       }
+       finishDebugVarsSetup()
        gcinit()
 
        // Allocate stack space that can be used when crashing due to bad stack
index 424745d2357dc97831d4523ec6b580978c216c58..15b546783b5e53cdacd16be9e7540ffcedd5c6ca 100644 (file)
@@ -402,7 +402,7 @@ var dbgvars = []*dbgVar{
        {name: "updatemaxprocs", value: &debug.updatemaxprocs, def: 1},
 }
 
-func parsedebugvars() {
+func parseRuntimeDebugVars(godebug string) {
        // defaults
        debug.cgocheck = 1
        debug.invalidptr = 1
@@ -420,12 +420,6 @@ func parsedebugvars() {
        }
        debug.traceadvanceperiod = defaultTraceAdvancePeriod
 
-       godebug := gogetenv("GODEBUG")
-
-       p := new(string)
-       *p = godebug
-       godebugEnv.Store(p)
-
        // apply runtime defaults, if any
        for _, v := range dbgvars {
                if v.def != 0 {
@@ -437,7 +431,6 @@ func parsedebugvars() {
                        }
                }
        }
-
        // apply compile-time GODEBUG settings
        parsegodebug(godebugDefault, nil)
 
@@ -463,6 +456,12 @@ func parsedebugvars() {
        if debug.gccheckmark > 0 {
                debug.asyncpreemptoff = 1
        }
+}
+
+func finishDebugVarsSetup() {
+       p := new(string)
+       *p = gogetenv("GODEBUG")
+       godebugEnv.Store(p)
 
        setTraceback(gogetenv("GOTRACEBACK"))
        traceback_env = traceback_cache
index 100c2bfecac60c90c089c91eb2adabcc723927fc..792d6ad34d9102daca8b3d317e79bfd69da0de7b 100644 (file)
@@ -14,9 +14,13 @@ import (
 
 var prSetVMAUnsupported atomic.Bool
 
+func setVMANameSupported() bool {
+       return !prSetVMAUnsupported.Load()
+}
+
 // setVMAName calls prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, start, len, name)
 func setVMAName(start unsafe.Pointer, length uintptr, name string) {
-       if debug.decoratemappings == 0 || prSetVMAUnsupported.Load() {
+       if debug.decoratemappings == 0 || !setVMANameSupported() {
                return
        }
 
index 38f65fd592ba347c2ee3354000d538ff25c36930..6cb01ebf50dcefc3f6d61f184d68ac5167c933bc 100644 (file)
@@ -10,3 +10,5 @@ import "unsafe"
 
 // setVMAName isn’t implemented
 func setVMAName(start unsafe.Pointer, len uintptr, name string) {}
+
+func setVMANameSupported() bool { return false }