diff options
| author | Ian Lance Taylor <iant@golang.org> | 2025-02-21 17:13:20 -0800 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-03-05 18:23:46 -0800 |
| commit | 645ea530191105dc89dd8d67640d61a4d0526df9 (patch) | |
| tree | 9773a3cbe6cef7053f74cc4ef24c1d9ffb8d99af /src/runtime | |
| parent | cad4dca518a3a984bfd6b19ee304a59f51937fd8 (diff) | |
| download | go-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.go | 7 | ||||
| -rw-r--r-- | src/runtime/asan/asan.go | 9 | ||||
| -rw-r--r-- | src/runtime/asan0.go | 2 | ||||
| -rw-r--r-- | src/runtime/asan_amd64.s | 14 | ||||
| -rw-r--r-- | src/runtime/asan_arm64.s | 14 | ||||
| -rw-r--r-- | src/runtime/asan_loong64.s | 14 | ||||
| -rw-r--r-- | src/runtime/asan_ppc64le.s | 14 | ||||
| -rw-r--r-- | src/runtime/asan_riscv64.s | 14 | ||||
| -rw-r--r-- | src/runtime/mem.go | 20 | ||||
| -rw-r--r-- | src/runtime/proc.go | 25 | ||||
| -rw-r--r-- | src/runtime/vdso_test.go | 5 |
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" { |
