On my Mac, if I run go test -cpuprofile=x.prof anything, and then go tool pprof x.prof, then the disasm command in pprof does not work, for any function at all:
% go test -cpuprofile=x.prof strings
ok strings 1.059s
% go tool pprof x.prof
Type: cpu
Time: Jan 28, 2022 at 2:06pm (EST)
Duration: 805.01ms, Total samples = 540ms (67.08%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top10
Showing nodes accounting for 540ms, 100% of 540ms total
Showing top 10 nodes out of 27
flat flat% sum% cum cum%
210ms 38.89% 38.89% 210ms 38.89% cmpbody
170ms 31.48% 70.37% 170ms 31.48% memeqbody
50ms 9.26% 79.63% 50ms 9.26% unicode.to
50ms 9.26% 88.89% 50ms 9.26% unicode/utf8.EncodeRune
30ms 5.56% 94.44% 140ms 25.93% strings.Map
10ms 1.85% 96.30% 10ms 1.85% runtime.decoderune
10ms 1.85% 98.15% 10ms 1.85% runtime.madvise
10ms 1.85% 100% 390ms 72.22% strings_test.TestCompareStrings
0 0% 100% 10ms 1.85% runtime.(*mcache).allocLarge
0 0% 100% 10ms 1.85% runtime.(*mheap).alloc
(pprof) disasm cmpbody
no matches found for regexp: cmpbody
(pprof) disasm unicode.to
no matches found for regexp: unicode.to
(pprof) disasm .*
no matches found for regexp: .*
(pprof)
I have not checked whether this is Mac-specific.
Comment From: ALTree
go test -cpuprofile=x.prof strings
will leave a strings.test
binary in the working directory. If pprof is invoked as go tool pprof x.prof
, disasm
fails because it's looking for the binary in /tmp/, and it's not there:
(pprof) disasm cmpbody
open /tmp/go-build477592094/b001/strings.test: no such file or directory
no matches found for regexp: cmpbody
but if the binary (which is in wd) is explicitly passed as go tool pprof strings.test x.prof
, then disasm
works correctly.
FWIW, I see the same behaviour with go1.17.5 (above tests are from tip).
(This is on Linux).
Comment From: ianlancetaylor
Agreed, as far as I can tell this has never worked.
This works (on linux-amd64):
go test -c strings
./strings.test -test.cpuprofile=x.prof
go tool pprof x.prof
disasm cmpbody
Removing the release-blocker label.
Comment From: cespare
I stub my toe on this UX about every other time I use pprof.
Comment From: cherrymui
It seems the go command first runs the binary in the tmp directory, collects the profile, then moves it to the current directory. The file path recorded is the one in the tmp directory.
$WORK/b001/strings.test -test.paniconexit0 -test.timeout=10m0s -test.outputdir=/tmp -test.cpuprofile=x.prof
mkdir -p /tmp/
mv $WORK/b001/strings.test /tmp/strings.test
Perhaps the go command can move the file first then run it?
Another part is that it seems the file names recorded in the profile are from profile.Mapping, which is from reading /proc/self/maps, which probably works only on Linux. So on macOS we may need to always pass the executable file name to pprof, even if the go command does the mv first.
Comment From: rsc
Thanks. Indeed, not a release blocker. It should be easy to run the final executable instead of the one from /tmp to fix Linux. For the Mac we should be able to record os.Executable, which might be sufficient. All good things for a future release.
Comment From: fumin
Getting the same problem on Windows:
PS C:\Users\a3367\work\misc\seg> go run .\cmd\vizccl\main.go
2022/03/29 13:28:17.416938 C:/Users/a3367/work/misc/seg/cmd/vizccl/main.go:45: png write start output\vizqqq.png
2022/03/29 13:28:48.685875 C:/Users/a3367/work/misc/seg/cmd/vizccl/main.go:55: png write end output\vizqqq.png
PS C:\Users\a3367\work\misc\seg>
PS C:\Users\a3367\work\misc\seg\output> go tool pprof .\png_encode.pprof
Type: cpu
Time: Mar 29, 2022 at 1:28pm (CST)
Duration: 31.43s, Total samples = 17.53s (55.77%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 16.92s, 96.52% of 17.53s total
Dropped 91 nodes (cum <= 0.09s)
Showing top 10 nodes out of 29
flat flat% sum% cum cum%
7.96s 45.41% 45.41% 13.07s 74.56% image/png.filter
2s 11.41% 56.82% 2.01s 11.47% image/png.abs
1.74s 9.93% 66.74% 1.74s 9.93% runtime.memmove
1.56s 8.90% 75.64% 1.56s 8.90% image/png.abs8
1.44s 8.21% 83.86% 3.46s 19.74% image/png.paeth
0.90s 5.13% 88.99% 0.90s 5.13% hash/adler32.update
0.80s 4.56% 93.55% 0.81s 4.62% compress/flate.(*deflateFast).matchLen
0.29s 1.65% 95.21% 0.31s 1.77% compress/flate.(*huffmanEncoder).bitCounts
0.12s 0.68% 95.89% 0.12s 0.68% runtime.asyncPreempt
0.11s 0.63% 96.52% 1.04s 5.93% compress/flate.(*deflateFast).encode
(pprof) disasm png.abs8
Total: 17.53s
(pprof)
$ go version go version go1.17.6 windows/amd64
go env
Output
$ go env set GO111MODULE= set GOARCH=amd64 set GOBIN= set GOCACHE=C:\Users\a3367\AppData\Local\go-build set GOENV=C:\Users\a3367\AppData\Roaming\go\env set GOEXE=.exe set GOEXPERIMENT= set GOFLAGS= set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOINSECURE= set GOMODCACHE=C:\Users\a3367\go\pkg\mod set GONOPROXY= set GONOSUMDB= set GOOS=windows set GOPATH=C:\Users\a3367\go set GOPRIVATE= set GOPROXY=https://proxy.golang.org,direct set GOROOT=C:\Program Files\Go set GOSUMDB=sum.golang.org set GOTMPDIR= set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64 set GOVCS= set GOVERSION=go1.17.6 set GCCGO=gccgo set AR=ar set CC=gcc set CXX=g++ set CGO_ENABLED=1 set GOMOD=NUL set CGO_CFLAGS=-g -O2 set CGO_CPPFLAGS= set CGO_CXXFLAGS=-g -O2 set CGO_FFLAGS=-g -O2 set CGO_LDFLAGS=-g -O2 set PKG_CONFIG=pkg-config set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\a3367\AppData\Local\Temp\go-build2334633044=/tmp/go-build -gno-record-gcc-switches gdb --version: GNU gdb (GDB) 8.1
Comment From: fangsm26
The interesting thing is that if you cross-compile an amd64 version of go on an m1 mac, and then use this go to test and profile, you can successfully execute disasm in pprof.
Comment From: erifan
Any update on this issue ? Given that gdb and perf tool are not available on m1 mac, pprof has become my first choice for debugging on m1 mac, and the ineffectiveness of disasm has caused great inconvenience.
Comment From: AlexanderYastrebov
but if the binary (which is in wd) is explicitly passed as go tool pprof strings.test x.prof, then disasm works correctly.
It also works after second run of go test -cpuprofile=x.prof strings
because build uses existing strings.test binary and new x.prof contains path to it.
Comment From: cosnicolaou
I've done a bit of digging into this since I distinctly recall this working circa 2016-17. In short, this did work before a change to pprof that broke things initially for intel (2018 onward) and then for arm when it was introduced in 2020. Fixing pprof is easy and this fixes intel, but not arm macs. For arm, the pc values available within a running process differ from those in the executable's symbol table; this only happens for apple silicon systems and not for other arm (linux/raspberry pi) systems. The symbol table values are "correct" in the sense that objdump displays correct looking assembly code at those locations but are modified, I presume by dyld, when the executable is loaded into memory. In addition, the values change every time the process is run, suggesting that dyld is non-deterministic, which if correct, kinda sucks.
For pprof, https://github.com/google/pprof/pull/313 introduced a check for 'textSegment.Addr > start' which will always fail since the text segments start at 0x100000000. The error occurs when testing for the first argument to pprof being an executable at startup (but the error is ignored there) and when attempting to load the executable when disassembly is requested. Again, no error is displayed. Fixing this fixes the problem for intel based macs, but not for arm based ones and explains why I recall this working correctly before 2018 or so. I am puzzled as to how some of the earlier notes on this thread state that the problem was the executable being in an inaccessible location.
For the eg.go shown below, here's the output on an arm mac and an intel one:
apple silicon - the values change every time it is run.
❯ go build -o eg eg.go
❯ ./eg
[100aa20e0 100aa20c9 100a49030 100a730e4]
100aa20df runtime.Callers
100aa20c8 main.main
100a4902f runtime.main
❯ nm eg | grep main.main
000000010008a090 t _main.main
intel:
❯ go build -o eg eg.go
❯ ~ ./eg
[108ac55 108ac43 10317d2 105a521]
108ac54 runtime.Callers
108ac42 main.main
10317d1 runtime.main
❯ ~ nm eg | grep main.main
000000000108ac00 t _main.main
arm64 on a linux raspberry pi:
./eg
[8ea50 8ea39 41bb0 6b474]
8ea4f runtime.Callers
8ea38 main.main
41baf runtime.main
cnicolaou@pa-dnsmasq-2:~$ nm eg | grep main.main
000000000008ea00 T main.main
package main
import (
"fmt"
"runtime"
)
func main() {
pcs := make([]uintptr, 100)
n := runtime.Callers(0, pcs)
pcs = pcs[:n]
fmt.Printf("%x\n", pcs)
frames := runtime.CallersFrames(pcs[:n])
for {
frame, more := frames.Next()
if !more {
break
}
fmt.Printf("%x %s\n", frame.PC, frame.Function)
}
}
Comment From: cosnicolaou
oh, it seems that src/runtime/pprof/proto_other.go's implementation of readMapping works only on linux - i.e. it reads /proc/self/maps which doesn't exist on macos. Maybe that should be using vmmap, or the underlying APIs that it uses?
Comment From: cosnicolaou
And sure enough, a quick hack (that runs vmmap) shows that this, at least for one test case, works. Ideally readMappings should use mach_vm_region rather than vmmap. With this in place, another issue with pprof shows up whereby listing source code via the webui (not the cli list command) breaks. This has not worked for so long now that other bugs have crept in.
Comment From: cosnicolaou
See https://go-review.googlesource.com/c/go/+/498375 for a proposed fix.
Comment From: cherrymui
@cosnicolaou thanks for the CL! As you noted, it is better to use the mach_vm_region
API if possible. I was actually looking into this a while ago, made some progress but hasn't finished. Would you like to give that a try? Otherwise I can resume and try that. Thanks.
Comment From: cosnicolaou
If you give me some pointers as to how you'd like the new system call added to the runtime I can give it a try. I'm reasonably confident it will work since I already wrote a stand-alone C program to get the appropriate information, assuming you're ok with the assumption that the text section we care about is the first/lowest address? I didn't prototype any means of getting the name of the binary associated with a segment.
Comment From: cherrymui
https://cs.opensource.google/go/go/+/master:src/runtime/sys_darwin.go this contains the syscall code. Basically, it needs a Go function like https://cs.opensource.google/go/go/+/master:src/runtime/sys_darwin.go;l=123 , an import pragma like https://cs.opensource.google/go/go/+/master:src/runtime/sys_darwin.go;l=123 , and an assembly trampoline like https://cs.opensource.google/go/go/+/master:src/runtime/sys_darwin_amd64.s;l=415.
These are defined in the runtime package. You'll need to export them to the runtime/pprof package. You could do something similar to what we did for crypto/x509: https://cs.opensource.google/go/go/+/master:src/runtime/sys_darwin.go;l=105 and https://cs.opensource.google/go/go/+/master:src/crypto/x509/internal/macos/corefoundation.go;l=62
Yeah, I tried a C program, too, but didn't get a chance to port it to the Go runtime :)
assuming you're ok with the assumption that the text section we care about is the first/lowest address?
Yes, I think that should be fine. In fact I'm not sure we actually need this assumption. I think you can look for just the executable mappings, matching with the executable segments from the binary. For disassembly we only need executable segments anyway.
Comment From: cosnicolaou
I've spent a little time looking at this, and of course, have some questions!
1. I need access to the various C types required by mach_vm_info. I see defs_darwin.go as being the place to access these, but the comments therein about using cgo -cdefs are out of date since cdefs no longer exists so far as I can tell. Is the current practice to run cgo --godefs and then to manually edit the defs_darwin_
Comment From: ianlancetaylor
@cosnicolaou I'm sorry to say that in the runtime package it's pretty much hand-editing these days for new constants and types. Getting back to a sane state is #23341.
Comment From: cosnicolaou
@ianlancetaylor thanks for responding so quickly! I'll just hand edit things which is fine, since it's not a lot of code. Do you have any suggestions for 2? Thanks!
Comment From: ianlancetaylor
You need to access a global variable defined in C code that is always linked in? Is that correct? And there is no C function that returns the value?
I'm not sure that a go:linkname
directive will work here. The best bet might be assembler code. But it sounds like you've already tried that. What goes wrong?
Comment From: cherrymui
For 2, I think cgo_import_dynamic is the right answer. Currently I think most use cases are for functions, not variables. So it is possible that the linker just doesn't handle variables well. I can take a look.
Comment From: cosnicolaou
The variable is 'mach_task_self_' declared as an extern in mach/mach_init.h, as 'extern mach_port_t mach_task_self_;' and is initialized by the OS when a process starts.
I've tried referencing it from go assembly, as in:
TEXT main·mach_task_self(SB),NOSPLIT,$0 //MOVD $100, R0 MOVD mach_task_self_<>+0(SB), R0 MOVD R0, ret+8(SP) RET
with various attempts of cgo_import_dynamic, none of which work. I believe the syntax for cgo_import_dynamic is cgo_import_dynamic 'my-name-for-symbol' 'name-of-symbol-in-library'. My C program that accesses it only depends on libSystem, so I assume that this is the correct library to use. It appears undefined in the executable as mach_task_self , hence I believe that its name in the library is also mach_task_self. This suggests that the following line, in a go file in the same package as the above assembly file, should work.
//go:cgo_import_dynamic mach_task_self_ mach_task_self "/usr/lib/libSystem.B.dylib"
I get a linker error of main.mach_task_self: relocation target
mach_task_self_ not defined
.
Would you expect this to work? Or am I missing something? I've tried many combinations of names, with/without leading underscores, the Core foundation libraries also.
I've never used this part of the system before so it's hard for me to know if I'm messing things up or if it's a bug somewhere.
Cheers, Cos.
On Wed, Jun 7, 2023 at 12:34 PM cherrymui @.***> wrote:
For 2, I think cgo_import_dynamic is the right answer. Currently I think most use cases are for functions, not variables. So it is possible that the linker just doesn't handle variables well. I can take a look.
— Reply to this email directly, view it on GitHub https://github.com/golang/go/issues/50891#issuecomment-1581398365, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACCMUYDVJHCMVLNQ6CWVASTXKDJUZANCNFSM5NBQ3LLQ . You are receiving this because you were mentioned.Message ID: @.***>
Comment From: ianlancetaylor
I don't know if this is the problem, but in this line:
MOVD mach_task_self_<>+0(SB), R0
you don't want the <>
there. I would write this more like
MOVD $mach_task_self_(SB), R0 # puts address of variable into R0
MOVD 0(R0), R0 #load value of variable into R0
Comment From: cherrymui
I took a look at the linker code and I think we just don't handle cgo_import_dynamic for variables well (because we never needed it before). I'll see if I can make the linker handle this. Thanks.
Comment From: cosnicolaou
Ah yes, Ian's suggestion that definitely helps - I forgot to remove the <> when trying out different data approaches. Now I get the error below: main.mach_task_self: program too large, address relocation distance = -4295532544
Which digging into the linker shows that a value is never set for the external symbol which could be because either it doesn't exist or the linker doesn't handle variables as well as functions for the cgo pragmas. Sounds like the latter. I extracted the libraries from the shared cache and found that this symbol is defined in the lowest level dyld library rather than libSystem (see below), but making that change to the cgo_import doesn't help, so I think it is the linker.
$nm ./libraries/usr/lib/dyld | grep mach_task_self 00000001db3ff6f0 s mach_task_self
Comment From: cherrymui
Yeah, it misses some case in the linker. The patch below seems to make it work:
diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go
index c91e37584c..0a76c71dc5 100644
--- a/src/cmd/link/internal/amd64/asm.go
+++ b/src/cmd/link/internal/amd64/asm.go
@@ -38,6 +38,7 @@ import (
"cmd/link/internal/sym"
"debug/elf"
"log"
+ "fmt"
)
func PADDR(x uint32) uint32 {
@@ -251,6 +252,20 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
// nothing to do, the relocation will be laid out in reloc
return true
}
+ if r.Type() == objabi.R_PCREL && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
+ fmt.Println("XXX adddynrel", ldr.SymName(s), ldr.SymName(targ))
+ ld.AddGotSym(target, ldr, syms, targ, 0)
+ // turn LEAQ symbol address to MOVQ of GOT entry
+ su := ldr.MakeSymbolUpdater(s)
+ if r.Off() >= 2 && su.Data()[r.Off()-2] == 0x8d {
+ su.MakeWritable()
+ su.Data()[r.Off()-2] = 0x8b
+ su.SetRelocSym(rIdx, syms.GOT)
+ su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
+ return true
+ }
+ log.Fatal("R_PCREL reloc to SDYNIMPORT symbol not preceded by LEAQ instruction")
+ }
if target.IsExternal() {
// External linker will do this relocation.
return true
(Currently AMD64 internal linking mode only. I still need to do external linking mode (i.e. linking with cgo) and ARM64.)
With this patch, this seems to work: in Go, add
//go:cgo_import_dynamic libc_mach_task_self_ mach_task_self_ "/usr/lib/libSystem.B.dylib"
and in assembly,
MOVQ $libc_mach_task_self_(SB), AX
MOVQ (AX), AX
MOVQ AX, ret+0(FP)
Thanks.
Comment From: cherrymui
diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go
index c91e37584c..b6aae0039a 100644
--- a/src/cmd/link/internal/amd64/asm.go
+++ b/src/cmd/link/internal/amd64/asm.go
@@ -38,6 +38,7 @@ import (
"cmd/link/internal/sym"
"debug/elf"
"log"
+ "fmt"
)
func PADDR(x uint32) uint32 {
@@ -251,6 +252,24 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
// nothing to do, the relocation will be laid out in reloc
return true
}
+ if r.Type() == objabi.R_PCREL && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
+ fmt.Println("XXX adddynrel", ldr.SymName(s), ldr.SymName(targ))
+ // turn LEAQ symbol address to MOVQ of GOT entry
+ su := ldr.MakeSymbolUpdater(s)
+ if r.Off() >= 2 && su.Data()[r.Off()-2] == 0x8d {
+ su.MakeWritable()
+ su.Data()[r.Off()-2] = 0x8b
+ if target.IsInternal() {
+ ld.AddGotSym(target, ldr, syms, targ, 0)
+ su.SetRelocSym(rIdx, syms.GOT)
+ su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
+ } else {
+ su.SetRelocType(rIdx, objabi.R_GOTPCREL)
+ }
+ return true
+ }
+ log.Fatal("R_PCREL reloc to SDYNIMPORT symbol not preceded by LEAQ instruction")
+ }
if target.IsExternal() {
// External linker will do this relocation.
return true
This should handle external linking. Still need to do ARM64. Thanks.
Comment From: cosnicolaou
ok, I don't have access to an intel mac right now so I tried the above in the arm64/asm.go. Unfortunately, I think a larger change is required. adddynrel is called for the symbol main.get_mach_task_self and a targ of libc_mach_task_self, but the type of the relocation is R_ADDRARM64 which is not caught by any of the switch arms in adddynrel. Note that main.get_mach_task_self is the function that references the variable. Hope this helps!
Comment From: cherrymui
@cosnicolaou this is the ARM64 version. It has different relocation types and instructions. Hopefully this will work. Thanks!
diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go
index 312ee27aa6..88ead8d897 100644
--- a/src/cmd/link/internal/arm64/asm.go
+++ b/src/cmd/link/internal/arm64/asm.go
@@ -306,6 +306,36 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
su.SetRelocAdd(rIdx, int64(ldr.SymPlt(targ)))
return true
+ case objabi.R_ADDRARM64:
+ if targType == sym.SDYNIMPORT && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
+ fmt.Println("XXX adddynrel", ldr.SymName(s), ldr.SymName(targ))
+ // turn MOVD $sym (adrp+add) into MOVD sym@GOT (adrp+ldr)
+ su := ldr.MakeSymbolUpdater(s)
+ data := ldr.Data(s)
+ off := r.Off()
+ if int(off+8) > len(data) {
+ ldr.Errorf(s, "unexpected R_ADDRARM64 reloc for dynamic symbol %s", ldr.SymName(targ))
+ return false
+ }
+ o := target.Arch.ByteOrder.Uint32(data[off+4:])
+ if o>>24 == 0x91 { // add
+ // rewrite to ldr
+ o = (0xf9 << 24) | 1<<22 | (o & (1<<22 - 1))
+ su.MakeWritable()
+ su.SetUint32(target.Arch, int64(off+4), o)
+ if target.IsInternal() {
+ ld.AddGotSym(target, ldr, syms, targ, 0)
+ su.SetRelocSym(rIdx, syms.GOT)
+ su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
+ su.SetRelocType(rIdx, objabi.R_ARM64_PCREL_LDST64)
+ } else {
+ su.SetRelocType(rIdx, objabi.R_ARM64_GOTPCREL)
+ }
+ return true
+ }
+ ldr.Errorf(s, "unexpected R_ADDRARM64 reloc for dynamic symbol %s", ldr.SymName(targ))
+ }
+
case objabi.R_ADDR:
if ldr.SymType(s) == sym.STEXT && target.IsElf() {
// The code is asking for the address of an external
I'll clean it up and turn it into a CL. Thanks.
Comment From: cosnicolaou
Yep - it does indeed work for arm64! Thanks! I'll try the intel version when I get back home in a week or so and have access to an intel mbp.
Comment From: gopherbot
Change https://go.dev/cl/501855 mentions this issue: cmd/link: handle dynamic import variables on Darwin
Comment From: cosnicolaou
FYI, I have the arm version of vm_region_info working. What are your plans for merging the linker changes and how should I construct the PRs for my changes - i.e. would you like to see one PR with the mach_vm_region assembly code and a second one with the pprof mapping changes, or would you prefer one? I would assume one, but lmk.
Comment From: cherrymui
This is great! Thanks.
We are currently in the freeze for Go 1.21 release, so I have to wait for the tree opens (probably late July) then I can submit the linker CL. (In the meantime, feel free to copy that code into your CL, if that's easier for you.)
For your change, yeah, a single CL is fine. Thanks.
Comment From: cosnicolaou
Take a look at https://go-review.googlesource.com/c/go/+/503919 when you have a moment, it includes your changes too.
Comment From: gopherbot
Change https://go.dev/cl/503919 mentions this issue: runtime,runtime/pprof: fixes support for assembler output
Comment From: gopherbot
Change https://go.dev/cl/516156 mentions this issue: runtime/pprof: correct field alignment in machVMRegionBasicInfoData
Comment From: gopherbot
Change https://go.dev/cl/518315 mentions this issue: runtime/pprof: don't run TestVMInfo on ios
Comment From: gopherbot
Change https://go.dev/cl/546475 mentions this issue: doc: add release notes for runtime/pprof changes
Comment From: gopherbot
Change https://go.dev/cl/592499 mentions this issue: cmd/link: handle dynamic import variables on Darwin in plugin mode
Comment From: gopherbot
Change https://go.dev/cl/595175 mentions this issue: [release-branch.go1.22] cmd/link: handle dynamic import variables on Darwin in plugin mode