mirror of
https://github.com/golang/go
synced 2024-11-22 19:14:53 -07:00
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>
This commit is contained in:
parent
d05ce23756
commit
28afa5b176
@ -5,12 +5,20 @@
|
||||
package windows
|
||||
|
||||
type MemoryBasicInformation struct {
|
||||
BaseAddress uintptr
|
||||
AllocationBase uintptr
|
||||
// A pointer to the base address of the region of pages.
|
||||
BaseAddress uintptr
|
||||
// A pointer to the base address of a range of pages allocated by the VirtualAlloc function.
|
||||
// The page pointed to by the BaseAddress member is contained within this allocation range.
|
||||
AllocationBase uintptr
|
||||
// The memory protection option when the region was initially allocated
|
||||
AllocationProtect uint32
|
||||
PartitionId uint16
|
||||
RegionSize uintptr
|
||||
State uint32
|
||||
Protect uint32
|
||||
Type uint32
|
||||
// The size of the region beginning at the base address in which all pages have identical attributes, in bytes.
|
||||
RegionSize uintptr
|
||||
// The state of the pages in the region.
|
||||
State uint32
|
||||
// The access protection of the pages in the region.
|
||||
Protect uint32
|
||||
// The type of pages in the region.
|
||||
Type uint32
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ func UTF16PtrToString(p *uint16) string {
|
||||
}
|
||||
|
||||
const (
|
||||
ERROR_BAD_LENGTH syscall.Errno = 24
|
||||
ERROR_SHARING_VIOLATION syscall.Errno = 32
|
||||
ERROR_LOCK_VIOLATION syscall.Errno = 33
|
||||
ERROR_NOT_SUPPORTED syscall.Errno = 50
|
||||
@ -156,6 +157,32 @@ const (
|
||||
//sys SetFileInformationByHandle(handle syscall.Handle, fileInformationClass uint32, buf uintptr, bufsize uint32) (err error) = kernel32.SetFileInformationByHandle
|
||||
//sys VirtualQuery(address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) = kernel32.VirtualQuery
|
||||
|
||||
const (
|
||||
// flags for CreateToolhelp32Snapshot
|
||||
TH32CS_SNAPMODULE = 0x08
|
||||
TH32CS_SNAPMODULE32 = 0x10
|
||||
)
|
||||
|
||||
const MAX_MODULE_NAME32 = 255
|
||||
|
||||
type ModuleEntry32 struct {
|
||||
Size uint32
|
||||
ModuleID uint32
|
||||
ProcessID uint32
|
||||
GlblcntUsage uint32
|
||||
ProccntUsage uint32
|
||||
ModBaseAddr uintptr
|
||||
ModBaseSize uint32
|
||||
ModuleHandle syscall.Handle
|
||||
Module [MAX_MODULE_NAME32 + 1]uint16
|
||||
ExePath [syscall.MAX_PATH]uint16
|
||||
}
|
||||
|
||||
const SizeofModuleEntry32 = unsafe.Sizeof(ModuleEntry32{})
|
||||
|
||||
//sys Module32First(snapshot syscall.Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW
|
||||
//sys Module32Next(snapshot syscall.Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32NextW
|
||||
|
||||
const (
|
||||
WSA_FLAG_OVERLAPPED = 0x01
|
||||
WSA_FLAG_NO_HANDLE_INHERIT = 0x80
|
||||
|
@ -62,6 +62,8 @@ var (
|
||||
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
|
||||
procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
|
||||
procLockFileEx = modkernel32.NewProc("LockFileEx")
|
||||
procModule32FirstW = modkernel32.NewProc("Module32FirstW")
|
||||
procModule32NextW = modkernel32.NewProc("Module32NextW")
|
||||
procMoveFileExW = modkernel32.NewProc("MoveFileExW")
|
||||
procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
|
||||
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||
@ -225,6 +227,22 @@ func LockFileEx(file syscall.Handle, flags uint32, reserved uint32, bytesLow uin
|
||||
return
|
||||
}
|
||||
|
||||
func Module32First(snapshot syscall.Handle, moduleEntry *ModuleEntry32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procModule32FirstW.Addr(), 2, uintptr(snapshot), uintptr(unsafe.Pointer(moduleEntry)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Module32Next(snapshot syscall.Handle, moduleEntry *ModuleEntry32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procModule32NextW.Addr(), 2, uintptr(snapshot), uintptr(unsafe.Pointer(moduleEntry)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func MoveFileEx(from *uint16, to *uint16, flags uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procMoveFileExW.Addr(), 3, uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)), uintptr(flags))
|
||||
if r1 == 0 {
|
||||
|
19
src/runtime/pprof/pe.go
Normal file
19
src/runtime/pprof/pe.go
Normal file
@ -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()
|
||||
}
|
@ -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")
|
||||
|
||||
|
30
src/runtime/pprof/proto_other.go
Normal file
30
src/runtime/pprof/proto_other.go
Normal file
@ -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")
|
||||
}
|
@ -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))
|
||||
|
73
src/runtime/pprof/proto_windows.go
Normal file
73
src/runtime/pprof/proto_windows.go
Normal file
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user