mirror of https://github.com/golang/go.git
debug/gosym: update for Go 1.2 pcln table
R=golang-dev, r CC=golang-dev https://golang.org/cl/11495043
This commit is contained in:
parent
39100543ff
commit
d7b4a09ca8
|
|
@ -26,9 +26,10 @@ BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||||
BYTE $2;
|
BYTE $2;
|
||||||
#include "pclinetest.h"
|
#include "pclinetest.h"
|
||||||
BYTE $2;
|
BYTE $2;
|
||||||
|
BYTE $255;
|
||||||
|
|
||||||
TEXT pcfromline(SB),7,$0 // Each record stores its line delta, then n, then n more bytes
|
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 $1; BYTE $0;
|
||||||
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 $4; BYTE $3; BYTE $0; BYTE $0; BYTE $0;
|
||||||
|
BYTE $255;
|
||||||
|
|
||||||
TEXT main(SB),7,$0
|
TEXT main(SB),7,$0
|
||||||
// Prevent GC of our test symbols
|
// Prevent GC of our test symbols
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,47 @@
|
||||||
|
|
||||||
package gosym
|
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 {
|
type LineTable struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
PC uint64
|
PC uint64
|
||||||
Line int
|
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.
|
// NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4,
|
||||||
const quantum = 1
|
// 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) {
|
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
|
// 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:
|
case code <= 128:
|
||||||
line -= int(code - 64)
|
line -= int(code - 64)
|
||||||
default:
|
default:
|
||||||
pc += quantum * uint64(code-128)
|
pc += oldQuantum * uint64(code-128)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pc += quantum
|
pc += oldQuantum
|
||||||
}
|
}
|
||||||
return b, pc, line
|
return b, pc, line
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *LineTable) slice(pc uint64) *LineTable {
|
func (t *LineTable) slice(pc uint64) *LineTable {
|
||||||
data, pc, line := t.parse(pc, -1)
|
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 {
|
func (t *LineTable) PCToLine(pc uint64) int {
|
||||||
|
if t.isGo12() {
|
||||||
|
return t.go12PCToLine(pc)
|
||||||
|
}
|
||||||
_, _, line := t.parse(pc, -1)
|
_, _, line := t.parse(pc, -1)
|
||||||
return line
|
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 {
|
func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
|
||||||
|
if t.isGo12() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
_, pc, line1 := t.parse(maxpc, line)
|
_, pc, line1 := t.parse(maxpc, line)
|
||||||
if line1 != line {
|
if line1 != line {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
// Subtract quantum from PC to account for post-line increment
|
// Subtract quantum from PC to account for post-line increment
|
||||||
return pc - quantum
|
return pc - oldQuantum
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLineTable returns a new PC/line table
|
// 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
|
// Text must be the start address of the
|
||||||
// corresponding text segment.
|
// corresponding text segment.
|
||||||
func NewLineTable(data []byte, text uint64) *LineTable {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,13 @@ var (
|
||||||
pclinetestBinary string
|
pclinetestBinary string
|
||||||
)
|
)
|
||||||
|
|
||||||
func dotest() bool {
|
func dotest(self bool) bool {
|
||||||
// For now, only works on ELF platforms.
|
// For now, only works on amd64 platforms.
|
||||||
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
|
if runtime.GOARCH != "amd64" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Self test reads test binary; only works on Linux.
|
||||||
|
if self && runtime.GOOS != "linux" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if pclinetestBinary != "" {
|
if pclinetestBinary != "" {
|
||||||
|
|
@ -41,7 +45,8 @@ func dotest() bool {
|
||||||
// the resulting binary looks like it was built from pclinetest.s,
|
// the resulting binary looks like it was built from pclinetest.s,
|
||||||
// but we have renamed it to keep it away from the go tool.
|
// but we have renamed it to keep it away from the go tool.
|
||||||
pclinetestBinary = filepath.Join(pclineTempDir, "pclinetest")
|
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)
|
pclinetestBinary, pclinetestBinary, pclinetestBinary)
|
||||||
cmd := exec.Command("sh", "-c", command)
|
cmd := exec.Command("sh", "-c", command)
|
||||||
cmd.Stdout = os.Stdout
|
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")
|
var goarch = os.Getenv("O")
|
||||||
|
|
||||||
func TestLineFromAline(t *testing.T) {
|
func TestLineFromAline(t *testing.T) {
|
||||||
if !dotest() {
|
if !dotest(true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer endtest()
|
defer endtest()
|
||||||
|
|
||||||
tab := getTable(t)
|
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
|
// Find the sym package
|
||||||
pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
|
pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
|
||||||
|
|
@ -148,12 +157,16 @@ func TestLineFromAline(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineAline(t *testing.T) {
|
func TestLineAline(t *testing.T) {
|
||||||
if !dotest() {
|
if !dotest(true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer endtest()
|
defer endtest()
|
||||||
|
|
||||||
tab := getTable(t)
|
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 {
|
for _, o := range tab.Files {
|
||||||
// A source file can appear multiple times in a
|
// A source file can appear multiple times in a
|
||||||
|
|
@ -190,7 +203,7 @@ func TestLineAline(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPCLine(t *testing.T) {
|
func TestPCLine(t *testing.T) {
|
||||||
if !dotest() {
|
if !dotest(false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer endtest()
|
defer endtest()
|
||||||
|
|
@ -206,16 +219,17 @@ func TestPCLine(t *testing.T) {
|
||||||
sym := tab.LookupFunc("linefrompc")
|
sym := tab.LookupFunc("linefrompc")
|
||||||
wantLine := 0
|
wantLine := 0
|
||||||
for pc := sym.Entry; pc < sym.End; pc++ {
|
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
|
off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g
|
||||||
|
if textdat[off] == 255 {
|
||||||
|
break
|
||||||
|
}
|
||||||
wantLine += int(textdat[off])
|
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 {
|
if fn == nil {
|
||||||
t.Errorf("failed to get line of PC %#x", pc)
|
t.Errorf("failed to get line of PC %#x", pc)
|
||||||
} else if !strings.HasSuffix(file, "pclinetest.asm") {
|
} else if !strings.HasSuffix(file, "pclinetest.asm") || line != wantLine || fn != sym {
|
||||||
t.Errorf("expected %s (%s) at PC %#x, got %s (%s)", "pclinetest.asm", sym.Name, pc, file, fn.Name)
|
t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.asm", wantLine, sym.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,6 +241,9 @@ func TestPCLine(t *testing.T) {
|
||||||
for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
|
for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
|
||||||
file, line, fn := tab.PCToLine(pc)
|
file, line, fn := tab.PCToLine(pc)
|
||||||
off = pc - text.Addr
|
off = pc - text.Addr
|
||||||
|
if textdat[off] == 255 {
|
||||||
|
break
|
||||||
|
}
|
||||||
wantLine += int(textdat[off])
|
wantLine += int(textdat[off])
|
||||||
if line != wantLine {
|
if line != wantLine {
|
||||||
t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
|
t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,26 @@ type Func struct {
|
||||||
Obj *Obj
|
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 {
|
type Obj struct {
|
||||||
|
// Funcs is a list of functions in the Obj.
|
||||||
Funcs []Func
|
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 {
|
type Table struct {
|
||||||
Syms []Sym
|
Syms []Sym
|
||||||
Funcs []Func
|
Funcs []Func
|
||||||
Files map[string]*Obj
|
Files map[string]*Obj // nil for Go 1.2 and later binaries
|
||||||
Objs []Obj
|
Objs []Obj // nil for Go 1.2 and later binaries
|
||||||
// textEnd uint64;
|
|
||||||
|
go12line *LineTable // Go 1.2 line number table
|
||||||
}
|
}
|
||||||
|
|
||||||
type sym struct {
|
type sym struct {
|
||||||
|
|
@ -105,10 +122,11 @@ type sym struct {
|
||||||
name []byte
|
name []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
|
var (
|
||||||
var bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
|
littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
|
||||||
|
bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
|
||||||
var oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
|
oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
|
||||||
|
)
|
||||||
|
|
||||||
func walksymtab(data []byte, fn func(sym) error) error {
|
func walksymtab(data []byte, fn func(sym) error) error {
|
||||||
var order binary.ByteOrder = binary.BigEndian
|
var order binary.ByteOrder = binary.BigEndian
|
||||||
|
|
@ -260,6 +278,9 @@ func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var t Table
|
var t Table
|
||||||
|
if pcln.isGo12() {
|
||||||
|
t.go12line = pcln
|
||||||
|
}
|
||||||
fname := make(map[uint16]string)
|
fname := make(map[uint16]string)
|
||||||
t.Syms = make([]Sym, 0, n)
|
t.Syms = make([]Sym, 0, n)
|
||||||
nf := 0
|
nf := 0
|
||||||
|
|
@ -316,17 +337,29 @@ func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Funcs = make([]Func, 0, nf)
|
t.Funcs = make([]Func, 0, nf)
|
||||||
t.Objs = make([]Obj, 0, nz)
|
|
||||||
t.Files = make(map[string]*Obj)
|
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
|
// Count text symbols and attach frame sizes, parameters, and
|
||||||
// locals to them. Also, find object file boundaries.
|
// locals to them. Also, find object file boundaries.
|
||||||
var obj *Obj
|
|
||||||
lastf := 0
|
lastf := 0
|
||||||
for i := 0; i < len(t.Syms); i++ {
|
for i := 0; i < len(t.Syms); i++ {
|
||||||
sym := &t.Syms[i]
|
sym := &t.Syms[i]
|
||||||
switch sym.Type {
|
switch sym.Type {
|
||||||
case 'Z', 'z': // path symbol
|
case 'Z', 'z': // path symbol
|
||||||
|
if t.go12line != nil {
|
||||||
|
// Go 1.2 binaries have the file information elsewhere. Ignore.
|
||||||
|
break
|
||||||
|
}
|
||||||
// Finish the current object
|
// Finish the current object
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
obj.Funcs = t.Funcs[lastf:]
|
obj.Funcs = t.Funcs[lastf:]
|
||||||
|
|
@ -395,7 +428,12 @@ func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
|
||||||
fn.Sym = sym
|
fn.Sym = sym
|
||||||
fn.Entry = sym.Value
|
fn.Entry = sym.Value
|
||||||
fn.Obj = obj
|
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)
|
fn.LineTable = pcln.slice(fn.Entry)
|
||||||
pcln = fn.LineTable
|
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 {
|
if fn = t.PCToFunc(pc); fn == nil {
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineToPC looks up the first program counter on the given line in
|
// 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.
|
// there is an error looking up this line.
|
||||||
func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
|
func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
|
||||||
obj, ok := t.Files[file]
|
obj, ok := t.Files[file]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, nil, UnknownFileError(file)
|
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)
|
abs, err := obj.alineFromLine(file, line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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.
|
// 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 {
|
func (t *Table) SymByAddr(addr uint64) *Sym {
|
||||||
// TODO(austin) Maybe make a map
|
|
||||||
for i := range t.Syms {
|
for i := range t.Syms {
|
||||||
s := &t.Syms[i]
|
s := &t.Syms[i]
|
||||||
switch s.Type {
|
switch s.Type {
|
||||||
|
|
@ -522,6 +572,13 @@ func (t *Table) SymByAddr(addr uint64) *Sym {
|
||||||
* Object files
|
* 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) {
|
func (o *Obj) lineFromAline(aline int) (string, int) {
|
||||||
type stackEnt struct {
|
type stackEnt struct {
|
||||||
path string
|
path string
|
||||||
|
|
@ -533,8 +590,6 @@ func (o *Obj) lineFromAline(aline int) (string, int) {
|
||||||
noPath := &stackEnt{"", 0, 0, nil}
|
noPath := &stackEnt{"", 0, 0, nil}
|
||||||
tos := noPath
|
tos := noPath
|
||||||
|
|
||||||
// TODO(austin) I have no idea how 'Z' symbols work, except
|
|
||||||
// that they pop the stack.
|
|
||||||
pathloop:
|
pathloop:
|
||||||
for _, s := range o.Paths {
|
for _, s := range o.Paths {
|
||||||
val := int(s.Value)
|
val := int(s.Value)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue