| // 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" |
| "strconv" |
| "strings" |
| ) |
| |
| // TODO: Implement a reflection based means of automatically doing these conversions. |
| func str2dec(what string) uint64 { |
| what = strings.TrimLeft(what, "0") |
| if len(what) == 0 { |
| return 0 |
| } |
| out, err := strconv.ParseUint(what, 10, 64) |
| if err != nil { |
| panic(err.Error()) |
| } |
| return out |
| } |
| |
| func str2int(what string) uint64 { |
| // str2int assumes that the |what| matches either decRegex or ptrRegex. |
| // If we come across a hex value, we don't want to trim the leading zero. |
| // If we come across anything else and it still matched one of dec or ptr |
| // regexes then we want to trim leading zeros. This lets us match things like |
| // "01234" which is important for pids and things like that but also prevents |
| // panics like those seen in TC-273. |
| if !strings.HasPrefix(what, "0x") { |
| what = strings.TrimLeft(what, "0") |
| if len(what) == 0 { |
| return 0 |
| } |
| } |
| out, err := strconv.ParseUint(what, 0, 64) |
| if err != nil { |
| panic(err.Error()) |
| } |
| return out |
| } |
| |
| func str2float(what string) float64 { |
| out, err := strconv.ParseFloat(what, 64) |
| if err != nil { |
| panic(err.Error()) |
| } |
| return out |
| } |
| |
| const ( |
| decRegexp = "(?:[[:digit:]]+)" |
| ptrRegexp = "(?:0|0x[[:xdigit:]]{1,16})" |
| strRegexp = "(?:[^{}:]*)" |
| spaceRegexp = `(?:\s*)` |
| floatRegexp = `(?:[[:digit:]]+)\.(?:[[:digit:]]+)` |
| ) |
| |
| type ParseLineFunc func(msg string) []Node |
| |
| func GetLineParser() ParseLineFunc { |
| var b regexpTokenizerBuilder |
| out := []Node{} |
| dec := decRegexp |
| ptr := ptrRegexp |
| str := strRegexp |
| num := fmt.Sprintf("(?:%s|%s)", dec, ptr) |
| b.addRule(fmt.Sprintf("{{{bt:(%s):(%s)(?::(%s))?}}}", dec, ptr, str), func(args ...string) { |
| out = append(out, &BacktraceElement{ |
| num: str2dec(args[1]), |
| vaddr: str2int(args[2]), |
| msg: args[3], |
| }) |
| }) |
| b.addRule(fmt.Sprintf("{{{pc:(%s)}}}", ptr), func(args ...string) { |
| out = append(out, &PCElement{vaddr: str2int(args[1])}) |
| }) |
| b.addRule(fmt.Sprintf("\033\\[(%s)m", dec), func(args ...string) { |
| out = append(out, &ColorCode{color: str2dec(args[1])}) |
| }) |
| b.addRule(fmt.Sprintf("{{{dumpfile:(%s):(%s)}}}", str, str), func(args ...string) { |
| out = append(out, &DumpfileElement{sinkType: args[1], name: args[2]}) |
| }) |
| b.addRule(fmt.Sprintf(`{{{module:(%s):(%s):elf:(%s)}}}`, num, str, str), func(args ...string) { |
| out = append(out, &ModuleElement{mod: Module{ |
| Id: str2int(args[1]), |
| Name: args[2], |
| Build: args[3], |
| }}) |
| }) |
| b.addRule(fmt.Sprintf(`{{{mmap:(%s):(%s):load:(%s):(%s):(%s)}}}`, ptr, num, num, str, ptr), func(args ...string) { |
| out = append(out, &MappingElement{seg: Segment{ |
| Vaddr: str2int(args[1]), |
| Size: str2int(args[2]), |
| Mod: str2int(args[3]), |
| Flags: args[4], |
| ModRelAddr: str2int(args[5]), |
| }}) |
| }) |
| b.addRule(`{{{reset}}}`, func(args ...string) { |
| out = append(out, &ResetElement{}) |
| }) |
| tokenizer, err := b.compile(func(text string) { |
| out = append(out, &Text{text: text}) |
| }) |
| if err != nil { |
| panic(err.Error()) |
| } |
| return func(msg string) []Node { |
| out = nil |
| tokenizer.run(msg) |
| return out |
| } |
| } |
| |
| 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 |
| var b regexpTokenizerBuilder |
| space := spaceRegexp |
| float := floatRegexp |
| dec := decRegexp |
| tags := `[^\[\]]*` |
| b.addRule(fmt.Sprintf(`\[(%s)\]%s(%s)[.:](%s)>(.*)$`, float, space, dec, dec), func(args ...string) { |
| var hdr logHeader |
| var line InputLine |
| hdr.time = str2float(args[1]) |
| hdr.process = str2dec(args[2]) |
| hdr.thread = str2dec(args[3]) |
| line.header = hdr |
| line.source = Process(hdr.process) |
| line.lineno = lineno |
| line.msg = args[4] |
| out <- line |
| }) |
| b.addRule(fmt.Sprintf(`\[(%s)\]\[(%s)\]\[(%s)\]\[(%s)\] ([A-Z]+):?(.*)$`, float, dec, dec, tags), func(args ...string) { |
| var hdr sysLogHeader |
| var line InputLine |
| hdr.time = str2float(args[1]) |
| hdr.process = str2dec(args[2]) |
| hdr.thread = str2dec(args[3]) |
| hdr.tags = args[4] |
| hdr.typ = args[5] |
| line.header = hdr |
| line.source = Process(hdr.process) |
| line.lineno = lineno |
| line.msg = args[6] |
| out <- line |
| }) |
| tokenizer, err := b.compile(func(text string) { |
| var line InputLine |
| line.source = DummySource{} |
| line.msg = text |
| line.lineno = lineno |
| out <- line |
| }) |
| if err != nil { |
| panic(err.Error()) |
| } |
| go func() { |
| defer close(out) |
| scanner := bufio.NewScanner(reader) |
| for ; scanner.Scan(); lineno++ { |
| select { |
| case <-ctx.Done(): |
| return |
| default: |
| tokenizer.run(scanner.Text()) |
| } |
| } |
| }() |
| return out |
| } |