diff --git a/misc/pprof b/misc/pprof index 777a45cb7c..2fe56503c9 100755 --- a/misc/pprof +++ b/misc/pprof @@ -1,7 +1,7 @@ #! /usr/bin/env perl # This is a copy of http://google-perftools.googlecode.com/svn/trunk/src/pprof -# with local modifications to handle generation of SVG images and +# with local modifications to handle generation of SVG images and # the Go-style pprof paths. These modifications will probably filter # back into the official source before long. # It's convenient to have a copy here because we need just the one @@ -9,11 +9,11 @@ # Copyright (c) 1998-2007, Google Inc. # All rights reserved. -# +# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: -# +# # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above @@ -23,7 +23,7 @@ # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. -# +# # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -1234,6 +1234,13 @@ sub Disassemble { my $cmd = sprintf("$objdump -C -d -l --no-show-raw-insn " . "--start-address=0x$start_addr " . "--stop-address=0x$end_addr $prog"); + + if (system("$objdump --help >/dev/null 2>&1") != 0) { + # objdump must not exist. Fall back to go tool objdump. + $objdump = "go tool objdump"; + $cmd = "$objdump $prog 0x$start_addr 0x$end_addr"; + } + open(OBJDUMP, "$cmd |") || error("$objdump: $!\n"); my @result = (); my $filename = ""; @@ -1305,10 +1312,10 @@ sub PrintListing { my $cumulative = shift; my $list_opts = shift; my $html = shift; - + my $output = \*STDOUT; my $fname = ""; - + if ($html) { # Arrange to write the output to a temporary file @@ -1323,7 +1330,7 @@ sub PrintListing { printf $output ("
%s
Total: %s %s
\n", $main::prog, Unparse($total), Units()); } - + my $listed = 0; foreach my $lib (@{$libs}) { my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts); @@ -2221,7 +2228,7 @@ function handleMouseWheel(evt) { z = 0.1; if(z > 10.0) z = 10.0; - + var g = svgDoc.getElementById("viewport"); var p = getEventPoint(evt); @@ -4391,6 +4398,12 @@ sub MapToSymbols { $cmd = "$addr2line --demangle -f -C -e $image"; } + if (system("$addr2line --help >/dev/null 2>&1") != 0) { + # addr2line must not exist. Fall back to go tool addr2line. + $addr2line = "go tool addr2line"; + $cmd = "$addr2line $image"; + } + # If "addr2line" isn't installed on the system at all, just use # nm to get what info we can (function names, but not line numbers). if (system("$addr2line --help >/dev/null 2>&1") != 0) { @@ -4434,7 +4447,7 @@ sub MapToSymbols { if ($debug) { print("----\n"); system("cat $main::tmpfile_sym"); - print("----\n"); + print("---- $cmd\n"); system("$cmd <$main::tmpfile_sym"); print("----\n"); } @@ -4544,7 +4557,7 @@ sub ShortFunctionName { # Trim overly long symbols found in disassembler output sub CleanDisassembly { my $d = shift; - while ($d =~ s/(?]*>/$1/g) { } # Remove template arguments return $d; } @@ -4625,7 +4638,7 @@ sub ConfigureTool { my $dirname = $`; # this is everything up to and including the last slash if (-x "$dirname$tool") { $path = "$dirname$tool"; - } else { + } else { $path = $tool; } } diff --git a/src/cmd/addr2line/main.c b/src/cmd/addr2line/main.c new file mode 100644 index 0000000000..6b2fe5dfe1 --- /dev/null +++ b/src/cmd/addr2line/main.c @@ -0,0 +1,68 @@ +// Copyright 2012 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. + +/* + * addr2line simulation - only enough to make pprof work on Macs + */ + +#include +#include +#include +#include + +void +usage(void) +{ + fprint(2, "usage: addr2line binary\n"); + fprint(2, "reads addresses from standard input and writes two lines for each:\n"); + fprint(2, "\tfunction name\n"); + fprint(2, "\tfile:line\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int fd; + char *p; + uvlong pc; + Symbol s; + Fhdr fhdr; + Biobuf bin, bout; + char file[1024]; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + if(argc != 1) + usage(); + + fd = open(argv[0], OREAD); + if(fd < 0) + sysfatal("open %s: %r", argv[0]); + if(crackhdr(fd, &fhdr) <= 0) + sysfatal("crackhdr: %r"); + machbytype(fhdr.type); + if(syminit(fd, &fhdr) <= 0) + sysfatal("syminit: %r"); + + Binit(&bin, 0, OREAD); + Binit(&bout, 1, OWRITE); + for(;;) { + p = Brdline(&bin, '\n'); + if(p == nil) + break; + p[Blinelen(&bin)-1] = '\0'; + pc = strtoull(p, 0, 16); + if(!findsym(pc, CTEXT, &s)) + s.name = "??"; + if(!fileline(file, sizeof file, pc)) + strcpy(file, "??:0"); + Bprint(&bout, "%s\n%s\n", s.name, file); + } + Bflush(&bout); + exits(0); +} diff --git a/src/cmd/dist/build.c b/src/cmd/dist/build.c index 5664c1890a..66b5c1f183 100644 --- a/src/cmd/dist/build.c +++ b/src/cmd/dist/build.c @@ -59,7 +59,7 @@ int find(char *p, char **l, int n) { int i; - + for(i=0; i 0) bwriteb(&b, &bmore); @@ -249,14 +249,14 @@ findgoversion(void) done: p = btake(&b); - - + + bfree(&b); bfree(&path); bfree(&bmore); bfree(&branch); vfree(&tags); - + return p; } @@ -325,7 +325,7 @@ setup(void) xremoveall(p); xmkdirall(p); } - + // Create object directory. // We keep it in pkg/ so that all the generated binaries // are in one tree. If pkg/obj/libgc.a exists, it is a dreg from @@ -393,7 +393,7 @@ static char *proto_gccargs[] = { static Vec gccargs; // deptab lists changes to the default dependencies for a given prefix. -// deps ending in /* read the whole directory; deps beginning with - +// deps ending in /* read the whole directory; deps beginning with - // exclude files with that prefix. static struct { char *prefix; // prefix of target @@ -559,7 +559,7 @@ install(char *dir) vinit(&clean); vinit(&lib); vinit(&extra); - + // path = full path to dir. bpathf(&path, "%s/src/%s", goroot, dir); name = lastelem(dir); @@ -599,7 +599,7 @@ install(char *dir) exe = ""; if(streq(gohostos, "windows")) exe = ".exe"; - + // Start final link command line. // Note: code below knows that link.p[targ] is the target. if(islib) { @@ -696,13 +696,13 @@ install(char *dir) } files.len = n; continue; - } + } vadd(&files, p); } } } vuniq(&files); - + // Convert to absolute paths. for(i=0; i ttarg) stale = 1; - + if(!stale) goto out; @@ -792,9 +792,9 @@ install(char *dir) copy(bpathf(&b, "%s/zasm_GOOS_GOARCH.h", workdir), bpathf(&b1, "%s/zasm_%s_%s.h", bstr(&path), goos, goarch), 0); } - + // Generate .c files from .goc files. - if(streq(dir, "pkg/runtime")) { + if(streq(dir, "pkg/runtime")) { for(i=0; i 1) @@ -833,13 +833,13 @@ install(char *dir) vadd(&compile, "-m32"); if(streq(dir, "lib9")) vadd(&compile, "-DPLAN9PORT"); - + vadd(&compile, "-I"); vadd(&compile, bpathf(&b, "%s/include", goroot)); - + vadd(&compile, "-I"); vadd(&compile, bstr(&path)); - + // lib9/goos.c gets the default constants hard-coded. if(streq(name, "goos.c")) { vadd(&compile, bprintf(&b, "-DGOOS=\"%s\"", goos)); @@ -849,7 +849,7 @@ install(char *dir) vadd(&compile, bprintf(&b, "-DGOROOT=\"%s\"", bstr(&b1))); vadd(&compile, bprintf(&b, "-DGOVERSION=\"%s\"", goversion)); } - + // gc/lex.c records the GOEXPERIMENT setting used during the build. if(streq(name, "lex.c")) { xgetenv(&b, "GOEXPERIMENT"); @@ -867,7 +867,7 @@ install(char *dir) vadd(&compile, workdir); vadd(&compile, bprintf(&b, "-DGOOS_%s", goos)); vadd(&compile, bprintf(&b, "-DGOARCH_%s", goarch)); - } + } bpathf(&b, "%s/%s", workdir, lastelem(files.p[i])); doclean = 1; @@ -893,7 +893,7 @@ install(char *dir) vadd(&clean, bstr(&b)); } bgwait(); - + if(isgo) { // The last loop was compiling individual files. // Hand the Go files to the compiler en masse. @@ -905,16 +905,16 @@ install(char *dir) vadd(&compile, bstr(&b)); vadd(&clean, bstr(&b)); vadd(&link, bstr(&b)); - + vadd(&compile, "-p"); if(hasprefix(dir, "pkg/")) vadd(&compile, dir+4); else vadd(&compile, "main"); - + if(streq(dir, "pkg/runtime")) vadd(&compile, "-+"); - + vcopy(&compile, go.p, go.len); runv(nil, bstr(&path), CheckExit, &compile); @@ -980,7 +980,7 @@ shouldbuild(char *file, char *dir) int i, j, ret; Buf b; Vec lines, fields; - + // Check file name for GOOS or GOARCH. name = lastelem(file); for(i=0; i 1) xprintf("cp %s %s\n", src, dst); @@ -1070,11 +1070,13 @@ static char *buildorder[] = { "lib9", "libbio", "libmach", - + "misc/pprof", + "cmd/addr2line", "cmd/cov", "cmd/nm", + "cmd/objdump", "cmd/pack", "cmd/prof", @@ -1122,12 +1124,12 @@ static char *buildorder[] = { "pkg/go/scanner", "pkg/go/ast", "pkg/go/parser", - "pkg/go/build", "pkg/os/exec", "pkg/net/url", "pkg/text/template/parse", "pkg/text/template", "pkg/go/doc", + "pkg/go/build", "cmd/go", }; @@ -1147,11 +1149,13 @@ static char *cleantab[] = { "cmd/8c", "cmd/8g", "cmd/8l", + "cmd/addr2line", "cmd/cc", "cmd/cov", "cmd/gc", "cmd/go", "cmd/nm", + "cmd/objdump", "cmd/pack", "cmd/prof", "lib9", @@ -1204,11 +1208,11 @@ clean(void) int i, j, k; Buf b, path; Vec dir; - + binit(&b); binit(&path); vinit(&dir); - + for(i=0; i 0) usage(); - + xprintf(format, "GOROOT", goroot); xprintf(format, "GOBIN", gobin); xprintf(format, "GOARCH", goarch); @@ -1346,7 +1350,7 @@ cmdbootstrap(int argc, char **argv) clean(); goversion = findgoversion(); setup(); - + // For the main bootstrap, building for host os/arch. oldgoos = goos; oldgoarch = goarch; @@ -1356,7 +1360,7 @@ cmdbootstrap(int argc, char **argv) gochar = gohostchar; xsetenv("GOARCH", goarch); xsetenv("GOOS", goos); - + for(i=0; i +#include +#include +#include + +void +usage(void) +{ + fprint(2, "usage: objdump binary start stop\n"); + fprint(2, "Disassembles binary from PC start up to stop.\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int fd, n; + uvlong pc, start, stop; + Fhdr fhdr; + Biobuf bout; + char buf[1024]; + Map *text; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + if(argc != 3) + usage(); + start = strtoull(argv[1], 0, 16); + stop = strtoull(argv[2], 0, 16); + + fd = open(argv[0], OREAD); + if(fd < 0) + sysfatal("open %s: %r", argv[0]); + if(crackhdr(fd, &fhdr) <= 0) + sysfatal("crackhdr: %r"); + machbytype(fhdr.type); + if(syminit(fd, &fhdr) <= 0) + sysfatal("syminit: %r"); + text = loadmap(nil, fd, &fhdr); + if(text == nil) + sysfatal("loadmap: %r"); + + Binit(&bout, 1, OWRITE); + for(pc=start; pcdas(text, pc, 0, buf, sizeof buf); + Bprint(&bout, " %llx: %s\n", pc, buf); + n = machdata->instsize(text, pc); + if(n <= 0) + break; + pc += n; + } + Bflush(&bout); + exits(0); +} diff --git a/src/libmach/8db.c b/src/libmach/8db.c index 3101e13730..ce1b4ddd76 100644 --- a/src/libmach/8db.c +++ b/src/libmach/8db.c @@ -1,11 +1,11 @@ // Inferno libmach/8db.c // http://code.google.com/p/inferno-os/source/browse/utils/libmach/8db.c // -// Copyright © 1994-1999 Lucent Technologies Inc. -// Power PC support Copyright © 1995-2004 C H Forsyth (forsyth@terzarima.net). -// Portions Copyright © 1997-1999 Vita Nuova Limited. -// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com). -// Revisions Copyright © 2000-2004 Lucent Technologies Inc. and others. +// Copyright © 1994-1999 Lucent Technologies Inc. +// Power PC support Copyright © 1995-2004 C H Forsyth (forsyth@terzarima.net). +// Portions Copyright © 1997-1999 Vita Nuova Limited. +// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com). +// Revisions Copyright © 2000-2004 Lucent Technologies Inc. and others. // Portions Copyright © 2009 The Go Authors. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -2088,18 +2088,23 @@ immediate(Instr *ip, vlong val) static void pea(Instr *ip) { + int base; + + base = ip->base; + if(base >= 0 && (ip->rex & REXB)) + base += 8; + if (ip->mod == 3) { if (ip->osize == 'B') bprint(ip, (ip->rex & REXB? breg64: breg)[(uchar)ip->base]); - else if(ip->rex & REXB) - bprint(ip, "%s%s", ANAME(ip), reg[ip->base+8]); else - bprint(ip, "%s%s", ANAME(ip), reg[(uchar)ip->base]); + bprint(ip, "%s%s", ANAME(ip), reg[base]); return; } + if (ip->segment) bprint(ip, ip->segment); - if (ip->asize == 'E' && ip->base == SP) + if (ip->asize == 'E' && base == SP) plocal(ip); else { if (ip->base < 0) diff --git a/src/pkg/runtime/lock_futex.c b/src/pkg/runtime/lock_futex.c index 6ec4aee7bf..b4465bff18 100644 --- a/src/pkg/runtime/lock_futex.c +++ b/src/pkg/runtime/lock_futex.c @@ -118,8 +118,12 @@ runtime·notewakeup(Note *n) void runtime·notesleep(Note *n) { + if(m->profilehz > 0) + runtime·setprof(false); while(runtime·atomicload(&n->key) == 0) runtime·futexsleep(&n->key, 0, -1); + if(m->profilehz > 0) + runtime·setprof(true); } void @@ -135,14 +139,18 @@ runtime·notetsleep(Note *n, int64 ns) if(runtime·atomicload(&n->key) != 0) return; + if(m->profilehz > 0) + runtime·setprof(false); deadline = runtime·nanotime() + ns; for(;;) { runtime·futexsleep(&n->key, 0, ns); if(runtime·atomicload(&n->key) != 0) - return; + break; now = runtime·nanotime(); if(now >= deadline) - return; + break; ns = deadline - now; } + if(m->profilehz > 0) + runtime·setprof(true); } diff --git a/src/pkg/runtime/lock_sema.c b/src/pkg/runtime/lock_sema.c index 28d2c3281e..1d9c37fdb6 100644 --- a/src/pkg/runtime/lock_sema.c +++ b/src/pkg/runtime/lock_sema.c @@ -154,7 +154,11 @@ runtime·notesleep(Note *n) return; } // Queued. Sleep. + if(m->profilehz > 0) + runtime·setprof(false); runtime·semasleep(-1); + if(m->profilehz > 0) + runtime·setprof(true); } void @@ -178,12 +182,16 @@ runtime·notetsleep(Note *n, int64 ns) return; } + if(m->profilehz > 0) + runtime·setprof(false); deadline = runtime·nanotime() + ns; for(;;) { // Registered. Sleep. if(runtime·semasleep(ns) >= 0) { // Acquired semaphore, semawakeup unregistered us. // Done. + if(m->profilehz > 0) + runtime·setprof(true); return; } @@ -196,6 +204,9 @@ runtime·notetsleep(Note *n, int64 ns) ns = deadline - now; } + if(m->profilehz > 0) + runtime·setprof(true); + // Deadline arrived. Still registered. Semaphore not acquired. // Want to give up and return, but have to unregister first, // so that any notewakeup racing with the return does not diff --git a/src/pkg/runtime/os_darwin.h b/src/pkg/runtime/os_darwin.h index 071a547177..eb5d2daa38 100644 --- a/src/pkg/runtime/os_darwin.h +++ b/src/pkg/runtime/os_darwin.h @@ -38,4 +38,7 @@ void runtime·raisesigpipe(void); #define NSIG 32 #define SI_USER 0 /* empirically true, but not what headers say */ +#define SIG_BLOCK 1 +#define SIG_UNBLOCK 2 #define SIG_SETMASK 3 + diff --git a/src/pkg/runtime/os_freebsd.h b/src/pkg/runtime/os_freebsd.h index da1d8de2eb..5e8de5434a 100644 --- a/src/pkg/runtime/os_freebsd.h +++ b/src/pkg/runtime/os_freebsd.h @@ -9,7 +9,6 @@ struct sigaction; void runtime·sigaction(int32, struct sigaction*, struct sigaction*); void runtime·sigprocmask(Sigset *, Sigset *); void runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool); -void runtiem·setitimerval(int32, Itimerval*, Itimerval*); void runtime·setitimer(int32, Itimerval*, Itimerval*); int32 runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr); diff --git a/src/pkg/runtime/os_netbsd.h b/src/pkg/runtime/os_netbsd.h index 67c58ecb2a..4ecf78d882 100644 --- a/src/pkg/runtime/os_netbsd.h +++ b/src/pkg/runtime/os_netbsd.h @@ -12,7 +12,6 @@ void runtime·sigaltstack(Sigaltstack*, Sigaltstack*); void runtime·sigaction(int32, struct sigaction*, struct sigaction*); void runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool); void runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp); -void runtime·setitimerval(int32, Itimerval*, Itimerval*); void runtime·setitimer(int32, Itimerval*, Itimerval*); int32 runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr); diff --git a/src/pkg/runtime/os_openbsd.h b/src/pkg/runtime/os_openbsd.h index 67c58ecb2a..4ecf78d882 100644 --- a/src/pkg/runtime/os_openbsd.h +++ b/src/pkg/runtime/os_openbsd.h @@ -12,7 +12,6 @@ void runtime·sigaltstack(Sigaltstack*, Sigaltstack*); void runtime·sigaction(int32, struct sigaction*, struct sigaction*); void runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool); void runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp); -void runtime·setitimerval(int32, Itimerval*, Itimerval*); void runtime·setitimer(int32, Itimerval*, Itimerval*); int32 runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr); diff --git a/src/pkg/runtime/pprof/pprof.go b/src/pkg/runtime/pprof/pprof.go index 099bb6a92f..f67e8a8f9a 100644 --- a/src/pkg/runtime/pprof/pprof.go +++ b/src/pkg/runtime/pprof/pprof.go @@ -20,8 +20,8 @@ import ( "text/tabwriter" ) -// BUG(rsc): CPU profiling is broken on OS X, due to an Apple kernel bug. -// For details, see http://code.google.com/p/go/source/detail?r=35b716c94225. +// BUG(rsc): A bug in the OS X Snow Leopard 64-bit kernel prevents +// CPU profiling from giving accurate results on that system. // A Profile is a collection of stack traces showing the call sequences // that led to instances of a particular event, such as allocation. @@ -156,7 +156,7 @@ func (p *Profile) Count() int { } // Add adds the current execution stack to the profile, associated with value. -// Add stores value in an internal map, so value must be suitable for use as +// Add stores value in an internal map, so value must be suitable for use as // a map key and will not be garbage collected until the corresponding // call to Remove. Add panics if the profile already contains a stack for value. // diff --git a/src/pkg/runtime/pprof/pprof_test.go b/src/pkg/runtime/pprof/pprof_test.go index 5f128c01cf..994ec9dde4 100644 --- a/src/pkg/runtime/pprof/pprof_test.go +++ b/src/pkg/runtime/pprof/pprof_test.go @@ -7,6 +7,7 @@ package pprof_test import ( "bytes" "hash/crc32" + "os/exec" "runtime" . "runtime/pprof" "strings" @@ -17,8 +18,15 @@ import ( func TestCPUProfile(t *testing.T) { switch runtime.GOOS { case "darwin": - // see Apple Bug Report #9177434 (copied into change description) - return + out, err := exec.Command("uname", "-a").CombinedOutput() + if err != nil { + t.Fatal(err) + } + vers := string(out) + t.Logf("uname -a: %v", vers) + if strings.Contains(vers, "Darwin Kernel Version 10.8.0") && strings.Contains(vers, "root:xnu-1504.15.3~1/RELEASE_X86_64") { + t.Logf("skipping test on known-broken kernel (64-bit Snow Leopard)") + } case "plan9": // unimplemented return diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c index d94bec8855..ddac048a00 100644 --- a/src/pkg/runtime/proc.c +++ b/src/pkg/runtime/proc.c @@ -338,7 +338,7 @@ mcommoninit(M *m) m->mcache = runtime·allocmcache(); runtime·callers(1, m->createstack, nelem(m->createstack)); - + // Add to runtime·allm so garbage collector doesn't free m // when it is just in a register or thread-local storage. m->alllink = runtime·allm; @@ -728,7 +728,6 @@ runtime·mstart(void) // so other calls can reuse this stack space. runtime·gosave(&m->g0->sched); m->g0->sched.pc = (void*)-1; // make sure it is never used - runtime·asminit(); runtime·minit(); schedule(nil); @@ -916,6 +915,9 @@ runtime·entersyscall(void) { uint32 v; + if(m->profilehz > 0) + runtime·setprof(false); + // Leave SP around for gc and traceback. runtime·gosave(&g->sched); g->gcsp = g->sched.sp; @@ -979,6 +981,9 @@ runtime·exitsyscall(void) // Garbage collector isn't running (since we are), // so okay to clear gcstack. g->gcstack = nil; + + if(m->profilehz > 0) + runtime·setprof(true); return; } diff --git a/src/pkg/runtime/runtime.h b/src/pkg/runtime/runtime.h index 3b0f505e72..f2669fdb7e 100644 --- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -730,3 +730,13 @@ bool runtime·showframe(Func*); void runtime·ifaceE2I(struct InterfaceType*, Eface, Iface*); uintptr runtime·memlimit(void); + +// If appropriate, ask the operating system to control whether this +// thread should receive profiling signals. This is only necessary on OS X. +// An operating system should not deliver a profiling signal to a +// thread that is not actually executing (what good is that?), but that's +// what OS X prefers to do. When profiling is turned on, we mask +// away the profiling signal when threads go to sleep, so that OS X +// is forced to deliver the signal to a thread that's actually running. +// This is a no-op on other systems. +void runtime·setprof(bool); diff --git a/src/pkg/runtime/signal_darwin_386.c b/src/pkg/runtime/signal_darwin_386.c index 1844f68a63..9e986352b4 100644 --- a/src/pkg/runtime/signal_darwin_386.c +++ b/src/pkg/runtime/signal_darwin_386.c @@ -40,7 +40,8 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp) r = &mc->ss; if(sig == SIGPROF) { - runtime·sigprof((uint8*)r->eip, (uint8*)r->esp, nil, gp); + if(gp != m->g0 && gp != m->gsignal) + runtime·sigprof((uint8*)r->eip, (uint8*)r->esp, nil, gp); return; } @@ -58,7 +59,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp) if(pc[0] == 0xF6 || pc[0] == 0xF7) info->si_code = FPE_INTDIV; } - + // Make it look like a call to the signal func. // Have to pass arguments out of band since // augmenting the stack frame would break diff --git a/src/pkg/runtime/signal_darwin_amd64.c b/src/pkg/runtime/signal_darwin_amd64.c index 32c73081c1..d9c5f48e7c 100644 --- a/src/pkg/runtime/signal_darwin_amd64.c +++ b/src/pkg/runtime/signal_darwin_amd64.c @@ -48,7 +48,8 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp) r = &mc->ss; if(sig == SIGPROF) { - runtime·sigprof((uint8*)r->rip, (uint8*)r->rsp, nil, gp); + if(gp != m->g0 && gp != m->gsignal) + runtime·sigprof((uint8*)r->rip, (uint8*)r->rsp, nil, gp); return; } @@ -68,7 +69,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp) if(pc[0] == 0xF6 || pc[0] == 0xF7) info->si_code = FPE_INTDIV; } - + // Make it look like a call to the signal func. // Have to pass arguments out of band since // augmenting the stack frame would break @@ -77,7 +78,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp) gp->sigcode0 = info->si_code; gp->sigcode1 = (uintptr)info->si_addr; gp->sigpc = r->rip; - + // Only push runtime·sigpanic if r->rip != 0. // If r->rip == 0, probably panicked because of a // call to a nil func. Not pushing that onto sp will @@ -92,7 +93,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp) r->rip = (uintptr)runtime·sigpanic; return; } - + if(info->si_code == SI_USER || (t->flags & SigNotify)) if(runtime·sigsend(sig)) return; diff --git a/src/pkg/runtime/signal_unix.c b/src/pkg/runtime/signal_unix.c index 13708415b9..0b9d2a55a1 100644 --- a/src/pkg/runtime/signal_unix.c +++ b/src/pkg/runtime/signal_unix.c @@ -55,17 +55,17 @@ void runtime·resetcpuprofiler(int32 hz) { Itimerval it; - + runtime·memclr((byte*)&it, sizeof it); if(hz == 0) { runtime·setitimer(ITIMER_PROF, &it, nil); - runtime·setsig(SIGPROF, SIG_IGN, true); + runtime·setprof(false); } else { - runtime·setsig(SIGPROF, runtime·sighandler, true); it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 1000000 / hz; it.it_value = it.it_interval; runtime·setitimer(ITIMER_PROF, &it, nil); + runtime·setprof(true); } m->profilehz = hz; } diff --git a/src/pkg/runtime/sys_darwin_386.s b/src/pkg/runtime/sys_darwin_386.s index e235a8473d..c2dab8931c 100644 --- a/src/pkg/runtime/sys_darwin_386.s +++ b/src/pkg/runtime/sys_darwin_386.s @@ -100,14 +100,14 @@ TEXT runtime·nanotime(SB), 7, $32 IMULL $1000, BX ADDL BX, AX ADCL $0, DX - + MOVL ret+0(FP), DI MOVL AX, 0(DI) MOVL DX, 4(DI) RET TEXT runtime·sigprocmask(SB),7,$0 - MOVL $48, AX + MOVL $329, AX // pthread_sigmask (on OS X, sigprocmask==entire process) INT $0x80 JAE 2(PC) CALL runtime·notok(SB) diff --git a/src/pkg/runtime/sys_darwin_amd64.s b/src/pkg/runtime/sys_darwin_amd64.s index 13882c8524..4b215d04d4 100644 --- a/src/pkg/runtime/sys_darwin_amd64.s +++ b/src/pkg/runtime/sys_darwin_amd64.s @@ -96,7 +96,7 @@ TEXT runtime·sigprocmask(SB),7,$0 MOVL 8(SP), DI MOVQ 16(SP), SI MOVQ 24(SP), DX - MOVL $(0x2000000+48), AX // syscall entry + MOVL $(0x2000000+329), AX // pthread_sigmask (on OS X, sigprocmask==entire process) SYSCALL JCC 2(PC) CALL runtime·notok(SB) diff --git a/src/pkg/runtime/thread_darwin.c b/src/pkg/runtime/thread_darwin.c index d170dfb3d3..556fb67e84 100644 --- a/src/pkg/runtime/thread_darwin.c +++ b/src/pkg/runtime/thread_darwin.c @@ -11,6 +11,7 @@ extern SigTab runtime·sigtab[]; static Sigset sigset_all = ~(Sigset)0; static Sigset sigset_none; +static Sigset sigset_prof = 1<<(SIGPROF-1); static void unimplemented(int8 *name) @@ -23,7 +24,14 @@ unimplemented(int8 *name) int32 runtime·semasleep(int64 ns) { - return runtime·mach_semacquire(m->waitsema, ns); + int32 v; + + if(m->profilehz > 0) + runtime·setprof(false); + v = runtime·mach_semacquire(m->waitsema, ns); + if(m->profilehz > 0) + runtime·setprof(true); + return v; } void @@ -84,7 +92,7 @@ runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void)) runtime·sigprocmask(SIG_SETMASK, &sigset_all, &oset); errno = runtime·bsdthread_create(stk, m, g, fn); runtime·sigprocmask(SIG_SETMASK, &oset, nil); - + if(errno < 0) { runtime·printf("runtime: failed to create new OS thread (have %d already; errno=%d)\n", runtime·mcount(), -errno); runtime·throw("runtime.newosproc"); @@ -98,7 +106,11 @@ runtime·minit(void) // Initialize signal handling. m->gsignal = runtime·malg(32*1024); // OS X wants >=8K, Linux >=2K runtime·signalstack(m->gsignal->stackguard - StackGuard, 32*1024); - runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil); + + if(m->profilehz > 0) + runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil); + else + runtime·sigprocmask(SIG_SETMASK, &sigset_prof, nil); } // Mach IPC, to get at semaphores @@ -434,3 +446,34 @@ runtime·memlimit(void) // the limit. return 0; } + +// NOTE(rsc): On OS X, when the CPU profiling timer expires, the SIGPROF +// signal is not guaranteed to be sent to the thread that was executing to +// cause it to expire. It can and often does go to a sleeping thread, which is +// not interesting for our profile. This is filed Apple Bug Report #9177434, +// copied to http://code.google.com/p/go/source/detail?r=35b716c94225. +// To work around this bug, we disable receipt of the profiling signal on +// a thread while in blocking system calls. This forces the kernel to deliver +// the profiling signal to an executing thread. +// +// The workaround fails on OS X machines using a 64-bit Snow Leopard kernel. +// In that configuration, the kernel appears to want to deliver SIGPROF to the +// sleeping threads regardless of signal mask and, worse, does not deliver +// the signal until the thread wakes up on its own. +// +// If necessary, we can switch to using ITIMER_REAL for OS X and handle +// the kernel-generated SIGALRM by generating our own SIGALRMs to deliver +// to all the running threads. SIGALRM does not appear to be affected by +// the 64-bit Snow Leopard bug. However, as of this writing Mountain Lion +// is in preview, making Snow Leopard two versions old, so it is unclear how +// much effort we need to spend on one buggy kernel. + +// Control whether profiling signal can be delivered to this thread. +void +runtime·setprof(bool on) +{ + if(on) + runtime·sigprocmask(SIG_UNBLOCK, &sigset_prof, nil); + else + runtime·sigprocmask(SIG_BLOCK, &sigset_prof, nil); +} diff --git a/src/pkg/runtime/thread_freebsd.c b/src/pkg/runtime/thread_freebsd.c index 7871827a97..77e8bb3dac 100644 --- a/src/pkg/runtime/thread_freebsd.c +++ b/src/pkg/runtime/thread_freebsd.c @@ -189,3 +189,9 @@ runtime·memlimit(void) return rl.rlim_cur - used; } + +void +runtime·setprof(bool on) +{ + USED(on); +} diff --git a/src/pkg/runtime/thread_linux.c b/src/pkg/runtime/thread_linux.c index d406a71240..6b428440e0 100644 --- a/src/pkg/runtime/thread_linux.c +++ b/src/pkg/runtime/thread_linux.c @@ -228,7 +228,7 @@ runtime·memlimit(void) Rlimit rl; extern byte text[], end[]; uintptr used; - + if(runtime·getrlimit(RLIMIT_AS, &rl) != 0) return 0; if(rl.rlim_cur >= 0x7fffffff) @@ -249,3 +249,9 @@ runtime·memlimit(void) return rl.rlim_cur - used; } + +void +runtime·setprof(bool on) +{ + USED(on); +} diff --git a/src/pkg/runtime/thread_netbsd.c b/src/pkg/runtime/thread_netbsd.c index 7d14e5c68b..62e133c449 100644 --- a/src/pkg/runtime/thread_netbsd.c +++ b/src/pkg/runtime/thread_netbsd.c @@ -207,3 +207,9 @@ runtime·memlimit(void) { return 0; } + +void +runtime·setprof(bool on) +{ + USED(on); +} diff --git a/src/pkg/runtime/thread_openbsd.c b/src/pkg/runtime/thread_openbsd.c index 704d95a3c6..bee0c5755f 100644 --- a/src/pkg/runtime/thread_openbsd.c +++ b/src/pkg/runtime/thread_openbsd.c @@ -207,3 +207,9 @@ runtime·memlimit(void) { return 0; } + +void +runtime·setprof(bool on) +{ + USED(on); +} diff --git a/src/pkg/runtime/thread_plan9.c b/src/pkg/runtime/thread_plan9.c index 7d5c38fc9a..aaed5050bb 100644 --- a/src/pkg/runtime/thread_plan9.c +++ b/src/pkg/runtime/thread_plan9.c @@ -87,7 +87,7 @@ runtime·nanotime(void) // The naïve implementation (without the cached // file descriptor) is roughly four times slower // in 9vx on a 2.16 GHz Intel Core 2 Duo. - + if(fd < 0 && (fd = runtime·open((byte*)"/dev/bintime", OREAD|OCEXEC)) < 0) return 0; if(runtime·pread(fd, b, sizeof b, 0) != sizeof b) @@ -241,3 +241,9 @@ runtime·memlimit(void) { return 0; } + +void +runtime·setprof(bool on) +{ + USED(on); +} diff --git a/src/pkg/runtime/thread_windows.c b/src/pkg/runtime/thread_windows.c index 8feac9711d..8a448bc37c 100644 --- a/src/pkg/runtime/thread_windows.c +++ b/src/pkg/runtime/thread_windows.c @@ -226,7 +226,7 @@ void time·now(int64 sec, int32 usec) { int64 ns; - + ns = runtime·nanotime(); sec = ns / 1000000000LL; usec = ns - sec * 1000000000LL; @@ -431,3 +431,9 @@ runtime·memlimit(void) { return 0; } + +void +runtime·setprof(bool on) +{ + USED(on); +}