aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2025-02-21 17:13:20 -0800
committerGopher Robot <gobot@golang.org>2025-03-05 18:23:46 -0800
commit645ea530191105dc89dd8d67640d61a4d0526df9 (patch)
tree9773a3cbe6cef7053f74cc4ef24c1d9ffb8d99af /src/runtime
parentcad4dca518a3a984bfd6b19ee304a59f51937fd8 (diff)
downloadgo-645ea530191105dc89dd8d67640d61a4d0526df9.tar.xz
runtime: in asan mode call __lsan_do_leak_check when exiting
This enables the ASAN default behavior of reporting C memory leaks. It can be disabled with ASAN_OPTIONS=detect_leaks=0. Fixes #67833 Change-Id: I420da1b5d79cf70d8cf134eaf97bf0a22f61ffd0 Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-asan-clang15,gotip-linux-arm64-asan-clang15 Reviewed-on: https://go-review.googlesource.com/c/go/+/651755 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Auto-Submit: Ian Lance Taylor <iant@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/asan.go7
-rw-r--r--src/runtime/asan/asan.go9
-rw-r--r--src/runtime/asan0.go2
-rw-r--r--src/runtime/asan_amd64.s14
-rw-r--r--src/runtime/asan_arm64.s14
-rw-r--r--src/runtime/asan_loong64.s14
-rw-r--r--src/runtime/asan_ppc64le.s14
-rw-r--r--src/runtime/asan_riscv64.s14
-rw-r--r--src/runtime/mem.go20
-rw-r--r--src/runtime/proc.go25
-rw-r--r--src/runtime/vdso_test.go5
11 files changed, 135 insertions, 3 deletions
diff --git a/src/runtime/asan.go b/src/runtime/asan.go
index 6fb1d00c3c..adef8fa7bf 100644
--- a/src/runtime/asan.go
+++ b/src/runtime/asan.go
@@ -61,6 +61,11 @@ func asanpoison(addr unsafe.Pointer, sz uintptr)
//go:noescape
func asanregisterglobals(addr unsafe.Pointer, n uintptr)
+//go:noescape
+func lsanregisterrootregion(addr unsafe.Pointer, n uintptr)
+
+func lsandoleakcheck()
+
// These are called from asan_GOARCH.s
//
//go:cgo_import_static __asan_read_go
@@ -68,3 +73,5 @@ func asanregisterglobals(addr unsafe.Pointer, n uintptr)
//go:cgo_import_static __asan_unpoison_go
//go:cgo_import_static __asan_poison_go
//go:cgo_import_static __asan_register_globals_go
+//go:cgo_import_static __lsan_register_root_region_go
+//go:cgo_import_static __lsan_do_leak_check_go
diff --git a/src/runtime/asan/asan.go b/src/runtime/asan/asan.go
index ef70b0145b..efdd911f2b 100644
--- a/src/runtime/asan/asan.go
+++ b/src/runtime/asan/asan.go
@@ -13,6 +13,7 @@ package asan
#include <stdbool.h>
#include <stdint.h>
#include <sanitizer/asan_interface.h>
+#include <sanitizer/lsan_interface.h>
void __asan_read_go(void *addr, uintptr_t sz, void *sp, void *pc) {
if (__asan_region_is_poisoned(addr, sz)) {
@@ -34,6 +35,14 @@ void __asan_poison_go(void *addr, uintptr_t sz) {
__asan_poison_memory_region(addr, sz);
}
+void __lsan_register_root_region_go(void *addr, uintptr_t sz) {
+ __lsan_register_root_region(addr, sz);
+}
+
+void __lsan_do_leak_check_go(void) {
+ __lsan_do_leak_check();
+}
+
// Keep in sync with the definition in compiler-rt
// https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/asan/asan_interface_internal.h#L41
// This structure is used to describe the source location of
diff --git a/src/runtime/asan0.go b/src/runtime/asan0.go
index bcfd96f1ab..eb70367a29 100644
--- a/src/runtime/asan0.go
+++ b/src/runtime/asan0.go
@@ -21,3 +21,5 @@ func asanwrite(addr unsafe.Pointer, sz uintptr) { throw("asan") }
func asanunpoison(addr unsafe.Pointer, sz uintptr) { throw("asan") }
func asanpoison(addr unsafe.Pointer, sz uintptr) { throw("asan") }
func asanregisterglobals(addr unsafe.Pointer, sz uintptr) { throw("asan") }
+func lsanregisterrootregion(unsafe.Pointer, uintptr) { throw("asan") }
+func lsandoleakcheck() { throw("asan") }
diff --git a/src/runtime/asan_amd64.s b/src/runtime/asan_amd64.s
index 195faf4e6d..3f9df4fec8 100644
--- a/src/runtime/asan_amd64.s
+++ b/src/runtime/asan_amd64.s
@@ -69,6 +69,20 @@ TEXT runtime·asanregisterglobals(SB), NOSPLIT, $0-16
MOVQ $__asan_register_globals_go(SB), AX
JMP asancall<>(SB)
+// func runtime·lsanregisterrootregion(addr unsafe.Pointer, n uintptr)
+TEXT runtime·lsanregisterrootregion(SB), NOSPLIT, $0-16
+ MOVQ addr+0(FP), RARG0
+ MOVQ n+8(FP), RARG1
+ // void __lsan_register_root_region_go(void *addr, uintptr_t sz)
+ MOVQ $__lsan_register_root_region_go(SB), AX
+ JMP asancall<>(SB)
+
+// func runtime·lsandoleakcheck()
+TEXT runtime·lsandoleakcheck(SB), NOSPLIT, $0-0
+ // void __lsan_do_leak_check_go(void);
+ MOVQ $__lsan_do_leak_check_go(SB), AX
+ JMP asancall<>(SB)
+
// Switches SP to g0 stack and calls (AX). Arguments already set.
TEXT asancall<>(SB), NOSPLIT, $0-0
get_tls(R12)
diff --git a/src/runtime/asan_arm64.s b/src/runtime/asan_arm64.s
index dfa3f81bf2..5447d210e5 100644
--- a/src/runtime/asan_arm64.s
+++ b/src/runtime/asan_arm64.s
@@ -58,6 +58,20 @@ TEXT runtime·asanregisterglobals(SB), NOSPLIT, $0-16
MOVD $__asan_register_globals_go(SB), FARG
JMP asancall<>(SB)
+// func runtime·lsanregisterrootregion(addr unsafe.Pointer, n uintptr)
+TEXT runtime·lsanregisterrootregion(SB), NOSPLIT, $0-16
+ MOVD addr+0(FP), RARG0
+ MOVD n+8(FP), RARG1
+ // void __lsan_register_root_region_go(void *addr, uintptr_t n);
+ MOVD $__lsan_register_root_region_go(SB), FARG
+ JMP asancall<>(SB)
+
+// func runtime·lsandoleakcheck()
+TEXT runtime·lsandoleakcheck(SB), NOSPLIT, $0-0
+ // void __lsan_do_leak_check_go(void);
+ MOVD $__lsan_do_leak_check_go(SB), FARG
+ JMP asancall<>(SB)
+
// Switches SP to g0 stack and calls (FARG). Arguments already set.
TEXT asancall<>(SB), NOSPLIT, $0-0
MOVD RSP, R19 // callee-saved
diff --git a/src/runtime/asan_loong64.s b/src/runtime/asan_loong64.s
index 0034a31687..3abcf889b8 100644
--- a/src/runtime/asan_loong64.s
+++ b/src/runtime/asan_loong64.s
@@ -58,6 +58,20 @@ TEXT runtime·asanregisterglobals(SB), NOSPLIT, $0-16
MOVV $__asan_register_globals_go(SB), FARG
JMP asancall<>(SB)
+// func runtime·lsanregisterrootregion(addr unsafe.Pointer, n uintptr)
+TEXT runtime·lsanregisterrootregion(SB), NOSPLIT, $0-16
+ MOVV addr+0(FP), RARG0
+ MOVV n+8(FP), RARG1
+ // void __lsan_register_root_region_go(void *addr, uintptr_t n);
+ MOVV $__lsan_register_root_region_go(SB), FARG
+ JMP asancall<>(SB)
+
+// func runtime·lsandoleakcheck()
+TEXT runtime·lsandoleakcheck(SB), NOSPLIT, $0-0
+ // void __lsan_do_leak_check_go(void);
+ MOVV $__lsan_do_leak_check_go(SB), FARG
+ JMP asancall<>(SB)
+
// Switches SP to g0 stack and calls (FARG). Arguments already set.
TEXT asancall<>(SB), NOSPLIT, $0-0
MOVV R3, R23 // callee-saved
diff --git a/src/runtime/asan_ppc64le.s b/src/runtime/asan_ppc64le.s
index d13301a1b1..2fc5772a28 100644
--- a/src/runtime/asan_ppc64le.s
+++ b/src/runtime/asan_ppc64le.s
@@ -58,6 +58,20 @@ TEXT runtime·asanregisterglobals(SB),NOSPLIT|NOFRAME,$0-16
MOVD $__asan_register_globals_go(SB), FARG
BR asancall<>(SB)
+// func runtime·lsanregisterrootregion(addr unsafe.Pointer, n uintptr)
+TEXT runtime·lsanregisterrootregion(SB),NOSPLIT|NOFRAME,$0-16
+ MOVD addr+0(FP), RARG0
+ MOVD n+8(FP), RARG1
+ // void __lsan_register_root_region_go(void *addr, uintptr_t n);
+ MOVD $__lsan_register_root_region_go(SB), FARG
+ BR asancall<>(SB)
+
+// func runtime·lsandoleakcheck()
+TEXT runtime·lsandoleakcheck(SB), NOSPLIT|NOFRAME, $0-0
+ // void __lsan_do_leak_check_go(void);
+ MOVD $__lsan_do_leak_check_go(SB), FARG
+ BR asancall<>(SB)
+
// Switches SP to g0 stack and calls (FARG). Arguments already set.
TEXT asancall<>(SB), NOSPLIT, $0-0
// LR saved in generated prologue
diff --git a/src/runtime/asan_riscv64.s b/src/runtime/asan_riscv64.s
index 6fcd94d4b1..f5ddb21a25 100644
--- a/src/runtime/asan_riscv64.s
+++ b/src/runtime/asan_riscv64.s
@@ -52,6 +52,20 @@ TEXT runtime·asanregisterglobals(SB), NOSPLIT, $0-16
MOV $__asan_register_globals_go(SB), X14
JMP asancall<>(SB)
+// func runtime·lsanregisterrootregion(addr unsafe.Pointer, n uintptr)
+TEXT runtime·lsanregisterrootregion(SB), NOSPLIT, $0-16
+ MOV addr+0(FP), X10
+ MOV n+8(FP), X11
+ // void __lsan_register_root_region_go(void *addr, uintptr_t n);
+ MOV $__lsan_register_root_region_go(SB), X14
+ JMP asancall<>(SB)
+
+// func runtime·lsandoleakcheck()
+TEXT runtime·lsandoleakcheck(SB), NOSPLIT, $0-0
+ // void __lsan_do_leak_check_go(void);
+ MOV $__lsan_do_leak_check_go(SB), X14
+ JMP asancall<>(SB)
+
// Switches SP to g0 stack and calls (X14). Arguments already set.
TEXT asancall<>(SB), NOSPLIT, $0-0
MOV X2, X8 // callee-saved
diff --git a/src/runtime/mem.go b/src/runtime/mem.go
index 6bb91b371a..d45a0ccfb8 100644
--- a/src/runtime/mem.go
+++ b/src/runtime/mem.go
@@ -49,7 +49,15 @@ import "unsafe"
func sysAlloc(n uintptr, sysStat *sysMemStat, vmaName string) unsafe.Pointer {
sysStat.add(int64(n))
gcController.mappedReady.Add(int64(n))
- return sysAllocOS(n, vmaName)
+ p := sysAllocOS(n, vmaName)
+
+ // When using ASAN leak detection, we must tell ASAN about
+ // cases where we store pointers in mmapped memory.
+ if asanenabled {
+ lsanregisterrootregion(p, n)
+ }
+
+ return p
}
// sysUnused transitions a memory region from Ready to Prepared. It notifies the
@@ -143,7 +151,15 @@ func sysFault(v unsafe.Pointer, n uintptr) {
// may use larger alignment, so the caller must be careful to realign the
// memory obtained by sysReserve.
func sysReserve(v unsafe.Pointer, n uintptr, vmaName string) unsafe.Pointer {
- return sysReserveOS(v, n, vmaName)
+ p := sysReserveOS(v, n, vmaName)
+
+ // When using ASAN leak detection, we must tell ASAN about
+ // cases where we store pointers in mmapped memory.
+ if asanenabled {
+ lsanregisterrootregion(p, n)
+ }
+
+ return p
}
// sysMap transitions a memory region from Reserved to Prepared. It ensures the
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index c467f4c49d..74c19e9e43 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -281,11 +281,27 @@ func main() {
}
fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
fn()
+
+ exitHooksRun := false
if raceenabled {
runExitHooks(0) // run hooks now, since racefini does not return
+ exitHooksRun = true
racefini()
}
+ // Check for C memory leaks if using ASAN and we've made cgo calls,
+ // or if we are running as a library in a C program.
+ // We always make one cgo call, above, to notify_runtime_init_done,
+ // so we ignore that one.
+ // No point in leak checking if no cgo calls, since leak checking
+ // just looks for objects allocated using malloc and friends.
+ // Just checking iscgo doesn't help because asan implies iscgo.
+ if asanenabled && (isarchive || islibrary || NumCgoCall() > 1) {
+ runExitHooks(0) // lsandoleakcheck may not return
+ exitHooksRun = true
+ lsandoleakcheck()
+ }
+
// Make racy client program work: if panicking on
// another goroutine at the same time as main returns,
// let the other goroutine finish printing the panic trace.
@@ -302,7 +318,9 @@ func main() {
if panicking.Load() != 0 {
gopark(nil, nil, waitReasonPanicWait, traceBlockForever, 1)
}
- runExitHooks(0)
+ if !exitHooksRun {
+ runExitHooks(0)
+ }
exit(0)
for {
@@ -319,6 +337,11 @@ func os_beforeExit(exitCode int) {
if exitCode == 0 && raceenabled {
racefini()
}
+
+ // See comment in main, above.
+ if exitCode == 0 && asanenabled && (isarchive || islibrary || NumCgoCall() > 1) {
+ lsandoleakcheck()
+ }
}
func init() {
diff --git a/src/runtime/vdso_test.go b/src/runtime/vdso_test.go
index d025ba50c2..b0f5fbe728 100644
--- a/src/runtime/vdso_test.go
+++ b/src/runtime/vdso_test.go
@@ -8,6 +8,7 @@ package runtime_test
import (
"bytes"
+ "internal/asan"
"internal/testenv"
"os"
"os/exec"
@@ -20,6 +21,10 @@ import (
// TestUsingVDSO tests that we are actually using the VDSO to fetch
// the time.
func TestUsingVDSO(t *testing.T) {
+ if asan.Enabled {
+ t.Skip("test fails with ASAN beause the ASAN leak checker won't run under strace")
+ }
+
const calls = 100
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {