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