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