diff options
Diffstat (limited to 'src')
71 files changed, 837 insertions, 982 deletions
diff --git a/src/bufio/scan.go b/src/bufio/scan.go index a41451524..364d15961 100644 --- a/src/bufio/scan.go +++ b/src/bufio/scan.go @@ -36,6 +36,7 @@ type Scanner struct { start int // First non-processed byte in buf. end int // End of data in buf. err error // Sticky error. + empties int // Count of successive empty tokens. } // SplitFunc is the signature of the split function used to tokenize the @@ -108,6 +109,8 @@ func (s *Scanner) Text() string { // After Scan returns false, the Err method will return any error that // occurred during scanning, except that if it was io.EOF, Err // will return nil. +// Split panics if the split function returns 100 empty tokens without +// advancing the input. This is a common error mode for scanners. func (s *Scanner) Scan() bool { // Loop until we have a token. for { @@ -125,6 +128,15 @@ func (s *Scanner) Scan() bool { } s.token = token if token != nil { + if s.err == nil || advance > 0 { + s.empties = 0 + } else { + // Returning tokens not advancing input at EOF. + s.empties++ + if s.empties > 100 { + panic("bufio.Scan: 100 empty tokens without progressing") + } + } return true } } @@ -172,6 +184,7 @@ func (s *Scanner) Scan() bool { break } if n > 0 { + s.empties = 0 break } loop++ diff --git a/src/bufio/scan_test.go b/src/bufio/scan_test.go index 1454a8113..eea87cbf7 100644 --- a/src/bufio/scan_test.go +++ b/src/bufio/scan_test.go @@ -455,3 +455,70 @@ func TestEmptyTokens(t *testing.T) { t.Fatal(err) } } + +func loopAtEOFSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if len(data) > 0 { + return 1, data[:1], nil + } + return 0, data, nil +} + +func TestDontLoopForever(t *testing.T) { + s := NewScanner(strings.NewReader("abc")) + s.Split(loopAtEOFSplit) + // Expect a panic + defer func() { + err := recover() + if err == nil { + t.Fatal("should have panicked") + } + if msg, ok := err.(string); !ok || !strings.Contains(msg, "empty tokens") { + panic(err) + } + }() + for count := 0; s.Scan(); count++ { + if count > 1000 { + t.Fatal("looping") + } + } + if s.Err() != nil { + t.Fatal("after scan:", s.Err()) + } +} + +func TestBlankLines(t *testing.T) { + s := NewScanner(strings.NewReader(strings.Repeat("\n", 1000))) + for count := 0; s.Scan(); count++ { + if count > 2000 { + t.Fatal("looping") + } + } + if s.Err() != nil { + t.Fatal("after scan:", s.Err()) + } +} + +type countdown int + +func (c *countdown) split(data []byte, atEOF bool) (advance int, token []byte, err error) { + if *c > 0 { + *c-- + return 1, data[:1], nil + } + return 0, nil, nil +} + +// Check that the looping-at-EOF check doesn't trigger for merely empty tokens. +func TestEmptyLinesOK(t *testing.T) { + c := countdown(10000) + s := NewScanner(strings.NewReader(strings.Repeat("\n", 10000))) + s.Split(c.split) + for s.Scan() { + } + if s.Err() != nil { + t.Fatal("after scan:", s.Err()) + } + if c != 0 { + t.Fatalf("stopped with %d left to process", c) + } +} diff --git a/src/cmd/5g/reg.c b/src/cmd/5g/reg.c index 8e49a2d9c..27d9d3e8b 100644 --- a/src/cmd/5g/reg.c +++ b/src/cmd/5g/reg.c @@ -199,7 +199,7 @@ regopt(Prog *firstp) proginfo(&info, p); // Avoid making variables for direct-called functions. - if(p->as == ABL && p->to.type == D_EXTERN) + if(p->as == ABL && p->to.name == D_EXTERN) continue; bit = mkvar(r, &p->from); diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index d77d56c22..abdd369d7 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -944,6 +944,8 @@ type typeConv struct { // Map from types to incomplete pointers to those types. ptrs map[dwarf.Type][]*Type + // Keys of ptrs in insertion order (deterministic worklist) + ptrKeys []dwarf.Type // Predeclared types. bool ast.Expr @@ -1061,16 +1063,17 @@ func (tr *TypeRepr) Set(repr string, fargs ...interface{}) { func (c *typeConv) FinishType(pos token.Pos) { // Completing one pointer type might produce more to complete. // Keep looping until they're all done. - for len(c.ptrs) > 0 { - for dtype := range c.ptrs { - // Note Type might invalidate c.ptrs[dtype]. - t := c.Type(dtype, pos) - for _, ptr := range c.ptrs[dtype] { - ptr.Go.(*ast.StarExpr).X = t.Go - ptr.C.Set("%s*", t.C) - } - delete(c.ptrs, dtype) + for len(c.ptrKeys) > 0 { + dtype := c.ptrKeys[0] + c.ptrKeys = c.ptrKeys[1:] + + // Note Type might invalidate c.ptrs[dtype]. + t := c.Type(dtype, pos) + for _, ptr := range c.ptrs[dtype] { + ptr.Go.(*ast.StarExpr).X = t.Go + ptr.C.Set("%s*", t.C) } + c.ptrs[dtype] = nil // retain the map key } } @@ -1237,6 +1240,9 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type { // Placeholder initialization; completed in FinishType. t.Go = &ast.StarExpr{} t.C.Set("<incomplete>*") + if _, ok := c.ptrs[dt.Type]; !ok { + c.ptrKeys = append(c.ptrKeys, dt.Type) + } c.ptrs[dt.Type] = append(c.ptrs[dt.Type], t) case *dwarf.QualType: diff --git a/src/cmd/dist/build.c b/src/cmd/dist/build.c index 8fd2e998a..9c81dd8b2 100644 --- a/src/cmd/dist/build.c +++ b/src/cmd/dist/build.c @@ -691,13 +691,6 @@ install(char *dir) bpathf(&final_path, "%s/src/%s", goroot_final, dir); name = lastelem(dir); - // For misc/prof, copy into the tool directory and we're done. - if(hasprefix(dir, "misc/")) { - copyfile(bpathf(&b, "%s/%s", tooldir, name), - bpathf(&b1, "%s/misc/%s", goroot, name), 1); - goto out; - } - // set up gcc command line on first run. if(gccargs.len == 0) { bprintf(&b, "%s %s", defaultcc, defaultcflags); @@ -1328,8 +1321,6 @@ static char *buildorder[] = { "libbio", "liblink", - "misc/pprof", - "cmd/cc", // must be before c "cmd/gc", // must be before g "cmd/%sl", // must be before a, c, g diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c index 2303b442c..523ba37aa 100644 --- a/src/cmd/gc/lex.c +++ b/src/cmd/gc/lex.c @@ -344,8 +344,8 @@ main(int argc, char *argv[]) break; } } - if(j == nelem(debugtab)) - fatal("unknown debug information -d '%s'\n", f[i]); + if(debugtab[j].name == nil) + sysfatal("unknown debug information -d '%s'\n", f[i]); } } diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go index 79a27116a..1dd4314da 100644 --- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1826,7 +1826,15 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error { inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch)) cfile = mkAbs(p.Dir, cfile) - args := stringList(tool(archChar+"c"), "-F", "-V", "-w", "-trimpath", b.work, "-I", objdir, "-I", inc, "-o", ofile, buildCcflags, "-D", "GOOS_"+goos, "-D", "GOARCH_"+goarch, cfile) + warn := []string{"-w"} + if p.usesSwig() { + // When using SWIG, this compiler is only used to + // compile the C files generated by SWIG. + // We don't want warnings. + // See issue 9065 for details. + warn = nil + } + args := stringList(tool(archChar+"c"), "-F", "-V", warn, "-trimpath", b.work, "-I", objdir, "-I", inc, "-o", ofile, buildCcflags, "-D", "GOOS_"+goos, "-D", "GOARCH_"+goarch, cfile) return b.run(p.Dir, p.ImportPath, nil, args) } diff --git a/src/cmd/go/doc.go b/src/cmd/go/doc.go index 314c69bd8..43a315944 100644 --- a/src/cmd/go/doc.go +++ b/src/cmd/go/doc.go @@ -247,7 +247,7 @@ The arguments are space-separated tokens or double-quoted strings passed to the generator as individual arguments when it is run. Quoted strings use Go syntax and are evaluated before execution; a -quoted string appears a single argument to the generator. +quoted string appears as a single argument to the generator. Go generate sets several variables when it runs the generator: @@ -260,7 +260,7 @@ Go generate sets several variables when it runs the generator: $GOPACKAGE The name of the package of the file containing the directive. -Other than variable substition and quoted-string evaluation, no +Other than variable substitution and quoted-string evaluation, no special processing such as "globbing" is performed on the command line. @@ -590,7 +590,7 @@ Usage: Vet runs the Go vet command on the packages named by the import paths. -For more about vet, see 'godoc code.google.com/p/go.tools/cmd/vet'. +For more about vet, see 'godoc golang.org/x/tools/cmd/vet'. For more about specifying packages, see 'go help packages'. To run the vet tool with specific options, run 'go tool vet'. diff --git a/src/cmd/go/generate.go b/src/cmd/go/generate.go index 4227abbe7..a83cce8f7 100644 --- a/src/cmd/go/generate.go +++ b/src/cmd/go/generate.go @@ -58,7 +58,7 @@ Go generate sets several variables when it runs the generator: $GOPACKAGE The name of the package of the file containing the directive. -Other than variable substition and quoted-string evaluation, no +Other than variable substitution and quoted-string evaluation, no special processing such as "globbing" is performed on the command line. diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index e17326442..b71feb7a6 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -383,9 +383,10 @@ func findInternal(path string) (index int, ok bool) { type targetDir int const ( - toRoot targetDir = iota // to bin dir inside package root (default) - toTool // GOROOT/pkg/tool - toBin // GOROOT/bin + toRoot targetDir = iota // to bin dir inside package root (default) + toTool // GOROOT/pkg/tool + toBin // GOROOT/bin + stalePath // the old import path; fail to build ) // goTools is a map of Go program import path to install target directory. @@ -398,10 +399,14 @@ var goTools = map[string]targetDir{ "cmd/nm": toTool, "cmd/objdump": toTool, "cmd/pack": toTool, + "cmd/pprof": toTool, "cmd/yacc": toTool, - "code.google.com/p/go.tools/cmd/cover": toTool, - "code.google.com/p/go.tools/cmd/godoc": toBin, - "code.google.com/p/go.tools/cmd/vet": toTool, + "golang.org/x/tools/cmd/cover": toTool, + "golang.org/x/tools/cmd/godoc": toBin, + "golang.org/x/tools/cmd/vet": toTool, + "code.google.com/p/go.tools/cmd/cover": stalePath, + "code.google.com/p/go.tools/cmd/godoc": stalePath, + "code.google.com/p/go.tools/cmd/vet": stalePath, } // expandScanner expands a scanner.List error into all the errors in the list. @@ -462,6 +467,13 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package } if p.Name == "main" { + // Report an error when the old code.google.com/p/go.tools paths are used. + if goTools[p.ImportPath] == stalePath { + newPath := strings.Replace(p.ImportPath, "code.google.com/p/go.", "golang.org/x/", 1) + e := fmt.Sprintf("the %v command has moved; use %v instead.", p.ImportPath, newPath) + p.Error = &PackageError{Err: e} + return p + } _, elem := filepath.Split(p.Dir) full := buildContext.GOOS + "_" + buildContext.GOARCH + "/" + elem if buildContext.GOOS != toolGOOS || buildContext.GOARCH != toolGOARCH { diff --git a/src/cmd/go/test.bash b/src/cmd/go/test.bash index 2b5230b1a..e0f066f18 100755 --- a/src/cmd/go/test.bash +++ b/src/cmd/go/test.bash @@ -433,20 +433,20 @@ TEST godoc installs into GOBIN d=$(mktemp -d -t testgoXXX) export GOPATH=$d mkdir $d/gobin -GOBIN=$d/gobin ./testgo get code.google.com/p/go.tools/cmd/godoc || ok=false +GOBIN=$d/gobin ./testgo get golang.org/x/tools/cmd/godoc || ok=false if [ ! -x $d/gobin/godoc ]; then echo did not install godoc to '$GOBIN' - GOBIN=$d/gobin ./testgo list -f 'Target: {{.Target}}' code.google.com/p/go.tools/cmd/godoc || true + GOBIN=$d/gobin ./testgo list -f 'Target: {{.Target}}' golang.org/x/tools/cmd/godoc || true ok=false fi TEST godoc installs into GOROOT GOROOT=$(./testgo env GOROOT) rm -f $GOROOT/bin/godoc -./testgo install code.google.com/p/go.tools/cmd/godoc || ok=false +./testgo install golang.org/x/tools/cmd/godoc || ok=false if [ ! -x $GOROOT/bin/godoc ]; then echo did not install godoc to '$GOROOT/bin' - ./testgo list -f 'Target: {{.Target}}' code.google.com/p/go.tools/cmd/godoc || true + ./testgo list -f 'Target: {{.Target}}' golang.org/x/tools/cmd/godoc || true ok=false fi @@ -561,8 +561,8 @@ fi TEST without GOPATH, go get fails d=$(mktemp -d -t testgoXXX) mkdir -p $d/src -if GOPATH= GOROOT=$d ./testgo get -d code.google.com/p/go.codereview/cmd/hgpatch ; then - echo 'go get code.google.com/p/go.codereview/cmd/hgpatch should not succeed with $GOPATH unset' +if GOPATH= GOROOT=$d ./testgo get -d golang.org/x/codereview/cmd/hgpatch ; then + echo 'go get golang.org/x/codereview/cmd/hgpatch should not succeed with $GOPATH unset' ok=false fi rm -rf $d @@ -571,8 +571,8 @@ rm -rf $d TEST with GOPATH=GOROOT, go get fails d=$(mktemp -d -t testgoXXX) mkdir -p $d/src -if GOPATH=$d GOROOT=$d ./testgo get -d code.google.com/p/go.codereview/cmd/hgpatch ; then - echo 'go get code.google.com/p/go.codereview/cmd/hgpatch should not succeed with GOPATH=$GOROOT' +if GOPATH=$d GOROOT=$d ./testgo get -d golang.org/x/codereview/cmd/hgpatch ; then + echo 'go get golang.org/x/codereview/cmd/hgpatch should not succeed with GOPATH=$GOROOT' ok=false fi rm -rf $d @@ -728,7 +728,7 @@ elif ! grep "case-insensitive file name collision" $d/out >/dev/null; then fi TEST go get cover -./testgo get code.google.com/p/go.tools/cmd/cover || ok=false +./testgo get golang.org/x/tools/cmd/cover || ok=false unset GOPATH rm -rf $d diff --git a/src/cmd/go/tool.go b/src/cmd/go/tool.go index 6d26f7a4b..c96161e0f 100644 --- a/src/cmd/go/tool.go +++ b/src/cmd/go/tool.go @@ -53,7 +53,7 @@ func tool(toolName string) string { // Give a nice message if there is no tool with that name. if _, err := os.Stat(toolPath); err != nil { if isInGoToolsRepo(toolName) { - fmt.Fprintf(os.Stderr, "go tool: no such tool %q; to install:\n\tgo get code.google.com/p/go.tools/cmd/%s\n", toolName, toolName) + fmt.Fprintf(os.Stderr, "go tool: no such tool %q; to install:\n\tgo get golang.org/x/tools/cmd/%s\n", toolName, toolName) } else { fmt.Fprintf(os.Stderr, "go tool: no such tool %q\n", toolName) } diff --git a/src/cmd/go/vet.go b/src/cmd/go/vet.go index de7befc61..02ff54b2a 100644 --- a/src/cmd/go/vet.go +++ b/src/cmd/go/vet.go @@ -17,7 +17,7 @@ var cmdVet = &Command{ Long: ` Vet runs the Go vet command on the packages named by the import paths. -For more about vet, see 'godoc code.google.com/p/go.tools/cmd/vet'. +For more about vet, see 'godoc golang.org/x/tools/cmd/vet'. For more about specifying packages, see 'go help packages'. To run the vet tool with specific options, run 'go tool vet'. diff --git a/src/cmd/internal/objfile/disasm.go b/src/cmd/internal/objfile/disasm.go new file mode 100644 index 000000000..1a339c321 --- /dev/null +++ b/src/cmd/internal/objfile/disasm.go @@ -0,0 +1,248 @@ +// Copyright 2014 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 objfile + +import ( + "bufio" + "debug/gosym" + "encoding/binary" + "fmt" + "io" + "regexp" + "sort" + "strings" + "text/tabwriter" + + "cmd/internal/rsc.io/arm/armasm" + "cmd/internal/rsc.io/x86/x86asm" +) + +// Disasm is a disassembler for a given File. +type Disasm struct { + syms []Sym //symbols in file, sorted by address + pcln *gosym.Table // pcln table + text []byte // bytes of text segment (actual instructions) + textStart uint64 // start PC of text + textEnd uint64 // end PC of text + goarch string // GOARCH string + disasm disasmFunc // disassembler function for goarch + byteOrder binary.ByteOrder // byte order for goarch +} + +// Disasm returns a disassembler for the file f. +func (f *File) Disasm() (*Disasm, error) { + syms, err := f.Symbols() + if err != nil { + return nil, err + } + + pcln, err := f.PCLineTable() + if err != nil { + return nil, err + } + + textStart, textBytes, err := f.Text() + if err != nil { + return nil, err + } + + goarch := f.GOARCH() + disasm := disasms[goarch] + byteOrder := byteOrders[goarch] + if disasm == nil || byteOrder == nil { + return nil, fmt.Errorf("unsupported architecture") + } + + // Filter out section symbols, overwriting syms in place. + keep := syms[:0] + for _, sym := range syms { + switch sym.Name { + case "runtime.text", "text", "_text", "runtime.etext", "etext", "_etext": + // drop + default: + keep = append(keep, sym) + } + } + syms = keep + d := &Disasm{ + syms: syms, + pcln: pcln, + text: textBytes, + textStart: textStart, + textEnd: textStart + uint64(len(textBytes)), + goarch: goarch, + disasm: disasm, + byteOrder: byteOrder, + } + + return d, nil +} + +// lookup finds the symbol name containing addr. +func (d *Disasm) lookup(addr uint64) (name string, base uint64) { + i := sort.Search(len(d.syms), func(i int) bool { return addr < d.syms[i].Addr }) + if i > 0 { + s := d.syms[i-1] + if s.Addr != 0 && s.Addr <= addr && addr < s.Addr+uint64(s.Size) { + return s.Name, s.Addr + } + } + return "", 0 +} + +// base returns the final element in the path. +// It works on both Windows and Unix paths, +// regardless of host operating system. +func base(path string) string { + path = path[strings.LastIndex(path, "/")+1:] + path = path[strings.LastIndex(path, `\`)+1:] + return path +} + +// Print prints a disassembly of the file to w. +// If filter is non-nil, the disassembly only includes functions with names matching filter. +// The disassembly only includes functions that overlap the range [start, end). +func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) { + if start < d.textStart { + start = d.textStart + } + if end > d.textEnd { + end = d.textEnd + } + printed := false + bw := bufio.NewWriter(w) + for _, sym := range d.syms { + symStart := sym.Addr + symEnd := sym.Addr + uint64(sym.Size) + if sym.Code != 'T' && sym.Code != 't' || + symStart < d.textStart || + symEnd <= start || end <= symStart || + filter != nil && !filter.MatchString(sym.Name) { + continue + } + if printed { + fmt.Fprintf(bw, "\n") + } + printed = true + + file, _, _ := d.pcln.PCToLine(sym.Addr) + fmt.Fprintf(bw, "TEXT %s(SB) %s\n", sym.Name, file) + + tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0) + if symEnd > end { + symEnd = end + } + code := d.text[:end-d.textStart] + d.Decode(symStart, symEnd, func(pc, size uint64, file string, line int, text string) { + i := pc - d.textStart + fmt.Fprintf(tw, "\t%s:%d\t%#x\t", base(file), line, pc) + if size%4 != 0 || d.goarch == "386" || d.goarch == "amd64" { + // Print instruction as bytes. + fmt.Fprintf(tw, "%x", code[i:i+size]) + } else { + // Print instruction as 32-bit words. + for j := uint64(0); j < size; j += 4 { + if j > 0 { + fmt.Fprintf(tw, " ") + } + fmt.Fprintf(tw, "%08x", d.byteOrder.Uint32(code[i+j:])) + } + } + fmt.Fprintf(tw, "\t%s\n", text) + }) + tw.Flush() + } + bw.Flush() +} + +// Decode disassembles the text segment range [start, end), calling f for each instruction. +func (d *Disasm) Decode(start, end uint64, f func(pc, size uint64, file string, line int, text string)) { + if start < d.textStart { + start = d.textStart + } + if end > d.textEnd { + end = d.textEnd + } + code := d.text[:end-d.textStart] + lookup := d.lookup + for pc := start; pc < end; { + i := pc - d.textStart + text, size := d.disasm(code[i:], pc, lookup) + file, line, _ := d.pcln.PCToLine(pc) + f(pc, uint64(size), file, line, text) + pc += uint64(size) + } +} + +type lookupFunc func(addr uint64) (sym string, base uint64) +type disasmFunc func(code []byte, pc uint64, lookup lookupFunc) (text string, size int) + +func disasm_386(code []byte, pc uint64, lookup lookupFunc) (string, int) { + return disasm_x86(code, pc, lookup, 32) +} + +func disasm_amd64(code []byte, pc uint64, lookup lookupFunc) (string, int) { + return disasm_x86(code, pc, lookup, 64) +} + +func disasm_x86(code []byte, pc uint64, lookup lookupFunc, arch int) (string, int) { + inst, err := x86asm.Decode(code, 64) + var text string + size := inst.Len + if err != nil || size == 0 || inst.Op == 0 { + size = 1 + text = "?" + } else { + text = x86asm.Plan9Syntax(inst, pc, lookup) + } + return text, size +} + +type textReader struct { + code []byte + pc uint64 +} + +func (r textReader) ReadAt(data []byte, off int64) (n int, err error) { + if off < 0 || uint64(off) < r.pc { + return 0, io.EOF + } + d := uint64(off) - r.pc + if d >= uint64(len(r.code)) { + return 0, io.EOF + } + n = copy(data, r.code[d:]) + if n < len(data) { + err = io.ErrUnexpectedEOF + } + return +} + +func disasm_arm(code []byte, pc uint64, lookup lookupFunc) (string, int) { + inst, err := armasm.Decode(code, armasm.ModeARM) + var text string + size := inst.Len + if err != nil || size == 0 || inst.Op == 0 { + size = 4 + text = "?" + } else { + text = armasm.Plan9Syntax(inst, pc, lookup, textReader{code, pc}) + } + return text, size +} + +var disasms = map[string]disasmFunc{ + "386": disasm_386, + "amd64": disasm_amd64, + "arm": disasm_arm, +} + +var byteOrders = map[string]binary.ByteOrder{ + "386": binary.LittleEndian, + "amd64": binary.LittleEndian, + "arm": binary.LittleEndian, + "power64": binary.BigEndian, + "power64le": binary.LittleEndian, +} diff --git a/src/cmd/internal/objfile/elf.go b/src/cmd/internal/objfile/elf.go index 8495fa753..17755b84d 100644 --- a/src/cmd/internal/objfile/elf.go +++ b/src/cmd/internal/objfile/elf.go @@ -8,6 +8,7 @@ package objfile import ( "debug/elf" + "fmt" "os" ) @@ -77,3 +78,27 @@ func (f *elfFile) pcln() (textStart uint64, symtab, pclntab []byte, err error) { } return textStart, symtab, pclntab, nil } + +func (f *elfFile) text() (textStart uint64, text []byte, err error) { + sect := f.elf.Section(".text") + if sect == nil { + return 0, nil, fmt.Errorf("text section not found") + } + textStart = sect.Addr + text, err = sect.Data() + return +} + +func (f *elfFile) goarch() string { + switch f.elf.Machine { + case elf.EM_386: + return "386" + case elf.EM_X86_64: + return "amd64" + case elf.EM_ARM: + return "arm" + case elf.EM_PPC64: + return "power64" + } + return "" +} diff --git a/src/cmd/internal/objfile/goobj.go b/src/cmd/internal/objfile/goobj.go index a4f49ebe4..a1d773023 100644 --- a/src/cmd/internal/objfile/goobj.go +++ b/src/cmd/internal/objfile/goobj.go @@ -79,3 +79,15 @@ func (f *goobjFile) symbols() ([]Sym, error) { func (f *goobjFile) pcln() (textStart uint64, symtab, pclntab []byte, err error) { return 0, nil, nil, fmt.Errorf("pcln not available in go object file") } + +// text does not make sense for Go object files, because +// each function has a separate section. +func (f *goobjFile) text() (textStart uint64, text []byte, err error) { + return 0, nil, fmt.Errorf("text not available in go object file") +} + +// goarch makes sense but is not exposed in debug/goobj's API, +// and we don't need it yet for any users of internal/objfile. +func (f *goobjFile) goarch() string { + return "GOARCH unimplemented for debug/goobj files" +} diff --git a/src/cmd/internal/objfile/macho.go b/src/cmd/internal/objfile/macho.go index f845792ff..7dd84a339 100644 --- a/src/cmd/internal/objfile/macho.go +++ b/src/cmd/internal/objfile/macho.go @@ -85,6 +85,30 @@ func (f *machoFile) pcln() (textStart uint64, symtab, pclntab []byte, err error) return textStart, symtab, pclntab, nil } +func (f *machoFile) text() (textStart uint64, text []byte, err error) { + sect := f.macho.Section("__text") + if sect == nil { + return 0, nil, fmt.Errorf("text section not found") + } + textStart = sect.Addr + text, err = sect.Data() + return +} + +func (f *machoFile) goarch() string { + switch f.macho.Cpu { + case macho.Cpu386: + return "386" + case macho.CpuAmd64: + return "amd64" + case macho.CpuArm: + return "arm" + case macho.CpuPpc64: + return "power64" + } + return "" +} + type uint64s []uint64 func (x uint64s) Len() int { return len(x) } diff --git a/src/cmd/internal/objfile/objfile.go b/src/cmd/internal/objfile/objfile.go index 09fa63e60..9227ef387 100644 --- a/src/cmd/internal/objfile/objfile.go +++ b/src/cmd/internal/objfile/objfile.go @@ -9,11 +9,14 @@ import ( "debug/gosym" "fmt" "os" + "sort" ) type rawFile interface { symbols() (syms []Sym, err error) pcln() (textStart uint64, symtab, pclntab []byte, err error) + text() (textStart uint64, text []byte, err error) + goarch() string } // A File is an opened executable file. @@ -60,9 +63,20 @@ func (f *File) Close() error { } func (f *File) Symbols() ([]Sym, error) { - return f.raw.symbols() + syms, err := f.raw.symbols() + if err != nil { + return nil, err + } + sort.Sort(byAddr(syms)) + return syms, nil } +type byAddr []Sym + +func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr } +func (x byAddr) Len() int { return len(x) } +func (x byAddr) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + func (f *File) PCLineTable() (*gosym.Table, error) { textStart, symtab, pclntab, err := f.raw.pcln() if err != nil { @@ -70,3 +84,11 @@ func (f *File) PCLineTable() (*gosym.Table, error) { } return gosym.NewTable(symtab, gosym.NewLineTable(pclntab, textStart)) } + +func (f *File) Text() (uint64, []byte, error) { + return f.raw.text() +} + +func (f *File) GOARCH() string { + return f.raw.goarch() +} diff --git a/src/cmd/internal/objfile/pe.go b/src/cmd/internal/objfile/pe.go index 868709eaf..67e59c226 100644 --- a/src/cmd/internal/objfile/pe.go +++ b/src/cmd/internal/objfile/pe.go @@ -133,6 +133,25 @@ func (f *peFile) pcln() (textStart uint64, symtab, pclntab []byte, err error) { return textStart, symtab, pclntab, nil } +func (f *peFile) text() (textStart uint64, text []byte, err error) { + var imageBase uint64 + switch oh := f.pe.OptionalHeader.(type) { + case *pe.OptionalHeader32: + imageBase = uint64(oh.ImageBase) + case *pe.OptionalHeader64: + imageBase = oh.ImageBase + default: + return 0, nil, fmt.Errorf("pe file format not recognized") + } + sect := f.pe.Section(".text") + if sect == nil { + return 0, nil, fmt.Errorf("text section not found") + } + textStart = imageBase + uint64(sect.VirtualAddress) + text, err = sect.Data() + return +} + func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { for _, s := range f.Symbols { if s.Name != name { @@ -168,3 +187,15 @@ func loadPETable(f *pe.File, sname, ename string) ([]byte, error) { } return data[ssym.Value:esym.Value], nil } + +func (f *peFile) goarch() string { + // Not sure how to get the info we want from PE header. + // Look in symbol table for telltale rt0 symbol. + if _, err := findPESymbol(f.pe, "_rt0_386_windows"); err == nil { + return "386" + } + if _, err := findPESymbol(f.pe, "_rt0_amd64_windows"); err == nil { + return "amd64" + } + return "" +} diff --git a/src/cmd/internal/objfile/plan9obj.go b/src/cmd/internal/objfile/plan9obj.go index 80744f82a..eb6cba5eb 100644 --- a/src/cmd/internal/objfile/plan9obj.go +++ b/src/cmd/internal/objfile/plan9obj.go @@ -88,6 +88,16 @@ func (f *plan9File) pcln() (textStart uint64, symtab, pclntab []byte, err error) return textStart, symtab, pclntab, nil } +func (f *plan9File) text() (textStart uint64, text []byte, err error) { + sect := f.plan9.Section("text") + if sect == nil { + return 0, nil, fmt.Errorf("text section not found") + } + textStart = f.plan9.LoadAddress + f.plan9.HdrSize + text, err = sect.Data() + return +} + func findPlan9Symbol(f *plan9obj.File, name string) (*plan9obj.Sym, error) { syms, err := f.Symbols() if err != nil { @@ -122,3 +132,15 @@ func loadPlan9Table(f *plan9obj.File, sname, ename string) ([]byte, error) { textStart := f.LoadAddress + f.HdrSize return data[ssym.Value-textStart : esym.Value-textStart], nil } + +func (f *plan9File) goarch() string { + switch f.plan9.Magic { + case plan9obj.Magic386: + return "386" + case plan9obj.MagicAMD64: + return "amd64" + case plan9obj.MagicARM: + return "arm" + } + return "" +} diff --git a/src/cmd/objdump/Makefile b/src/cmd/objdump/Makefile deleted file mode 100644 index 1b66c26ba..000000000 --- a/src/cmd/objdump/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -all: x86.go armasm.go - -x86.go: bundle - ./bundle -p main -x x86_ rsc.io/x86/x86asm | gofmt >x86.go - -armasm.go: bundle - ./bundle -p main -x arm_ rsc.io/arm/armasm | gofmt >armasm.go - -bundle: - go build -o bundle code.google.com/p/rsc/cmd/bundle diff --git a/src/cmd/objdump/elf.go b/src/cmd/objdump/elf.go deleted file mode 100644 index 906e90353..000000000 --- a/src/cmd/objdump/elf.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2013 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. - -// Parsing of ELF executables (Linux, FreeBSD, and so on). - -package main - -import ( - "debug/elf" - "os" -) - -func elfSymbols(f *os.File) (syms []Sym, goarch string) { - p, err := elf.NewFile(f) - if err != nil { - errorf("parsing %s: %v", f.Name(), err) - return - } - - elfSyms, err := p.Symbols() - if err != nil { - errorf("parsing %s: %v", f.Name(), err) - return - } - - switch p.Machine { - case elf.EM_X86_64: - goarch = "amd64" - case elf.EM_386: - goarch = "386" - case elf.EM_ARM: - goarch = "arm" - } - - for _, s := range elfSyms { - sym := Sym{Addr: s.Value, Name: s.Name, Size: int64(s.Size), Code: '?'} - switch s.Section { - case elf.SHN_UNDEF: - sym.Code = 'U' - case elf.SHN_COMMON: - sym.Code = 'B' - default: - i := int(s.Section) - if i < 0 || i >= len(p.Sections) { - break - } - sect := p.Sections[i] - switch sect.Flags & (elf.SHF_WRITE | elf.SHF_ALLOC | elf.SHF_EXECINSTR) { - case elf.SHF_ALLOC | elf.SHF_EXECINSTR: - sym.Code = 'T' - case elf.SHF_ALLOC: - sym.Code = 'R' - case elf.SHF_ALLOC | elf.SHF_WRITE: - sym.Code = 'D' - } - } - if elf.ST_BIND(s.Info) == elf.STB_LOCAL { - sym.Code += 'a' - 'A' - } - syms = append(syms, sym) - } - - return -} diff --git a/src/cmd/objdump/macho.go b/src/cmd/objdump/macho.go deleted file mode 100644 index 6e0ad223d..000000000 --- a/src/cmd/objdump/macho.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2013 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. - -// Parsing of Mach-O executables (OS X). - -package main - -import ( - "debug/macho" - "os" - "sort" -) - -func machoSymbols(f *os.File) (syms []Sym, goarch string) { - p, err := macho.NewFile(f) - if err != nil { - errorf("parsing %s: %v", f.Name(), err) - return - } - - if p.Symtab == nil { - errorf("%s: no symbol table", f.Name()) - return - } - - switch p.Cpu { - case macho.Cpu386: - goarch = "386" - case macho.CpuAmd64: - goarch = "amd64" - case macho.CpuArm: - goarch = "arm" - } - - // Build sorted list of addresses of all symbols. - // We infer the size of a symbol by looking at where the next symbol begins. - var addrs []uint64 - for _, s := range p.Symtab.Syms { - addrs = append(addrs, s.Value) - } - sort.Sort(uint64s(addrs)) - - for _, s := range p.Symtab.Syms { - sym := Sym{Name: s.Name, Addr: s.Value, Code: '?'} - i := sort.Search(len(addrs), func(x int) bool { return addrs[x] > s.Value }) - if i < len(addrs) { - sym.Size = int64(addrs[i] - s.Value) - } - if s.Sect == 0 { - sym.Code = 'U' - } else if int(s.Sect) <= len(p.Sections) { - sect := p.Sections[s.Sect-1] - switch sect.Seg { - case "__TEXT": - sym.Code = 'R' - case "__DATA": - sym.Code = 'D' - } - switch sect.Seg + " " + sect.Name { - case "__TEXT __text": - sym.Code = 'T' - case "__DATA __bss", "__DATA __noptrbss": - sym.Code = 'B' - } - } - syms = append(syms, sym) - } - - return -} - -type uint64s []uint64 - -func (x uint64s) Len() int { return len(x) } -func (x uint64s) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x uint64s) Less(i, j int) bool { return x[i] < x[j] } diff --git a/src/cmd/objdump/main.go b/src/cmd/objdump/main.go index 0f66f20a4..708a85370 100644 --- a/src/cmd/objdump/main.go +++ b/src/cmd/objdump/main.go @@ -32,27 +32,15 @@ package main import ( - "bufio" - "bytes" - "debug/elf" - "debug/gosym" - "debug/macho" - "debug/pe" - "debug/plan9obj" - "encoding/binary" "flag" "fmt" - "io" "log" "os" "regexp" - "sort" "strconv" "strings" - "text/tabwriter" - "cmd/internal/rsc.io/arm/armasm" - "cmd/internal/rsc.io/x86/x86asm" + "cmd/internal/objfile" ) var symregexp = flag.String("s", "", "only dump symbols matching this regexp") @@ -85,446 +73,35 @@ func main() { symRE = re } - f, err := os.Open(flag.Arg(0)) + f, err := objfile.Open(flag.Arg(0)) if err != nil { log.Fatal(err) } - textStart, textData, symtab, pclntab, err := loadTables(f) + dis, err := f.Disasm() if err != nil { - log.Fatalf("reading %s: %v", flag.Arg(0), err) + log.Fatal("disassemble %s: %v", flag.Arg(0), err) } - syms, goarch, err := loadSymbols(f) - if err != nil { - log.Fatalf("reading %s: %v", flag.Arg(0), err) - } - - // Filter out section symbols, overwriting syms in place. - keep := syms[:0] - for _, sym := range syms { - switch sym.Name { - case "runtime.text", "text", "_text", "runtime.etext", "etext", "_etext": - // drop - default: - keep = append(keep, sym) - } - } - syms = keep - - disasm := disasms[goarch] - if disasm == nil { - log.Fatalf("reading %s: unknown architecture", flag.Arg(0)) - } - - lookup := func(addr uint64) (string, uint64) { - i := sort.Search(len(syms), func(i int) bool { return syms[i].Addr > addr }) - if i > 0 { - s := syms[i-1] - if s.Addr <= addr && addr < s.Addr+uint64(s.Size) && s.Name != "runtime.etext" && s.Name != "etext" && s.Name != "_etext" { - return s.Name, s.Addr - } - } - return "", 0 - } - - pcln := gosym.NewLineTable(pclntab, textStart) - tab, err := gosym.NewTable(symtab, pcln) - if err != nil { - log.Fatalf("reading %s: %v", flag.Arg(0), err) - } - - if flag.NArg() == 1 { - // disassembly of entire object - our format - dump(tab, lookup, disasm, goarch, syms, textData, textStart) - os.Exit(exitCode) - } - - // disassembly of specific piece of object - gnu objdump format for pprof - gnuDump(tab, lookup, disasm, textData, textStart) - os.Exit(exitCode) -} - -// base returns the final element in the path. -// It works on both Windows and Unix paths. -func base(path string) string { - path = path[strings.LastIndex(path, "/")+1:] - path = path[strings.LastIndex(path, `\`)+1:] - return path -} - -func dump(tab *gosym.Table, lookup lookupFunc, disasm disasmFunc, goarch string, syms []Sym, textData []byte, textStart uint64) { - stdout := bufio.NewWriter(os.Stdout) - defer stdout.Flush() - - printed := false - for _, sym := range syms { - if (sym.Code != 'T' && sym.Code != 't') || sym.Size == 0 || sym.Name == "_text" || sym.Name == "text" || sym.Addr < textStart || symRE != nil && !symRE.MatchString(sym.Name) { - continue - } - if sym.Addr >= textStart+uint64(len(textData)) || sym.Addr+uint64(sym.Size) > textStart+uint64(len(textData)) { - break - } - if printed { - fmt.Fprintf(stdout, "\n") - } else { - printed = true - } - file, _, _ := tab.PCToLine(sym.Addr) - fmt.Fprintf(stdout, "TEXT %s(SB) %s\n", sym.Name, file) - tw := tabwriter.NewWriter(stdout, 1, 8, 1, '\t', 0) - start := sym.Addr - end := sym.Addr + uint64(sym.Size) - for pc := start; pc < end; { - i := pc - textStart - text, size := disasm(textData[i:end-textStart], pc, lookup) - file, line, _ := tab.PCToLine(pc) - - // ARM is word-based, so show actual word hex, not byte hex. - // Since ARM is little endian, they're different. - if goarch == "arm" && size == 4 { - fmt.Fprintf(tw, "\t%s:%d\t%#x\t%08x\t%s\n", base(file), line, pc, binary.LittleEndian.Uint32(textData[i:i+uint64(size)]), text) - } else { - fmt.Fprintf(tw, "\t%s:%d\t%#x\t%x\t%s\n", base(file), line, pc, textData[i:i+uint64(size)], text) - } - pc += uint64(size) - } - tw.Flush() - } -} - -func disasm_386(code []byte, pc uint64, lookup lookupFunc) (string, int) { - return disasm_x86(code, pc, lookup, 32) -} - -func disasm_amd64(code []byte, pc uint64, lookup lookupFunc) (string, int) { - return disasm_x86(code, pc, lookup, 64) -} - -func disasm_x86(code []byte, pc uint64, lookup lookupFunc, arch int) (string, int) { - inst, err := x86asm.Decode(code, 64) - var text string - size := inst.Len - if err != nil || size == 0 || inst.Op == 0 { - size = 1 - text = "?" - } else { - text = x86asm.Plan9Syntax(inst, pc, lookup) - } - return text, size -} - -type textReader struct { - code []byte - pc uint64 -} - -func (r textReader) ReadAt(data []byte, off int64) (n int, err error) { - if off < 0 || uint64(off) < r.pc { - return 0, io.EOF - } - d := uint64(off) - r.pc - if d >= uint64(len(r.code)) { - return 0, io.EOF - } - n = copy(data, r.code[d:]) - if n < len(data) { - err = io.ErrUnexpectedEOF - } - return -} - -func disasm_arm(code []byte, pc uint64, lookup lookupFunc) (string, int) { - inst, err := armasm.Decode(code, armasm.ModeARM) - var text string - size := inst.Len - if err != nil || size == 0 || inst.Op == 0 { - size = 4 - text = "?" - } else { - text = armasm.Plan9Syntax(inst, pc, lookup, textReader{code, pc}) - } - return text, size -} - -var disasms = map[string]disasmFunc{ - "386": disasm_386, - "amd64": disasm_amd64, - "arm": disasm_arm, -} - -func gnuDump(tab *gosym.Table, lookup lookupFunc, disasm disasmFunc, textData []byte, textStart uint64) { - start, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(1), "0x"), 16, 64) - if err != nil { - log.Fatalf("invalid start PC: %v", err) - } - end, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(2), "0x"), 16, 64) - if err != nil { - log.Fatalf("invalid end PC: %v", err) - } - if start < textStart { - start = textStart - } - if end < start { - end = start - } - if end > textStart+uint64(len(textData)) { - end = textStart + uint64(len(textData)) - } - - stdout := bufio.NewWriter(os.Stdout) - defer stdout.Flush() - - // For now, find spans of same PC/line/fn and - // emit them as having dummy instructions. - var ( - spanPC uint64 - spanFile string - spanLine int - spanFn *gosym.Func - ) - - flush := func(endPC uint64) { - if spanPC == 0 { - return - } - fmt.Fprintf(stdout, "%s:%d\n", spanFile, spanLine) - for pc := spanPC; pc < endPC; { - text, size := disasm(textData[pc-textStart:], pc, lookup) - fmt.Fprintf(stdout, " %x: %s\n", pc, text) - pc += uint64(size) - } - spanPC = 0 - } - - for pc := start; pc < end; pc++ { - file, line, fn := tab.PCToLine(pc) - if file != spanFile || line != spanLine || fn != spanFn { - flush(pc) - spanPC, spanFile, spanLine, spanFn = pc, file, line, fn - } - } - flush(end) -} - -func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte, err error) { - if obj, err := elf.NewFile(f); err == nil { - if sect := obj.Section(".text"); sect != nil { - textStart = sect.Addr - textData, _ = sect.Data() - } - if sect := obj.Section(".gosymtab"); sect != nil { - if symtab, err = sect.Data(); err != nil { - return 0, nil, nil, nil, err - } - } - if sect := obj.Section(".gopclntab"); sect != nil { - if pclntab, err = sect.Data(); err != nil { - return 0, nil, nil, nil, err - } - } - return textStart, textData, symtab, pclntab, nil - } - - if obj, err := macho.NewFile(f); err == nil { - if sect := obj.Section("__text"); sect != nil { - textStart = sect.Addr - textData, _ = sect.Data() - } - if sect := obj.Section("__gosymtab"); sect != nil { - if symtab, err = sect.Data(); err != nil { - return 0, nil, nil, nil, err - } - } - if sect := obj.Section("__gopclntab"); sect != nil { - if pclntab, err = sect.Data(); err != nil { - return 0, nil, nil, nil, err - } - } - return textStart, textData, symtab, pclntab, nil - } - - if obj, err := pe.NewFile(f); err == nil { - var imageBase uint64 - switch oh := obj.OptionalHeader.(type) { - case *pe.OptionalHeader32: - imageBase = uint64(oh.ImageBase) - case *pe.OptionalHeader64: - imageBase = oh.ImageBase - default: - return 0, nil, nil, nil, fmt.Errorf("pe file format not recognized") - } - if sect := obj.Section(".text"); sect != nil { - textStart = imageBase + uint64(sect.VirtualAddress) - textData, _ = sect.Data() - } - if pclntab, err = loadPETable(obj, "runtime.pclntab", "runtime.epclntab"); err != nil { - // We didn't find the symbols, so look for the names used in 1.3 and earlier. - // TODO: Remove code looking for the old symbols when we no longer care about 1.3. - var err2 error - if pclntab, err2 = loadPETable(obj, "pclntab", "epclntab"); err2 != nil { - return 0, nil, nil, nil, err - } - } - if symtab, err = loadPETable(obj, "runtime.symtab", "runtime.esymtab"); err != nil { - // Same as above. - var err2 error - if symtab, err2 = loadPETable(obj, "symtab", "esymtab"); err2 != nil { - return 0, nil, nil, nil, err - } - } - return textStart, textData, symtab, pclntab, nil - } - - if obj, err := plan9obj.NewFile(f); err == nil { - textStart = obj.LoadAddress + obj.HdrSize - if sect := obj.Section("text"); sect != nil { - textData, _ = sect.Data() - } - if pclntab, err = loadPlan9Table(obj, "runtime.pclntab", "runtime.epclntab"); err != nil { - // We didn't find the symbols, so look for the names used in 1.3 and earlier. - // TODO: Remove code looking for the old symbols when we no longer care about 1.3. - var err2 error - if pclntab, err2 = loadPlan9Table(obj, "pclntab", "epclntab"); err2 != nil { - return 0, nil, nil, nil, err - } - } - if symtab, err = loadPlan9Table(obj, "runtime.symtab", "runtime.esymtab"); err != nil { - // Same as above. - var err2 error - if symtab, err2 = loadPlan9Table(obj, "symtab", "esymtab"); err2 != nil { - return 0, nil, nil, nil, err - } - } - return textStart, textData, symtab, pclntab, nil - } - - return 0, nil, nil, nil, fmt.Errorf("unrecognized binary format") -} - -func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { - for _, s := range f.Symbols { - if s.Name != name { - continue - } - if s.SectionNumber <= 0 { - return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber) - } - if len(f.Sections) < int(s.SectionNumber) { - return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections)) - } - return s, nil - } - return nil, fmt.Errorf("no %s symbol found", name) -} - -func loadPETable(f *pe.File, sname, ename string) ([]byte, error) { - ssym, err := findPESymbol(f, sname) - if err != nil { - return nil, err - } - esym, err := findPESymbol(f, ename) - if err != nil { - return nil, err - } - if ssym.SectionNumber != esym.SectionNumber { - return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename) - } - sect := f.Sections[ssym.SectionNumber-1] - data, err := sect.Data() - if err != nil { - return nil, err - } - return data[ssym.Value:esym.Value], nil -} - -func findPlan9Symbol(f *plan9obj.File, name string) (*plan9obj.Sym, error) { - syms, err := f.Symbols() - if err != nil { - return nil, err - } - for _, s := range syms { - if s.Name != name { - continue + switch flag.NArg() { + default: + usage() + case 1: + // disassembly of entire object + dis.Print(os.Stdout, symRE, 0, ^uint64(0)) + os.Exit(0) + + case 3: + // disassembly of PC range + start, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(1), "0x"), 16, 64) + if err != nil { + log.Fatalf("invalid start PC: %v", err) } - return &s, nil - } - return nil, fmt.Errorf("no %s symbol found", name) -} - -func loadPlan9Table(f *plan9obj.File, sname, ename string) ([]byte, error) { - ssym, err := findPlan9Symbol(f, sname) - if err != nil { - return nil, err - } - esym, err := findPlan9Symbol(f, ename) - if err != nil { - return nil, err - } - sect := f.Section("text") - if sect == nil { - return nil, err - } - data, err := sect.Data() - if err != nil { - return nil, err - } - textStart := f.LoadAddress + f.HdrSize - return data[ssym.Value-textStart : esym.Value-textStart], nil -} - -// TODO(rsc): This code is taken from cmd/nm. Arrange some way to share the code. - -var exitCode = 0 - -func errorf(format string, args ...interface{}) { - log.Printf(format, args...) - exitCode = 1 -} - -func loadSymbols(f *os.File) (syms []Sym, goarch string, err error) { - f.Seek(0, 0) - buf := make([]byte, 16) - io.ReadFull(f, buf) - f.Seek(0, 0) - - for _, p := range parsers { - if bytes.HasPrefix(buf, p.prefix) { - syms, goarch = p.parse(f) - sort.Sort(byAddr(syms)) - return + end, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(2), "0x"), 16, 64) + if err != nil { + log.Fatalf("invalid end PC: %v", err) } + dis.Print(os.Stdout, symRE, start, end) + os.Exit(0) } - err = fmt.Errorf("unknown file format") - return -} - -type Sym struct { - Addr uint64 - Size int64 - Code rune - Name string - Type string } - -var parsers = []struct { - prefix []byte - parse func(*os.File) ([]Sym, string) -}{ - {[]byte("\x7FELF"), elfSymbols}, - {[]byte("\xFE\xED\xFA\xCE"), machoSymbols}, - {[]byte("\xFE\xED\xFA\xCF"), machoSymbols}, - {[]byte("\xCE\xFA\xED\xFE"), machoSymbols}, - {[]byte("\xCF\xFA\xED\xFE"), machoSymbols}, - {[]byte("MZ"), peSymbols}, - {[]byte("\x00\x00\x01\xEB"), plan9Symbols}, // 386 - {[]byte("\x00\x00\x04\x07"), plan9Symbols}, // mips - {[]byte("\x00\x00\x06\x47"), plan9Symbols}, // arm - {[]byte("\x00\x00\x8A\x97"), plan9Symbols}, // amd64 -} - -type byAddr []Sym - -func (x byAddr) Len() int { return len(x) } -func (x byAddr) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr } diff --git a/src/cmd/objdump/objdump_test.go b/src/cmd/objdump/objdump_test.go index 0a2d2565a..2bb74663c 100644 --- a/src/cmd/objdump/objdump_test.go +++ b/src/cmd/objdump/objdump_test.go @@ -5,113 +5,15 @@ package main import ( - "bufio" - "bytes" - "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "runtime" - "strconv" "strings" "testing" ) -func loadSyms(t *testing.T) map[string]string { - switch runtime.GOOS { - case "android", "nacl": - t.Skipf("skipping on %s", runtime.GOOS) - } - - cmd := exec.Command("go", "tool", "nm", os.Args[0]) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("go tool nm %v: %v\n%s", os.Args[0], err, string(out)) - } - syms := make(map[string]string) - scanner := bufio.NewScanner(bytes.NewReader(out)) - for scanner.Scan() { - f := strings.Fields(scanner.Text()) - if len(f) < 3 { - continue - } - syms[f[2]] = f[0] - } - if err := scanner.Err(); err != nil { - t.Fatalf("error reading symbols: %v", err) - } - return syms -} - -func runObjDump(t *testing.T, exe, startaddr, endaddr string) (path, lineno string) { - switch runtime.GOOS { - case "android", "nacl": - t.Skipf("skipping on %s", runtime.GOOS) - } - - cmd := exec.Command(exe, os.Args[0], startaddr, endaddr) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("go tool objdump %v: %v\n%s", os.Args[0], err, string(out)) - } - f := strings.Split(string(out), "\n") - if len(f) < 1 { - t.Fatal("objdump output must have at least one line") - } - pathAndLineNo := f[0] - f = strings.Split(pathAndLineNo, ":") - if runtime.GOOS == "windows" { - switch len(f) { - case 2: - return f[0], f[1] - case 3: - return f[0] + ":" + f[1], f[2] - default: - t.Fatalf("no line number found in %q", pathAndLineNo) - } - } - if len(f) != 2 { - t.Fatalf("no line number found in %q", pathAndLineNo) - } - return f[0], f[1] -} - -func testObjDump(t *testing.T, exe, startaddr, endaddr string, line int) { - srcPath, srcLineNo := runObjDump(t, exe, startaddr, endaddr) - fi1, err := os.Stat("objdump_test.go") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - fi2, err := os.Stat(srcPath) - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !os.SameFile(fi1, fi2) { - t.Fatalf("objdump_test.go and %s are not same file", srcPath) - } - if srcLineNo != fmt.Sprint(line) { - t.Fatalf("line number = %v; want %d", srcLineNo, line) - } -} - -func TestObjDump(t *testing.T) { - _, _, line, _ := runtime.Caller(0) - syms := loadSyms(t) - - tmp, exe := buildObjdump(t) - defer os.RemoveAll(tmp) - - startaddr := syms["cmd/objdump.TestObjDump"] - addr, err := strconv.ParseUint(startaddr, 16, 64) - if err != nil { - t.Fatalf("invalid start address %v: %v", startaddr, err) - } - endaddr := fmt.Sprintf("%x", addr+10) - testObjDump(t, exe, startaddr, endaddr, line-1) - testObjDump(t, exe, "0x"+startaddr, "0x"+endaddr, line-1) -} - func buildObjdump(t *testing.T) (tmp, exe string) { switch runtime.GOOS { case "android", "nacl": @@ -143,7 +45,7 @@ var x86Need = []string{ var armNeed = []string{ "fmthello.go:6", "TEXT main.main(SB)", - "B.LS main.main(SB)", + //"B.LS main.main(SB)", // TODO(rsc): restore; golang.org/issue/9021 "BL fmt.Println(SB)", "RET", } diff --git a/src/cmd/objdump/pe.go b/src/cmd/objdump/pe.go deleted file mode 100644 index 38190095a..000000000 --- a/src/cmd/objdump/pe.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2013 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. - -// Parsing of PE executables (Microsoft Windows). - -package main - -import ( - "debug/pe" - "os" - "sort" -) - -func peSymbols(f *os.File) (syms []Sym, goarch string) { - p, err := pe.NewFile(f) - if err != nil { - errorf("parsing %s: %v", f.Name(), err) - return - } - - // Build sorted list of addresses of all symbols. - // We infer the size of a symbol by looking at where the next symbol begins. - var addrs []uint64 - - var imageBase uint64 - switch oh := p.OptionalHeader.(type) { - case *pe.OptionalHeader32: - imageBase = uint64(oh.ImageBase) - goarch = "386" - case *pe.OptionalHeader64: - imageBase = oh.ImageBase - goarch = "amd64" - default: - errorf("parsing %s: file format not recognized", f.Name()) - return - } - - for _, s := range p.Symbols { - const ( - N_UNDEF = 0 // An undefined (extern) symbol - N_ABS = -1 // An absolute symbol (e_value is a constant, not an address) - N_DEBUG = -2 // A debugging symbol - ) - sym := Sym{Name: s.Name, Addr: uint64(s.Value), Code: '?'} - switch s.SectionNumber { - case N_UNDEF: - sym.Code = 'U' - case N_ABS: - sym.Code = 'C' - case N_DEBUG: - sym.Code = '?' - default: - if s.SectionNumber < 0 { - errorf("parsing %s: invalid section number %d", f.Name(), s.SectionNumber) - return - } - if len(p.Sections) < int(s.SectionNumber) { - errorf("parsing %s: section number %d is large then max %d", f.Name(), s.SectionNumber, len(p.Sections)) - return - } - sect := p.Sections[s.SectionNumber-1] - const ( - text = 0x20 - data = 0x40 - bss = 0x80 - permX = 0x20000000 - permR = 0x40000000 - permW = 0x80000000 - ) - ch := sect.Characteristics - switch { - case ch&text != 0: - sym.Code = 'T' - case ch&data != 0: - if ch&permW == 0 { - sym.Code = 'R' - } else { - sym.Code = 'D' - } - case ch&bss != 0: - sym.Code = 'B' - } - sym.Addr += imageBase + uint64(sect.VirtualAddress) - } - syms = append(syms, sym) - addrs = append(addrs, sym.Addr) - } - - sort.Sort(uint64s(addrs)) - for i := range syms { - j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr }) - if j < len(addrs) { - syms[i].Size = int64(addrs[j] - syms[i].Addr) - } - } - - return -} diff --git a/src/cmd/objdump/plan9obj.go b/src/cmd/objdump/plan9obj.go deleted file mode 100644 index f851d4158..000000000 --- a/src/cmd/objdump/plan9obj.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2014 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. - -// Parsing of Plan 9 a.out executables. - -package main - -import ( - "debug/plan9obj" - "os" - "sort" -) - -var validSymType = map[rune]bool{ - 'T': true, - 't': true, - 'D': true, - 'd': true, - 'B': true, - 'b': true, -} - -func plan9Symbols(f *os.File) (syms []Sym, goarch string) { - p, err := plan9obj.NewFile(f) - if err != nil { - errorf("parsing %s: %v", f.Name(), err) - return - } - - plan9Syms, err := p.Symbols() - if err != nil { - errorf("parsing %s: %v", f.Name(), err) - return - } - - switch p.Magic { - case plan9obj.MagicAMD64: - goarch = "amd64" - case plan9obj.Magic386: - goarch = "386" - case plan9obj.MagicARM: - goarch = "arm" - } - - // Build sorted list of addresses of all symbols. - // We infer the size of a symbol by looking at where the next symbol begins. - var addrs []uint64 - for _, s := range plan9Syms { - if !validSymType[s.Type] { - continue - } - addrs = append(addrs, s.Value) - } - sort.Sort(uint64s(addrs)) - - for _, s := range plan9Syms { - if !validSymType[s.Type] { - continue - } - sym := Sym{Addr: s.Value, Name: s.Name, Code: rune(s.Type)} - i := sort.Search(len(addrs), func(x int) bool { return addrs[x] > s.Value }) - if i < len(addrs) { - sym.Size = int64(addrs[i] - s.Value) - } - syms = append(syms, sym) - } - - return -} diff --git a/src/cmd/pprof/README b/src/cmd/pprof/README new file mode 100644 index 000000000..a728ef235 --- /dev/null +++ b/src/cmd/pprof/README @@ -0,0 +1,8 @@ +The pprof in this directory is adapted from the pprof used inside Google +for C++, Java, and Go programs. Because it was developed for that broader +context, it is overgeneralized when used here for the specific use case +of profiling standard Go programs. However, we've left the abstractions +intact in order to share updates between this copy and Google's internal one. + +Please do not take the level of abstraction in this program as an example +to follow in your own. diff --git a/src/cmd/pprof/doc.go b/src/cmd/pprof/doc.go new file mode 100644 index 000000000..c6ff11d10 --- /dev/null +++ b/src/cmd/pprof/doc.go @@ -0,0 +1,12 @@ +// Copyright 2014 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. + +// Pprof interprets and displays profiles of Go programs. +// +// Usage: +// +// go tool pprof binary profile +// +// For more information, see http://blog.golang.org/profiling-go-programs. +package main diff --git a/src/cmd/pprof/pprof.go b/src/cmd/pprof/pprof.go index 89a5bb7d2..44f4f6cb7 100644 --- a/src/cmd/pprof/pprof.go +++ b/src/cmd/pprof/pprof.go @@ -11,6 +11,7 @@ import ( "os" "regexp" "strings" + "sync" "cmd/internal/objfile" "cmd/pprof/internal/commands" @@ -100,7 +101,10 @@ func (flags) ExtraUsage() string { // objTool implements plugin.ObjTool using Go libraries // (instead of invoking GNU binutils). -type objTool struct{} +type objTool struct { + mu sync.Mutex + disasmCache map[string]*objfile.Disasm +} func (*objTool) Open(name string, start uint64) (plugin.ObjFile, error) { of, err := objfile.Open(name) @@ -119,8 +123,39 @@ func (*objTool) Demangle(names []string) (map[string]string, error) { return make(map[string]string), nil } -func (*objTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { - return nil, fmt.Errorf("disassembly not supported") +func (t *objTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { + d, err := t.cachedDisasm(file) + if err != nil { + return nil, err + } + var asm []plugin.Inst + d.Decode(start, end, func(pc, size uint64, file string, line int, text string) { + asm = append(asm, plugin.Inst{Addr: pc, File: file, Line: line, Text: text}) + }) + return asm, nil +} + +func (t *objTool) cachedDisasm(file string) (*objfile.Disasm, error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.disasmCache == nil { + t.disasmCache = make(map[string]*objfile.Disasm) + } + d := t.disasmCache[file] + if d != nil { + return d, nil + } + f, err := objfile.Open(file) + if err != nil { + return nil, err + } + d, err = f.Disasm() + f.Close() + if err != nil { + return nil, err + } + t.disasmCache[file] = d + return d, nil } func (*objTool) SetConfig(config string) { diff --git a/src/compress/lzw/reader.go b/src/compress/lzw/reader.go index 0835bd8b9..526620c82 100644 --- a/src/compress/lzw/reader.go +++ b/src/compress/lzw/reader.go @@ -11,7 +11,7 @@ // two non-literal codes are a clear code and an EOF code. // // The TIFF file format uses a similar but incompatible version of the LZW -// algorithm. See the code.google.com/p/go.image/tiff/lzw package for an +// algorithm. See the golang.org/x/image/tiff/lzw package for an // implementation. package lzw diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 5a91baca0..59b23e93f 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -21,7 +21,7 @@ func (h Hash) HashFunc() Hash { } const ( - MD4 Hash = 1 + iota // import code.google.com/p/go.crypto/md4 + MD4 Hash = 1 + iota // import golang.org/x/crypto/md4 MD5 // import crypto/md5 SHA1 // import crypto/sha1 SHA224 // import crypto/sha256 @@ -29,11 +29,11 @@ const ( SHA384 // import crypto/sha512 SHA512 // import crypto/sha512 MD5SHA1 // no implementation; MD5+SHA1 used for TLS RSA - RIPEMD160 // import code.google.com/p/go.crypto/ripemd160 - SHA3_224 // import code.google.com/p/go.crypto/sha3 - SHA3_256 // import code.google.com/p/go.crypto/sha3 - SHA3_384 // import code.google.com/p/go.crypto/sha3 - SHA3_512 // import code.google.com/p/go.crypto/sha3 + RIPEMD160 // import golang.org/x/crypto/ripemd160 + SHA3_224 // import golang.org/x/crypto/sha3 + SHA3_256 // import golang.org/x/crypto/sha3 + SHA3_384 // import golang.org/x/crypto/sha3 + SHA3_512 // import golang.org/x/crypto/sha3 maxHash ) diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go index 171c322d4..a993fd46e 100644 --- a/src/database/sql/fakedb_test.go +++ b/src/database/sql/fakedb_test.go @@ -141,6 +141,8 @@ type Dummy struct { } func TestDrivers(t *testing.T) { + unregisterAllDrivers() + Register("test", fdriver) Register("invalid", Dummy{}) all := Drivers() if len(all) < 2 || !sort.StringsAreSorted(all) || !contains(all, "test") || !contains(all, "invalid") { diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go index ad9179cf7..6e6f246ae 100644 --- a/src/database/sql/sql.go +++ b/src/database/sql/sql.go @@ -37,6 +37,11 @@ func Register(name string, driver driver.Driver) { drivers[name] = driver } +func unregisterAllDrivers() { + // For tests. + drivers = make(map[string]driver.Driver) +} + // Drivers returns a sorted list of the names of the registered drivers. func Drivers() []string { var list []string diff --git a/src/debug/goobj/read_test.go b/src/debug/goobj/read_test.go index dee140533..cc991e5d9 100644 --- a/src/debug/goobj/read_test.go +++ b/src/debug/goobj/read_test.go @@ -12,7 +12,7 @@ var importPathToPrefixTests = []struct { }{ {"runtime", "runtime"}, {"sync/atomic", "sync/atomic"}, - {"code.google.com/p/go.tools/godoc", "code.google.com/p/go.tools/godoc"}, + {"golang.org/x/tools/godoc", "golang.org/x/tools/godoc"}, {"foo.bar/baz.quux", "foo.bar/baz%2equux"}, {"", ""}, {"%foo%bar", "%25foo%25bar"}, diff --git a/src/net/http/cookiejar/jar.go b/src/net/http/cookiejar/jar.go index 389ab58e4..0e0fac928 100644 --- a/src/net/http/cookiejar/jar.go +++ b/src/net/http/cookiejar/jar.go @@ -30,7 +30,7 @@ import ( // set a cookie for bar.com. // // A public suffix list implementation is in the package -// code.google.com/p/go.net/publicsuffix. +// golang.org/x/net/publicsuffix. type PublicSuffixList interface { // PublicSuffix returns the public suffix of domain. // diff --git a/src/net/http/main_test.go b/src/net/http/main_test.go index 9f1dfc372..b8c71fd19 100644 --- a/src/net/http/main_test.go +++ b/src/net/http/main_test.go @@ -70,7 +70,7 @@ func goroutineLeaked() bool { } fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n") for stack, count := range stackCount { - fmt.Fprintf(os.Stderr, "%d instances of:\n%s", count, stack) + fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack) } return true } diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index bb44ac853..5e0a0053c 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -2819,6 +2819,7 @@ func benchmarkClientServerParallel(b *testing.B, parallelism int, useTLS bool) { InsecureSkipVerify: true, }, } + defer noVerifyTransport.CloseIdleConnections() client := &Client{Transport: noVerifyTransport} for pb.Next() { res, err := client.Get(ts.URL) diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go index bc9c00eff..197d3e8b4 100644 --- a/src/os/exec/exec_test.go +++ b/src/os/exec/exec_test.go @@ -246,7 +246,7 @@ func TestPipeLookPathLeak(t *testing.T) { } func numOpenFDS(t *testing.T) (n int, lsof []byte) { - lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() + lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output() if err != nil { t.Skip("skipping test; error finding or running lsof") } diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go index 5efc2a4f1..132594eed 100644 --- a/src/os/file_plan9.go +++ b/src/os/file_plan9.go @@ -25,7 +25,8 @@ type file struct { dirinfo *dirInfo // nil unless directory being read } -// Fd returns the integer Unix file descriptor referencing the open file. +// Fd returns the integer Plan 9 file descriptor referencing the open file. +// The file descriptor is valid only until f.Close is called or f is garbage collected. func (f *File) Fd() uintptr { if f == nil { return ^(uintptr(0)) diff --git a/src/os/file_unix.go b/src/os/file_unix.go index f59d563e6..ff4fc7d12 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -29,6 +29,7 @@ type file struct { } // Fd returns the integer Unix file descriptor referencing the open file. +// The file descriptor is valid only until f.Close is called or f is garbage collected. func (f *File) Fd() uintptr { if f == nil { return ^(uintptr(0)) diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 3b5519390..2a90a5055 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -36,6 +36,7 @@ type file struct { } // Fd returns the Windows handle referencing the open file. +// The handle is valid only until f.Close is called or f is garbage collected. func (file *File) Fd() uintptr { if file == nil { return uintptr(syscall.InvalidHandle) diff --git a/src/runtime/asm_386.s b/src/runtime/asm_386.s index d456e6bca..501e64b09 100644 --- a/src/runtime/asm_386.s +++ b/src/runtime/asm_386.s @@ -2284,3 +2284,9 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$0 MOVL m_curg(AX), AX MOVL (g_stack+stack_hi)(AX), AX RET + +// The top-most function running on a goroutine +// returns to goexit+PCQuantum. +TEXT runtime·goexit(SB),NOSPLIT,$0-0 + BYTE $0x90 // NOP + CALL runtime·goexit1(SB) // does not return diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index 5d176575c..1aa2d71a8 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -2229,3 +2229,9 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$0 MOVQ m_curg(AX), AX MOVQ (g_stack+stack_hi)(AX), AX RET + +// The top-most function running on a goroutine +// returns to goexit+PCQuantum. +TEXT runtime·goexit(SB),NOSPLIT,$0-0 + BYTE $0x90 // NOP + CALL runtime·goexit1(SB) // does not return diff --git a/src/runtime/asm_amd64p32.s b/src/runtime/asm_amd64p32.s index 2b2155753..153564b14 100644 --- a/src/runtime/asm_amd64p32.s +++ b/src/runtime/asm_amd64p32.s @@ -1079,3 +1079,9 @@ TEXT runtime·fastrand1(SB), NOSPLIT, $0-4 TEXT runtime·return0(SB), NOSPLIT, $0 MOVL $0, AX RET + +// The top-most function running on a goroutine +// returns to goexit+PCQuantum. +TEXT runtime·goexit(SB),NOSPLIT,$0-0 + BYTE $0x90 // NOP + CALL runtime·goexit1(SB) // does not return diff --git a/src/runtime/asm_arm.s b/src/runtime/asm_arm.s index 9a58fdc51..58aebf388 100644 --- a/src/runtime/asm_arm.s +++ b/src/runtime/asm_arm.s @@ -1320,3 +1320,9 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$8 MOVW saveG-8(SP), g MOVW saveR11-4(SP), R11 RET + +// The top-most function running on a goroutine +// returns to goexit+PCQuantum. +TEXT runtime·goexit(SB),NOSPLIT,$-4-0 + MOVW R0, R0 // NOP + BL runtime·goexit1(SB) // does not return diff --git a/src/runtime/cgo/dragonfly.c b/src/runtime/cgo/dragonfly.c index 3c95ff354..c233c8ba9 100644 --- a/src/runtime/cgo/dragonfly.c +++ b/src/runtime/cgo/dragonfly.c @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build dragonfly + #include "textflag.h" // Supply environ and __progname, because we don't diff --git a/src/runtime/cgo/freebsd.c b/src/runtime/cgo/freebsd.c index aefc481e6..4876b2abe 100644 --- a/src/runtime/cgo/freebsd.c +++ b/src/runtime/cgo/freebsd.c @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build freebsd + #include "textflag.h" // Supply environ and __progname, because we don't diff --git a/src/runtime/cgo/netbsd.c b/src/runtime/cgo/netbsd.c index de38bb770..076cc87f1 100644 --- a/src/runtime/cgo/netbsd.c +++ b/src/runtime/cgo/netbsd.c @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build netbsd + #include "textflag.h" // Supply environ and __progname, because we don't diff --git a/src/runtime/cgo/openbsd.c b/src/runtime/cgo/openbsd.c index 7c2b6c173..476649544 100644 --- a/src/runtime/cgo/openbsd.c +++ b/src/runtime/cgo/openbsd.c @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build openbsd + #include "textflag.h" // Supply environ, __progname and __guard_local, because diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go index 5958ad891..972eedc62 100644 --- a/src/runtime/crash_cgo_test.go +++ b/src/runtime/crash_cgo_test.go @@ -36,10 +36,14 @@ func TestCgoTraceback(t *testing.T) { } func TestCgoExternalThreadPanic(t *testing.T) { - if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + if runtime.GOOS == "plan9" { t.Skipf("no pthreads on %s", runtime.GOOS) } - got := executeTest(t, cgoExternalThreadPanicSource, nil, "main.c", cgoExternalThreadPanicC) + csrc := cgoExternalThreadPanicC + if runtime.GOOS == "windows" { + csrc = cgoExternalThreadPanicC_windows + } + got := executeTest(t, cgoExternalThreadPanicSource, nil, "main.c", csrc) want := "panic: BOOM" if !strings.Contains(got, want) { t.Fatalf("want failure containing %q. output:\n%s\n", want, got) @@ -169,3 +173,24 @@ start(void) printf("pthread_create failed\n"); } ` + +const cgoExternalThreadPanicC_windows = ` +#include <stdlib.h> +#include <stdio.h> + +void gopanic(void); + +static void* +die(void* x) +{ + gopanic(); + return 0; +} + +void +start(void) +{ + if(_beginthreadex(0, 0, die, 0, 0, 0) != 0) + printf("_beginthreadex failed\n"); +} +` diff --git a/src/runtime/extern.go b/src/runtime/extern.go index 1b8052bb5..6cc5df810 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -117,11 +117,20 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) { return } -// Callers fills the slice pc with the program counters of function invocations +// Callers fills the slice pc with the return program counters of function invocations // on the calling goroutine's stack. The argument skip is the number of stack frames // to skip before recording in pc, with 0 identifying the frame for Callers itself and // 1 identifying the caller of Callers. // It returns the number of entries written to pc. +// +// Note that since each slice entry pc[i] is a return program counter, +// looking up the file and line for pc[i] (for example, using (*Func).FileLine) +// will return the file and line number of the instruction immediately +// following the call. +// To look up the file and line number of the call itself, use pc[i]-1. +// As an exception to this rule, if pc[i-1] corresponds to the function +// runtime.sigpanic, then pc[i] is the program counter of a faulting +// instruction and should be used without any subtraction. func Callers(skip int, pc []uintptr) int { // runtime.callers uses pc.array==nil as a signal // to print a stack trace. Pick off 0-length pc here diff --git a/src/runtime/heapdump.c b/src/runtime/heapdump.c index 5ac37803b..da14f2d24 100644 --- a/src/runtime/heapdump.c +++ b/src/runtime/heapdump.c @@ -251,7 +251,9 @@ dumpbv(BitVector *bv, uintptr offset) for(i = 0; i < bv->n; i += BitsPerPointer) { switch(bv->bytedata[i/8] >> i%8 & 3) { case BitsDead: - return; + // BitsDead has already been processed in makeheapobjbv. + // We should only see it in stack maps, in which case we should continue processing. + break; case BitsScalar: break; case BitsPointer: @@ -400,7 +402,7 @@ dumpgoroutine(G *gp) child.sp = nil; child.depth = 0; fn = dumpframe; - runtime·gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, &fn, &child, false); + runtime·gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, &fn, &child, 0); // dump defer & panic records for(d = gp->defer; d != nil; d = d->link) { diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go index a18e77421..fab8cf269 100644 --- a/src/runtime/malloc.go +++ b/src/runtime/malloc.go @@ -41,7 +41,7 @@ var zerobase uintptr // Allocate an object of size bytes. // Small objects are allocated from the per-P cache's free lists. // Large objects (> 32 kB) are allocated straight from the heap. -func mallocgc(size uintptr, typ *_type, flags int) unsafe.Pointer { +func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer { if size == 0 { return unsafe.Pointer(&zerobase) } @@ -391,7 +391,7 @@ func loadPtrMask(typ *_type) []uint8 { // implementation of new builtin func newobject(typ *_type) unsafe.Pointer { - flags := 0 + flags := uint32(0) if typ.kind&kindNoPointers != 0 { flags |= flagNoScan } @@ -400,7 +400,7 @@ func newobject(typ *_type) unsafe.Pointer { // implementation of make builtin for slices func newarray(typ *_type, n uintptr) unsafe.Pointer { - flags := 0 + flags := uint32(0) if typ.kind&kindNoPointers != 0 { flags |= flagNoScan } diff --git a/src/runtime/mgc0.c b/src/runtime/mgc0.c index 214b9ebc2..3248b0f49 100644 --- a/src/runtime/mgc0.c +++ b/src/runtime/mgc0.c @@ -1021,7 +1021,7 @@ scanstack(G *gp) runtime·throw("can't scan gchelper stack"); fn = scanframe; - runtime·gentraceback(~(uintptr)0, ~(uintptr)0, 0, gp, 0, nil, 0x7fffffff, &fn, nil, false); + runtime·gentraceback(~(uintptr)0, ~(uintptr)0, 0, gp, 0, nil, 0x7fffffff, &fn, nil, 0); runtime·tracebackdefers(gp, &fn, nil); } @@ -2635,7 +2635,7 @@ runtime·getgcmask(byte *p, Type *t, byte **mask, uintptr *len) frame.fn = nil; frame.sp = (uintptr)p; cb = getgcmaskcb; - runtime·gentraceback(g->m->curg->sched.pc, g->m->curg->sched.sp, 0, g->m->curg, 0, nil, 1000, &cb, &frame, false); + runtime·gentraceback(g->m->curg->sched.pc, g->m->curg->sched.sp, 0, g->m->curg, 0, nil, 1000, &cb, &frame, 0); if(frame.fn != nil) { Func *f; StackMap *stackmap; diff --git a/src/runtime/mprof.go b/src/runtime/mprof.go index 803da5667..d409c6c30 100644 --- a/src/runtime/mprof.go +++ b/src/runtime/mprof.go @@ -528,8 +528,6 @@ var allgs []*g // proc.c // Most clients should use the runtime/pprof package instead // of calling GoroutineProfile directly. func GoroutineProfile(p []StackRecord) (n int, ok bool) { - sp := getcallersp(unsafe.Pointer(&p)) - pc := getcallerpc(unsafe.Pointer(&p)) n = NumGoroutine() if n <= len(p) { @@ -542,7 +540,11 @@ func GoroutineProfile(p []StackRecord) (n int, ok bool) { if n <= len(p) { ok = true r := p - saveg(pc, sp, gp, &r[0]) + sp := getcallersp(unsafe.Pointer(&p)) + pc := getcallerpc(unsafe.Pointer(&p)) + onM(func() { + saveg(pc, sp, gp, &r[0]) + }) r = r[1:] for _, gp1 := range allgs { if gp1 == gp || readgstatus(gp1) == _Gdead { @@ -562,7 +564,7 @@ func GoroutineProfile(p []StackRecord) (n int, ok bool) { } func saveg(pc, sp uintptr, gp *g, r *StackRecord) { - n := gentraceback(pc, sp, 0, gp, 0, &r.Stack0[0], len(r.Stack0), nil, nil, false) + n := gentraceback(pc, sp, 0, gp, 0, &r.Stack0[0], len(r.Stack0), nil, nil, 0) if n < len(r.Stack0) { r.Stack0[n] = 0 } @@ -573,8 +575,6 @@ func saveg(pc, sp uintptr, gp *g, r *StackRecord) { // If all is true, Stack formats stack traces of all other goroutines // into buf after the trace for the current goroutine. func Stack(buf []byte, all bool) int { - sp := getcallersp(unsafe.Pointer(&buf)) - pc := getcallerpc(unsafe.Pointer(&buf)) mp := acquirem() gp := mp.curg if all { @@ -589,14 +589,19 @@ func Stack(buf []byte, all bool) int { n := 0 if len(buf) > 0 { - gp.writebuf = buf[0:0:len(buf)] - goroutineheader(gp) - traceback(pc, sp, 0, gp) - if all { - tracebackothers(gp) - } - n = len(gp.writebuf) - gp.writebuf = nil + sp := getcallersp(unsafe.Pointer(&buf)) + pc := getcallerpc(unsafe.Pointer(&buf)) + onM(func() { + g0 := getg() + g0.writebuf = buf[0:0:len(buf)] + goroutineheader(gp) + traceback(pc, sp, 0, gp) + if all { + tracebackothers(gp) + } + n = len(g0.writebuf) + g0.writebuf = nil + }) } if all { @@ -623,7 +628,11 @@ func tracealloc(p unsafe.Pointer, size uintptr, typ *_type) { } if gp.m.curg == nil || gp == gp.m.curg { goroutineheader(gp) - traceback(getcallerpc(unsafe.Pointer(&p)), getcallersp(unsafe.Pointer(&p)), 0, gp) + pc := getcallerpc(unsafe.Pointer(&p)) + sp := getcallersp(unsafe.Pointer(&p)) + onM(func() { + traceback(pc, sp, 0, gp) + }) } else { goroutineheader(gp.m.curg) traceback(^uintptr(0), ^uintptr(0), 0, gp.m.curg) @@ -639,7 +648,11 @@ func tracefree(p unsafe.Pointer, size uintptr) { gp.m.traceback = 2 print("tracefree(", p, ", ", hex(size), ")\n") goroutineheader(gp) - traceback(getcallerpc(unsafe.Pointer(&p)), getcallersp(unsafe.Pointer(&p)), 0, gp) + pc := getcallerpc(unsafe.Pointer(&p)) + sp := getcallersp(unsafe.Pointer(&p)) + onM(func() { + traceback(pc, sp, 0, gp) + }) print("\n") gp.m.traceback = 0 unlock(&tracelock) diff --git a/src/runtime/os_android.c b/src/runtime/os_android.c index 58e0dac93..5805f6871 100644 --- a/src/runtime/os_android.c +++ b/src/runtime/os_android.c @@ -9,7 +9,7 @@ // Export the runtime entry point symbol. // // Used by the app package to start the Go runtime after loading -// a shared library via JNI. See code.google.com/p/go.mobile/app. +// a shared library via JNI. See golang.org/x/mobile/app. void _rt0_arm_linux1(); #pragma cgo_export_static _rt0_arm_linux1 diff --git a/src/runtime/os_plan9_386.c b/src/runtime/os_plan9_386.c index 349086224..42c6d161c 100644 --- a/src/runtime/os_plan9_386.c +++ b/src/runtime/os_plan9_386.c @@ -114,7 +114,7 @@ Throw: if(runtime·gotraceback(&crash)) { runtime·goroutineheader(gp); - runtime·traceback(ureg->pc, ureg->sp, 0, gp); + runtime·tracebacktrap(ureg->pc, ureg->sp, 0, gp); runtime·tracebackothers(gp); runtime·printf("\n"); runtime·dumpregs(ureg); diff --git a/src/runtime/os_plan9_amd64.c b/src/runtime/os_plan9_amd64.c index 6b0f8ae3a..a9dc0eb96 100644 --- a/src/runtime/os_plan9_amd64.c +++ b/src/runtime/os_plan9_amd64.c @@ -122,7 +122,7 @@ Throw: if(runtime·gotraceback(&crash)) { runtime·goroutineheader(gp); - runtime·traceback(ureg->ip, ureg->sp, 0, gp); + runtime·tracebacktrap(ureg->ip, ureg->sp, 0, gp); runtime·tracebackothers(gp); runtime·printf("\n"); runtime·dumpregs(ureg); diff --git a/src/runtime/os_windows_386.c b/src/runtime/os_windows_386.c index 213582799..9962f0dc2 100644 --- a/src/runtime/os_windows_386.c +++ b/src/runtime/os_windows_386.c @@ -97,7 +97,7 @@ runtime·lastcontinuehandler(ExceptionRecord *info, Context *r, G *gp) runtime·printf("\n"); if(runtime·gotraceback(&crash)){ - runtime·traceback(r->Eip, r->Esp, 0, gp); + runtime·tracebacktrap(r->Eip, r->Esp, 0, gp); runtime·tracebackothers(gp); runtime·dumpregs(r); } diff --git a/src/runtime/os_windows_amd64.c b/src/runtime/os_windows_amd64.c index b96cf70d1..e4617e4ce 100644 --- a/src/runtime/os_windows_amd64.c +++ b/src/runtime/os_windows_amd64.c @@ -119,7 +119,7 @@ runtime·lastcontinuehandler(ExceptionRecord *info, Context *r, G *gp) runtime·printf("\n"); if(runtime·gotraceback(&crash)){ - runtime·traceback(r->Rip, r->Rsp, 0, gp); + runtime·tracebacktrap(r->Rip, r->Rsp, 0, gp); runtime·tracebackothers(gp); runtime·dumpregs(r); } diff --git a/src/runtime/proc.c b/src/runtime/proc.c index c1df40d02..ce39db4ab 100644 --- a/src/runtime/proc.c +++ b/src/runtime/proc.c @@ -994,7 +994,7 @@ runtime·newextram(void) // the goroutine stack ends. mp = runtime·allocm(nil); gp = runtime·malg(4096); - gp->sched.pc = (uintptr)runtime·goexit; + gp->sched.pc = (uintptr)runtime·goexit + PCQuantum; gp->sched.sp = gp->stack.hi; gp->sched.sp -= 4*sizeof(uintreg); // extra space in case of reads slightly beyond frame gp->sched.lr = 0; @@ -1647,12 +1647,10 @@ runtime·gosched_m(G *gp) } // Finishes execution of the current goroutine. -// Need to mark it as nosplit, because it runs with sp > stackbase. -// Since it does not return it does not matter. But if it is preempted -// at the split stack check, GC will complain about inconsistent sp. +// Must be NOSPLIT because it is called from Go. #pragma textflag NOSPLIT void -runtime·goexit(void) +runtime·goexit1(void) { void (*fn)(G*); @@ -2198,7 +2196,7 @@ runtime·newproc1(FuncVal *fn, byte *argp, int32 narg, int32 nret, void *callerp runtime·memclr((byte*)&newg->sched, sizeof newg->sched); newg->sched.sp = (uintptr)sp; - newg->sched.pc = (uintptr)runtime·goexit; + newg->sched.pc = (uintptr)runtime·goexit + PCQuantum; // +PCQuantum so that previous instruction is in same function newg->sched.g = newg; runtime·gostartcallfn(&newg->sched, fn); newg->gopc = (uintptr)callerpc; @@ -2432,9 +2430,10 @@ static struct ProfState { int32 hz; } prof; -static void System(void) {} -static void ExternalCode(void) {} -static void GC(void) {} +static void System(void) { System(); } +static void ExternalCode(void) { ExternalCode(); } +static void GC(void) { GC(); } + extern void runtime·cpuproftick(uintptr*, int32); extern byte runtime·etext[]; @@ -2538,7 +2537,7 @@ runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp, M *mp) n = 0; if(traceback) - n = runtime·gentraceback((uintptr)pc, (uintptr)sp, (uintptr)lr, gp, 0, stk, nelem(stk), nil, nil, false); + n = runtime·gentraceback((uintptr)pc, (uintptr)sp, (uintptr)lr, gp, 0, stk, nelem(stk), nil, nil, TraceTrap); if(!traceback || n <= 0) { // Normal traceback is impossible or has failed. // See if it falls into several common cases. @@ -2548,13 +2547,13 @@ runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp, M *mp) // Cgo, we can't unwind and symbolize arbitrary C code, // so instead collect Go stack that leads to the cgo call. // This is especially important on windows, since all syscalls are cgo calls. - n = runtime·gentraceback(mp->curg->syscallpc, mp->curg->syscallsp, 0, mp->curg, 0, stk, nelem(stk), nil, nil, false); + n = runtime·gentraceback(mp->curg->syscallpc, mp->curg->syscallsp, 0, mp->curg, 0, stk, nelem(stk), nil, nil, 0); } #ifdef GOOS_windows if(n == 0 && mp->libcallg != nil && mp->libcallpc != 0 && mp->libcallsp != 0) { // Libcall, i.e. runtime syscall on windows. // Collect Go stack that leads to the call. - n = runtime·gentraceback(mp->libcallpc, mp->libcallsp, 0, mp->libcallg, 0, stk, nelem(stk), nil, nil, false); + n = runtime·gentraceback(mp->libcallpc, mp->libcallsp, 0, mp->libcallg, 0, stk, nelem(stk), nil, nil, 0); } #endif if(n == 0) { diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index fec224390..330ed429b 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -737,9 +737,15 @@ struct Stkframe BitVector* argmap; // force use of this argmap }; -intgo runtime·gentraceback(uintptr, uintptr, uintptr, G*, intgo, uintptr*, intgo, bool(**)(Stkframe*, void*), void*, bool); +enum +{ + TraceRuntimeFrames = 1<<0, // include frames for internal runtime functions. + TraceTrap = 1<<1, // the initial PC, SP are from a trap, not a return PC from a call +}; +intgo runtime·gentraceback(uintptr, uintptr, uintptr, G*, intgo, uintptr*, intgo, bool(**)(Stkframe*, void*), void*, uintgo); void runtime·tracebackdefers(G*, bool(**)(Stkframe*, void*), void*); void runtime·traceback(uintptr pc, uintptr sp, uintptr lr, G* gp); +void runtime·tracebacktrap(uintptr pc, uintptr sp, uintptr lr, G* gp); void runtime·tracebackothers(G*); bool runtime·haszeroargs(uintptr pc); bool runtime·topofstack(Func*); diff --git a/src/runtime/sema.go b/src/runtime/sema.go index a42a29988..d2a028c01 100644 --- a/src/runtime/sema.go +++ b/src/runtime/sema.go @@ -259,6 +259,7 @@ func syncsemrelease(s *syncSema, n uint32) { } s.tail = w goparkunlock(&s.lock, "semarelease") + releaseSudog(w) } else { unlock(&s.lock) } diff --git a/src/runtime/signal_386.c b/src/runtime/signal_386.c index d55e30452..30a7488bd 100644 --- a/src/runtime/signal_386.c +++ b/src/runtime/signal_386.c @@ -109,7 +109,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *ctxt, G *gp) if(runtime·gotraceback(&crash)){ runtime·goroutineheader(gp); - runtime·traceback(SIG_EIP(info, ctxt), SIG_ESP(info, ctxt), 0, gp); + runtime·tracebacktrap(SIG_EIP(info, ctxt), SIG_ESP(info, ctxt), 0, gp); runtime·tracebackothers(gp); runtime·printf("\n"); runtime·dumpregs(info, ctxt); diff --git a/src/runtime/signal_amd64x.c b/src/runtime/signal_amd64x.c index 44e68cecf..feb4afcce 100644 --- a/src/runtime/signal_amd64x.c +++ b/src/runtime/signal_amd64x.c @@ -143,7 +143,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *ctxt, G *gp) if(runtime·gotraceback(&crash)){ runtime·goroutineheader(gp); - runtime·traceback(SIG_RIP(info, ctxt), SIG_RSP(info, ctxt), 0, gp); + runtime·tracebacktrap(SIG_RIP(info, ctxt), SIG_RSP(info, ctxt), 0, gp); runtime·tracebackothers(gp); runtime·printf("\n"); runtime·dumpregs(info, ctxt); diff --git a/src/runtime/signal_arm.c b/src/runtime/signal_arm.c index 3571cf3ac..afad5e7d1 100644 --- a/src/runtime/signal_arm.c +++ b/src/runtime/signal_arm.c @@ -108,7 +108,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *ctxt, G *gp) if(runtime·gotraceback(&crash)){ runtime·goroutineheader(gp); - runtime·traceback(SIG_PC(info, ctxt), SIG_SP(info, ctxt), SIG_LR(info, ctxt), gp); + runtime·tracebacktrap(SIG_PC(info, ctxt), SIG_SP(info, ctxt), SIG_LR(info, ctxt), gp); runtime·tracebackothers(gp); runtime·printf("\n"); runtime·dumpregs(info, ctxt); diff --git a/src/runtime/stack.c b/src/runtime/stack.c index a4947a53b..ffae73a2a 100644 --- a/src/runtime/stack.c +++ b/src/runtime/stack.c @@ -582,7 +582,7 @@ copystack(G *gp, uintptr newsize) adjinfo.old = old; adjinfo.delta = new.hi - old.hi; cb = adjustframe; - runtime·gentraceback(~(uintptr)0, ~(uintptr)0, 0, gp, 0, nil, 0x7fffffff, &cb, &adjinfo, false); + runtime·gentraceback(~(uintptr)0, ~(uintptr)0, 0, gp, 0, nil, 0x7fffffff, &cb, &adjinfo, 0); // adjust other miscellaneous things that have pointers into stacks. adjustctxt(gp, &adjinfo); diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go index 421ab04e5..9889567d6 100644 --- a/src/runtime/stubs.go +++ b/src/runtime/stubs.go @@ -228,6 +228,34 @@ func atomicloaduint(ptr *uint) uint //go:noescape func setcallerpc(argp unsafe.Pointer, pc uintptr) +// getcallerpc returns the program counter (PC) of its caller's caller. +// getcallersp returns the stack pointer (SP) of its caller's caller. +// For both, the argp must be a pointer to the caller's first function argument. +// The implementation may or may not use argp, depending on +// the architecture. +// +// For example: +// +// func f(arg1, arg2, arg3 int) { +// pc := getcallerpc(unsafe.Pointer(&arg1)) +// sp := getcallerpc(unsafe.Pointer(&arg2)) +// } +// +// These two lines find the PC and SP immediately following +// the call to f (where f will return). +// +// The call to getcallerpc and getcallersp must be done in the +// frame being asked about. It would not be correct for f to pass &arg1 +// to another function g and let g call getcallerpc/getcallersp. +// The call inside g might return information about g's caller or +// information about f's caller or complete garbage. +// +// The result of getcallersp is correct at the time of the return, +// but it may be invalidated by any subsequent call to a function +// that might relocate the stack in order to grow or shrink it. +// A general rule is that the result of getcallersp should be used +// immediately and can only be passed to nosplit functions. + //go:noescape func getcallerpc(argp unsafe.Pointer) uintptr diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 24dc3eea9..1c6ce6e64 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -96,11 +96,27 @@ func tracebackdefers(gp *g, callback func(*stkframe, unsafe.Pointer) bool, v uns // the runtime.Callers function (pcbuf != nil), as well as the garbage // collector (callback != nil). A little clunky to merge these, but avoids // duplicating the code and all its subtlety. -func gentraceback(pc0 uintptr, sp0 uintptr, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, printall bool) int { +func gentraceback(pc0 uintptr, sp0 uintptr, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, flags uint) int { if goexitPC == 0 { gothrow("gentraceback before goexitPC initialization") } g := getg() + if g == gp && g == g.m.curg { + // The starting sp has been passed in as a uintptr, and the caller may + // have other uintptr-typed stack references as well. + // If during one of the calls that got us here or during one of the + // callbacks below the stack must be grown, all these uintptr references + // to the stack will not be updated, and gentraceback will continue + // to inspect the old stack memory, which may no longer be valid. + // Even if all the variables were updated correctly, it is not clear that + // we want to expose a traceback that begins on one stack and ends + // on another stack. That could confuse callers quite a bit. + // Instead, we require that gentraceback and any other function that + // accepts an sp for the current goroutine (typically obtained by + // calling getcallersp) must not run on that goroutine's stack but + // instead on the g0 stack. + gothrow("gentraceback cannot trace user goroutine on its own stack") + } gotraceback := gotraceback(nil) if pc0 == ^uintptr(0) && sp0 == ^uintptr(0) { // Signal to fetch saved values from gp. if gp.syscallsp != 0 { @@ -297,13 +313,13 @@ func gentraceback(pc0 uintptr, sp0 uintptr, lr0 uintptr, gp *g, skip int, pcbuf } } if printing { - if printall || showframe(f, gp) { + if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp) { // Print during crash. // main(0x1, 0x2, 0x3) // /home/rsc/go/src/runtime/x.go:23 +0xf // tracepc := frame.pc // back up to CALL instruction for funcline. - if n > 0 && frame.pc > f.entry && !waspanic { + if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry && !waspanic { tracepc-- } print(gofuncname(f), "(") @@ -475,17 +491,32 @@ func printcreatedby(gp *g) { } func traceback(pc uintptr, sp uintptr, lr uintptr, gp *g) { + traceback1(pc, sp, lr, gp, 0) +} + +// tracebacktrap is like traceback but expects that the PC and SP were obtained +// from a trap, not from gp->sched or gp->syscallpc/gp->syscallsp or getcallerpc/getcallersp. +// Because they are from a trap instead of from a saved pair, +// the initial PC must not be rewound to the previous instruction. +// (All the saved pairs record a PC that is a return address, so we +// rewind it into the CALL instruction.) +func tracebacktrap(pc uintptr, sp uintptr, lr uintptr, gp *g) { + traceback1(pc, sp, lr, gp, _TraceTrap) +} + +func traceback1(pc uintptr, sp uintptr, lr uintptr, gp *g, flags uint) { var n int if readgstatus(gp)&^_Gscan == _Gsyscall { - // Override signal registers if blocked in system call. + // Override registers if blocked in system call. pc = gp.syscallpc sp = gp.syscallsp + flags &^= _TraceTrap } // Print traceback. By default, omits runtime frames. // If that means we print nothing at all, repeat forcing all frames printed. - n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, false) - if n == 0 { - n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, true) + n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags) + if n == 0 && (flags&_TraceRuntimeFrames) == 0 { + n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags|_TraceRuntimeFrames) } if n == _TracebackMaxFrames { print("...additional frames elided...\n") @@ -496,11 +527,15 @@ func traceback(pc uintptr, sp uintptr, lr uintptr, gp *g) { func callers(skip int, pcbuf *uintptr, m int) int { sp := getcallersp(unsafe.Pointer(&skip)) pc := uintptr(getcallerpc(unsafe.Pointer(&skip))) - return gentraceback(pc, sp, 0, getg(), skip, pcbuf, m, nil, nil, false) + var n int + onM(func() { + n = gentraceback(pc, sp, 0, getg(), skip, pcbuf, m, nil, nil, 0) + }) + return n } func gcallers(gp *g, skip int, pcbuf *uintptr, m int) int { - return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, pcbuf, m, nil, nil, false) + return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, pcbuf, m, nil, nil, 0) } func showframe(f *_func, gp *g) bool { diff --git a/src/sync/atomic/value.go b/src/sync/atomic/value.go index ab46d9a24..ab3aa1128 100644 --- a/src/sync/atomic/value.go +++ b/src/sync/atomic/value.go @@ -38,7 +38,7 @@ func (v *Value) Load() (x interface{}) { return } -// Store sets the value of the Value to v. +// Store sets the value of the Value to x. // All calls to Store for a given Value must use values of the same concrete type. // Store of an inconsistent type panics, as does Store(nil). func (v *Value) Store(x interface{}) { |