#1015·pprof

`go tool pprof` web UI Disassemble sends `^func$`, but `disasm` matches PrintableName

Author: orisanoCreated Aug 17, 2026Updated Aug 17, 2026

Summary

I hit this through go tool pprof (Go's vendored copy of this repo), not the standalone pprof binary.

In the web UI (go tool pprof -http=...), clicking a function and then Disassemble fails with

no matches found for regexp ^mypkg\.MyFunc$

Source for the same f= works. Unanchored disasm MyFunc in the CLI also works.

This is not darwin PIE / missing-binary. The binary is passed in, and list ^MyFunc$ succeeds.

Environment

  • go version go1.24.4 darwin/arm64 (also reproduced with toolchain go1.26.2)
  • Invocation: go tool pprof -http=:8080 ./foo.test cpu.prof

When it broke

Until Go 1.22, the web UI escaped the selected name but did not anchor it

javascript
// Go 1.22 and earlier (vendored pprof)
function quotemeta(str) {
  return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
}

Click → Disassemble produced f=mypkg.MyFunc, which still matched as a substring of PrintableName (ADDRESS mypkg.MyFunc file.go:N).

Exact-match quoting was added in google/pprof on 2023-08-16:

javascript
// convert a string to a regexp that matches exactly that string.
function pprofQuoteMeta(str) {
  return '^' + str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1') + '$';
}

The intent was Focus/Hide on flame graphs (UniqueName vs FunctionName). The same helper is used for the Disassemble button.

Go vendored that snapshot on 2024-02-16

That landed on tip during the 1.23 cycle. Go 1.22 still has unanchored quotemeta. Go 1.23.0 (2024-08-13) is the first release where go tool pprof web UI Disassemble is broken this way. Still present in 1.24 / 1.26.

disasm matching PrintableName (address granularity) is older; the UI change is what made click → Disassemble fail.

Reproduction (go tool pprof)

go
package foo

import "testing"

func work() int {
	s := 0
	for i := 0; i < 1_000_000; i++ {
		s += i
	}
	return s
}

func BenchmarkWork(b *testing.B) {
	for i := 0; i < b.N; i++ {
		work()
	}
}
bash
go test -c -o foo.test
./foo.test -test.bench=BenchmarkWork -test.cpuprofile=cpu.prof
go tool pprof -http=:8080 ./foo.test cpu.prof
  1. Open Top, click example.com/foo.BenchmarkWork.
  2. Click Source → annotated source appears.
  3. Click Disassemble → no matches found for regexp ^example.com/foo\.BenchmarkWork$.

CLI:

$ go tool pprof ./foo.test cpu.prof
(pprof) list ^example.com/foo\.BenchmarkWork$     # works
(pprof) disasm ^example.com/foo\.BenchmarkWork$   # fails
(pprof) disasm example.com/foo.BenchmarkWork      # works

go tool nm ./foo.test shows the symbol.

Expected

The regexp the web UI puts in f= (^name$) should work for both Source and Disassemble.

Actual

disasm matches NodeInfo.PrintableName(). With address granularity that string is

000000010010bfb4 example.com/foo.BenchmarkWork /tmp/foo_test.go:14

so ^example.com/foo\.BenchmarkWork$ cannot match.

list / weblist compare n.Info.Name only, which is why Source still works.

internal/report.symbolsFromBinaries skips mappings unless PrintableName matches:

go
if name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != "" {
    fileHasSamplesAndMatched[n.Info.Objfile] = true
}

For disasm, applyCommandOverrides sets Granularity = "addresses". If that filter fails, ObjFile.Symbols(rx) is never called. That API matches binary symbol names (mypkg.MyFunc), which would match ^mypkg\.MyFunc$.

The function is in the profile and in the binary; the filter uses the wrong name.

Related (not duplicates)

  • google/pprof#789: /disasm with empty/nil f=. This report is f=^funcname$ from the go tool pprof web UI; Source still works.
  • golang/go#50891 / google/pprof#709: darwin PIE address mapping. Unanchored go tool pprof disasm Func works here once the test binary is passed.
  • google/pprof#914: OSX weblist vs _-prefixed Mach-O names.
  • google/pprof#230: inlined functions have no standalone symbol.
  • google/pprof#796: introduced pprofQuoteMeta (^name$) for Focus; that is what the Disassemble button now sends.