]> Cypherpunks repositories - gostls13.git/commitdiff
debug/gosym: update for Go 1.2 pcln table
authorRuss Cox <rsc@golang.org>
Thu, 18 Jul 2013 14:12:14 +0000 (10:12 -0400)
committerRuss Cox <rsc@golang.org>
Thu, 18 Jul 2013 14:12:14 +0000 (10:12 -0400)
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11495043

src/pkg/debug/gosym/pclinetest.asm
src/pkg/debug/gosym/pclntab.go
src/pkg/debug/gosym/pclntab_test.go
src/pkg/debug/gosym/symtab.go

index 6305435b09e1308b6a77c3682fca7e6f2469a9d1..868afc66041cc333f1149d9c940c9332c88346c5 100644 (file)
@@ -26,9 +26,10 @@ BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
 BYTE $2;
 #include "pclinetest.h"
 BYTE $2;
+BYTE $255;
 
 TEXT pcfromline(SB),7,$0       // Each record stores its line delta, then n, then n more bytes
-BYTE $31; BYTE $0;
+BYTE $32; BYTE $0;
 BYTE $1; BYTE $1; BYTE $0;
 BYTE $1; BYTE $0;
 
@@ -44,6 +45,7 @@ BYTE $3; BYTE $3; BYTE $0; BYTE $0; BYTE $0;
 
 
 BYTE $4; BYTE $3; BYTE $0; BYTE $0; BYTE $0;
+BYTE $255;
 
 TEXT main(SB),7,$0
        // Prevent GC of our test symbols
index 9d7b0d15f3610dce11803c51df4ffcb2e7fc7c28..2b9bedb45bc598ad4a394118d5bab2b5534e9df3 100644 (file)
@@ -8,16 +8,47 @@
 
 package gosym
 
-import "encoding/binary"
+import (
+       "encoding/binary"
+       "sync"
+)
 
+// A LineTable is a data structure mapping program counters to line numbers.
+//
+// In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable,
+// and the line number corresponded to a numbering of all source lines in the
+// program, across all files. That absolute line number would then have to be
+// converted separately to a file name and line number within the file.
+//
+// In Go 1.2, the format of the data changed so that there is a single LineTable
+// for the entire program, shared by all Funcs, and there are no absolute line
+// numbers, just line numbers within specific files.
+//
+// For the most part, LineTable's methods should be treated as an internal
+// detail of the package; callers should use the methods on Table instead.
 type LineTable struct {
        Data []byte
        PC   uint64
        Line int
+
+       // Go 1.2 state
+       mu       sync.Mutex
+       go12     int // is this in Go 1.2 format? -1 no, 0 unknown, 1 yes
+       binary   binary.ByteOrder
+       quantum  uint32
+       ptrsize  uint32
+       functab  []byte
+       nfunctab uint32
+       filetab  []byte
+       nfiletab uint32
+       fileMap  map[string]uint32
 }
 
-// TODO(rsc): Need to pull in quantum from architecture definition.
-const quantum = 1
+// NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4,
+// but we have no idea whether we're using arm or not. This only
+// matters in the old (pre-Go 1.2) symbol table format, so it's not worth
+// fixing.
+const oldQuantum = 1
 
 func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) {
        // The PC/line table can be thought of as a sequence of
@@ -46,31 +77,42 @@ func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64,
                case code <= 128:
                        line -= int(code - 64)
                default:
-                       pc += quantum * uint64(code-128)
+                       pc += oldQuantum * uint64(code-128)
                        continue
                }
-               pc += quantum
+               pc += oldQuantum
        }
        return b, pc, line
 }
 
 func (t *LineTable) slice(pc uint64) *LineTable {
        data, pc, line := t.parse(pc, -1)
-       return &LineTable{data, pc, line}
+       return &LineTable{Data: data, PC: pc, Line: line}
 }
 
+// PCToLine returns the line number for the given program counter.
+// Callers should use Table's PCToLine method instead.
 func (t *LineTable) PCToLine(pc uint64) int {
+       if t.isGo12() {
+               return t.go12PCToLine(pc)
+       }
        _, _, line := t.parse(pc, -1)
        return line
 }
 
+// LineToPC returns the program counter for the given line number,
+// considering only program counters before maxpc.
+// Callers should use Table's LineToPC method instead.
 func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
+       if t.isGo12() {
+               return 0
+       }
        _, pc, line1 := t.parse(maxpc, line)
        if line1 != line {
                return 0
        }
        // Subtract quantum from PC to account for post-line increment
-       return pc - quantum
+       return pc - oldQuantum
 }
 
 // NewLineTable returns a new PC/line table
@@ -78,5 +120,307 @@ func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
 // Text must be the start address of the
 // corresponding text segment.
 func NewLineTable(data []byte, text uint64) *LineTable {
-       return &LineTable{data, text, 0}
+       return &LineTable{Data: data, PC: text, Line: 0}
+}
+
+// Go 1.2 symbol table format.
+// See golang.org/s/go12symtab.
+//
+// A general note about the methods here: rather than try to avoid
+// index out of bounds errors, we trust Go to detect them, and then
+// we recover from the panics and treat them as indicative of a malformed
+// or incomplete table.
+//
+// The methods called by symtab.go, which begin with "go12" prefixes,
+// are expected to have that recovery logic.
+
+// isGo12 reports whether this is a Go 1.2 (or later) symbol table.
+func (t *LineTable) isGo12() bool {
+       t.go12Init()
+       return t.go12 == 1
+}
+
+const go12magic = 0xfffffffb
+
+// uintptr returns the pointer-sized value encoded at b.
+// The pointer size is dictated by the table being read.
+func (t *LineTable) uintptr(b []byte) uint64 {
+       if t.ptrsize == 4 {
+               return uint64(t.binary.Uint32(b))
+       }
+       return t.binary.Uint64(b)
+}
+
+// go12init initializes the Go 1.2 metadata if t is a Go 1.2 symbol table.
+func (t *LineTable) go12Init() {
+       t.mu.Lock()
+       defer t.mu.Unlock()
+       if t.go12 != 0 {
+               return
+       }
+
+       defer func() {
+               // If we panic parsing, assume it's not a Go 1.2 symbol table.
+               recover()
+       }()
+
+       // Check header: 4-byte magic, two zeros, pc quantum, pointer size.
+       t.go12 = -1 // not Go 1.2 until proven otherwise
+       if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 ||
+               (t.Data[6] != 1 && t.Data[6] != 4) || // pc quantum
+               (t.Data[7] != 4 && t.Data[7] != 8) { // pointer size
+               return
+       }
+
+       switch uint32(go12magic) {
+       case binary.LittleEndian.Uint32(t.Data):
+               t.binary = binary.LittleEndian
+       case binary.BigEndian.Uint32(t.Data):
+               t.binary = binary.BigEndian
+       default:
+               return
+       }
+
+       t.quantum = uint32(t.Data[6])
+       t.ptrsize = uint32(t.Data[7])
+
+       t.nfunctab = uint32(t.uintptr(t.Data[8:]))
+       t.functab = t.Data[8+t.ptrsize:]
+       functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
+       fileoff := t.binary.Uint32(t.functab[functabsize:])
+       t.functab = t.functab[:functabsize]
+       t.filetab = t.Data[fileoff:]
+       t.nfiletab = t.binary.Uint32(t.filetab)
+       t.filetab = t.filetab[:t.nfiletab*4]
+
+       t.go12 = 1 // so far so good
+}
+
+// findFunc returns the func corresponding to the given program counter.
+func (t *LineTable) findFunc(pc uint64) []byte {
+       if pc < t.uintptr(t.functab) || pc >= t.uintptr(t.functab[len(t.functab)-int(t.ptrsize):]) {
+               return nil
+       }
+
+       // The function table is a list of 2*nfunctab+1 uintptrs,
+       // alternating program counters and offsets to func structures.
+       f := t.functab
+       nf := t.nfunctab
+       for nf > 0 {
+               m := nf / 2
+               fm := f[2*t.ptrsize*m:]
+               if t.uintptr(fm) <= pc && pc < t.uintptr(fm[2*t.ptrsize:]) {
+                       return t.Data[t.uintptr(fm[t.ptrsize:]):]
+               } else if pc < t.uintptr(fm) {
+                       nf = m
+               } else {
+                       f = f[(m+1)*2*t.ptrsize:]
+                       nf -= m + 1
+               }
+       }
+       return nil
+}
+
+// readvarint reads, removes, and returns a varint from *pp.
+func (t *LineTable) readvarint(pp *[]byte) uint32 {
+       var v, shift uint32
+       p := *pp
+       for shift = 0; ; shift += 7 {
+               b := p[0]
+               p = p[1:]
+               v |= (uint32(b) & 0x7F) << shift
+               if b&0x80 == 0 {
+                       break
+               }
+       }
+       *pp = p
+       return v
+}
+
+// string returns a Go string found at off.
+func (t *LineTable) string(off uint32) string {
+       for i := off; ; i++ {
+               if t.Data[i] == 0 {
+                       return string(t.Data[off:i])
+               }
+       }
+}
+
+// step advances to the next pc, value pair in the encoded table.
+func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
+       uvdelta := t.readvarint(p)
+       if uvdelta == 0 && !first {
+               return false
+       }
+       if uvdelta&1 != 0 {
+               uvdelta = ^(uvdelta >> 1)
+       } else {
+               uvdelta >>= 1
+       }
+       vdelta := int32(uvdelta)
+       pcdelta := t.readvarint(p) * t.quantum
+       *pc += uint64(pcdelta)
+       *val += vdelta
+       return true
+}
+
+// pcvalue reports the value associated with the target pc.
+// off is the offset to the beginning of the pc-value table,
+// and entry is the start PC for the corresponding function.
+func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 {
+       if off == 0 {
+               return -1
+       }
+       p := t.Data[off:]
+
+       val := int32(-1)
+       pc := entry
+       for t.step(&p, &pc, &val, pc == entry) {
+               if targetpc < pc {
+                       return val
+               }
+       }
+       return -1
+}
+
+// findFileLine scans one function in the binary looking for a
+// program counter in the given file on the given line.
+// It does so by running the pc-value tables mapping program counter
+// to file number. Since most functions come from a single file, these
+// are usually short and quick to scan. If a file match is found, then the
+// code goes to the expense of looking for a simultaneous line number match.
+func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32) uint64 {
+       if filetab == 0 || linetab == 0 {
+               return 0
+       }
+
+       fp := t.Data[filetab:]
+       fl := t.Data[linetab:]
+       fileVal := int32(-1)
+       filePC := entry
+       lineVal := int32(-1)
+       linePC := entry
+       fileStartPC := filePC
+       for t.step(&fp, &filePC, &fileVal, filePC == entry) {
+               if fileVal == filenum && fileStartPC < filePC {
+                       // fileVal is in effect starting at fileStartPC up to
+                       // but not including filePC, and it's the file we want.
+                       // Run the PC table looking for a matching line number
+                       // or until we reach filePC.
+                       lineStartPC := linePC
+                       for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) {
+                               // lineVal is in effect until linePC, and lineStartPC < filePC.
+                               if lineVal == line {
+                                       if fileStartPC <= lineStartPC {
+                                               return lineStartPC
+                                       }
+                                       if fileStartPC < linePC {
+                                               return fileStartPC
+                                       }
+                               }
+                               lineStartPC = linePC
+                       }
+               }
+               fileStartPC = filePC
+       }
+       return 0
+}
+
+// go12PCToLine maps program counter to line number for the Go 1.2 pcln table.
+func (t *LineTable) go12PCToLine(pc uint64) (line int) {
+       defer func() {
+               if recover() != nil {
+                       line = -1
+               }
+       }()
+
+       f := t.findFunc(pc)
+       if f == nil {
+               return -1
+       }
+       entry := t.uintptr(f)
+       linetab := t.binary.Uint32(f[t.ptrsize+8*4:])
+       return int(t.pcvalue(linetab, entry, pc))
+}
+
+// go12PCToFile maps program counter to file name for the Go 1.2 pcln table.
+func (t *LineTable) go12PCToFile(pc uint64) (file string) {
+       defer func() {
+               if recover() != nil {
+                       file = ""
+               }
+       }()
+
+       f := t.findFunc(pc)
+       if f == nil {
+               return ""
+       }
+       entry := t.uintptr(f)
+       filetab := t.binary.Uint32(f[t.ptrsize+7*4:])
+       fno := t.pcvalue(filetab, entry, pc)
+       if fno <= 0 {
+               return ""
+       }
+       return t.string(t.binary.Uint32(t.filetab[4*fno:]))
+}
+
+// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2 pcln table.
+func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
+       defer func() {
+               if recover() != nil {
+                       pc = 0
+               }
+       }()
+
+       t.initFileMap()
+       filenum := t.fileMap[file]
+       if filenum == 0 {
+               return 0
+       }
+
+       // Scan all functions.
+       // If this turns out to be a bottleneck, we could build a map[int32][]int32
+       // mapping file number to a list of functions with code from that file.
+       for i := uint32(0); i < t.nfunctab; i++ {
+               f := t.Data[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):]
+               entry := t.uintptr(f)
+               filetab := t.binary.Uint32(f[t.ptrsize+7*4:])
+               linetab := t.binary.Uint32(f[t.ptrsize+8*4:])
+               pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line))
+               if pc != 0 {
+                       return pc
+               }
+       }
+       return 0
+}
+
+// initFileMap initializes the map from file name to file number.
+func (t *LineTable) initFileMap() {
+       t.mu.Lock()
+       defer t.mu.Unlock()
+
+       if t.fileMap != nil {
+               return
+       }
+       m := make(map[string]uint32)
+
+       for i := uint32(1); i < t.nfiletab; i++ {
+               s := t.string(t.binary.Uint32(t.filetab[4*i:]))
+               m[s] = i
+       }
+       t.fileMap = m
+}
+
+// go12MapFiles adds to m a key for every file in the Go 1.2 LineTable.
+// Every key maps to obj. That's not a very interesting map, but it provides
+// a way for callers to obtain the list of files in the program.
+func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) {
+       defer func() {
+               recover()
+       }()
+
+       t.initFileMap()
+       for file := range t.fileMap {
+               m[file] = obj
+       }
 }
index 20acba612f2f015dd23a4aec081b1cf92dc47d2c..5616cdbd5691a9c3f2d77fc699e9c22605611613 100644 (file)
@@ -21,9 +21,13 @@ var (
        pclinetestBinary string
 )
 
-func dotest() bool {
-       // For now, only works on ELF platforms.
-       if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
+func dotest(self bool) bool {
+       // For now, only works on amd64 platforms.
+       if runtime.GOARCH != "amd64" {
+               return false
+       }
+       // Self test reads test binary; only works on Linux.
+       if self && runtime.GOOS != "linux" {
                return false
        }
        if pclinetestBinary != "" {
@@ -41,7 +45,8 @@ func dotest() bool {
        // the resulting binary looks like it was built from pclinetest.s,
        // but we have renamed it to keep it away from the go tool.
        pclinetestBinary = filepath.Join(pclineTempDir, "pclinetest")
-       command := fmt.Sprintf("go tool 6a -o %s.6 pclinetest.asm && go tool 6l -E main -o %s %s.6",
+       pclinetestBinary = "pclinetest"
+       command := fmt.Sprintf("go tool 6a -o %s.6 pclinetest.asm && go tool 6l -H linux -E main -o %s %s.6",
                pclinetestBinary, pclinetestBinary, pclinetestBinary)
        cmd := exec.Command("sh", "-c", command)
        cmd.Stdout = os.Stdout
@@ -100,12 +105,16 @@ func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) {
 var goarch = os.Getenv("O")
 
 func TestLineFromAline(t *testing.T) {
-       if !dotest() {
+       if !dotest(true) {
                return
        }
        defer endtest()
 
        tab := getTable(t)
+       if tab.go12line != nil {
+               // aline's don't exist in the Go 1.2 table.
+               t.Skip("not relevant to Go 1.2 symbol table")
+       }
 
        // Find the sym package
        pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
@@ -148,12 +157,16 @@ func TestLineFromAline(t *testing.T) {
 }
 
 func TestLineAline(t *testing.T) {
-       if !dotest() {
+       if !dotest(true) {
                return
        }
        defer endtest()
 
        tab := getTable(t)
+       if tab.go12line != nil {
+               // aline's don't exist in the Go 1.2 table.
+               t.Skip("not relevant to Go 1.2 symbol table")
+       }
 
        for _, o := range tab.Files {
                // A source file can appear multiple times in a
@@ -190,7 +203,7 @@ func TestLineAline(t *testing.T) {
 }
 
 func TestPCLine(t *testing.T) {
-       if !dotest() {
+       if !dotest(false) {
                return
        }
        defer endtest()
@@ -206,16 +219,17 @@ func TestPCLine(t *testing.T) {
        sym := tab.LookupFunc("linefrompc")
        wantLine := 0
        for pc := sym.Entry; pc < sym.End; pc++ {
-               file, line, fn := tab.PCToLine(pc)
                off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g
+               if textdat[off] == 255 {
+                       break
+               }
                wantLine += int(textdat[off])
-               t.Logf("off is %d", off)
+               t.Logf("off is %d %#x (max %d)", off, textdat[off], sym.End-pc)
+               file, line, fn := tab.PCToLine(pc)
                if fn == nil {
                        t.Errorf("failed to get line of PC %#x", pc)
-               } else if !strings.HasSuffix(file, "pclinetest.asm") {
-                       t.Errorf("expected %s (%s) at PC %#x, got %s (%s)", "pclinetest.asm", sym.Name, pc, file, fn.Name)
-               } else if line != wantLine || fn != sym {
-                       t.Errorf("expected :%d (%s) at PC %#x, got :%d (%s)", wantLine, sym.Name, pc, line, fn.Name)
+               } else if !strings.HasSuffix(file, "pclinetest.asm") || line != wantLine || fn != sym {
+                       t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.asm", wantLine, sym.Name)
                }
        }
 
@@ -227,6 +241,9 @@ func TestPCLine(t *testing.T) {
        for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
                file, line, fn := tab.PCToLine(pc)
                off = pc - text.Addr
+               if textdat[off] == 255 {
+                       break
+               }
                wantLine += int(textdat[off])
                if line != wantLine {
                        t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
index 81ed4fb27dc5c4320e9359d710797e2b9464415b..6a60b51e37a10faa34cd01d120e26c588a87b013 100644 (file)
@@ -77,10 +77,26 @@ type Func struct {
        Obj       *Obj
 }
 
-// An Obj represents a single object file.
+// An Obj represents a collection of functions in a symbol table.
+//
+// The exact method of division of a binary into separate Objs is an internal detail
+// of the symbol table format.
+//
+// In early versions of Go each source file became a different Obj.
+//
+// In Go 1 and Go 1.1, each package produced one Obj for all Go sources
+// and one Obj per C source file.
+//
+// In Go 1.2, there is a single Obj for the entire program.
 type Obj struct {
+       // Funcs is a list of functions in the Obj.
        Funcs []Func
-       Paths []Sym
+
+       // In Go 1.1 and earlier, Paths is a list of symbols corresponding
+       // to the source file names that produced the Obj.
+       // In Go 1.2, Paths is nil.
+       // Use the keys of Table.Files to obtain a list of source files.
+       Paths []Sym // meta
 }
 
 /*
@@ -93,9 +109,10 @@ type Obj struct {
 type Table struct {
        Syms  []Sym
        Funcs []Func
-       Files map[string]*Obj
-       Objs  []Obj
-       //      textEnd uint64;
+       Files map[string]*Obj // nil for Go 1.2 and later binaries
+       Objs  []Obj           // nil for Go 1.2 and later binaries
+
+       go12line *LineTable // Go 1.2 line number table
 }
 
 type sym struct {
@@ -105,10 +122,11 @@ type sym struct {
        name   []byte
 }
 
-var littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
-var bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
-
-var oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
+var (
+       littleEndianSymtab    = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
+       bigEndianSymtab       = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
+       oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
+)
 
 func walksymtab(data []byte, fn func(sym) error) error {
        var order binary.ByteOrder = binary.BigEndian
@@ -260,6 +278,9 @@ func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
        }
 
        var t Table
+       if pcln.isGo12() {
+               t.go12line = pcln
+       }
        fname := make(map[uint16]string)
        t.Syms = make([]Sym, 0, n)
        nf := 0
@@ -316,17 +337,29 @@ func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
        }
 
        t.Funcs = make([]Func, 0, nf)
-       t.Objs = make([]Obj, 0, nz)
        t.Files = make(map[string]*Obj)
 
+       var obj *Obj
+       if t.go12line != nil {
+               // Put all functions into one Obj.
+               t.Objs = make([]Obj, 1)
+               obj = &t.Objs[0]
+               t.go12line.go12MapFiles(t.Files, obj)
+       } else {
+               t.Objs = make([]Obj, 0, nz)
+       }
+
        // Count text symbols and attach frame sizes, parameters, and
        // locals to them.  Also, find object file boundaries.
-       var obj *Obj
        lastf := 0
        for i := 0; i < len(t.Syms); i++ {
                sym := &t.Syms[i]
                switch sym.Type {
                case 'Z', 'z': // path symbol
+                       if t.go12line != nil {
+                               // Go 1.2 binaries have the file information elsewhere. Ignore.
+                               break
+                       }
                        // Finish the current object
                        if obj != nil {
                                obj.Funcs = t.Funcs[lastf:]
@@ -395,7 +428,12 @@ func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
                        fn.Sym = sym
                        fn.Entry = sym.Value
                        fn.Obj = obj
-                       if pcln != nil {
+                       if t.go12line != nil {
+                               // All functions share the same line table.
+                               // It knows how to narrow down to a specific
+                               // function quickly.
+                               fn.LineTable = t.go12line
+                       } else if pcln != nil {
                                fn.LineTable = pcln.slice(fn.Entry)
                                pcln = fn.LineTable
                        }
@@ -448,18 +486,32 @@ func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) {
        if fn = t.PCToFunc(pc); fn == nil {
                return
        }
-       file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))
+       if t.go12line != nil {
+               file = t.go12line.go12PCToFile(pc)
+               line = t.go12line.go12PCToLine(pc)
+       } else {
+               file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))
+       }
        return
 }
 
 // LineToPC looks up the first program counter on the given line in
-// the named file.  Returns UnknownPathError or UnknownLineError if
+// the named file.  It returns UnknownPathError or UnknownLineError if
 // there is an error looking up this line.
 func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
        obj, ok := t.Files[file]
        if !ok {
                return 0, nil, UnknownFileError(file)
        }
+
+       if t.go12line != nil {
+               pc := t.go12line.go12LineToPC(file, line)
+               if pc == 0 {
+                       return 0, nil, &UnknownLineError{file, line}
+               }
+               return pc, t.PCToFunc(pc), nil
+       }
+
        abs, err := obj.alineFromLine(file, line)
        if err != nil {
                return
@@ -503,9 +555,7 @@ func (t *Table) LookupFunc(name string) *Func {
 }
 
 // SymByAddr returns the text, data, or bss symbol starting at the given address.
-// TODO(rsc): Allow lookup by any address within the symbol.
 func (t *Table) SymByAddr(addr uint64) *Sym {
-       // TODO(austin) Maybe make a map
        for i := range t.Syms {
                s := &t.Syms[i]
                switch s.Type {
@@ -522,6 +572,13 @@ func (t *Table) SymByAddr(addr uint64) *Sym {
  * Object files
  */
 
+// This is legacy code for Go 1.1 and earlier, which used the
+// Plan 9 format for pc-line tables. This code was never quite
+// correct. It's probably very close, and it's usually correct, but
+// we never quite found all the corner cases.
+//
+// Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab.
+
 func (o *Obj) lineFromAline(aline int) (string, int) {
        type stackEnt struct {
                path   string
@@ -533,8 +590,6 @@ func (o *Obj) lineFromAline(aline int) (string, int) {
        noPath := &stackEnt{"", 0, 0, nil}
        tos := noPath
 
-       // TODO(austin) I have no idea how 'Z' symbols work, except
-       // that they pop the stack.
 pathloop:
        for _, s := range o.Paths {
                val := int(s.Value)