| // Copyright 2018 The Fuchsia 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 symbolize |
| |
| import ( |
| "fmt" |
| "io" |
| "unicode" |
| ) |
| |
| // BacktracePresenter intercepts backtrace elements on their own line and |
| // presents them in text. Inlines are output as separate lines. |
| // A PostProcessor is taken as an input to synchronously compose another |
| // PostProcessor |
| type BacktracePresenter struct { |
| out io.Writer |
| next PostProcessor |
| } |
| |
| // NewBacktracePresenter constructs a BacktracePresenter. |
| func NewBacktracePresenter(out io.Writer, next PostProcessor) *BacktracePresenter { |
| return &BacktracePresenter{ |
| out: out, |
| next: next, |
| } |
| } |
| |
| func printBacktrace(out io.Writer, hdr LineHeader, frame uint64, msg string, info addressInfo) { |
| modRelAddr := info.addr - info.seg.Vaddr + info.seg.ModRelAddr |
| var hdrString string |
| if hdr != nil { |
| hdrString = hdr.Present() |
| } |
| if len(info.locs) == 0 { |
| fmt.Fprintf(out, "%s #%-4d %#016x in <%s>+%#x %s\n", hdrString, frame, info.addr, info.mod.Name, modRelAddr, msg) |
| return |
| } |
| for i, loc := range info.locs { |
| i = len(info.locs) - i - 1 |
| fmt.Fprintf(out, "%s ", hdrString) |
| var frameStr string |
| if i == 0 { |
| frameStr = fmt.Sprintf("#%d", frame) |
| } else { |
| frameStr = fmt.Sprintf("#%d.%d", frame, i) |
| } |
| fmt.Fprintf(out, "%-5s", frameStr) |
| fmt.Fprintf(out, " %#016x", info.addr) |
| if !loc.function.IsEmpty() { |
| fmt.Fprintf(out, " in %v", loc.function) |
| } |
| if !loc.file.IsEmpty() { |
| fmt.Fprintf(out, " %s:%d", loc.file, loc.line) |
| } |
| fmt.Fprintf(out, " <%s>+%#x", info.mod.Name, modRelAddr) |
| if msg != "" { |
| fmt.Fprintf(out, " %s", msg) |
| } |
| fmt.Fprintf(out, "\n") |
| } |
| } |
| |
| func isSpace(s string) bool { |
| for _, r := range s { |
| if !unicode.IsSpace(r) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func (b *BacktracePresenter) Process(line OutputLine, out chan<- OutputLine) { |
| if len(line.line) == 1 { |
| if bt, ok := line.line[0].(*BacktraceElement); ok { |
| printBacktrace(b.out, line.header, bt.num, bt.msg, bt.info) |
| // Don't process a backtrace we've already output. |
| return |
| } |
| } |
| if len(line.line) == 2 { |
| // Note that we're going to discard the text in front. |
| if txt, ok := line.line[0].(*Text); ok && isSpace(txt.text) { |
| if bt, ok := line.line[1].(*BacktraceElement); ok { |
| printBacktrace(b.out, line.header, bt.num, bt.msg, bt.info) |
| // Don't process a backtrace we've already output. |
| return |
| } |
| } |
| } |
| b.next.Process(line, out) |
| } |
| |
| // FilterContextElements filters out lines that only contain contextual |
| // elements and colors. |
| type FilterContextElements struct { |
| } |
| |
| func (f *FilterContextElements) Process(line OutputLine, out chan<- OutputLine) { |
| for _, token := range line.line { |
| switch t := token.(type) { |
| case *ColorCode, *ResetElement, *ModuleElement, *MappingElement: |
| continue |
| case *Text: |
| if isSpace(t.text) { |
| continue |
| } |
| } |
| out <- line |
| return |
| } |
| } |
| |
| // OptimizeColor attempts to transform output elements to use as few color |
| // transisitions as is possible |
| type OptimizeColor struct { |
| } |
| |
| func (o *OptimizeColor) Process(line OutputLine, out chan<- OutputLine) { |
| // Maintain a current simulated color state |
| curColor := uint64(0) |
| curBold := false |
| // Keep track of the color state at the end of 'out' |
| color := uint64(0) |
| bold := false |
| // The new list of tokens we will output |
| newLine := []Node{} |
| // Go though each token |
| for _, token := range line.line { |
| if colorCode, ok := token.(*ColorCode); ok { |
| // If we encounter a color update the simulated color state |
| if colorCode.color == 1 { |
| curBold = true |
| } else if colorCode.color == 0 { |
| curColor = 0 |
| curBold = false |
| } else { |
| curColor = colorCode.color |
| } |
| } else { |
| // If we encounter a non-color token make sure we output |
| // colors to handle the transition from the last color to the |
| // new color. |
| if curColor == 0 && color != 0 { |
| color = 0 |
| bold = false |
| newLine = append(newLine, &ColorCode{color: 0}) |
| } else if curColor != color { |
| color = curColor |
| newLine = append(newLine, &ColorCode{color: curColor}) |
| } |
| // Make sure to bold the output even if a color 0 code was just output |
| if curBold && !bold { |
| bold = true |
| newLine = append(newLine, &ColorCode{color: 1}) |
| } |
| // Append all non-color nodes |
| newLine = append(newLine, token) |
| } |
| } |
| // If the color state isn't already clear, clear it |
| if color != 0 || bold != false { |
| newLine = append(newLine, &ColorCode{color: 0}) |
| } |
| line.line = newLine |
| out <- line |
| } |
| |
| // BasicPresenter is a presenter to output very basic uncolored output |
| type BasicPresenter struct { |
| enableColor bool |
| output io.Writer |
| } |
| |
| func NewBasicPresenter(output io.Writer, enableColor bool) *BasicPresenter { |
| return &BasicPresenter{output: output, enableColor: enableColor} |
| } |
| |
| func (b *BasicPresenter) printSrcLoc(loc SourceLocation, info addressInfo) { |
| modRelAddr := info.addr - info.seg.Vaddr + info.seg.ModRelAddr |
| if !loc.function.IsEmpty() { |
| fmt.Fprintf(b.output, "%s at ", loc.function) |
| } |
| if !loc.file.IsEmpty() { |
| fmt.Fprintf(b.output, "%s:%d", loc.file, loc.line) |
| } else { |
| fmt.Fprintf(b.output, "<%s>+0x%x", info.mod.Name, modRelAddr) |
| } |
| } |
| |
| func (b *BasicPresenter) Process(res OutputLine, out chan<- OutputLine) { |
| if res.header != nil { |
| fmt.Fprintf(b.output, "%s", res.header.Present()) |
| } |
| for _, token := range res.line { |
| switch node := token.(type) { |
| case *BacktraceElement: |
| if len(node.info.locs) == 0 { |
| b.printSrcLoc(SourceLocation{}, node.info) |
| } |
| for i, loc := range node.info.locs { |
| b.printSrcLoc(loc, node.info) |
| if i != len(node.info.locs)-1 { |
| fmt.Fprintf(b.output, " inlined from ") |
| } |
| } |
| case *PCElement: |
| if len(node.info.locs) > 0 { |
| b.printSrcLoc(node.info.locs[0], node.info) |
| } else { |
| b.printSrcLoc(SourceLocation{}, node.info) |
| } |
| case *ColorCode: |
| if b.enableColor { |
| fmt.Fprintf(b.output, "\033[%dm", node.color) |
| } |
| case *Text: |
| fmt.Fprintf(b.output, "%s", node.text) |
| case *DumpfileElement: |
| fmt.Fprintf(b.output, "{{{dumpfile:%s:%s}}}", node.sinkType, node.name) |
| case *ResetElement: |
| fmt.Fprintf(b.output, "{{{reset}}}") |
| case *ModuleElement: |
| fmt.Fprintf(b.output, "{{{module:%s:%s:%d}}}", node.mod.Build, node.mod.Name, node.mod.Id) |
| case *MappingElement: |
| fmt.Fprintf(b.output, "{{{mmap:0x%x:0x%x:load:%d:%s:0x%x}}}", node.seg.Vaddr, node.seg.Size, node.seg.Mod, node.seg.Flags, node.seg.ModRelAddr) |
| } |
| } |
| fmt.Fprintf(b.output, "\n") |
| } |