[symbolize] Add info msg to backtraces

This change adds support for adding an additional message to the end
of backtraces. This can be used for instance to add information about
stack pointers and other such information.

Bug: TC-303
Test: New unit tests + CQ
Change-Id: I9c4538a97b9668eeaae23f699284454d5c0ed550
diff --git a/symbolize/ast.go b/symbolize/ast.go
index 6374548..4c9d9b8 100644
--- a/symbolize/ast.go
+++ b/symbolize/ast.go
@@ -72,6 +72,7 @@
 type BacktraceElement struct {
 	vaddr uint64
 	num   uint64
+	msg   string
 	info  addressInfo
 }
 
diff --git a/symbolize/demuxer_test.go b/symbolize/demuxer_test.go
index ae6ee90..ebbb916 100644
--- a/symbolize/demuxer_test.go
+++ b/symbolize/demuxer_test.go
@@ -233,6 +233,53 @@
 	//[131.604] 01234.05678> Error at atan2 at atan2.c:49
 }
 
+func TestMsgSimpleBacktrace(t *testing.T) {
+	msg := "{{{bt:0:0xdeadbeef:this is a message}}}\n"
+	symbo := newMockSymbolizer([]mockModule{})
+	demuxer := NewDemuxer(testBinaries, symbo)
+	ctx := context.Background()
+	in := StartParsing(ctx, strings.NewReader(msg))
+	out := demuxer.Start(ctx, in)
+	buf := new(bytes.Buffer)
+	Consume(ComposePostProcessors(ctx, out, &FilterContextElements{},
+		NewBacktracePresenter(buf, NewBasicPresenter(buf, false))))
+	expected := "    #0    0x00000000deadbeee in <>+0xdeadbeee this is a message\n"
+	actual := buf.String()
+	if actual != expected {
+		t.Errorf("want %q got %q", expected, actual)
+	}
+}
+
+func ExampleMsgBacktrace() {
+	// mock the input and outputs of llvm-symbolizer
+	symbo := newMockSymbolizer([]mockModule{
+		{"testdata/libc.elf", map[uint64][]SourceLocation{
+			0x43680: {{NewOptStr("pow.c"), 23, NewOptStr("pow")}},
+		}},
+	})
+
+	// make a demuxer
+	demuxer := NewDemuxer(testBinaries, symbo)
+
+	// define a little message that will need to be parsed
+	msg := "{{{module:1:libc.so:elf:4fcb712aa6387724a9f465a32cd8c14b}}}\n" +
+		"{{{mmap:0x12345000:0xcf6bc:load:1:rx:0x0}}}\n" +
+		"{{{bt:0:0x12388681:sp 0xdeadbaaf bp 0xdeadbeef}}}\n"
+
+	// start sending InputLines to the demuxer
+	ctx := context.Background()
+	in := StartParsing(ctx, strings.NewReader(msg))
+	// start the demuxer which will cause filters to send output lines to 'out'
+	out := demuxer.Start(ctx, in)
+
+	Consume(ComposePostProcessors(ctx, out,
+		&FilterContextElements{},
+		NewBacktracePresenter(os.Stdout, NewBasicPresenter(os.Stdout, false))))
+
+	//Output:
+	//     #0    0x0000000012388680 in pow pow.c:23 <libc.so>+0x43680 sp 0xdeadbaaf bp 0xdeadbeef
+}
+
 func ExampleNoHeaderBacktrace() {
 	// mock the input and outputs of llvm-symbolizer
 	symbo := newMockSymbolizer([]mockModule{
diff --git a/symbolize/parser.go b/symbolize/parser.go
index 31b8f6a..eb14950 100644
--- a/symbolize/parser.go
+++ b/symbolize/parser.go
@@ -71,10 +71,11 @@
 	ptr := ptrRegexp
 	str := strRegexp
 	num := fmt.Sprintf("(?:%s|%s)", dec, ptr)
-	b.addRule(fmt.Sprintf("{{{bt:(%s):(%s)}}}", dec, ptr), func(args ...string) {
+	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) {
diff --git a/symbolize/presenter.go b/symbolize/presenter.go
index 5b93205..3430b78 100644
--- a/symbolize/presenter.go
+++ b/symbolize/presenter.go
@@ -27,14 +27,14 @@
 	}
 }
 
-func printBacktrace(out io.Writer, hdr LineHeader, frame uint64, info addressInfo) {
+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\n", hdrString, frame, info.addr, info.mod.Name, modRelAddr)
+		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 {
@@ -54,7 +54,11 @@
 		if !loc.file.IsEmpty() {
 			fmt.Fprintf(out, " %s:%d", loc.file, loc.line)
 		}
-		fmt.Fprintf(out, " <%s>+%#x\n", info.mod.Name, modRelAddr)
+		fmt.Fprintf(out, " <%s>+%#x", info.mod.Name, modRelAddr)
+		if msg != "" {
+			fmt.Fprintf(out, " %s", msg)
+		}
+		fmt.Fprintf(out, "\n")
 	}
 }
 
@@ -70,7 +74,7 @@
 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.info)
+			printBacktrace(b.out, line.header, bt.num, bt.msg, bt.info)
 			// Don't process a backtrace we've already output.
 			return
 		}
@@ -79,7 +83,7 @@
 		// 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.info)
+				printBacktrace(b.out, line.header, bt.num, bt.msg, bt.info)
 				// Don't process a backtrace we've already output.
 				return
 			}
diff --git a/symbolize/regextokenizer.go b/symbolize/regextokenizer.go
index c629e02..f94a871 100644
--- a/symbolize/regextokenizer.go
+++ b/symbolize/regextokenizer.go
@@ -93,7 +93,12 @@
 				for i := 0; i < regex.groupCount; i++ {
 					groupBeginIdx := locs[2*(regex.index+i)]
 					groupEndIdx := locs[2*(regex.index+i)+1]
-					groups = append(groups, input[groupBeginIdx:groupEndIdx])
+					// When a group is optional it may not be included. Check for that.
+					if groupBeginIdx == -1 || groupEndIdx == -1 {
+						groups = append(groups, "")
+					} else {
+						groups = append(groups, input[groupBeginIdx:groupEndIdx])
+					}
 				}
 				// Pass the regex's groups to it
 				regex.action(groups...)