From 0effc3f961337abd0edb7c1a3dcd4b98636c085c Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 7 Mar 2012 01:16:20 +0000 Subject: libgo: Implement and use runtime.Caller, runtime.Func.FileLine. From-SVN: r185025 --- libgo/Makefile.am | 4 +- libgo/Makefile.in | 4 +- libgo/go/debug/dwarf/const.go | 27 +++ libgo/go/debug/dwarf/entry.go | 9 + libgo/go/debug/dwarf/line.go | 416 ++++++++++++++++++++++++++++++++ libgo/go/debug/dwarf/line_test.go | 53 ++++ libgo/go/debug/dwarf/unit.go | 10 + libgo/go/debug/elf/elf_test.go | 3 +- libgo/go/debug/elf/file.go | 6 +- libgo/go/debug/elf/file_test.go | 3 +- libgo/go/debug/elf/runtime.go | 161 ++++++++++++ libgo/go/debug/macho/file.go | 6 +- libgo/go/debug/macho/file_test.go | 3 +- libgo/go/encoding/binary/binary_test.go | 5 +- libgo/go/encoding/binary/export_test.go | 15 ++ libgo/go/encoding/binary/varint_test.go | 7 +- libgo/go/log/log.go | 1 + libgo/go/log/log_test.go | 6 +- libgo/go/net/http/pprof/pprof.go | 1 + libgo/go/net/ip_test.go | 1 + libgo/go/runtime/debug/stack.go | 1 + libgo/go/runtime/extern.go | 16 +- libgo/go/runtime/pprof/pprof.go | 1 + libgo/go/testing/testing.go | 1 + libgo/runtime/go-caller.c | 125 ++++++++-- libgo/runtime/go-callers.c | 12 +- libgo/runtime/mprof.goc | 4 - libgo/runtime/runtime.c | 19 -- libgo/runtime/runtime.h | 9 + 29 files changed, 861 insertions(+), 68 deletions(-) create mode 100644 libgo/go/debug/dwarf/line.go create mode 100644 libgo/go/debug/dwarf/line_test.go create mode 100644 libgo/go/debug/elf/runtime.go create mode 100644 libgo/go/encoding/binary/export_test.go (limited to 'libgo') diff --git a/libgo/Makefile.am b/libgo/Makefile.am index d43f054..99294f1 100644 --- a/libgo/Makefile.am +++ b/libgo/Makefile.am @@ -1024,12 +1024,14 @@ go_debug_dwarf_files = \ go/debug/dwarf/buf.go \ go/debug/dwarf/const.go \ go/debug/dwarf/entry.go \ + go/debug/dwarf/line.go \ go/debug/dwarf/open.go \ go/debug/dwarf/type.go \ go/debug/dwarf/unit.go go_debug_elf_files = \ go/debug/elf/elf.go \ - go/debug/elf/file.go + go/debug/elf/file.go \ + go/debug/elf/runtime.go go_debug_gosym_files = \ go/debug/gosym/pclntab.go \ go/debug/gosym/symtab.go diff --git a/libgo/Makefile.in b/libgo/Makefile.in index 418c787..b57d929 100644 --- a/libgo/Makefile.in +++ b/libgo/Makefile.in @@ -1340,13 +1340,15 @@ go_debug_dwarf_files = \ go/debug/dwarf/buf.go \ go/debug/dwarf/const.go \ go/debug/dwarf/entry.go \ + go/debug/dwarf/line.go \ go/debug/dwarf/open.go \ go/debug/dwarf/type.go \ go/debug/dwarf/unit.go go_debug_elf_files = \ go/debug/elf/elf.go \ - go/debug/elf/file.go + go/debug/elf/file.go \ + go/debug/elf/runtime.go go_debug_gosym_files = \ go/debug/gosym/pclntab.go \ diff --git a/libgo/go/debug/dwarf/const.go b/libgo/go/debug/dwarf/const.go index 918b153..5301edc 100644 --- a/libgo/go/debug/dwarf/const.go +++ b/libgo/go/debug/dwarf/const.go @@ -431,3 +431,30 @@ const ( encUnsignedChar = 0x08 encImaginaryFloat = 0x09 ) + +// Line number opcodes. +const ( + LineExtendedOp = 0 + LineCopy = 1 + LineAdvancePC = 2 + LineAdvanceLine = 3 + LineSetFile = 4 + LineSetColumn = 5 + LineNegateStmt = 6 + LineSetBasicBlock = 7 + LineConstAddPC = 8 + LineFixedAdvancePC = 9 + // next 3 are DWARF 3 + LineSetPrologueEnd = 10 + LineSetEpilogueBegin = 11 + LineSetISA = 12 +) + +// Line number extended opcodes. +const ( + LineExtEndSequence = 1 + LineExtSetAddress = 2 + LineExtDefineFile = 3 + // next 1 is DWARF 4 + LineExtSetDiscriminator = 4 +) diff --git a/libgo/go/debug/dwarf/entry.go b/libgo/go/debug/dwarf/entry.go index 2885d8f..f9a4c1b 100644 --- a/libgo/go/debug/dwarf/entry.go +++ b/libgo/go/debug/dwarf/entry.go @@ -246,6 +246,15 @@ func (d *Data) Reader() *Reader { return r } +// unitReader returns a new reader starting at a specific unit. +func (d *Data) unitReader(i int) *Reader { + r := &Reader{d: d} + r.unit = i + u := &d.unit[i] + r.b = makeBuf(d, "info", u.off, u.data, u.addrsize) + return r +} + // Seek positions the Reader at offset off in the encoded entry stream. // Offset 0 can be used to denote the first entry. func (r *Reader) Seek(off Offset) { diff --git a/libgo/go/debug/dwarf/line.go b/libgo/go/debug/dwarf/line.go new file mode 100644 index 0000000..091ebe0 --- /dev/null +++ b/libgo/go/debug/dwarf/line.go @@ -0,0 +1,416 @@ +// 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. + +// DWARF line number information. + +package dwarf + +import ( + "errors" + "path/filepath" + "sort" + "strconv" +) + +// A Line holds all the available information about the source code +// corresponding to a specific program counter address. +type Line struct { + Filename string // source file name + OpIndex int // index of operation in VLIW instruction + Line int // line number + Column int // column number + ISA int // instruction set code + Discriminator int // block discriminator + Stmt bool // instruction starts statement + Block bool // instruction starts basic block + EndPrologue bool // instruction ends function prologue + BeginEpilogue bool // instruction begins function epilogue +} + +// LineForPc returns the line number information for a program counter +// address, if any. When this returns multiple Line structures in a +// context where only one can be used, the last one is the best. +func (d *Data) LineForPC(pc uint64) ([]*Line, error) { + for i := range d.unit { + u := &d.unit[i] + if u.pc == nil { + if err := d.readUnitLine(i, u); err != nil { + return nil, err + } + } + for _, ar := range u.pc { + if pc >= ar.low && pc < ar.high { + return d.findLine(u, pc) + } + } + } + return nil, nil +} + +// readUnitLine reads in the line number information for a compilation +// unit. +func (d *Data) readUnitLine(i int, u *unit) error { + r := d.unitReader(i) + setLineOff := false + for { + e, err := r.Next() + if err != nil { + return err + } + if e == nil { + break + } + if r.unit != i { + break + } + switch e.Tag { + case TagCompileUnit, TagSubprogram, TagEntryPoint, TagInlinedSubroutine: + low, lowok := e.Val(AttrLowpc).(uint64) + high, highok := e.Val(AttrHighpc).(uint64) + if lowok && highok { + u.pc = append(u.pc, addrRange{low, high}) + } else if f, ok := e.Val(AttrRanges).(Offset); ok { + // TODO: Handle AttrRanges and .debug_ranges. + _ = f + } + if off, ok := e.Val(AttrStmtList).(int64); ok { + u.lineoff = Offset(off) + setLineOff = true + } + if dir, ok := e.Val(AttrCompDir).(string); ok { + u.dir = dir + } + } + } + if !setLineOff { + u.lineoff = Offset(0) + u.lineoff-- + } + return nil +} + +// findLine finds the line information for a PC value, given the unit +// containing the information. +func (d *Data) findLine(u *unit, pc uint64) ([]*Line, error) { + if u.lines == nil { + if err := d.parseLine(u); err != nil { + return nil, err + } + } + + for _, ln := range u.lines { + if pc < ln.addrs[0].pc || pc > ln.addrs[len(ln.addrs)-1].pc { + continue + } + i := sort.Search(len(ln.addrs), + func(i int) bool { return ln.addrs[i].pc > pc }) + i-- + p := new(Line) + *p = ln.line + p.Line = ln.addrs[i].line + ret := []*Line{p} + for i++; i < len(ln.addrs) && ln.addrs[i].pc == pc; i++ { + p = new(Line) + *p = ln.line + p.Line = ln.addrs[i].line + ret = append(ret, p) + } + return ret, nil + } + + return nil, nil +} + +// FileLine returns the file name and line number for a program +// counter address, or "", 0 if unknown. +func (d *Data) FileLine(pc uint64) (string, int, error) { + r, err := d.LineForPC(pc) + if err != nil { + return "", 0, err + } + if r == nil { + return "", 0, nil + } + ln := r[len(r)-1] + return ln.Filename, ln.Line, nil +} + +// A mapLineInfo holds the PC values and line numbers associated with +// a single Line structure. This representation is chosen to reduce +// memory usage based on typical debug info. +type mapLineInfo struct { + line Line // line.Line will be zero + addrs lineAddrs // sorted by PC +} + +// A list of lines. This will be sorted by PC. +type lineAddrs []oneLineInfo + +func (p lineAddrs) Len() int { return len(p) } +func (p lineAddrs) Less(i, j int) bool { return p[i].pc < p[j].pc } +func (p lineAddrs) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// A oneLineInfo is a single PC and line number. +type oneLineInfo struct { + pc uint64 + line int +} + +// A lineHdr holds the relevant information from a line number +// program header. +type lineHdr struct { + version uint16 // version of line number encoding + minInsnLen uint8 // minimum instruction length + maxOpsPerInsn uint8 // maximum number of ops per instruction + defStmt bool // initial value of stmt register + lineBase int8 // line adjustment base + lineRange uint8 // line adjustment step + opBase uint8 // base of special opcode values + opLen []uint8 // lengths of standard opcodes + dirs []string // directories + files []string // file names +} + +// parseLine parses the line number information for a compilation unit +func (d *Data) parseLine(u *unit) error { + if u.lineoff+1 == 0 { + return errors.New("unknown line offset") + } + b := makeBuf(d, "line", u.lineoff, d.line, u.addrsize) + len := uint64(b.uint32()) + offSize := 4 + if len == 0xffffffff { + len = b.uint64() + offSize = 8 + } + end := b.off + Offset(len) + hdr := d.parseLineHdr(u, &b, offSize) + if b.err == nil { + d.parseLineProgram(u, &b, hdr, end) + } + return b.err +} + +// parseLineHdr parses a line number program header. +func (d *Data) parseLineHdr(u *unit, b *buf, offSize int) (hdr lineHdr) { + hdr.version = b.uint16() + if hdr.version < 2 || hdr.version > 4 { + b.error("unsupported DWARF version " + strconv.Itoa(int(hdr.version))) + return + } + + b.bytes(offSize) // header length + + hdr.minInsnLen = b.uint8() + if hdr.version < 4 { + hdr.maxOpsPerInsn = 1 + } else { + hdr.maxOpsPerInsn = b.uint8() + } + + if b.uint8() == 0 { + hdr.defStmt = false + } else { + hdr.defStmt = true + } + hdr.lineBase = int8(b.uint8()) + hdr.lineRange = b.uint8() + hdr.opBase = b.uint8() + hdr.opLen = b.bytes(int(hdr.opBase - 1)) + + for d := b.string(); len(d) > 0; d = b.string() { + hdr.dirs = append(hdr.dirs, d) + } + + for f := b.string(); len(f) > 0; f = b.string() { + d := b.uint() + if !filepath.IsAbs(f) { + if d > 0 { + if d > uint64(len(hdr.dirs)) { + b.error("DWARF directory index out of range") + return + } + f = filepath.Join(hdr.dirs[d-1], f) + } else if u.dir != "" { + f = filepath.Join(u.dir, f) + } + } + b.uint() // file's last mtime + b.uint() // file length + hdr.files = append(hdr.files, f) + } + + return +} + +// parseLineProgram parses a line program, adding information to +// d.lineInfo as it goes. +func (d *Data) parseLineProgram(u *unit, b *buf, hdr lineHdr, end Offset) { + address := uint64(0) + line := 1 + resetLineInfo := Line{ + Filename: "", + OpIndex: 0, + Line: 0, + Column: 0, + ISA: 0, + Discriminator: 0, + Stmt: hdr.defStmt, + Block: false, + EndPrologue: false, + BeginEpilogue: false, + } + if len(hdr.files) > 0 { + resetLineInfo.Filename = hdr.files[0] + } + lineInfo := resetLineInfo + + var lines []mapLineInfo + + minInsnLen := uint64(hdr.minInsnLen) + maxOpsPerInsn := uint64(hdr.maxOpsPerInsn) + lineBase := int(hdr.lineBase) + lineRange := hdr.lineRange + newLineInfo := true + for b.off < end && b.err == nil { + op := b.uint8() + if op >= hdr.opBase { + // This is a special opcode. + op -= hdr.opBase + advance := uint64(op / hdr.lineRange) + opIndex := uint64(lineInfo.OpIndex) + address += minInsnLen * ((opIndex + advance) / maxOpsPerInsn) + newOpIndex := int((opIndex + advance) % maxOpsPerInsn) + line += lineBase + int(op%lineRange) + if newOpIndex != lineInfo.OpIndex { + lineInfo.OpIndex = newOpIndex + newLineInfo = true + } + lines, lineInfo, newLineInfo = d.addLine(lines, lineInfo, address, line, newLineInfo) + } else if op == LineExtendedOp { + c := b.uint() + op = b.uint8() + switch op { + case LineExtEndSequence: + u.lines = append(u.lines, lines...) + lineInfo = resetLineInfo + lines = nil + case LineExtSetAddress: + address = b.addr() + case LineExtDefineFile: + f := b.string() + d := b.uint() + b.uint() // mtime + b.uint() // length + if d > 0 && !filepath.IsAbs(f) { + if d >= uint64(len(hdr.dirs)) { + b.error("DWARF directory index out of range") + return + } + f = filepath.Join(hdr.dirs[d-1], f) + } + hdr.files = append(hdr.files, f) + case LineExtSetDiscriminator: + lineInfo.Discriminator = int(b.uint()) + newLineInfo = true + default: + if c > 0 { + b.bytes(int(c) - 1) + } + } + } else { + switch op { + case LineCopy: + lines, lineInfo, newLineInfo = d.addLine(lines, lineInfo, address, line, newLineInfo) + case LineAdvancePC: + advance := b.uint() + opIndex := uint64(lineInfo.OpIndex) + address += minInsnLen * ((opIndex + advance) / maxOpsPerInsn) + newOpIndex := int((opIndex + advance) % maxOpsPerInsn) + if newOpIndex != lineInfo.OpIndex { + lineInfo.OpIndex = newOpIndex + newLineInfo = true + } + case LineAdvanceLine: + line += int(b.int()) + case LineSetFile: + i := b.uint() + if i > uint64(len(hdr.files)) { + b.error("DWARF file number out of range") + return + } + lineInfo.Filename = hdr.files[i] + newLineInfo = true + case LineSetColumn: + lineInfo.Column = int(b.uint()) + newLineInfo = true + case LineNegateStmt: + lineInfo.Stmt = !lineInfo.Stmt + newLineInfo = true + case LineSetBasicBlock: + lineInfo.Block = true + newLineInfo = true + case LineConstAddPC: + op = 255 - hdr.opBase + advance := uint64(op / hdr.lineRange) + opIndex := uint64(lineInfo.OpIndex) + address += minInsnLen * ((opIndex + advance) / maxOpsPerInsn) + newOpIndex := int((opIndex + advance) % maxOpsPerInsn) + if newOpIndex != lineInfo.OpIndex { + lineInfo.OpIndex = newOpIndex + newLineInfo = true + } + case LineFixedAdvancePC: + address += uint64(b.uint16()) + if lineInfo.OpIndex != 0 { + lineInfo.OpIndex = 0 + newLineInfo = true + } + case LineSetPrologueEnd: + lineInfo.EndPrologue = true + newLineInfo = true + case LineSetEpilogueBegin: + lineInfo.BeginEpilogue = true + newLineInfo = true + case LineSetISA: + lineInfo.ISA = int(b.uint()) + newLineInfo = true + default: + if int(op) >= len(hdr.opLen) { + b.error("DWARF line opcode has unknown length") + return + } + for i := hdr.opLen[op-1]; i > 0; i-- { + b.int() + } + } + } + } +} + +// addLine adds the current address and line to lines using lineInfo. +// If newLineInfo is true this is a new lineInfo. This returns the +// updated lines, lineInfo, and newLineInfo. +func (d *Data) addLine(lines []mapLineInfo, lineInfo Line, address uint64, line int, newLineInfo bool) ([]mapLineInfo, Line, bool) { + if newLineInfo { + if len(lines) > 0 { + sort.Sort(lines[len(lines)-1].addrs) + } + lines = append(lines, mapLineInfo{line: lineInfo}) + } + p := &lines[len(lines)-1] + p.addrs = append(p.addrs, oneLineInfo{address, line}) + + if lineInfo.Block || lineInfo.EndPrologue || lineInfo.BeginEpilogue || lineInfo.Discriminator != 0 { + lineInfo.Block = false + lineInfo.EndPrologue = false + lineInfo.BeginEpilogue = false + lineInfo.Discriminator = 0 + newLineInfo = true + } else { + newLineInfo = false + } + + return lines, lineInfo, newLineInfo +} diff --git a/libgo/go/debug/dwarf/line_test.go b/libgo/go/debug/dwarf/line_test.go new file mode 100644 index 0000000..2476a6f --- /dev/null +++ b/libgo/go/debug/dwarf/line_test.go @@ -0,0 +1,53 @@ +// 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. + +package dwarf_test + +import ( + . "debug/dwarf" + "path/filepath" + "testing" +) + +type lineTest struct { + pc uint64 + file string + line int +} + +var elfLineTests = [...]lineTest{ + {0x4004c4, "typedef.c", 83}, + {0x4004c8, "typedef.c", 84}, + {0x4004ca, "typedef.c", 84}, + {0x4003e0, "", 0}, +} + +var machoLineTests = [...]lineTest{ + {0x0, "typedef.c", 83}, +} + +func TestLineElf(t *testing.T) { + testLine(t, elfData(t, "testdata/typedef.elf"), elfLineTests[:], "elf") +} + +func TestLineMachO(t *testing.T) { + testLine(t, machoData(t, "testdata/typedef.macho"), machoLineTests[:], "macho") +} + +func testLine(t *testing.T, d *Data, tests []lineTest, kind string) { + for _, v := range tests { + file, line, err := d.FileLine(v.pc) + if err != nil { + t.Errorf("%s: %v", kind, err) + continue + } + if file != "" { + file = filepath.Base(file) + } + if file != v.file || line != v.line { + t.Errorf("%s: for %d have %q:%d want %q:%d", + kind, v.pc, file, line, v.file, v.line) + } + } +} diff --git a/libgo/go/debug/dwarf/unit.go b/libgo/go/debug/dwarf/unit.go index c10d75d..931468a 100644 --- a/libgo/go/debug/dwarf/unit.go +++ b/libgo/go/debug/dwarf/unit.go @@ -12,9 +12,19 @@ import "strconv" type unit struct { base Offset // byte offset of header within the aggregate info off Offset // byte offset of data within the aggregate info + lineoff Offset // byte offset of data within the line info data []byte atable abbrevTable addrsize int + dir string + pc []addrRange // PC ranges in this compilation unit + lines []mapLineInfo // PC -> line mapping +} + +// A range is an address range. +type addrRange struct { + low uint64 + high uint64 } func (d *Data) parseUnits() ([]unit, error) { diff --git a/libgo/go/debug/elf/elf_test.go b/libgo/go/debug/elf/elf_test.go index 67b961b..b8cdbcc 100644 --- a/libgo/go/debug/elf/elf_test.go +++ b/libgo/go/debug/elf/elf_test.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package elf +package elf_test import ( + . "debug/elf" "fmt" "testing" ) diff --git a/libgo/go/debug/elf/file.go b/libgo/go/debug/elf/file.go index 184ca83..c2c03d2 100644 --- a/libgo/go/debug/elf/file.go +++ b/libgo/go/debug/elf/file.go @@ -563,7 +563,7 @@ func (f *File) DWARF() (*dwarf.Data, error) { // There are many other DWARF sections, but these // are the required ones, and the debug/dwarf package // does not use the others, so don't bother loading them. - var names = [...]string{"abbrev", "info", "str"} + var names = [...]string{"abbrev", "info", "line", "str"} var dat [len(names)][]byte for i, name := range names { name = ".debug_" + name @@ -592,8 +592,8 @@ func (f *File) DWARF() (*dwarf.Data, error) { } } - abbrev, info, str := dat[0], dat[1], dat[2] - return dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str) + abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3] + return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str) } // Symbols returns the symbol table for f. diff --git a/libgo/go/debug/elf/file_test.go b/libgo/go/debug/elf/file_test.go index 98f2723c..105b697 100644 --- a/libgo/go/debug/elf/file_test.go +++ b/libgo/go/debug/elf/file_test.go @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package elf +package elf_test import ( "debug/dwarf" + . "debug/elf" "encoding/binary" "net" "os" diff --git a/libgo/go/debug/elf/runtime.go b/libgo/go/debug/elf/runtime.go new file mode 100644 index 0000000..23e79bf --- /dev/null +++ b/libgo/go/debug/elf/runtime.go @@ -0,0 +1,161 @@ +// 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. + +// This is gccgo-specific code that uses DWARF information to fetch +// file/line information for PC values. This package registers itself +// with the runtime package. + +package elf + +import ( + "debug/dwarf" + "debug/macho" + "os" + "runtime" + "sort" + "sync" +) + +func init() { + // Register our lookup functions with the runtime package. + runtime.RegisterDebugLookup(funcFileLine, symbolValue) +} + +// The file struct holds information for a specific file that is part +// of the execution. +type file struct { + elf *File // If ELF + macho *macho.File // If Mach-O + dwarf *dwarf.Data // DWARF information + + symsByName []sym // Sorted by name + symsByAddr []sym // Sorted by address +} + +// Sort symbols by name. +type symsByName []sym + +func (s symsByName) Len() int { return len(s) } +func (s symsByName) Less(i, j int) bool { return s[i].name < s[j].name } +func (s symsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// Sort symbols by address. +type symsByAddr []sym + +func (s symsByAddr) Len() int { return len(s) } +func (s symsByAddr) Less(i, j int) bool { return s[i].addr < s[j].addr } +func (s symsByAddr) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// The sym structure holds the information we care about for a symbol, +// namely name and address. +type sym struct { + name string + addr uintptr +} + +// Open an input file. +func open(name string) (*file, error) { + efile, err := Open(name) + var mfile *macho.File + if err != nil { + var merr error + mfile, merr = macho.Open(name) + if merr != nil { + return nil, err + } + } + + r := &file{elf: efile, macho: mfile} + + if efile != nil { + r.dwarf, err = efile.DWARF() + } else { + r.dwarf, err = mfile.DWARF() + } + if err != nil { + return nil, err + } + + var syms []sym + if efile != nil { + esyms, err := efile.Symbols() + if err != nil { + return nil, err + } + syms = make([]sym, 0, len(esyms)) + for _, s := range esyms { + if ST_TYPE(s.Info) == STT_FUNC { + syms = append(syms, sym{s.Name, uintptr(s.Value)}) + } + } + } else { + syms = make([]sym, 0, len(mfile.Symtab.Syms)) + for _, s := range mfile.Symtab.Syms { + syms = append(syms, sym{s.Name, uintptr(s.Value)}) + } + } + + r.symsByName = make([]sym, len(syms)) + copy(r.symsByName, syms) + sort.Sort(symsByName(r.symsByName)) + + r.symsByAddr = syms + sort.Sort(symsByAddr(r.symsByAddr)) + + return r, nil +} + +// The main executable +var executable *file + +// Only open the executable once. +var executableOnce sync.Once + +func openExecutable() { + executableOnce.Do(func() { + f, err := open("/proc/self/exe") + if err != nil { + f, err = open(os.Args[0]) + if err != nil { + return + } + } + executable = f + }) +} + +// The funcFileLine function looks up the function name, file name, +// and line number for a PC value. +func funcFileLine(pc uintptr, function *string, file *string, line *int) bool { + openExecutable() + if executable.dwarf == nil { + return false + } + f, ln, err := executable.dwarf.FileLine(uint64(pc)) + if err != nil { + return false + } + *file = f + *line = ln + + *function = "" + if len(executable.symsByAddr) > 0 && pc >= executable.symsByAddr[0].addr { + i := sort.Search(len(executable.symsByAddr), + func(i int) bool { return executable.symsByAddr[i].addr > pc }) + *function = executable.symsByAddr[i-1].name + } + + return true +} + +// The symbolValue function fetches the value of a symbol. +func symbolValue(name string, val *uintptr) bool { + i := sort.Search(len(executable.symsByName), + func(i int) bool { return executable.symsByName[i].name >= name }) + if i >= len(executable.symsByName) || executable.symsByName[i].name != name { + return false + } + *val = executable.symsByName[i].addr + return true +} diff --git a/libgo/go/debug/macho/file.go b/libgo/go/debug/macho/file.go index fa73a31..6577803 100644 --- a/libgo/go/debug/macho/file.go +++ b/libgo/go/debug/macho/file.go @@ -467,7 +467,7 @@ func (f *File) DWARF() (*dwarf.Data, error) { // There are many other DWARF sections, but these // are the required ones, and the debug/dwarf package // does not use the others, so don't bother loading them. - var names = [...]string{"abbrev", "info", "str"} + var names = [...]string{"abbrev", "info", "line", "str"} var dat [len(names)][]byte for i, name := range names { name = "__debug_" + name @@ -482,8 +482,8 @@ func (f *File) DWARF() (*dwarf.Data, error) { dat[i] = b } - abbrev, info, str := dat[0], dat[1], dat[2] - return dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str) + abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3] + return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str) } // ImportedSymbols returns the names of all symbols diff --git a/libgo/go/debug/macho/file_test.go b/libgo/go/debug/macho/file_test.go index 640225b..ecc6f68 100644 --- a/libgo/go/debug/macho/file_test.go +++ b/libgo/go/debug/macho/file_test.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package macho +package macho_test import ( + . "debug/macho" "reflect" "testing" ) diff --git a/libgo/go/encoding/binary/binary_test.go b/libgo/go/encoding/binary/binary_test.go index ff361b7..dec47a1 100644 --- a/libgo/go/encoding/binary/binary_test.go +++ b/libgo/go/encoding/binary/binary_test.go @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package binary +package binary_test import ( "bytes" + . "encoding/binary" "io" "math" "reflect" @@ -187,7 +188,7 @@ func BenchmarkReadStruct(b *testing.B) { bsr := &byteSliceReader{} var buf bytes.Buffer Write(&buf, BigEndian, &s) - n := dataSize(reflect.ValueOf(s)) + n := DataSize(reflect.ValueOf(s)) b.SetBytes(int64(n)) t := s b.ResetTimer() diff --git a/libgo/go/encoding/binary/export_test.go b/libgo/go/encoding/binary/export_test.go new file mode 100644 index 0000000..9eae2a9 --- /dev/null +++ b/libgo/go/encoding/binary/export_test.go @@ -0,0 +1,15 @@ +// 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. + +package binary + +import "reflect" + +// Export for testing. + +func DataSize(v reflect.Value) int { + return dataSize(v) +} + +var Overflow = overflow diff --git a/libgo/go/encoding/binary/varint_test.go b/libgo/go/encoding/binary/varint_test.go index 9476bd5..f67ca63 100644 --- a/libgo/go/encoding/binary/varint_test.go +++ b/libgo/go/encoding/binary/varint_test.go @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package binary +package binary_test import ( "bytes" + . "encoding/binary" "io" "testing" ) @@ -134,8 +135,8 @@ func testOverflow(t *testing.T, buf []byte, n0 int, err0 error) { } func TestOverflow(t *testing.T) { - testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x2}, -10, overflow) - testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x1, 0, 0}, -13, overflow) + testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x2}, -10, Overflow) + testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x1, 0, 0}, -13, Overflow) } func TestNonCanonicalZero(t *testing.T) { diff --git a/libgo/go/log/log.go b/libgo/go/log/log.go index a5d88fd..02a407e 100644 --- a/libgo/go/log/log.go +++ b/libgo/go/log/log.go @@ -14,6 +14,7 @@ package log import ( "bytes" + _ "debug/elf" "fmt" "io" "os" diff --git a/libgo/go/log/log_test.go b/libgo/go/log/log_test.go index 72ebf39..158c3d9 100644 --- a/libgo/go/log/log_test.go +++ b/libgo/go/log/log_test.go @@ -17,9 +17,9 @@ const ( Rdate = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]` Rtime = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]` Rmicroseconds = `\.[0-9][0-9][0-9][0-9][0-9][0-9]` - Rline = `[0-9]+:` // must update if the calls to l.Printf / l.Print below move - Rlongfile = `.*/[A-Za-z0-9_\-]+\.go:|\?\?\?:` + Rline - Rshortfile = `[A-Za-z0-9_\-]+\.go:|\?\?\?:` + Rline + Rline = `(54|56):` // must update if the calls to l.Printf / l.Print below move + Rlongfile = `.*/[A-Za-z0-9_\-]+\.go:` + Rline + Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline ) type tester struct { diff --git a/libgo/go/net/http/pprof/pprof.go b/libgo/go/net/http/pprof/pprof.go index 06fcde1..b8874f3 100644 --- a/libgo/go/net/http/pprof/pprof.go +++ b/libgo/go/net/http/pprof/pprof.go @@ -35,6 +35,7 @@ package pprof import ( "bufio" "bytes" + _ "debug/elf" "fmt" "html/template" "io" diff --git a/libgo/go/net/ip_test.go b/libgo/go/net/ip_test.go index df647ef..d0e987b 100644 --- a/libgo/go/net/ip_test.go +++ b/libgo/go/net/ip_test.go @@ -6,6 +6,7 @@ package net import ( "bytes" + _ "debug/elf" "reflect" "runtime" "testing" diff --git a/libgo/go/runtime/debug/stack.go b/libgo/go/runtime/debug/stack.go index a533a5c..fc74e53 100644 --- a/libgo/go/runtime/debug/stack.go +++ b/libgo/go/runtime/debug/stack.go @@ -8,6 +8,7 @@ package debug import ( "bytes" + _ "debug/elf" "fmt" "io/ioutil" "os" diff --git a/libgo/go/runtime/extern.go b/libgo/go/runtime/extern.go index 5fbfe54..2c097b0 100644 --- a/libgo/go/runtime/extern.go +++ b/libgo/go/runtime/extern.go @@ -32,16 +32,8 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) func Callers(skip int, pc []uintptr) int type Func struct { // Keep in sync with runtime.h:struct Func - name string - typ string // go type string - src string // src file name - pcln []byte // pc/ln tab for this func - entry uintptr // entry pc - pc0 uintptr // starting pc, ln for table - ln0 int32 - frame int32 // stack frame size - args int32 // number of 32-bit in/out args - locals int32 // number of 32-bit locals + name string + entry uintptr // entry pc } // FuncForPC returns a *Func describing the function that contains the @@ -65,6 +57,10 @@ func (f *Func) FileLine(pc uintptr) (file string, line int) { // implemented in symtab.c func funcline_go(*Func, uintptr) (string, int) +// A gccgo specific hook to use debug info to get file/line info. +func RegisterDebugLookup(func(pc uintptr, function *string, file *string, line *int) bool, + func(sym string, val *uintptr) bool) + // mid returns the current os thread (m) id. func mid() uint32 diff --git a/libgo/go/runtime/pprof/pprof.go b/libgo/go/runtime/pprof/pprof.go index f67e8a8..87f17d2 100644 --- a/libgo/go/runtime/pprof/pprof.go +++ b/libgo/go/runtime/pprof/pprof.go @@ -11,6 +11,7 @@ package pprof import ( "bufio" "bytes" + _ "debug/elf" "fmt" "io" "runtime" diff --git a/libgo/go/testing/testing.go b/libgo/go/testing/testing.go index 477d2ac..7072262 100644 --- a/libgo/go/testing/testing.go +++ b/libgo/go/testing/testing.go @@ -79,6 +79,7 @@ package testing import ( + _ "debug/elf" "flag" "fmt" "os" diff --git a/libgo/runtime/go-caller.c b/libgo/runtime/go-caller.c index b18759f..f2bebeb 100644 --- a/libgo/runtime/go-caller.c +++ b/libgo/runtime/go-caller.c @@ -8,8 +8,64 @@ #include +#include "runtime.h" #include "go-string.h" +/* Get the function name, file name, and line number for a PC value. + We use the DWARF debug information to get this. Rather than write + a whole new library in C, we use the existing Go library. + Unfortunately, the Go library is only available if the debug/elf + package is imported (we use debug/elf for both ELF and Mach-O, in + this case). We arrange for the debug/elf package to register + itself, and tweak the various packages that need this information + to import debug/elf where possible. */ + +/* The function that returns function/file/line information. */ + +typedef _Bool (*infofn_type) (uintptr_t, struct __go_string *, + struct __go_string *, int *); +static infofn_type infofn; + +/* The function that returns the value of a symbol, used to get the + entry address of a function. */ + +typedef _Bool (*symvalfn_type) (struct __go_string, uintptr_t *); +static symvalfn_type symvalfn; + +/* This is called by debug/elf to register the function that returns + function/file/line information. */ + +void RegisterDebugLookup (infofn_type, symvalfn_type) + __asm__ ("libgo_runtime.runtime.RegisterDebugLookup"); + +void +RegisterDebugLookup (infofn_type pi, symvalfn_type ps) +{ + infofn = pi; + symvalfn = ps; +} + +/* Return function/file/line information for PC. */ + +_Bool +__go_file_line (uintptr_t pc, struct __go_string *fn, struct __go_string *file, + int *line) +{ + if (infofn == NULL) + return 0; + return infofn (pc, fn, file, line); +} + +/* Return the value of a symbol. */ + +_Bool +__go_symbol_value (struct __go_string sym, uintptr_t *val) +{ + if (symvalfn == NULL) + return 0; + return symvalfn (sym, val); +} + /* The values returned by runtime.Caller. */ struct caller_ret @@ -20,32 +76,71 @@ struct caller_ret _Bool ok; }; -/* Implement runtime.Caller. */ - struct caller_ret Caller (int n) asm ("libgo_runtime.runtime.Caller"); +Func *FuncForPC (uintptr_t) asm ("libgo_runtime.runtime.FuncForPC"); + +/* Implement runtime.Caller. */ + struct caller_ret -Caller (int n __attribute__ ((unused))) +Caller (int skip) { struct caller_ret ret; + uintptr pc; + int32 n; + struct __go_string fn; - /* A proper implementation needs to dig through the debugging - information. */ - ret.pc = (uint64_t) (uintptr_t) __builtin_return_address (0); - ret.file.__data = NULL; - ret.file.__length = 0; - ret.line = 0; - ret.ok = 0; - + runtime_memclr (&ret, sizeof ret); + n = runtime_callers (skip + 1, &pc, 1); + if (n < 1) + return ret; + ret.pc = pc; + ret.ok = __go_file_line (pc, &fn, &ret.file, &ret.line); return ret; } /* Implement runtime.FuncForPC. */ -void *FuncForPC (uintptr_t) asm ("libgo_runtime.runtime.FuncForPC"); +Func * +FuncForPC (uintptr_t pc) +{ + Func *ret; + struct __go_string fn; + struct __go_string file; + int line; + uintptr_t val; -void * -FuncForPC(uintptr_t pc __attribute__ ((unused))) + if (!__go_file_line (pc, &fn, &file, &line)) + return NULL; + if (!__go_symbol_value (fn, &val)) + return NULL; + + ret = (Func *) runtime_malloc (sizeof (*ret)); + ret->name = fn; + ret->entry = val; + return ret; +} + +/* Look up the file and line information for a PC within a + function. */ + +struct funcline_go_return { - return NULL; + struct __go_string retfile; + int retline; +}; + +struct funcline_go_return +runtime_funcline_go (Func *f, uintptr targetpc) + __asm__ ("libgo_runtime.runtime.funcline_go"); + +struct funcline_go_return +runtime_funcline_go (Func *f __attribute__((unused)), uintptr targetpc) +{ + struct funcline_go_return ret; + struct __go_string fn; + + if (!__go_file_line (targetpc, &fn, &ret.retfile, &ret.retline)) + runtime_memclr (&ret, sizeof ret); + return ret; } diff --git a/libgo/runtime/go-callers.c b/libgo/runtime/go-callers.c index 0089c67..09556c3 100644 --- a/libgo/runtime/go-callers.c +++ b/libgo/runtime/go-callers.c @@ -25,8 +25,13 @@ backtrace (struct _Unwind_Context *context, void *varg) { struct callers_data *arg = (struct callers_data *) varg; uintptr pc; + int ip_before_insn = 0; +#ifdef HAVE_GETIPINFO + pc = _Unwind_GetIPInfo (context, &ip_before_insn); +#else pc = _Unwind_GetIP (context); +#endif /* FIXME: If PC is in the __morestack routine, we should ignore it. */ @@ -37,6 +42,11 @@ backtrace (struct _Unwind_Context *context, void *varg) return _URC_END_OF_STACK; else { + /* Here PC will be the return address. We actually want the + address of the call instruction, so back up one byte and + count on the lookup routines handling that correctly. */ + if (!ip_before_insn) + --pc; arg->pcbuf[arg->index] = pc; ++arg->index; } @@ -48,7 +58,7 @@ runtime_callers (int32 skip, uintptr *pcbuf, int32 m) { struct callers_data arg; - arg.skip = skip; + arg.skip = skip + 1; arg.pcbuf = pcbuf; arg.index = 0; arg.max = m; diff --git a/libgo/runtime/mprof.goc b/libgo/runtime/mprof.goc index e40dd61..c61c65c 100644 --- a/libgo/runtime/mprof.goc +++ b/libgo/runtime/mprof.goc @@ -225,11 +225,7 @@ runtime_MProf_Malloc(void *p, uintptr size) return; m->nomemprof++; -#if 0 nstk = runtime_callers(1, stk, 32); -#else - nstk = 0; -#endif runtime_lock(&proflock); b = stkbucket(stk, nstk, true); b->recent_allocs++; diff --git a/libgo/runtime/runtime.c b/libgo/runtime/runtime.c index 78c865b..d1ce26d 100644 --- a/libgo/runtime/runtime.c +++ b/libgo/runtime/runtime.c @@ -211,22 +211,3 @@ runtime_cputicks(void) return 0; #endif } - -struct funcline_go_return -{ - String retfile; - int32 retline; -}; - -struct funcline_go_return -runtime_funcline_go(void *f, uintptr targetpc) - __asm__("libgo_runtime.runtime.funcline_go"); - -struct funcline_go_return -runtime_funcline_go(void *f __attribute__((unused)), - uintptr targetpc __attribute__((unused))) -{ - struct funcline_go_return ret; - runtime_memclr(&ret, sizeof ret); - return ret; -} diff --git a/libgo/runtime/runtime.h b/libgo/runtime/runtime.h index 40c59a8..e012e18 100644 --- a/libgo/runtime/runtime.h +++ b/libgo/runtime/runtime.h @@ -48,6 +48,7 @@ typedef unsigned int uintptr __attribute__ ((mode (pointer))); typedef uint8 bool; typedef uint8 byte; +typedef struct Func Func; typedef struct G G; typedef union Lock Lock; typedef struct M M; @@ -201,6 +202,14 @@ enum #define NSIG 32 #endif +// NOTE(rsc): keep in sync with extern.go:/type.Func. +// Eventually, the loaded symbol table should be closer to this form. +struct Func +{ + String name; + uintptr entry; // entry pc +}; + /* Macros. */ #ifdef GOOS_windows -- cgit v1.1