blob: 9e460ae3252e79a21ac21cdeba2c595c1db0dd69 [file] [log] [blame]
// 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 (
"bufio"
"context"
"fmt"
"io"
"regexp"
)
const (
elemSuffix string = "}}}"
colorPrefix string = "\033["
modulePrefix string = "{{{module:"
mmapPrefix string = "{{{mmap:"
pcPrefix string = "{{{pc:"
btPrefix string = "{{{bt:"
)
var (
endTextRegex = regexp.MustCompile("({{{.*}}})|(\033\\[[0-9]+m)")
beginLogLineRegex = regexp.MustCompile(`\[[0-9]+\.[0-9]+\] [0-9]+\.[0-9]+>`)
)
func ParseText(b *ParserState) interface{} {
var idx int
if loc := endTextRegex.FindStringIndex(string(*b)); loc != nil {
idx = loc[0]
} else {
idx = len(*b)
}
if idx == 0 {
return nil
}
text := string((*b)[:idx])
*b = (*b)[idx:]
return &Text{text}
}
func ParseBt(b *ParserState) interface{} {
return b.prefix(btPrefix, func(b *ParserState) interface{} {
num, err := b.intBefore(":")
if err != nil {
return nil
}
addr, err := b.intBefore(elemSuffix)
if err != nil {
return nil
}
return &BacktraceElement{num: num, vaddr: addr}
})
}
func ParsePc(b *ParserState) interface{} {
return b.prefix(pcPrefix, func(b *ParserState) interface{} {
addr, err := b.intBefore(elemSuffix)
if err != nil {
return nil
}
return &PCElement{vaddr: addr}
})
}
func ParseColor(b *ParserState) interface{} {
return b.prefix(colorPrefix, func(b *ParserState) interface{} {
var out ColorGroup
var err error
if out.color, err = b.intBefore("m"); err != nil {
return nil
}
b.many(&out.children, func(b *ParserState) interface{} {
return b.choice(ParsePc, ParseText)
})
return &out
})
}
func ParseModule(b *ParserState) interface{} {
return b.prefix(modulePrefix, func(b *ParserState) interface{} {
var out Module
var err error
if out.id, err = b.intBefore(":"); err != nil {
return nil
}
if out.name, err = b.before(":"); err != nil {
return nil
}
if !b.expect("elf:") {
return nil
}
if out.build, err = b.before(elemSuffix); err != nil {
return nil
}
return &ModuleElement{out}
})
}
func ParseMapping(b *ParserState) interface{} {
return b.prefix(mmapPrefix, func(b *ParserState) interface{} {
var out Segment
var err error
if out.vaddr, err = b.intBefore(":"); err != nil {
return nil
}
if out.size, err = b.intBefore(":"); err != nil {
return nil
}
if b.expect("load:") {
if out.mod, err = b.intBefore(":"); err != nil {
return nil
}
if out.flags, err = b.before(":"); err != nil {
return nil
}
if out.modRelAddr, err = b.intBefore(elemSuffix); err != nil {
return nil
}
} else {
if _, err = b.before("}}}"); err != nil {
return nil
}
}
return &MappingElement{out}
})
}
func ParseReset(b *ParserState) interface{} {
if b.expect("{{{reset}}}") {
return &ResetElement{}
}
return nil
}
func ParsePresentationGroup(b *ParserState) interface{} {
var p PresentationGroup
b.many(&p.children, func(b *ParserState) interface{} {
return b.choice(ParseColor, ParseModule, ParseMapping, ParseBt, ParseReset, ParsePc, ParseText)
})
return &p
}
func ParseLine(line string) Node {
buf := ParserState(line)
b := &buf
out := ParsePresentationGroup(b)
if len(buf) != 0 || out == nil {
return nil
}
return out.(Node)
// TODO: add node type for malformed text and emit warning
}
func ParseLogLine(b *ParserState) (InputLine, error) {
var out InputLine
if b.expect("[") {
var err error
time, err := b.floatBefore("]")
if err != nil {
return out, err
}
b.whitespace()
pid, err := b.decBefore(".")
if err != nil {
return out, err
}
out.source = process(pid)
tid, err := b.decBefore(">")
if err != nil {
return out, err
}
out.header = logHeader{process: pid, thread: tid, time: time}
b.whitespace()
out.msg = string(*b)
return out, nil
}
return out, fmt.Errorf("malformed serial log line")
}
func StartParsing(ctx context.Context, reader io.Reader) <-chan InputLine {
out := make(chan InputLine)
// This is not used for demuxing. It is a human readable line number.
var lineno uint64 = 1
go func() {
defer close(out)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
select {
case <-ctx.Done():
return
default: // Continue.
}
text := ParserState(scanner.Text())
b := &text
// Get the dummyText and needed text.
locs := beginLogLineRegex.FindStringIndex(string(text))
if locs == nil {
// This means the whole thing is dummy text.
var line InputLine
line.source = dummySource{}
line.msg = string(text)
line.lineno = lineno
// Make sure to increase the lineno
lineno++
out <- line
// Read next line
continue
}
// Check for dummy text
if locs[0] > 0 {
// This means we found a match but there was some junk at the start.
dummyText := string(text[:locs[0]])
var line InputLine
line.source = dummySource{}
line.msg = dummyText
// Use the same lineno for both lines as it is for debugging from the original.
line.lineno = lineno
out <- line
}
// Advance b to the correct start
*b = (*b)[locs[0]:]
// Now attempt to parse the log line
line, err := ParseLogLine(b)
line.lineno = lineno
lineno++
// If we error out send this line whole to the dummy process
if err != nil {
// Some partial bit of this might have parsed so just modify it.
line.msg = string(*b)
}
out <- line
}
}()
return out
}