• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.tools.r8;
18 
19 import java.nio.charset.StandardCharsets;
20 import java.nio.file.Files;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.nio.file.StandardOpenOption;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.function.Predicate;
30 
31 public class CreateStacktraceFromDexDumpTool {
32 
33   private static final String CLASS = "com.example.android.helloactivitywithr8.HelloActivityWithR8";
34   private static final String SOURCE_FILE_IDX = "source_file_idx";
35   private static final String INVOKE_VIRTUAL = ": invoke-virtual";
36   private static final String INVOKE_DIRECT = ": invoke-direct";
37   private static final String METHOD_ON_CREATE = "onCreate";
38   private static final String METHOD_GET_VIEW = "getView";
39   private static final String METHOD_GET_LAYOUT_INFLATER = "getLayoutInflater";
40   private static final String R8_MARKER_PREFIX = "~~R8{";
41 
42   private final List<String> inputLines;
43   private final List<String> outputLines = new ArrayList<>();
44   private final String sourceFile;
45 
CreateStacktraceFromDexDumpTool(List<String> lines)46   private CreateStacktraceFromDexDumpTool(List<String> lines) {
47     this.inputLines = lines;
48     sourceFile = getSourceFile(lines);
49     outputLines.add("java.lang.RuntimeException: error");
50   }
51 
52   // Find the source file line.
getSourceFile(List<String> lines)53   private static String getSourceFile(List<String> lines) {
54     for (String line : lines) {
55       if (line.contains(SOURCE_FILE_IDX)) {
56         // Read <source-file> from line:
57         //   source_file_idx   : <idx> (<source-file>)
58         int start = line.indexOf('(');
59         if (start > 0) {
60           int end = line.indexOf(')', start);
61           if (end > 0) {
62             return line.substring(start + 1, end);
63           }
64         }
65       }
66     }
67     return "NoSourceFile";
68   }
69 
skipUntil(Iterator<String> iterator, Predicate<String> fn)70   private static String skipUntil(Iterator<String> iterator, Predicate<String> fn) {
71     while (iterator.hasNext()) {
72       String next = iterator.next();
73       if (fn.test(next)) {
74         return next;
75       }
76     }
77     return null;
78   }
79 
skipUntilInMethod(Iterator<String> iterator, Predicate<String> fn)80   private static String skipUntilInMethod(Iterator<String> iterator, Predicate<String> fn) {
81     String line = skipUntil(iterator, fn.or(l -> isMethodHeader(l)));
82     return line == null || isMethodHeader(line) ? null : line;
83   }
84 
isMethodHeader(String line)85   private static boolean isMethodHeader(String line) {
86     return line.endsWith("'") && line.contains("name");
87   }
88 
isDebug()89   private boolean isDebug() {
90     String marker = skipUntil(inputLines.iterator(), l -> l.contains(R8_MARKER_PREFIX));
91     return marker != null && marker.contains("\"debug\"");
92   }
93 
mapPcInLineNumberTable( Iterator<String> iterator, int invokePcValue, String invokePcString)94   private static String mapPcInLineNumberTable(
95       Iterator<String> iterator, int invokePcValue, String invokePcString) {
96     Map<Integer, String> lineTable = new HashMap<>();
97     String lineTableEntry;
98     do {
99       lineTableEntry = skipUntilInMethod(iterator, line -> line.contains(" line="));
100       if (lineTableEntry != null) {
101         // Read a line table mapping entry:
102         // 0x<addr> line=<linenumber>
103         String stripped = lineTableEntry.strip();
104         int split = stripped.indexOf(" line=");
105         if (split > 0 && stripped.startsWith("0x")) {
106           try {
107             int pc = Integer.parseInt(stripped.substring(2, split), 16);
108             lineTable.put(pc, stripped.substring(split + " line=".length()));
109           } catch (NumberFormatException e) {
110             return "InvalidLineTablePc";
111           }
112         }
113       }
114     } while (lineTableEntry != null);
115     // If there is no line number table return the PC as the line.
116     if (lineTable.isEmpty()) {
117       return invokePcString;
118     }
119     String lineNumber = lineTable.get(invokePcValue);
120     if (lineNumber != null) {
121       return lineNumber;
122     }
123     return "PcNotInLineNumberTable";
124   }
125 
addLineFor(String methodName, String invokeType, String invokedMethod)126   private void addLineFor(String methodName, String invokeType, String invokedMethod) {
127     Iterator<String> iterator = inputLines.iterator();
128     // Find the method entry.
129     if (skipUntil(iterator, line -> line.endsWith("'" + methodName + "'") && isMethodHeader(line))
130         == null) {
131       outputLines.add("MethodNotFound: " + methodName);
132       return;
133     }
134     // Find the code section.
135     if (skipUntilInMethod(iterator, line -> line.contains("insns size")) == null) {
136       outputLines.add("InstructionsNotFound: " + methodName);
137       return;
138     }
139     // Find the invoke instruction.
140     String invokeLine =
141         skipUntilInMethod(
142             iterator, line -> line.contains(invokeType) && line.contains(invokedMethod));
143     if (invokeLine == null) {
144       outputLines.add(
145           "InvokeNotFound: " + methodName + " calling " + invokeType + " " + invokedMethod);
146       return;
147     }
148     String invokePcString = "NoPcInfo";
149     int invokePcValue = -1;
150     // Read <pc> from line:
151     // <addr>: <bytes> |<pc>: <invoke-type> {vX}, <type-desc>.<method-name>:<method-desc>;
152     int end = invokeLine.indexOf(invokeType);
153     if (end > 0) {
154       int start = invokeLine.lastIndexOf('|', end);
155       if (start > 0) {
156         String pcString = invokeLine.substring(start + 1, end);
157         try {
158           int pc = Integer.parseInt(pcString, 16);
159           invokePcValue = pc;
160           invokePcString = "" + pc;
161         } catch (NumberFormatException e) {
162           invokePcString = "PcParseError";
163         }
164       }
165     }
166     String lineNumber = mapPcInLineNumberTable(iterator, invokePcValue, invokePcString);
167     outputLines.add(
168         String.format("    at %s.%s(%s:%s)", CLASS, methodName, sourceFile, lineNumber));
169   }
170 
main(String[] args)171   public static void main(String[] args) throws Exception {
172     Path dexdumpPath = Paths.get(args[0]);
173     Path outputPath = Paths.get(args[1]);
174     List<String> inputLines = Files.readAllLines(dexdumpPath);
175     CreateStacktraceFromDexDumpTool tool = new CreateStacktraceFromDexDumpTool(inputLines);
176     if (tool.isDebug()) {
177       // In debug builds onCreate calls getView which calls getLayoutInflater.
178       tool.addLineFor(METHOD_GET_VIEW, INVOKE_VIRTUAL, METHOD_GET_LAYOUT_INFLATER);
179       tool.addLineFor(METHOD_ON_CREATE, INVOKE_DIRECT, METHOD_GET_VIEW);
180     } else {
181       // In release builds getView is inlined away.
182       tool.addLineFor(METHOD_ON_CREATE, INVOKE_VIRTUAL, METHOD_GET_LAYOUT_INFLATER);
183     }
184     Files.write(
185         outputPath,
186         tool.outputLines,
187         StandardCharsets.UTF_8,
188         StandardOpenOption.CREATE,
189         StandardOpenOption.TRUNCATE_EXISTING);
190   }
191 }
192