Source file src/runtime/valgrind.go

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

View as plain text