1 /* 2 * Copyright (C) 2007 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.dx.command.dump; 18 19 import com.android.dx.cf.code.BasicBlocker; 20 import com.android.dx.cf.code.ByteBlock; 21 import com.android.dx.cf.code.ByteBlockList; 22 import com.android.dx.cf.code.ByteCatchList; 23 import com.android.dx.cf.code.BytecodeArray; 24 import com.android.dx.cf.code.ConcreteMethod; 25 import com.android.dx.cf.code.Ropper; 26 import com.android.dx.cf.direct.CodeObserver; 27 import com.android.dx.cf.direct.DirectClassFile; 28 import com.android.dx.cf.direct.StdAttributeFactory; 29 import com.android.dx.cf.iface.Member; 30 import com.android.dx.cf.iface.Method; 31 import com.android.dx.rop.code.AccessFlags; 32 import com.android.dx.rop.code.BasicBlock; 33 import com.android.dx.rop.code.BasicBlockList; 34 import com.android.dx.rop.code.DexTranslationAdvice; 35 import com.android.dx.rop.code.Insn; 36 import com.android.dx.rop.code.InsnList; 37 import com.android.dx.rop.code.RopMethod; 38 import com.android.dx.rop.code.TranslationAdvice; 39 import com.android.dx.rop.cst.CstType; 40 import com.android.dx.ssa.Optimizer; 41 import com.android.dx.util.ByteArray; 42 import com.android.dx.util.Hex; 43 import com.android.dx.util.IntList; 44 import java.io.PrintStream; 45 46 /** 47 * Utility to dump basic block info from methods in a human-friendly form. 48 */ 49 public class BlockDumper 50 extends BaseDumper { 51 /** whether or not to registerize (make rop blocks) */ 52 private final boolean rop; 53 54 /** 55 * {@code null-ok;} the class file object being constructed; 56 * becomes non-null during {@link #dump} 57 */ 58 protected DirectClassFile classFile; 59 60 /** whether or not to suppress dumping */ 61 protected boolean suppressDump; 62 63 /** whether this is the first method being dumped */ 64 private boolean first; 65 66 /** whether or not to run the ssa optimziations */ 67 private final boolean optimize; 68 69 /** 70 * Dumps the given array, interpreting it as a class file and dumping 71 * methods with indications of block-level stuff. 72 * 73 * @param bytes {@code non-null;} bytes of the (alleged) class file 74 * @param out {@code non-null;} where to dump to 75 * @param filePath the file path for the class, excluding any base 76 * directory specification 77 * @param rop whether or not to registerize (make rop blocks) 78 * @param args commandline parsedArgs 79 */ dump(byte[] bytes, PrintStream out, String filePath, boolean rop, Args args)80 public static void dump(byte[] bytes, PrintStream out, 81 String filePath, boolean rop, Args args) { 82 BlockDumper bd = new BlockDumper(bytes, out, filePath, 83 rop, args); 84 bd.dump(); 85 } 86 87 /** 88 * Constructs an instance. This class is not publicly instantiable. 89 * Use {@link #dump}. 90 */ BlockDumper(byte[] bytes, PrintStream out, String filePath, boolean rop, Args args)91 BlockDumper(byte[] bytes, PrintStream out, String filePath, 92 boolean rop, Args args) { 93 super(bytes, out, filePath, args); 94 95 this.rop = rop; 96 this.classFile = null; 97 this.suppressDump = true; 98 this.first = true; 99 this.optimize = args.optimize; 100 } 101 102 /** 103 * Does the dumping. 104 */ dump()105 public void dump() { 106 byte[] bytes = getBytes(); 107 ByteArray ba = new ByteArray(bytes); 108 109 /* 110 * First, parse the file completely, so we can safely refer to 111 * attributes, etc. 112 */ 113 classFile = new DirectClassFile(ba, getFilePath(), getStrictParse()); 114 classFile.setAttributeFactory(StdAttributeFactory.THE_ONE); 115 classFile.getMagic(); // Force parsing to happen. 116 117 // Next, reparse it and observe the process. 118 DirectClassFile liveCf = 119 new DirectClassFile(ba, getFilePath(), getStrictParse()); 120 liveCf.setAttributeFactory(StdAttributeFactory.THE_ONE); 121 liveCf.setObserver(this); 122 liveCf.getMagic(); // Force parsing to happen. 123 } 124 125 /** {@inheritDoc} */ 126 @Override changeIndent(int indentDelta)127 public void changeIndent(int indentDelta) { 128 if (!suppressDump) { 129 super.changeIndent(indentDelta); 130 } 131 } 132 133 /** {@inheritDoc} */ 134 @Override parsed(ByteArray bytes, int offset, int len, String human)135 public void parsed(ByteArray bytes, int offset, int len, String human) { 136 if (!suppressDump) { 137 super.parsed(bytes, offset, len, human); 138 } 139 } 140 141 /** 142 * @param name method name 143 * @return true if this method should be dumped 144 */ shouldDumpMethod(String name)145 protected boolean shouldDumpMethod(String name) { 146 return args.method == null || args.method.equals(name); 147 } 148 149 /** {@inheritDoc} */ 150 @Override startParsingMember(ByteArray bytes, int offset, String name, String descriptor)151 public void startParsingMember(ByteArray bytes, int offset, String name, 152 String descriptor) { 153 if (descriptor.indexOf('(') < 0) { 154 // It's a field, not a method 155 return; 156 } 157 158 if (!shouldDumpMethod(name)) { 159 return; 160 } 161 162 suppressDump = false; 163 164 if (first) { 165 first = false; 166 } else { 167 parsed(bytes, offset, 0, "\n"); 168 } 169 170 parsed(bytes, offset, 0, "method " + name + " " + descriptor); 171 suppressDump = true; 172 } 173 174 /** {@inheritDoc} */ 175 @Override endParsingMember(ByteArray bytes, int offset, String name, String descriptor, Member member)176 public void endParsingMember(ByteArray bytes, int offset, String name, 177 String descriptor, Member member) { 178 if (!(member instanceof Method)) { 179 return; 180 } 181 182 if (!shouldDumpMethod(name)) { 183 return; 184 } 185 186 if ((member.getAccessFlags() & (AccessFlags.ACC_ABSTRACT | 187 AccessFlags.ACC_NATIVE)) != 0) { 188 return; 189 } 190 191 ConcreteMethod meth = 192 new ConcreteMethod((Method) member, classFile, true, true); 193 194 if (rop) { 195 ropDump(meth); 196 } else { 197 regularDump(meth); 198 } 199 } 200 201 /** 202 * Does a regular basic block dump. 203 * 204 * @param meth {@code non-null;} method data to dump 205 */ regularDump(ConcreteMethod meth)206 private void regularDump(ConcreteMethod meth) { 207 BytecodeArray code = meth.getCode(); 208 ByteArray bytes = code.getBytes(); 209 ByteBlockList list = BasicBlocker.identifyBlocks(meth); 210 int sz = list.size(); 211 CodeObserver codeObserver = new CodeObserver(bytes, BlockDumper.this); 212 213 suppressDump = false; 214 215 int byteAt = 0; 216 for (int i = 0; i < sz; i++) { 217 ByteBlock bb = list.get(i); 218 int start = bb.getStart(); 219 int end = bb.getEnd(); 220 221 if (byteAt < start) { 222 parsed(bytes, byteAt, start - byteAt, 223 "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(start)); 224 } 225 226 parsed(bytes, start, 0, 227 "block " + Hex.u2(bb.getLabel()) + ": " + 228 Hex.u2(start) + ".." + Hex.u2(end)); 229 changeIndent(1); 230 231 int len; 232 for (int j = start; j < end; j += len) { 233 len = code.parseInstruction(j, codeObserver); 234 codeObserver.setPreviousOffset(j); 235 } 236 237 IntList successors = bb.getSuccessors(); 238 int ssz = successors.size(); 239 if (ssz == 0) { 240 parsed(bytes, end, 0, "returns"); 241 } else { 242 for (int j = 0; j < ssz; j++) { 243 int succ = successors.get(j); 244 parsed(bytes, end, 0, "next " + Hex.u2(succ)); 245 } 246 } 247 248 ByteCatchList catches = bb.getCatches(); 249 int csz = catches.size(); 250 for (int j = 0; j < csz; j++) { 251 ByteCatchList.Item one = catches.get(j); 252 CstType exceptionClass = one.getExceptionClass(); 253 parsed(bytes, end, 0, 254 "catch " + 255 ((exceptionClass == CstType.OBJECT) ? "<any>" : 256 exceptionClass.toHuman()) + " -> " + 257 Hex.u2(one.getHandlerPc())); 258 } 259 260 changeIndent(-1); 261 byteAt = end; 262 } 263 264 int end = bytes.size(); 265 if (byteAt < end) { 266 parsed(bytes, byteAt, end - byteAt, 267 "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(end)); 268 } 269 270 suppressDump = true; 271 } 272 273 /** 274 * Does a registerizing dump. 275 * 276 * @param meth {@code non-null;} method data to dump 277 */ ropDump(ConcreteMethod meth)278 private void ropDump(ConcreteMethod meth) { 279 TranslationAdvice advice = DexTranslationAdvice.THE_ONE; 280 BytecodeArray code = meth.getCode(); 281 ByteArray bytes = code.getBytes(); 282 RopMethod rmeth = Ropper.convert(meth, advice, classFile.getMethods(), dexOptions); 283 StringBuilder sb = new StringBuilder(2000); 284 285 if (optimize) { 286 boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags()); 287 int paramWidth = computeParamWidth(meth, isStatic); 288 rmeth = 289 Optimizer.optimize(rmeth, paramWidth, isStatic, true, advice); 290 } 291 292 BasicBlockList blocks = rmeth.getBlocks(); 293 int[] order = blocks.getLabelsInOrder(); 294 295 sb.append("first " + Hex.u2(rmeth.getFirstLabel()) + "\n"); 296 297 for (int label : order) { 298 BasicBlock bb = blocks.get(blocks.indexOfLabel(label)); 299 sb.append("block "); 300 sb.append(Hex.u2(label)); 301 sb.append("\n"); 302 303 IntList preds = rmeth.labelToPredecessors(label); 304 int psz = preds.size(); 305 for (int i = 0; i < psz; i++) { 306 sb.append(" pred "); 307 sb.append(Hex.u2(preds.get(i))); 308 sb.append("\n"); 309 } 310 311 InsnList il = bb.getInsns(); 312 int ilsz = il.size(); 313 for (int i = 0; i < ilsz; i++) { 314 Insn one = il.get(i); 315 sb.append(" "); 316 sb.append(il.get(i).toHuman()); 317 sb.append("\n"); 318 } 319 320 IntList successors = bb.getSuccessors(); 321 int ssz = successors.size(); 322 if (ssz == 0) { 323 sb.append(" returns\n"); 324 } else { 325 int primary = bb.getPrimarySuccessor(); 326 for (int i = 0; i < ssz; i++) { 327 int succ = successors.get(i); 328 sb.append(" next "); 329 sb.append(Hex.u2(succ)); 330 331 if ((ssz != 1) && (succ == primary)) { 332 sb.append(" *"); 333 } 334 335 sb.append("\n"); 336 } 337 } 338 } 339 340 suppressDump = false; 341 parsed(bytes, 0, bytes.size(), sb.toString()); 342 suppressDump = true; 343 } 344 } 345