1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sts.common.util;
18 
19 import com.android.server.os.TombstoneProtos.*;
20 import com.android.tradefed.log.LogUtil.CLog;
21 
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.function.Consumer;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29 
30 /** Parses tombstones and from a tombstone file or logcat. */
31 public class TombstoneParser {
32 
33     private static final String TOMBSTONE_HEADER =
34             "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";
35     private static final Pattern TOMBSTONE_HEADER_PATTERN =
36             Pattern.compile(TOMBSTONE_HEADER.replace("*", "\\*"));
37     private static final Pattern NATIVE_CRASH_TIME_PATTERN =
38             Pattern.compile("Native Crash TIME: (?<time>\\d+)");
39     private static final Pattern FINGERPRINT_PATTERN =
40             Pattern.compile("Build fingerprint: '(?<fingerprint>.*)'");
41     private static final Pattern REVISION_PATTERN =
42             Pattern.compile("Revision: '(?<revision>.*)'\\s*");
43     private static final Pattern ABI_PATTERN = Pattern.compile("ABI: '(?<abi>.*)'");
44     private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("Timestamp: (?<timestamp>.*)");
45     private static final Pattern UPTIME_PATTERN =
46             Pattern.compile("Process uptime: (?<uptime>\\d+)s");
47     private static final Pattern GET_MAIN_THREAD_FAILURE_PATTERN =
48             Pattern.compile("failed to find entry for main thread in tombstone");
49     private static final Pattern THREAD_SEPARATOR_PATTERN =
50             Pattern.compile("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
51     // "    fd %d: %s (%s)"
52     private static final Pattern OPEN_FILE_ROW_PATTERN =
53             Pattern.compile(
54                     " *fd (?<fd>\\d+?): (?<path>.*?) \\((?:(?<unowned>unowned)|(?:owned by"
55                             + " (?<owner>\\S+) 0x(?<tag>\\p{XDigit}{1,16})))\\)");
56     private static final Pattern SIGNAL_MISSING_PATTERN =
57             Pattern.compile("signal information missing");
58     // "signal %d (%s), code %d (%s%s), fault addr %s"
59     // "signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x7e772b8cfbe0"
60     private static final Pattern SIGNAL_PATTERN =
61             Pattern.compile(
62                     "signal (?<number>\\d+?) \\((?<name>.+?)\\), code (?<code>(?:-)?\\d+?)"
63                             + " \\((?<codename>\\S+?)(?: from pid (?<senderpid>\\d+?), uid"
64                             + " (?<senderuid>\\d+?))?\\), fault addr"
65                             + " (?:0x)?(?<faultaddress>\\p{XDigit}{1,16}|--------)"
66                             + "( (?<register>\\(.+\\)))?");
67     private static final Pattern CAUSE_PATTERN = Pattern.compile("Cause: (?<cause>.*?)");
68     // Be greedy because some abort messages are multiple lines long
69     private static final Pattern ABORT_PATTERN =
70             Pattern.compile(
71                     "^Abort message: '(?<message>.*)'$", Pattern.MULTILINE | Pattern.DOTALL);
72     private static final Pattern DEALLOC_PATTERN =
73             Pattern.compile("deallocated by thread (?<tid>\\d+):");
74     private static final Pattern ALLOC_PATTERN =
75             Pattern.compile("allocated by thread (?<tid>\\d+):");
76     private static final Pattern NO_MEMORY_MAPS_PATTERN = Pattern.compile("No memory maps found");
77     // "memory map (%d %s):" + ("\n--->Fault address falls at %s before any mapped regions" | "
78     // (fault address prefixed with --->)")
79     private static final Pattern MEMORY_MAP_HEADER_PATTERN =
80             Pattern.compile(
81                     "memory map \\((?<count>\\d+?) entr(?:y|ies)\\):(?: \\(fault address prefixed"
82                             + " with --->\\))?");
83     private static final Pattern MEMORY_MAP_FAULT_ADDRESS_BETWEEN_PATTERN =
84             Pattern.compile(
85                     "--->Fault address falls at"
86                         + " (?<faultaddresshigh>\\p{XDigit}{4,8})'(?<faultaddresslow>\\p{XDigit}{4,8})"
87                         + " between mapped regions");
88     // "    " + ("--->")? + "%s-%s" + %s%s%s" + "  %8" PRIx64 "  %8" PRIx64" + ("  %s")? + ("
89     // (BuildId: %s)")? + (" (load bias 0x%" PRIx64 ")")?
90     private static final Pattern MEMORY_MAP_LINE_PATTERN =
91             Pattern.compile(
92                     " *(?:--->)?(?<beginaddresshigh>\\p{XDigit}{4,8})'(?<beginaddresslow>\\p{XDigit}{4,8})-(?<endaddresshigh>\\p{XDigit}{4,8})'(?<endaddresslow>\\p{XDigit}{4,8})"
93                         + " (?<read>\\S)(?<write>\\S)(?<execute>\\S)  "
94                         + " {0,7}(?<offset>\\p{XDigit}{1,8})   {0,7}(?<length>\\p{XDigit}{1,8})(?:"
95                         + "  (?<mappingname>.+?))?(?: \\(BuildId: (?<buildid>\\d+)\\))?(?: \\(load"
96                         + " bias 0x(?<loadbias>\\p{XDigit}+)\\))?");
97     private static final Pattern MEMORY_MAP_FAULT_ADDRESS_AFTER_PATTERN =
98             Pattern.compile(
99                     "--->Fault address falls at"
100                         + " (?<faultaddresshigh>\\p{XDigit}{4,8})'(?<faultaddresslow>\\p{XDigit}{4,8})"
101                         + " after any mapped regions");
102     private static final Pattern CMD_LINE_PATTERN = Pattern.compile("Cmdline: (?<cmd>.*)");
103     private static final Pattern THREAD_HEADER_1_PATTERN =
104             Pattern.compile(
105                     "pid: (?<pid>\\d+), tid: (?<tid>\\d+), name: (?<threadname>.+?)  >>>"
106                             + " (?<processname>.+?) <<<");
107     private static final Pattern THREAD_HEADER_2_PATTERN = Pattern.compile("uid: (?<uid>\\d+)\\s*");
108     private static final Pattern TAGGED_ADDR_CTRL_PATTERN =
109             Pattern.compile(
110                     "tagged_addr_ctrl: (?<taggedaddrctrl>\\p{XDigit}{16})(?<description>.+)?");
111     // TODO: finish description
112     private static final Pattern PAC_ENABLED_KEYS_PATTERN =
113             Pattern.compile(
114                     "pac_enabled_keys: (?<pacenabledkeys>\\p{XDigit}{16})(?<description>.+)?");
115     private static final Pattern REGISTER_ROW_PATTERN = Pattern.compile("  .*");
116     private static final Pattern BACKTRACE_HEADER_PATTERN = Pattern.compile("backtrace:");
117     private static final Pattern BACKTRACE_NOTE_PATTERN = Pattern.compile(" *NOTE: (?<note>.*)");
118     // "      #05 pc 000000000004faf6  /apex/com.android.runtime/lib64/bionic/libc.so
119     // (__libc_init+86) (BuildId: 284d864ffe434d73dc722b84a1d3d9ca)"
120     private static final Pattern BACKTRACE_PATTERN =
121             Pattern.compile(
122                     " *#(?<index>\\d{2}) pc (?<programcounter>\\p{XDigit}{8,16}) "
123                         + " (?<filename>.+?)(?:"
124                         + " \\((?<functionname>.*?)\\+(?<functionoffset>\\d+)\\))?(?: \\(BuildId:"
125                         + " (?<buildid>.*?)\\))?");
126     private static final Pattern MEMORY_NEAR_PATTERN =
127             Pattern.compile("memory near (?<registername>\\S+?)(?: \\((?<mappingname>\\S+?)\\))?:");
128     // "    0000006f0bb2a8a0 6f735c65646f635c 6d61675c65637275  \code\source\gam"
129     private static final Pattern MEMORY_DUMP_ROW_PATTERN =
130             Pattern.compile(
131                     " *(?<address>\\p{XDigit}{8,16}) (?<memory1>(?:\\p{XDigit}{8,16}|-{8,16}))(?:"
132                             + " (?<memory2>(?:\\p{XDigit}{8,16}|-{8,16})))?(?:"
133                             + " (?<memory3>(?:\\p{XDigit}{8,16}|-{8,16})))?(?:"
134                             + " (?<memory4>(?:\\p{XDigit}{8,16}|-{8,16})))?  (?<ascii>.{1,16})");
135     private static final Pattern MEMORY_TAG_PATTERN =
136             Pattern.compile(
137                     "Memory tags around the fault address"
138                             + " \\(0x(?<faultaddress>\\p{XDigit}{8,16})\\), one tag per"
139                             + " (?<taggranulesize>) bytes:");
140     private static final Pattern MEMORY_TAG_ROW_PATTERN =
141             Pattern.compile("    (?:=>|  )0x(?<address>\\p{XDigit}{8,16}):(?<tags>.*)");
142     private static final Pattern LOG_PATTERN =
143             Pattern.compile("---------(?<istail> tail end of) log (?<buffername>.*)");
144     private static final Pattern LOG_LINE_PATTERN =
145             Pattern.compile(
146                     "(?<timestamp>.+?) {1,5}(?<pid>\\d+) {1,5}(?<tid>\\d+) (?<priority>\\S)"
147                             + " (?<tag>\\S*) {0,8}: (?<message>.*)");
148 
149     private static final Pattern CURRENT_BACKTRACE_BLOB_PATTERN =
150             Pattern.compile(
151                     String.format(
152                             "^%s\n(?:%s\n)*(?:%s\n)*",
153                             BACKTRACE_HEADER_PATTERN.pattern(),
154                             BACKTRACE_NOTE_PATTERN.pattern(),
155                             BACKTRACE_PATTERN.pattern()),
156                     Pattern.MULTILINE);
157     private static final Pattern ALLOC_BACKTRACE_BLOB_PATTERN =
158             Pattern.compile(
159                     String.format(
160                             "^%s\n(?:%s\n)*", ALLOC_PATTERN.pattern(), BACKTRACE_PATTERN.pattern()),
161                     Pattern.MULTILINE);
162     private static final Pattern DEALLOC_BACKTRACE_BLOB_PATTERN =
163             Pattern.compile(
164                     String.format(
165                             "^%s\n(?:%s\n)*",
166                             DEALLOC_PATTERN.pattern(), BACKTRACE_PATTERN.pattern()),
167                     Pattern.MULTILINE);
168 
169     /** Parse a logcat snippet and build a list of tombstones */
parseLogcat(String logcat)170     public static final List<Tombstone> parseLogcat(String logcat) {
171         String[] potentialTombstones = splitPattern(TOMBSTONE_HEADER_PATTERN).split(logcat);
172 
173         List<Tombstone> tombstones = new ArrayList<>();
174         for (String potentialTombstone : potentialTombstones) {
175             Tombstone.Builder tombstoneBuilder = Tombstone.newBuilder();
176             List<String> lines = lines(potentialTombstone);
177             if (lines.isEmpty()) {
178                 continue;
179             }
180             if (!lines.get(0).contains(TOMBSTONE_HEADER)) {
181                 continue;
182             }
183             if (NATIVE_CRASH_TIME_PATTERN.matcher(lines.get(1)).find()) {
184                 CLog.d("ignoring crash time");
185                 continue;
186             }
187 
188             String tombstoneBlob =
189                     lines.stream()
190                             .filter(line -> line.contains("DEBUG   :"))
191                             .map(
192                                     line -> {
193                                         // logcat removes trailing space after ":" in the case of
194                                         // empty lines
195                                         String[] split = line.split("DEBUG   :(?: )?", 2);
196                                         return split.length == 2 ? split[1] : "<empty>";
197                                     })
198                             .collect(
199                                     StringBuilder::new,
200                                     (sb, line) -> {
201                                         sb.append(line);
202                                         sb.append('\n');
203                                     },
204                                     StringBuilder::append)
205                             .toString();
206 
207             if (!parseTombstone(tombstoneBlob, tombstoneBuilder)) {
208                 CLog.w("parsing tombstone failed: \n" + tombstoneBlob);
209             }
210             Tombstone tombstone = tombstoneBuilder.build();
211             tombstones.add(tombstone);
212         }
213         return tombstones;
214     }
215 
parseTombstone(String tombstoneBlob, Tombstone.Builder tombstoneBuilder)216     public static boolean parseTombstone(String tombstoneBlob, Tombstone.Builder tombstoneBuilder) {
217         // get build or bail
218         // get revision or bail
219         // get ABI or bail
220         // get timestamp or bail
221         // get uptime or bail
222         // try "failed to find entry for main thread..." and add to notes(?) (if present, return)
223         //
224         // get main thread:
225         //
226         // get logs:
227         // try ----- tail end of log %x
228         // else log %x
229         // loop
230         //   try jfeiowajfklewjaoi
231         //
232         // loop
233         //   try --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
234         //   get thread
235         // try ""
236         // ifp open files
237         // ifp loop
238         //   "    fd ..."
239         // get logs
240 
241         String[] threadBlobs = THREAD_SEPARATOR_PATTERN.split(tombstoneBlob);
242         String headerAndMainThreadBlob = threadBlobs[0];
243         List<String> headerAndMainThreadLines = lines(headerAndMainThreadBlob);
244 
245         // get fingerprint
246         if (!matchLine(
247                 headerAndMainThreadLines.iterator(),
248                 FINGERPRINT_PATTERN,
249                 m -> {
250                     String fingerprint = m.group("fingerprint");
251                     CLog.i(fingerprint);
252                     tombstoneBuilder.setBuildFingerprint(fingerprint);
253                 })) {
254             CLog.w("fingerprint failed");
255             return false;
256         }
257 
258         // get revision
259         if (!matchLine(
260                 headerAndMainThreadLines.iterator(),
261                 REVISION_PATTERN,
262                 m -> {
263                     tombstoneBuilder.setRevision(m.group("revision"));
264                 })) {
265             CLog.w("revision failed");
266             return false;
267         }
268 
269         // get ABI
270         if (!matchLine(
271                 headerAndMainThreadLines.iterator(),
272                 ABI_PATTERN,
273                 m -> {
274                     final String abi = m.group("abi");
275                     Architecture arch = null;
276                     switch (abi) {
277                         case "arm":
278                             arch = Architecture.ARM32;
279                             break;
280                         case "arm64":
281                             arch = Architecture.ARM64;
282                             break;
283                         case "riscv64":
284                             arch = Architecture.RISCV64;
285                             break;
286                         case "x86":
287                             arch = Architecture.X86;
288                             break;
289                         case "x86_64":
290                             arch = Architecture.X86_64;
291                             break;
292                         default:
293                             CLog.i("unknown arch");
294                             return;
295                     }
296                     CLog.d("set arch to: " + arch);
297                     tombstoneBuilder.setArch(arch);
298                 })) {
299             CLog.w("abi failed");
300             return false;
301         }
302 
303         // get timestamp
304         matchLine(
305                 headerAndMainThreadLines.iterator(),
306                 TIMESTAMP_PATTERN,
307                 m -> {
308                     tombstoneBuilder.setTimestamp(m.group("timestamp"));
309                 });
310 
311         // get process uptime
312         matchLine(
313                 headerAndMainThreadLines.iterator(),
314                 UPTIME_PATTERN,
315                 m -> {
316                     String uptime = m.group("uptime");
317                     tombstoneBuilder.setProcessUptime(Integer.valueOf(uptime));
318                 });
319 
320         // try main thread get failure note
321         if (matchLine(headerAndMainThreadLines.iterator(), GET_MAIN_THREAD_FAILURE_PATTERN, null)) {
322             // tombstoned couldn't get the main thread info, so that's all we get
323             return true;
324         }
325 
326         // get main thread
327         if (!parseMainThread(headerAndMainThreadBlob, tombstoneBuilder)) {
328             CLog.w("main thread failed");
329             return false;
330         }
331 
332         // get logs
333         if (!parseLogs(headerAndMainThreadBlob, tombstoneBuilder)) {
334             CLog.w("logs failed");
335             return false;
336         }
337 
338         // get threads
339         for (int i = 1; i < threadBlobs.length; i++) {
340             String threadBlob = threadBlobs[i];
341             com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder =
342                     com.android.server.os.TombstoneProtos.Thread.newBuilder();
343             if (!parseThread(threadBlob, tombstoneBuilder, threadBuilder)) {
344                 CLog.w("thread failed");
345                 return false;
346             }
347             tombstoneBuilder.putThreads(threadBuilder.getId(), threadBuilder.build());
348         }
349 
350         // get end of blob
351         String tailBlob = threadBlobs[threadBlobs.length - 1];
352         List<String> tailLines = lines(tailBlob);
353 
354         // get open files
355         matchLines(
356                 tailLines.iterator(),
357                 OPEN_FILE_ROW_PATTERN,
358                 m -> {
359                     FD.Builder fdBuilder =
360                             FD.newBuilder()
361                                     .setFd(Integer.valueOf(m.group("fd")))
362                                     .setPath(m.group("path"));
363                     String owner = m.group("owner");
364                     String tag = m.group("tag");
365                     if (owner != null && tag != null) {
366                         fdBuilder.setOwner(owner).setTag(parsePointer(tag));
367                     } else {
368                         fdBuilder.setOwner("unowned");
369                     }
370                     tombstoneBuilder.addOpenFds(fdBuilder.build());
371                 });
372 
373         if (!parseLogs(tailBlob, tombstoneBuilder)) {
374             CLog.w("logs failed");
375             return false;
376         }
377 
378         return true;
379     }
380 
parseMainThread( String headerAndMainThreadBlob, Tombstone.Builder tombstoneBuilder)381     private static boolean parseMainThread(
382             String headerAndMainThreadBlob, Tombstone.Builder tombstoneBuilder) {
383         com.android.server.os.TombstoneProtos.Thread.Builder mainThreadBuilder =
384                 com.android.server.os.TombstoneProtos.Thread.newBuilder();
385 
386         List<String> headerAndMainThreadLines = lines(headerAndMainThreadBlob);
387         String[] causeBlobs = splitPattern(CAUSE_PATTERN).split(headerAndMainThreadBlob);
388         String headerBlob = causeBlobs[0];
389         List<String> headerLines = lines(headerBlob);
390         String tailBlob = causeBlobs[causeBlobs.length - 1];
391         List<String> tailLines = lines(tailBlob);
392 
393         try {
394             if (!parseThreadHeader(
395                     headerLines, tombstoneBuilder, mainThreadBuilder, /* isMainThread */ true)) {
396                 CLog.w("main thread get header failed");
397                 return false;
398             }
399 
400             // get signal or no signal
401             boolean matchedSignal = false;
402             matchedSignal |= matchLine(headerLines.iterator(), SIGNAL_MISSING_PATTERN, null);
403             matchedSignal |=
404                     matchLine(
405                             headerLines.iterator(),
406                             SIGNAL_PATTERN,
407                             m -> {
408                                 Signal.Builder signalBuilder =
409                                         Signal.newBuilder()
410                                                 .setNumber(Integer.valueOf(m.group("number")))
411                                                 .setName(m.group("name"))
412                                                 .setCode(Integer.valueOf(m.group("code")))
413                                                 .setCodeName(m.group("codename"));
414 
415                                 String faultAddress = m.group("faultaddress");
416                                 if (!faultAddress.equals("--------")) {
417                                     signalBuilder
418                                             .setHasFaultAddress(true)
419                                             .setFaultAddress(parsePointer(faultAddress));
420                                 } else {
421                                     signalBuilder.setHasFaultAddress(false);
422                                 }
423                                 String senderUid = m.group("senderuid");
424                                 String senderPid = m.group("senderpid");
425                                 if (senderUid != null && senderPid != null) {
426                                     signalBuilder
427                                             .setSenderUid(Integer.valueOf(senderUid))
428                                             .setSenderPid(Integer.valueOf(senderPid));
429                                 }
430                                 // TODO: add fault-adjacent metadata, which we don't get until the
431                                 // tag dump :(
432                                 tombstoneBuilder.setSignalInfo(signalBuilder.build());
433                             });
434             if (!matchedSignal) {
435                 // must match one or the other
436                 CLog.w("couldn't match signal messages");
437                 return false;
438             }
439 
440             // single cause pattern goes here, so the "header blob" won't contain stuff after
441 
442             // get abort if present
443             {
444                 Matcher m = ABORT_PATTERN.matcher(headerAndMainThreadBlob);
445                 if (m.find()) {
446                     tombstoneBuilder.setAbortMessage(m.group("message"));
447                 }
448             }
449 
450             if (!parseThreadRegisters(headerAndMainThreadLines, mainThreadBuilder)) {
451                 CLog.w("main thread get thread registers failed");
452                 return false;
453             }
454 
455             if (!parseThreadBacktrace(headerAndMainThreadBlob, mainThreadBuilder)) {
456                 CLog.w("main thread get thread backtrace failed");
457                 return false;
458             }
459 
460             // get causes
461             for (int i = 1; i < causeBlobs.length; i++) {
462                 String causeBlob = causeBlobs[i];
463                 List<String> causeLines = lines(causeBlob);
464                 Cause.Builder causeBuilder = Cause.newBuilder();
465                 if (!matchLine(
466                         causeLines.iterator(),
467                         CAUSE_PATTERN,
468                         m -> {
469                             // must delay adding cause because the memory error is printed later
470                             CLog.d("cause message matched");
471                             causeBuilder.setHumanReadable(m.group("cause"));
472                         })) {
473                     // not a cause
474                     continue;
475                 }
476 
477                 boolean hasMemoryError = false;
478                 MemoryError.Builder memoryErrorBuilder = MemoryError.newBuilder();
479                 HeapObject.Builder heapObjectBuilder = HeapObject.newBuilder();
480 
481                 {
482                     Matcher deallocBlobMatcher = DEALLOC_BACKTRACE_BLOB_PATTERN.matcher(causeBlob);
483                     if (deallocBlobMatcher.find()) {
484                         List<String> deallocLines = lines(deallocBlobMatcher.group(0));
485                         hasMemoryError = true;
486                         CLog.d("dealloc matched");
487                         List<BacktraceFrame> backtraceFrames = new ArrayList<>();
488                         if (!parseBacktrace(deallocLines, backtraceFrames)) {
489                             return false;
490                         }
491                         heapObjectBuilder.addAllDeallocationBacktrace(backtraceFrames);
492                     }
493                 }
494 
495                 {
496                     Matcher allocBlobMatcher = ALLOC_BACKTRACE_BLOB_PATTERN.matcher(causeBlob);
497                     if (allocBlobMatcher.find()) {
498                         List<String> allocLines = lines(allocBlobMatcher.group(0));
499                         hasMemoryError = true;
500                         CLog.d("alloc matched");
501                         List<BacktraceFrame> backtraceFrames = new ArrayList<>();
502                         if (!parseBacktrace(allocLines, backtraceFrames)) {
503                             return false;
504                         }
505                         heapObjectBuilder.addAllAllocationBacktrace(backtraceFrames);
506                     }
507                 }
508 
509                 if (hasMemoryError) {
510                     memoryErrorBuilder.setHeap(heapObjectBuilder.build());
511                     causeBuilder.setMemoryError(memoryErrorBuilder.build());
512                 } else {
513                     CLog.d("no memory errors");
514                 }
515                 tombstoneBuilder.addCauses(causeBuilder.build());
516             }
517 
518             if (!parseTagDump(tailLines, tombstoneBuilder)) {
519                 CLog.w("tag dump failed");
520                 return false;
521             }
522 
523             if (!parseThreadMemoryDump(tailBlob, mainThreadBuilder)) {
524                 CLog.w("memory dump failed");
525                 return false;
526             }
527         } finally {
528             tombstoneBuilder.putThreads(mainThreadBuilder.getId(), mainThreadBuilder.build());
529         }
530 
531         // check if no memory maps
532         if (matchLine(tailLines.iterator(), NO_MEMORY_MAPS_PATTERN, null)) {
533             // no more lines follow
534             return true;
535         }
536 
537         String[] memoryMapBlobs = splitPattern(MEMORY_MAP_HEADER_PATTERN).split(tailBlob);
538 
539         for (int i = 1; i < memoryMapBlobs.length; i++) {
540             MemoryMapping.Builder memoryMappingBuilder = MemoryMapping.newBuilder();
541 
542             String memoryMapBlob = memoryMapBlobs[i];
543             List<String> memoryMapLines = lines(memoryMapBlob);
544 
545             matchLine(
546                     memoryMapLines.iterator(),
547                     MEMORY_MAP_FAULT_ADDRESS_BETWEEN_PATTERN,
548                     m -> {
549                         CLog.d("got fault address between");
550                         // TODO: put fault address in memoryMappingBuilder
551                     });
552 
553             matchLine(
554                     memoryMapLines.iterator(),
555                     MEMORY_MAP_HEADER_PATTERN,
556                     m -> {
557                         int count = Integer.valueOf(m.group("count"));
558                         CLog.d("got memory map entries count: " + count);
559                         matchLines(
560                                 memoryMapLines.iterator(),
561                                 MEMORY_MAP_LINE_PATTERN,
562                                 mLine -> {
563                                     // TODO: parse
564                                     tombstoneBuilder.addMemoryMappings(
565                                             memoryMappingBuilder.build());
566                                 });
567                     });
568 
569             matchLine(
570                     memoryMapLines.iterator(),
571                     MEMORY_MAP_FAULT_ADDRESS_AFTER_PATTERN,
572                     m -> {
573                         CLog.d("got fault address after");
574                         // TODO: put fault address in memoryMappingBuilder
575                     });
576         }
577 
578         return true;
579     }
580 
parseThread( String blob, Tombstone.Builder tombstoneBuilder, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder)581     private static boolean parseThread(
582             String blob,
583             Tombstone.Builder tombstoneBuilder,
584             com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder) {
585         List<String> lines = lines(blob);
586         if (!parseThreadHeader(lines, tombstoneBuilder, threadBuilder, /* isMainThread */ false)) {
587             return false;
588         }
589         if (!parseThreadRegisters(lines, threadBuilder)) {
590             return false;
591         }
592         if (!parseThreadBacktrace(blob, threadBuilder)) {
593             return false;
594         }
595         if (!parseThreadMemoryDump(blob, threadBuilder)) {
596             return false;
597         }
598         return true;
599     }
600 
parseThreadHeader( List<String> lines, Tombstone.Builder tombstoneBuilder, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder, boolean isMainThread)601     private static boolean parseThreadHeader(
602             List<String> lines,
603             Tombstone.Builder tombstoneBuilder,
604             com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder,
605             boolean isMainThread) {
606         matchLine(
607                 lines.iterator(),
608                 CMD_LINE_PATTERN,
609                 m -> {
610                     if (isMainThread) {
611                         tombstoneBuilder.addAllCommandLine(
612                                 Arrays.asList(m.group("cmd").split(" ")));
613                     }
614                 });
615         matchLine(
616                 lines.iterator(),
617                 THREAD_HEADER_1_PATTERN,
618                 m -> {
619                     int tid = Integer.valueOf(m.group("tid"));
620                     if (isMainThread) {
621                         tombstoneBuilder.setPid(Integer.valueOf(m.group("pid"))).setTid(tid);
622                         if (tombstoneBuilder.getCommandLineList().isEmpty()) {
623                             tombstoneBuilder.addCommandLine(m.group("processname"));
624                         }
625                     }
626                     threadBuilder.setId(tid).setName(m.group("threadname"));
627                 });
628         matchLine(
629                 lines.iterator(),
630                 THREAD_HEADER_2_PATTERN,
631                 m -> {
632                     if (isMainThread) {
633                         tombstoneBuilder.setUid(Integer.valueOf(m.group("uid")));
634                     }
635                 });
636 
637         matchLine(
638                 lines.iterator(),
639                 TAGGED_ADDR_CTRL_PATTERN,
640                 m -> {
641                     // TODO: add to tombstone
642                 });
643 
644         matchLine(
645                 lines.iterator(),
646                 PAC_ENABLED_KEYS_PATTERN,
647                 m -> {
648                     // TODO: add to tombstone
649                 });
650 
651         return true;
652     }
653 
parseThreadRegisters( List<String> lines, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder)654     private static boolean parseThreadRegisters(
655             List<String> lines,
656             com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder) {
657         return matchLines(
658                         lines.iterator(),
659                         REGISTER_ROW_PATTERN,
660                         m -> {
661                             // TODO: parse line and add to tombstone
662                         })
663                 > 0;
664     }
665 
parseThreadBacktrace( String blob, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder)666     private static boolean parseThreadBacktrace(
667             String blob, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder) {
668         Matcher threadBacktraceMatcher = CURRENT_BACKTRACE_BLOB_PATTERN.matcher(blob);
669         if (!threadBacktraceMatcher.find()) {
670             CLog.i("didn't match backtrace blob");
671             return true; // no backtrace
672         }
673         String threadBacktraceBlob = threadBacktraceMatcher.group(0); // entire match
674         List<String> lines = lines(threadBacktraceBlob);
675 
676         matchLines(
677                 lines.iterator(),
678                 BACKTRACE_NOTE_PATTERN,
679                 m -> {
680                     threadBuilder.addBacktraceNote(m.group("note"));
681                 });
682 
683         List<BacktraceFrame> backtraceFrames = new ArrayList<>();
684         if (!parseBacktrace(lines, backtraceFrames)) {
685             return false;
686         }
687         threadBuilder.addAllCurrentBacktrace(backtraceFrames);
688         return true;
689     }
690 
parseBacktrace( List<String> lines, List<BacktraceFrame> backtraceFrames)691     private static boolean parseBacktrace(
692             List<String> lines, List<BacktraceFrame> backtraceFrames) {
693         matchLines(
694                 lines.iterator(),
695                 BACKTRACE_PATTERN,
696                 m -> {
697                     BacktraceFrame.Builder backtraceFrameBuilder =
698                             BacktraceFrame.newBuilder()
699                                     .setRelPc(parsePointer(m.group("programcounter")))
700                                     .setFileName(m.group("filename"));
701                     String functionName = m.group("functionname");
702                     String functionOffset = m.group("functionoffset");
703                     if (functionName != null && functionOffset != null) {
704                         backtraceFrameBuilder
705                                 .setFunctionName(functionName)
706                                 .setFunctionOffset(Long.valueOf(functionOffset));
707                     }
708                     String buildId = m.group("buildid");
709                     if (buildId != null) {
710                         backtraceFrameBuilder.setBuildId(buildId);
711                     }
712                     backtraceFrames.add(backtraceFrameBuilder.build());
713                 });
714 
715         return true;
716     }
717 
parseThreadMemoryDump( String blob, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder)718     private static boolean parseThreadMemoryDump(
719             String blob, com.android.server.os.TombstoneProtos.Thread.Builder threadBuilder) {
720 
721         String[] memoryDumpBlobs = splitPattern(MEMORY_NEAR_PATTERN).split(blob);
722         String headerBlob = memoryDumpBlobs[0];
723         List<String> headerLines = lines(headerBlob);
724 
725         for (int i = 1; i < memoryDumpBlobs.length; i++) {
726             String memoryDumpBlob = memoryDumpBlobs[i];
727             List<String> lines = lines(memoryDumpBlob);
728 
729             matchLine(
730                     lines.iterator(),
731                     MEMORY_NEAR_PATTERN,
732                     m -> {
733                         // TODO: add memory dump to tombstone
734                     });
735 
736             matchLines(
737                     lines.iterator(),
738                     MEMORY_DUMP_ROW_PATTERN,
739                     m -> {
740                         // TODO: add memory dump to tombstone
741                     });
742         }
743 
744         return true;
745     }
746 
parseTagDump(List<String> lines, Tombstone.Builder tombstoneBuilder)747     private static boolean parseTagDump(List<String> lines, Tombstone.Builder tombstoneBuilder) {
748 
749         matchLine(
750                 lines.iterator(),
751                 MEMORY_TAG_PATTERN,
752                 m -> {
753                     // TODO: add memory tag to tombstone builder
754                 });
755 
756         matchLines(
757                 lines.iterator(),
758                 MEMORY_TAG_ROW_PATTERN,
759                 m -> {
760                     // TODO: add memory tag to tombstone builder
761                 });
762 
763         return true;
764     }
765 
parseLogs(String blob, Tombstone.Builder tombstoneBuilder)766     private static boolean parseLogs(String blob, Tombstone.Builder tombstoneBuilder) {
767 
768         String[] logBlobs = LOG_PATTERN.split(blob);
769         for (int i = 1; i < logBlobs.length; i++) {
770             List<String> lines = lines(logBlobs[i]);
771 
772             matchLine(
773                     lines.iterator(),
774                     LOG_PATTERN,
775                     m -> {
776                         CLog.i("found logs");
777                         // TODO: determine if we're the tail end of the logs
778                         // TODO: add logs to tombstone builder
779                     });
780 
781             matchLines(
782                     lines.iterator(),
783                     LOG_LINE_PATTERN,
784                     m -> {
785                         // TODO: add logs to tombstone builder
786                     });
787         }
788 
789         return true;
790     }
791 
matchLine( Iterator<String> lines, Pattern pattern, Consumer<Matcher> onMatch)792     private static boolean matchLine(
793             Iterator<String> lines, Pattern pattern, Consumer<Matcher> onMatch) {
794         return matchLines(lines, pattern, 1, onMatch) == 1;
795     }
796 
matchLines( Iterator<String> lines, Pattern pattern, Consumer<Matcher> onMatch)797     private static int matchLines(
798             Iterator<String> lines, Pattern pattern, Consumer<Matcher> onMatch) {
799         return matchLines(lines, pattern, -1, onMatch);
800     }
801 
matchLines( Iterator<String> lines, Pattern pattern, int times, Consumer<Matcher> onMatch)802     private static int matchLines(
803             Iterator<String> lines, Pattern pattern, int times, Consumer<Matcher> onMatch) {
804         int matches = 0;
805         while (lines.hasNext() && (times < 0 || matches < times)) {
806             String line = lines.next();
807             Matcher m = pattern.matcher(line);
808             if (m.matches()) {
809                 matches++;
810                 if (onMatch != null) {
811                     onMatch.accept(m);
812                 }
813             }
814         }
815         return matches;
816     }
817 
lines(String s)818     private static List<String> lines(String s) {
819         return Arrays.asList(s.split("\\R"));
820     }
821 
splitPattern(Pattern pattern)822     private static Pattern splitPattern(Pattern pattern) {
823         return Pattern.compile(String.format("((?=^.*%s))", pattern.pattern()), Pattern.MULTILINE);
824     }
825 
parsePointer(String pointerString)826     public static long parsePointer(String pointerString) {
827         return Long.parseUnsignedLong(pointerString, 16);
828     }
829 }
830