diff options
| author | Egon Elbre <egonelbre@gmail.com> | 2022-07-11 11:58:39 +0300 |
|---|---|---|
| committer | Alex Brainman <alex.brainman@gmail.com> | 2022-08-20 00:18:04 +0000 |
| commit | 28afa5b1761ba6bb51a4c831e9ee0b9812de8bc5 (patch) | |
| tree | 9b4a8640b9a90acb40016545625444d3eca5d56b /src/runtime | |
| parent | d05ce23756573c6dc2c5026d936f2ef6ac140ee2 (diff) | |
| download | go-28afa5b1761ba6bb51a4c831e9ee0b9812de8bc5.tar.xz | |
runtime/pprof: add memory mapping info for Windows
Fixes #43296
Change-Id: Ib277c2e82c95f71a7a9b7fe1b22215ead7a54a88
Reviewed-on: https://go-review.googlesource.com/c/go/+/416975
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/pprof/pe.go | 19 | ||||
| -rw-r--r-- | src/runtime/pprof/proto.go | 24 | ||||
| -rw-r--r-- | src/runtime/pprof/proto_other.go | 30 | ||||
| -rw-r--r-- | src/runtime/pprof/proto_test.go | 30 | ||||
| -rw-r--r-- | src/runtime/pprof/proto_windows.go | 73 |
5 files changed, 157 insertions, 19 deletions
diff --git a/src/runtime/pprof/pe.go b/src/runtime/pprof/pe.go new file mode 100644 index 0000000000..41054585e9 --- /dev/null +++ b/src/runtime/pprof/pe.go @@ -0,0 +1,19 @@ +// 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 pprof + +import "os" + +// peBuildID returns a best effort unique ID for the named executable. +// +// It would be wasteful to calculate the hash of the whole file, +// instead use the binary name and the last modified time for the buildid. +func peBuildID(file string) string { + s, err := os.Stat(file) + if err != nil { + return file + } + return file + s.ModTime().String() +} diff --git a/src/runtime/pprof/proto.go b/src/runtime/pprof/proto.go index 085027cd98..9c82925165 100644 --- a/src/runtime/pprof/proto.go +++ b/src/runtime/pprof/proto.go @@ -10,7 +10,6 @@ import ( "fmt" "internal/abi" "io" - "os" "runtime" "strconv" "strings" @@ -46,10 +45,11 @@ type profileBuilder struct { type memMap struct { // initialized as reading mapping - start uintptr - end uintptr - offset uint64 - file, buildID string + start uintptr // Address at which the binary (or DLL) is loaded into memory. + end uintptr // The limit of the address range occupied by this mapping. + offset uint64 // Offset in the binary that corresponds to the first mapped address. + file string // The object this entry is loaded from. + buildID string // A string that uniquely identifies a particular program version with high probability. funcs symbolizeFlag fake bool // map entry was faked; /proc/self/maps wasn't available @@ -640,20 +640,6 @@ func (b *profileBuilder) emitLocation() uint64 { return id } -// readMapping reads /proc/self/maps and writes mappings to b.pb. -// It saves the address ranges of the mappings in b.mem for use -// when emitting locations. -func (b *profileBuilder) readMapping() { - data, _ := os.ReadFile("/proc/self/maps") - parseProcSelfMaps(data, b.addMapping) - if len(b.mem) == 0 { // pprof expects a map entry, so fake one. - b.addMappingEntry(0, 0, 0, "", "", true) - // TODO(hyangah): make addMapping return *memMap or - // take a memMap struct, and get rid of addMappingEntry - // that takes a bunch of positional arguments. - } -} - var space = []byte(" ") var newline = []byte("\n") diff --git a/src/runtime/pprof/proto_other.go b/src/runtime/pprof/proto_other.go new file mode 100644 index 0000000000..4a7fe79501 --- /dev/null +++ b/src/runtime/pprof/proto_other.go @@ -0,0 +1,30 @@ +// 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. + +//go:build !windows + +package pprof + +import ( + "errors" + "os" +) + +// readMapping reads /proc/self/maps and writes mappings to b.pb. +// It saves the address ranges of the mappings in b.mem for use +// when emitting locations. +func (b *profileBuilder) readMapping() { + data, _ := os.ReadFile("/proc/self/maps") + parseProcSelfMaps(data, b.addMapping) + if len(b.mem) == 0 { // pprof expects a map entry, so fake one. + b.addMappingEntry(0, 0, 0, "", "", true) + // TODO(hyangah): make addMapping return *memMap or + // take a memMap struct, and get rid of addMappingEntry + // that takes a bunch of positional arguments. + } +} + +func readMainModuleMapping() (start, end uint64, err error) { + return 0, 0, errors.New("not implemented") +} diff --git a/src/runtime/pprof/proto_test.go b/src/runtime/pprof/proto_test.go index 84a051a536..797c6502b4 100644 --- a/src/runtime/pprof/proto_test.go +++ b/src/runtime/pprof/proto_test.go @@ -101,6 +101,36 @@ func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) { addr2 = mprof.Mapping[1].Start map2 = mprof.Mapping[1] map2.BuildID, _ = elfBuildID(map2.File) + case "windows": + addr1 = uint64(abi.FuncPCABIInternal(f1)) + addr2 = uint64(abi.FuncPCABIInternal(f2)) + + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + + start, end, err := readMainModuleMapping() + if err != nil { + t.Fatal(err) + } + + map1 = &profile.Mapping{ + ID: 1, + Start: start, + Limit: end, + File: exe, + BuildID: peBuildID(exe), + HasFunctions: true, + } + map2 = &profile.Mapping{ + ID: 1, + Start: start, + Limit: end, + File: exe, + BuildID: peBuildID(exe), + HasFunctions: true, + } case "js": addr1 = uint64(abi.FuncPCABIInternal(f1)) addr2 = uint64(abi.FuncPCABIInternal(f2)) diff --git a/src/runtime/pprof/proto_windows.go b/src/runtime/pprof/proto_windows.go new file mode 100644 index 0000000000..d5ae4a5eec --- /dev/null +++ b/src/runtime/pprof/proto_windows.go @@ -0,0 +1,73 @@ +// 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 pprof + +import ( + "errors" + "internal/syscall/windows" + "syscall" +) + +// readMapping adds memory mapping information to the profile. +func (b *profileBuilder) readMapping() { + snap, err := createModuleSnapshot() + if err != nil { + // pprof expects a map entry, so fake one, when we haven't added anything yet. + b.addMappingEntry(0, 0, 0, "", "", true) + return + } + defer func() { _ = syscall.CloseHandle(snap) }() + + var module windows.ModuleEntry32 + module.Size = uint32(windows.SizeofModuleEntry32) + err = windows.Module32First(snap, &module) + if err != nil { + // pprof expects a map entry, so fake one, when we haven't added anything yet. + b.addMappingEntry(0, 0, 0, "", "", true) + return + } + for err == nil { + exe := syscall.UTF16ToString(module.ExePath[:]) + b.addMappingEntry( + uint64(module.ModBaseAddr), + uint64(module.ModBaseAddr)+uint64(module.ModBaseSize), + 0, + exe, + peBuildID(exe), + false, + ) + err = windows.Module32Next(snap, &module) + } +} + +func readMainModuleMapping() (start, end uint64, err error) { + snap, err := createModuleSnapshot() + if err != nil { + return 0, 0, err + } + defer func() { _ = syscall.CloseHandle(snap) }() + + var module windows.ModuleEntry32 + module.Size = uint32(windows.SizeofModuleEntry32) + err = windows.Module32First(snap, &module) + if err != nil { + return 0, 0, err + } + + return uint64(module.ModBaseAddr), uint64(module.ModBaseAddr) + uint64(module.ModBaseSize), nil +} + +func createModuleSnapshot() (syscall.Handle, error) { + for { + snap, err := syscall.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, uint32(syscall.Getpid())) + var errno syscall.Errno + if err != nil && errors.As(err, &errno) && errno == windows.ERROR_BAD_LENGTH { + // When CreateToolhelp32Snapshot(SNAPMODULE|SNAPMODULE32, ...) fails + // with ERROR_BAD_LENGTH then it should be retried until it succeeds. + continue + } + return snap, err + } +} |
