Commit e24228af authored by Ian Lance Taylor's avatar Ian Lance Taylor

runtime: enable/disable SIGPROF if needed when profiling

This ensures that SIGPROF is handled correctly when using
runtime/pprof in a c-archive or c-shared library.

Separate profiler handling into pre-process changes and per-thread
changes. Simplify the Windows code slightly accordingly.

Fixes #18220.

Change-Id: I5060f7084c91ef0bbe797848978bdc527c312777
Reviewed-on: https://go-review.googlesource.com/34018
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
parent 6a29806e
...@@ -557,3 +557,38 @@ func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool { ...@@ -557,3 +557,38 @@ func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
} }
return false return false
} }
func TestSIGPROF(t *testing.T) {
switch GOOS {
case "windows", "plan9":
t.Skipf("skipping SIGPROF test on %s", GOOS)
}
t.Parallel()
defer func() {
os.Remove("testp6" + exeSuffix)
os.Remove("libgo6.a")
os.Remove("libgo6.h")
}()
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "libgo6")
cmd.Env = gopathEnv
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a")
if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
argv := cmdToRun("./testp6")
cmd = exec.Command(argv[0], argv[1:]...)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
}
// Copyright 2016 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.
// Test that using the Go profiler in a C program does not crash.
#include <stddef.h>
#include <sys/time.h>
#include "libgo6.h"
int main(int argc, char **argv) {
struct timeval tvstart, tvnow;
int diff;
gettimeofday(&tvstart, NULL);
go_start_profile();
// Busy wait so we have something to profile.
// If we just sleep the profiling signal will never fire.
while (1) {
gettimeofday(&tvnow, NULL);
diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec);
// Profile frequency is 100Hz so we should definitely
// get a signal in 50 milliseconds.
if (diff > 50 * 1000)
break;
}
go_stop_profile();
return 0;
}
// Copyright 2016 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 main
import (
"io/ioutil"
"runtime/pprof"
)
import "C"
//export go_start_profile
func go_start_profile() {
pprof.StartCPUProfile(ioutil.Discard)
}
//export go_stop_profile
func go_stop_profile() {
pprof.StopCPUProfile()
}
func main() {
}
...@@ -146,7 +146,10 @@ func sigdisable(sig uint32) { ...@@ -146,7 +146,10 @@ func sigdisable(sig uint32) {
func sigignore(sig uint32) { func sigignore(sig uint32) {
} }
func resetcpuprofiler(hz int32) { func setProcessCPUProfiler(hz int32) {
}
func setThreadCPUProfiler(hz int32) {
// TODO: Enable profiling interrupts. // TODO: Enable profiling interrupts.
getg().m.profilehz = hz getg().m.profilehz = hz
} }
...@@ -273,7 +273,8 @@ func raisebadsignal(sig uint32) { ...@@ -273,7 +273,8 @@ func raisebadsignal(sig uint32) {
func madvise(addr unsafe.Pointer, n uintptr, flags int32) {} func madvise(addr unsafe.Pointer, n uintptr, flags int32) {}
func munmap(addr unsafe.Pointer, n uintptr) {} func munmap(addr unsafe.Pointer, n uintptr) {}
func resetcpuprofiler(hz int32) {} func setProcessCPUProfiler(hz int32) {}
func setThreadCPUProfiler(hz int32) {}
func sigdisable(uint32) {} func sigdisable(uint32) {}
func sigenable(uint32) {} func sigenable(uint32) {}
func sigignore(uint32) {} func sigignore(uint32) {}
......
...@@ -744,10 +744,7 @@ func profileloop1(param uintptr) uint32 { ...@@ -744,10 +744,7 @@ func profileloop1(param uintptr) uint32 {
} }
} }
var cpuprofilerlock mutex func setProcessCPUProfiler(hz int32) {
func resetcpuprofiler(hz int32) {
lock(&cpuprofilerlock)
if profiletimer == 0 { if profiletimer == 0 {
timer := stdcall3(_CreateWaitableTimerA, 0, 0, 0) timer := stdcall3(_CreateWaitableTimerA, 0, 0, 0)
atomic.Storeuintptr(&profiletimer, timer) atomic.Storeuintptr(&profiletimer, timer)
...@@ -755,8 +752,9 @@ func resetcpuprofiler(hz int32) { ...@@ -755,8 +752,9 @@ func resetcpuprofiler(hz int32) {
stdcall2(_SetThreadPriority, thread, _THREAD_PRIORITY_HIGHEST) stdcall2(_SetThreadPriority, thread, _THREAD_PRIORITY_HIGHEST)
stdcall1(_CloseHandle, thread) stdcall1(_CloseHandle, thread)
} }
unlock(&cpuprofilerlock) }
func setThreadCPUProfiler(hz int32) {
ms := int32(0) ms := int32(0)
due := ^int64(^uint64(1 << 63)) due := ^int64(^uint64(1 << 63))
if hz > 0 { if hz > 0 {
......
...@@ -1879,7 +1879,7 @@ func execute(gp *g, inheritTime bool) { ...@@ -1879,7 +1879,7 @@ func execute(gp *g, inheritTime bool) {
// Check whether the profiler needs to be turned on or off. // Check whether the profiler needs to be turned on or off.
hz := sched.profilehz hz := sched.profilehz
if _g_.m.profilehz != hz { if _g_.m.profilehz != hz {
resetcpuprofiler(hz) setThreadCPUProfiler(hz)
} }
if trace.enabled { if trace.enabled {
...@@ -2780,7 +2780,7 @@ func beforefork() { ...@@ -2780,7 +2780,7 @@ func beforefork() {
// Ensure that we stay on the same M where we disable profiling. // Ensure that we stay on the same M where we disable profiling.
gp.m.locks++ gp.m.locks++
if gp.m.profilehz != 0 { if gp.m.profilehz != 0 {
resetcpuprofiler(0) setThreadCPUProfiler(0)
} }
// This function is called before fork in syscall package. // This function is called before fork in syscall package.
...@@ -2805,7 +2805,7 @@ func afterfork() { ...@@ -2805,7 +2805,7 @@ func afterfork() {
hz := sched.profilehz hz := sched.profilehz
if hz != 0 { if hz != 0 {
resetcpuprofiler(hz) setThreadCPUProfiler(hz)
} }
gp.m.locks-- gp.m.locks--
} }
...@@ -3439,12 +3439,15 @@ func setcpuprofilerate_m(hz int32) { ...@@ -3439,12 +3439,15 @@ func setcpuprofilerate_m(hz int32) {
// Stop profiler on this thread so that it is safe to lock prof. // Stop profiler on this thread so that it is safe to lock prof.
// if a profiling signal came in while we had prof locked, // if a profiling signal came in while we had prof locked,
// it would deadlock. // it would deadlock.
resetcpuprofiler(0) setThreadCPUProfiler(0)
for !atomic.Cas(&prof.lock, 0, 1) { for !atomic.Cas(&prof.lock, 0, 1) {
osyield() osyield()
} }
prof.hz = hz if prof.hz != hz {
setProcessCPUProfiler(hz)
prof.hz = hz
}
atomic.Store(&prof.lock, 0) atomic.Store(&prof.lock, 0)
lock(&sched.lock) lock(&sched.lock)
...@@ -3452,7 +3455,7 @@ func setcpuprofilerate_m(hz int32) { ...@@ -3452,7 +3455,7 @@ func setcpuprofilerate_m(hz int32) {
unlock(&sched.lock) unlock(&sched.lock)
if hz != 0 { if hz != 0 {
resetcpuprofiler(hz) setThreadCPUProfiler(hz)
} }
_g_.m.locks-- _g_.m.locks--
......
...@@ -138,6 +138,11 @@ func sigenable(sig uint32) { ...@@ -138,6 +138,11 @@ func sigenable(sig uint32) {
return return
} }
// SIGPROF is handled specially for profiling.
if sig == _SIGPROF {
return
}
t := &sigtable[sig] t := &sigtable[sig]
if t.flags&_SigNotify != 0 { if t.flags&_SigNotify != 0 {
ensureSigM() ensureSigM()
...@@ -158,6 +163,11 @@ func sigdisable(sig uint32) { ...@@ -158,6 +163,11 @@ func sigdisable(sig uint32) {
return return
} }
// SIGPROF is handled specially for profiling.
if sig == _SIGPROF {
return
}
t := &sigtable[sig] t := &sigtable[sig]
if t.flags&_SigNotify != 0 { if t.flags&_SigNotify != 0 {
ensureSigM() ensureSigM()
...@@ -182,6 +192,11 @@ func sigignore(sig uint32) { ...@@ -182,6 +192,11 @@ func sigignore(sig uint32) {
return return
} }
// SIGPROF is handled specially for profiling.
if sig == _SIGPROF {
return
}
t := &sigtable[sig] t := &sigtable[sig]
if t.flags&_SigNotify != 0 { if t.flags&_SigNotify != 0 {
atomic.Store(&handlingSig[sig], 0) atomic.Store(&handlingSig[sig], 0)
...@@ -189,7 +204,31 @@ func sigignore(sig uint32) { ...@@ -189,7 +204,31 @@ func sigignore(sig uint32) {
} }
} }
func resetcpuprofiler(hz int32) { // setProcessCPUProfiler is called when the profiling timer changes.
// It is called with prof.lock held. hz is the new timer, and is 0 if
// profiling is being disabled. Enable or disable the signal as
// required for -buildmode=c-archive.
func setProcessCPUProfiler(hz int32) {
if hz != 0 {
// Enable the Go signal handler if not enabled.
if atomic.Cas(&handlingSig[_SIGPROF], 0, 1) {
atomic.Storeuintptr(&fwdSig[_SIGPROF], getsig(_SIGPROF))
setsig(_SIGPROF, funcPC(sighandler))
}
} else {
// If the Go signal handler should be disabled by default,
// disable it if it is enabled.
if !sigInstallGoHandler(_SIGPROF) {
if atomic.Cas(&handlingSig[_SIGPROF], 1, 0) {
setsig(_SIGPROF, atomic.Loaduintptr(&fwdSig[_SIGPROF]))
}
}
}
}
// setThreadCPUProfiler makes any thread-specific changes required to
// implement profiling at a rate of hz.
func setThreadCPUProfiler(hz int32) {
var it itimerval var it itimerval
if hz == 0 { if hz == 0 {
setitimer(_ITIMER_PROF, &it, nil) setitimer(_ITIMER_PROF, &it, nil)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment