| // 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 |
| } |