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