1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
// 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.
//go:build valgrind && linux && (arm64 || amd64)
package runtime
import "unsafe"
const valgrindenabled = true
// Valgrind provides a mechanism to allow programs under test to modify
// Valgrinds behavior in certain ways, referred to as client requests [0]. These
// requests are triggered putting the address of a series of uints in a specific
// register and emitting a very specific sequence of assembly instructions. The
// result of the request (if there is one) is then put in another register for
// the program to retrieve. Each request is identified by a unique uint, which
// is passed as the first "argument".
//
// Valgrind provides headers (valgrind/valgrind.h, valgrind/memcheck.h) with
// macros that emit the correct assembly for these requests. Instead of copying
// these headers into the tree and using cgo to call the macros, we implement
// the client request assembly ourselves. Since both the magic instruction
// sequences, and the request uint's are stable, it is safe for us to implement.
//
// The client requests we add are used to describe our memory allocator to
// Valgrind, per [1]. We describe the allocator using the two-level mempool
// structure a We also add annotations which allow Valgrind to track which
// memory we use for stacks, which seems necessary to let it properly function.
//
// We describe the memory model to Valgrind as follows: we treat heap arenas as
// "pools" created with VALGRIND_CREATE_MEMPOOL_EXT (so that we can use
// VALGRIND_MEMPOOL_METAPOOL and VALGRIND_MEMPOOL_AUTO_FREE). Within the pool we
// treat spans as "superblocks", annotated with VALGRIND_MEMPOOL_ALLOC. We then
// allocate individual objects within spans with VALGRIND_MALLOCLIKE_BLOCK.
//
// [0] https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
// [1] https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools
const (
// Valgrind request IDs, copied from valgrind/valgrind.h.
vg_userreq__malloclike_block = 0x1301
vg_userreq__freelike_block = 0x1302
vg_userreq__create_mempool = 0x1303
vg_userreq__mempool_alloc = 0x1305
vg_userreq__mempool_free = 0x1306
vg_userreq__stack_register = 0x1501
vg_userreq__stack_deregister = 0x1502
vg_userreq__stack_change = 0x1503
)
const (
// Memcheck request IDs are offset from ('M'&0xff) << 24 | ('C'&0xff) << 16, or 0x4d430000,
// copied from valgrind/memcheck.h.
vg_userreq__make_mem_noaccess = iota + ('M'&0xff)<<24 | ('C'&0xff)<<16
vg_userreq__make_mem_undefined
vg_userreq__make_mem_defined
)
const (
// VALGRIND_CREATE_MEMPOOL_EXT flags, copied from valgrind/valgrind.h.
valgrind_mempool_auto_free = 1
valgrind_mempool_metapool = 2
)
//
//go:noescape
func valgrindClientRequest(uintptr, uintptr, uintptr, uintptr, uintptr, uintptr) uintptr
//go:nosplit
func valgrindRegisterStack(start, end unsafe.Pointer) uintptr {
// VALGRIND_STACK_REGISTER
return valgrindClientRequest(vg_userreq__stack_register, uintptr(start), uintptr(end), 0, 0, 0)
}
//go:nosplit
func valgrindDeregisterStack(id uintptr) {
// VALGRIND_STACK_DEREGISTER
valgrindClientRequest(vg_userreq__stack_deregister, id, 0, 0, 0, 0)
}
//go:nosplit
func valgrindChangeStack(id uintptr, start, end unsafe.Pointer) {
// VALGRIND_STACK_CHANGE
valgrindClientRequest(vg_userreq__stack_change, id, uintptr(start), uintptr(end), 0, 0)
}
//go:nosplit
func valgrindMalloc(addr unsafe.Pointer, size uintptr) {
// VALGRIND_MALLOCLIKE_BLOCK
valgrindClientRequest(vg_userreq__malloclike_block, uintptr(addr), size, 0, 1, 0)
}
//go:nosplit
func valgrindFree(addr unsafe.Pointer) {
// VALGRIND_FREELIKE_BLOCK
valgrindClientRequest(vg_userreq__freelike_block, uintptr(addr), 0, 0, 0, 0)
}
//go:nosplit
func valgrindCreateMempool(addr unsafe.Pointer) {
// VALGRIND_CREATE_MEMPOOL_EXT
valgrindClientRequest(vg_userreq__create_mempool, uintptr(addr), 0, 1, valgrind_mempool_auto_free|valgrind_mempool_metapool, 0)
}
//go:nosplit
func valgrindMempoolMalloc(pool, addr unsafe.Pointer, size uintptr) {
// VALGRIND_MEMPOOL_ALLOC
valgrindClientRequest(vg_userreq__mempool_alloc, uintptr(pool), uintptr(addr), size, 0, 0)
}
//go:nosplit
func valgrindMempoolFree(pool, addr unsafe.Pointer) {
// VALGRIND_MEMPOOL_FREE
valgrindClientRequest(vg_userreq__mempool_free, uintptr(pool), uintptr(addr), 0, 0, 0)
}
// Memcheck client requests, copied from valgrind/memcheck.h
//go:nosplit
func valgrindMakeMemUndefined(addr unsafe.Pointer, size uintptr) {
// VALGRIND_MAKE_MEM_UNDEFINED
valgrindClientRequest(vg_userreq__make_mem_undefined, uintptr(addr), size, 0, 0, 0)
}
//go:nosplit
func valgrindMakeMemDefined(addr unsafe.Pointer, size uintptr) {
// VALGRIND_MAKE_MEM_DEFINED
valgrindClientRequest(vg_userreq__make_mem_defined, uintptr(addr), size, 0, 0, 0)
}
//go:nosplit
func valgrindMakeMemNoAccess(addr unsafe.Pointer, size uintptr) {
// VALGRIND_MAKE_MEM_NOACCESS
valgrindClientRequest(vg_userreq__make_mem_noaccess, uintptr(addr), size, 0, 0, 0)
}
|