Patchwork libgo patch committed: Implement runtime.Caller and friends

login
register
mail settings
Submitter Ian Taylor
Date March 7, 2012, 1:16 a.m.
Message ID <mcry5rd8b2v.fsf@dhcp-172-18-216-180.mtv.corp.google.com>
Download mbox | patch
Permalink /patch/145134/
State New
Headers show

Comments

Ian Taylor - March 7, 2012, 1:16 a.m.
The standard Go library provides a couple of functions that can be used
to get filename and line number information of the running program.  An
example is runtime.Caller, which can be used to return the filename and
line number of the caller of the current function.  In the gccgo world,
this kind of information is available in the debug info, but not in the
program itself.

This patch implements this feature for gccgo without requiring me to
write yet another ELF and DWARF reader in the C runtime support (I did
have to add line number support to the debug/dwarf package).  I arranged
for the debug/elf package to register a filename/line number routine
with the runtime package, and then have the runtime package call it when
appropriate (although the package is debug/elf, the code also works with
Mach-O and could be extended for other file formats as well).  This
approach works provided that any program that needs this information
imports debug/elf.  This is imperfect, but it suffices for the most
common use of this functionality, which is for the standard log package.

Bootstrapped and ran Go testsuite on x86_64-unknown-linux-gnu.
Committed to mainline.

Ian

Patch

diff -r 5f7b1adc8ca1 libgo/Makefile.am
--- a/libgo/Makefile.am	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/Makefile.am	Tue Mar 06 17:02:14 2012 -0800
@@ -1024,12 +1024,14 @@ 
 	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 -r 5f7b1adc8ca1 libgo/go/debug/dwarf/const.go
--- a/libgo/go/debug/dwarf/const.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/dwarf/const.go	Tue Mar 06 17:02:14 2012 -0800
@@ -431,3 +431,30 @@ 
 	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 -r 5f7b1adc8ca1 libgo/go/debug/dwarf/entry.go
--- a/libgo/go/debug/dwarf/entry.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/dwarf/entry.go	Tue Mar 06 17:02:14 2012 -0800
@@ -246,6 +246,15 @@ 
 	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 -r 5f7b1adc8ca1 libgo/go/debug/dwarf/line.go
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgo/go/debug/dwarf/line.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/debug/dwarf/line_test.go
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgo/go/debug/dwarf/line_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/debug/dwarf/unit.go
--- a/libgo/go/debug/dwarf/unit.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/dwarf/unit.go	Tue Mar 06 17:02:14 2012 -0800
@@ -12,9 +12,19 @@ 
 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 -r 5f7b1adc8ca1 libgo/go/debug/elf/elf_test.go
--- a/libgo/go/debug/elf/elf_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/elf/elf_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/debug/elf/file.go
--- a/libgo/go/debug/elf/file.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/elf/file.go	Tue Mar 06 17:02:14 2012 -0800
@@ -563,7 +563,7 @@ 
 	// 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 @@ 
 		}
 	}
 
-	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 -r 5f7b1adc8ca1 libgo/go/debug/elf/file_test.go
--- a/libgo/go/debug/elf/file_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/elf/file_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/debug/elf/runtime.go
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgo/go/debug/elf/runtime.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/debug/macho/file.go
--- a/libgo/go/debug/macho/file.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/macho/file.go	Tue Mar 06 17:02:14 2012 -0800
@@ -467,7 +467,7 @@ 
 	// 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 @@ 
 		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 -r 5f7b1adc8ca1 libgo/go/debug/macho/file_test.go
--- a/libgo/go/debug/macho/file_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/debug/macho/file_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/encoding/binary/binary_test.go
--- a/libgo/go/encoding/binary/binary_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/encoding/binary/binary_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 @@ 
 	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 -r 5f7b1adc8ca1 libgo/go/encoding/binary/export_test.go
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgo/go/encoding/binary/export_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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 -r 5f7b1adc8ca1 libgo/go/encoding/binary/varint_test.go
--- a/libgo/go/encoding/binary/varint_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/encoding/binary/varint_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -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) {
-	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 -r 5f7b1adc8ca1 libgo/go/log/log.go
--- a/libgo/go/log/log.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/log/log.go	Tue Mar 06 17:02:14 2012 -0800
@@ -14,6 +14,7 @@ 
 
 import (
 	"bytes"
+	_ "debug/elf"
 	"fmt"
 	"io"
 	"os"
diff -r 5f7b1adc8ca1 libgo/go/log/log_test.go
--- a/libgo/go/log/log_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/log/log_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -17,9 +17,9 @@ 
 	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 -r 5f7b1adc8ca1 libgo/go/net/http/pprof/pprof.go
--- a/libgo/go/net/http/pprof/pprof.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/net/http/pprof/pprof.go	Tue Mar 06 17:02:14 2012 -0800
@@ -35,6 +35,7 @@ 
 import (
 	"bufio"
 	"bytes"
+	_ "debug/elf"
 	"fmt"
 	"html/template"
 	"io"
diff -r 5f7b1adc8ca1 libgo/go/net/ip_test.go
--- a/libgo/go/net/ip_test.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/net/ip_test.go	Tue Mar 06 17:02:14 2012 -0800
@@ -6,6 +6,7 @@ 
 
 import (
 	"bytes"
+	_ "debug/elf"
 	"reflect"
 	"runtime"
 	"testing"
diff -r 5f7b1adc8ca1 libgo/go/runtime/debug/stack.go
--- a/libgo/go/runtime/debug/stack.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/runtime/debug/stack.go	Tue Mar 06 17:02:14 2012 -0800
@@ -8,6 +8,7 @@ 
 
 import (
 	"bytes"
+	_ "debug/elf"
 	"fmt"
 	"io/ioutil"
 	"os"
diff -r 5f7b1adc8ca1 libgo/go/runtime/extern.go
--- a/libgo/go/runtime/extern.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/runtime/extern.go	Tue Mar 06 17:02:14 2012 -0800
@@ -33,15 +33,7 @@ 
 
 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
 }
 
 // FuncForPC returns a *Func describing the function that contains the
@@ -65,6 +57,10 @@ 
 // 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 -r 5f7b1adc8ca1 libgo/go/runtime/pprof/pprof.go
--- a/libgo/go/runtime/pprof/pprof.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/runtime/pprof/pprof.go	Tue Mar 06 17:02:14 2012 -0800
@@ -11,6 +11,7 @@ 
 import (
 	"bufio"
 	"bytes"
+	_ "debug/elf"
 	"fmt"
 	"io"
 	"runtime"
diff -r 5f7b1adc8ca1 libgo/go/testing/testing.go
--- a/libgo/go/testing/testing.go	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/go/testing/testing.go	Tue Mar 06 17:02:14 2012 -0800
@@ -79,6 +79,7 @@ 
 package testing
 
 import (
+	_ "debug/elf"
 	"flag"
 	"fmt"
 	"os"
diff -r 5f7b1adc8ca1 libgo/runtime/go-caller.c
--- a/libgo/runtime/go-caller.c	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/runtime/go-caller.c	Tue Mar 06 17:02:14 2012 -0800
@@ -8,8 +8,64 @@ 
 
 #include <stdint.h>
 
+#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 @@ 
   _Bool ok;
 };
 
+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) asm ("libgo_runtime.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 -r 5f7b1adc8ca1 libgo/runtime/go-callers.c
--- a/libgo/runtime/go-callers.c	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/runtime/go-callers.c	Tue Mar 06 17:02:14 2012 -0800
@@ -25,8 +25,13 @@ 
 {
   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 @@ 
     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 @@ 
 {
   struct callers_data arg;
 
-  arg.skip = skip;
+  arg.skip = skip + 1;
   arg.pcbuf = pcbuf;
   arg.index = 0;
   arg.max = m;
diff -r 5f7b1adc8ca1 libgo/runtime/mprof.goc
--- a/libgo/runtime/mprof.goc	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/runtime/mprof.goc	Tue Mar 06 17:02:14 2012 -0800
@@ -225,11 +225,7 @@ 
 		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 -r 5f7b1adc8ca1 libgo/runtime/runtime.c
--- a/libgo/runtime/runtime.c	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/runtime/runtime.c	Tue Mar 06 17:02:14 2012 -0800
@@ -211,22 +211,3 @@ 
   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 -r 5f7b1adc8ca1 libgo/runtime/runtime.h
--- a/libgo/runtime/runtime.h	Tue Mar 06 09:48:39 2012 -0800
+++ b/libgo/runtime/runtime.h	Tue Mar 06 17:02:14 2012 -0800
@@ -48,6 +48,7 @@ 
 
 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 @@ 
 #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