mirror of
https://github.com/beego/bee.git
synced 2024-12-29 01:25:47 +00:00
250 lines
7.1 KiB
Go
250 lines
7.1 KiB
Go
// Copyright 2016 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
|
|
|
|
import (
|
|
"sort"
|
|
)
|
|
|
|
// pcToFuncEntries maps PC ranges to function entries.
|
|
//
|
|
// Each element contains a *Entry for a function and its corresponding start PC.
|
|
// If we know the address one past the last instruction of a function, and it is
|
|
// not equal to the start address of the next function, we mark that with
|
|
// another element containing that address and a nil entry. The elements are
|
|
// sorted by PC. Among elements with the same PC, those with non-nil *Entry
|
|
// are put earlier.
|
|
type pcToFuncEntries []pcToFuncEntry
|
|
type pcToFuncEntry struct {
|
|
pc uint64
|
|
entry *Entry
|
|
}
|
|
|
|
func (p pcToFuncEntries) Len() int { return len(p) }
|
|
func (p pcToFuncEntries) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p pcToFuncEntries) Less(i, j int) bool {
|
|
if p[i].pc != p[j].pc {
|
|
return p[i].pc < p[j].pc
|
|
}
|
|
return p[i].entry != nil && p[j].entry == nil
|
|
}
|
|
|
|
// nameCache maps each symbol name to a linked list of the entries with that name.
|
|
type nameCache map[string]*nameCacheEntry
|
|
type nameCacheEntry struct {
|
|
entry *Entry
|
|
link *nameCacheEntry
|
|
}
|
|
|
|
// pcToLineEntries maps PCs to line numbers.
|
|
//
|
|
// It is a slice of (PC, line, file number) triples, sorted by PC. The file
|
|
// number is an index into the source files slice.
|
|
// If (PC1, line1, file1) and (PC2, line2, file2) are two consecutive elements,
|
|
// then the span of addresses [PC1, PC2) belongs to (line1, file1). If an
|
|
// element's file number is zero, it only marks the end of a span.
|
|
//
|
|
// TODO: could save memory by changing pcToLineEntries and lineToPCEntries to use
|
|
// interval trees containing references into .debug_line.
|
|
type pcToLineEntries []pcToLineEntry
|
|
type pcToLineEntry struct {
|
|
pc uint64
|
|
line uint64
|
|
file uint64
|
|
}
|
|
|
|
func (p pcToLineEntries) Len() int { return len(p) }
|
|
func (p pcToLineEntries) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p pcToLineEntries) Less(i, j int) bool {
|
|
if p[i].pc != p[j].pc {
|
|
return p[i].pc < p[j].pc
|
|
}
|
|
return p[i].file > p[j].file
|
|
}
|
|
|
|
// byFileLine is used temporarily while building lineToPCEntries.
|
|
type byFileLine []pcToLineEntry
|
|
|
|
func (b byFileLine) Len() int { return len(b) }
|
|
func (b byFileLine) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
func (b byFileLine) Less(i, j int) bool {
|
|
if b[i].file != b[j].file {
|
|
return b[i].file < b[j].file
|
|
}
|
|
return b[i].line < b[j].line
|
|
}
|
|
|
|
// lineToPCEntries maps line numbers to breakpoint addresses.
|
|
//
|
|
// The slice contains, for each source file in Data, a slice of (line, PC)
|
|
// pairs, sorted by line. Note that there may be more than one PC for a line.
|
|
type lineToPCEntries [][]lineToPCEntry
|
|
type lineToPCEntry struct {
|
|
line uint64
|
|
pc uint64
|
|
}
|
|
|
|
func (d *Data) buildLineToPCCache(pclfs pcToLineEntries) {
|
|
// TODO: only include lines where is_stmt is true
|
|
sort.Sort(byFileLine(pclfs))
|
|
// Make a slice of (line, PC) pairs for each (non-zero) file.
|
|
var (
|
|
c = make(lineToPCEntries, len(d.sourceFiles))
|
|
curSlice []lineToPCEntry
|
|
)
|
|
for i, pclf := range pclfs {
|
|
if pclf.file == 0 {
|
|
// This entry indicated the end of an instruction sequence, not a breakpoint.
|
|
continue
|
|
}
|
|
curSlice = append(curSlice, lineToPCEntry{line: pclf.line, pc: pclf.pc})
|
|
if i+1 == len(pclfs) || pclf.file != pclfs[i+1].file {
|
|
// curSlice now contains all of the entries for pclf.file.
|
|
if pclf.file > 0 && pclf.file < uint64(len(c)) {
|
|
c[pclf.file] = curSlice
|
|
}
|
|
curSlice = nil
|
|
}
|
|
}
|
|
d.lineToPCEntries = c
|
|
}
|
|
|
|
func (d *Data) buildPCToLineCache(cache pcToLineEntries) {
|
|
// Sort cache by PC (in increasing order), then by file number (in decreasing order).
|
|
sort.Sort(cache)
|
|
|
|
// Build a copy without redundant entries.
|
|
var out pcToLineEntries
|
|
for i, pclf := range cache {
|
|
if i > 0 && pclf.pc == cache[i-1].pc {
|
|
// This entry is for the same PC as the previous entry.
|
|
continue
|
|
}
|
|
if i > 0 && pclf.file == cache[i-1].file && pclf.line == cache[i-1].line {
|
|
// This entry is for the same file and line as the previous entry.
|
|
continue
|
|
}
|
|
out = append(out, pclf)
|
|
}
|
|
d.pcToLineEntries = out
|
|
}
|
|
|
|
// buildLineCaches constructs d.sourceFiles, d.lineToPCEntries, d.pcToLineEntries.
|
|
func (d *Data) buildLineCaches() {
|
|
if len(d.line) == 0 {
|
|
return
|
|
}
|
|
var m lineMachine
|
|
// Assume the address_size in the first unit applies to the whole program.
|
|
// TODO: we could handle executables containing code for multiple address
|
|
// sizes using DW_AT_stmt_list attributes.
|
|
if len(d.unit) == 0 {
|
|
return
|
|
}
|
|
buf := makeBuf(d, &d.unit[0], "line", 0, d.line)
|
|
if err := m.parseHeader(&buf); err != nil {
|
|
return
|
|
}
|
|
for _, f := range m.header.file {
|
|
d.sourceFiles = append(d.sourceFiles, f.name)
|
|
}
|
|
var cache pcToLineEntries
|
|
fn := func(m *lineMachine) bool {
|
|
if m.endSequence {
|
|
cache = append(cache, pcToLineEntry{
|
|
pc: m.address,
|
|
line: 0,
|
|
file: 0,
|
|
})
|
|
} else {
|
|
cache = append(cache, pcToLineEntry{
|
|
pc: m.address,
|
|
line: m.line,
|
|
file: m.file,
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
m.evalCompilationUnit(&buf, fn)
|
|
d.buildLineToPCCache(cache)
|
|
d.buildPCToLineCache(cache)
|
|
}
|
|
|
|
// buildInfoCaches initializes nameCache and pcToFuncEntries by walking the
|
|
// top-level entries under each compile unit. It swallows any errors in parsing.
|
|
func (d *Data) buildInfoCaches() {
|
|
// TODO: record errors somewhere?
|
|
d.nameCache = make(map[string]*nameCacheEntry)
|
|
|
|
var pcToFuncEntries pcToFuncEntries
|
|
|
|
r := d.Reader()
|
|
loop:
|
|
for {
|
|
entry, err := r.Next()
|
|
if entry == nil || err != nil {
|
|
break loop
|
|
}
|
|
if entry.Tag != TagCompileUnit /* DW_TAG_compile_unit */ {
|
|
r.SkipChildren()
|
|
continue
|
|
}
|
|
for {
|
|
entry, err := r.Next()
|
|
if entry == nil || err != nil {
|
|
break loop
|
|
}
|
|
if entry.Tag == 0 {
|
|
// End of children of current compile unit.
|
|
break
|
|
}
|
|
r.SkipChildren()
|
|
// Update name-to-entry cache.
|
|
if name, ok := entry.Val(AttrName).(string); ok {
|
|
d.nameCache[name] = &nameCacheEntry{entry: entry, link: d.nameCache[name]}
|
|
}
|
|
|
|
// If this entry is a function, update PC-to-containing-function cache.
|
|
if entry.Tag != TagSubprogram /* DW_TAG_subprogram */ {
|
|
continue
|
|
}
|
|
|
|
// DW_AT_low_pc, if present, is the address of the first instruction of
|
|
// the function.
|
|
lowpc, ok := entry.Val(AttrLowpc).(uint64)
|
|
if !ok {
|
|
continue
|
|
}
|
|
pcToFuncEntries = append(pcToFuncEntries, pcToFuncEntry{lowpc, entry})
|
|
|
|
// DW_AT_high_pc, if present (TODO: and of class address) is the address
|
|
// one past the last instruction of the function.
|
|
highpc, ok := entry.Val(AttrHighpc).(uint64)
|
|
if !ok {
|
|
continue
|
|
}
|
|
pcToFuncEntries = append(pcToFuncEntries, pcToFuncEntry{highpc, nil})
|
|
}
|
|
}
|
|
// Sort elements by PC. If there are multiple elements with the same PC,
|
|
// those with non-nil *Entry are placed earlier.
|
|
sort.Sort(pcToFuncEntries)
|
|
|
|
// Copy only the first element for each PC to out.
|
|
n := 0
|
|
for i, ce := range pcToFuncEntries {
|
|
if i == 0 || ce.pc != pcToFuncEntries[i-1].pc {
|
|
n++
|
|
}
|
|
}
|
|
out := make([]pcToFuncEntry, 0, n)
|
|
for i, ce := range pcToFuncEntries {
|
|
if i == 0 || ce.pc != pcToFuncEntries[i-1].pc {
|
|
out = append(out, ce)
|
|
}
|
|
}
|
|
d.pcToFuncEntries = out
|
|
}
|