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