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 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 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 */ 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 */ 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 */ 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 127 public void changeIndent(int indentDelta) { 128 if (!suppressDump) { 129 super.changeIndent(indentDelta); 130 } 131 } 132 133 /** {@inheritDoc} */ 134 @Override 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 */ 145 protected boolean shouldDumpMethod(String name) { 146 return args.method == null || args.method.equals(name); 147 } 148 149 /** {@inheritDoc} */ 150 @Override 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 // Reset the dump cursor to the start of the method. 163 setAt(bytes, offset); 164 165 suppressDump = false; 166 167 if (first) { 168 first = false; 169 } else { 170 parsed(bytes, offset, 0, "\n"); 171 } 172 173 parsed(bytes, offset, 0, "method " + name + " " + descriptor); 174 suppressDump = true; 175 } 176 177 /** {@inheritDoc} */ 178 @Override 179 public void endParsingMember(ByteArray bytes, int offset, String name, 180 String descriptor, Member member) { 181 if (!(member instanceof Method)) { 182 return; 183 } 184 185 if (!shouldDumpMethod(name)) { 186 return; 187 } 188 189 if ((member.getAccessFlags() & (AccessFlags.ACC_ABSTRACT | 190 AccessFlags.ACC_NATIVE)) != 0) { 191 return; 192 } 193 194 ConcreteMethod meth = 195 new ConcreteMethod((Method) member, classFile, true, true); 196 197 if (rop) { 198 ropDump(meth); 199 } else { 200 regularDump(meth); 201 } 202 } 203 204 /** 205 * Does a regular basic block dump. 206 * 207 * @param meth {@code non-null;} method data to dump 208 */ 209 private void regularDump(ConcreteMethod meth) { 210 BytecodeArray code = meth.getCode(); 211 ByteArray bytes = code.getBytes(); 212 ByteBlockList list = BasicBlocker.identifyBlocks(meth); 213 int sz = list.size(); 214 CodeObserver codeObserver = new CodeObserver(bytes, BlockDumper.this); 215 216 // Reset the dump cursor to the start of the bytecode. 217 setAt(bytes, 0); 218 219 suppressDump = false; 220 221 int byteAt = 0; 222 for (int i = 0; i < sz; i++) { 223 ByteBlock bb = list.get(i); 224 int start = bb.getStart(); 225 int end = bb.getEnd(); 226 227 if (byteAt < start) { 228 parsed(bytes, byteAt, start - byteAt, 229 "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(start)); 230 } 231 232 parsed(bytes, start, 0, 233 "block " + Hex.u2(bb.getLabel()) + ": " + 234 Hex.u2(start) + ".." + Hex.u2(end)); 235 changeIndent(1); 236 237 int len; 238 for (int j = start; j < end; j += len) { 239 len = code.parseInstruction(j, codeObserver); 240 codeObserver.setPreviousOffset(j); 241 } 242 243 IntList successors = bb.getSuccessors(); 244 int ssz = successors.size(); 245 if (ssz == 0) { 246 parsed(bytes, end, 0, "returns"); 247 } else { 248 for (int j = 0; j < ssz; j++) { 249 int succ = successors.get(j); 250 parsed(bytes, end, 0, "next " + Hex.u2(succ)); 251 } 252 } 253 254 ByteCatchList catches = bb.getCatches(); 255 int csz = catches.size(); 256 for (int j = 0; j < csz; j++) { 257 ByteCatchList.Item one = catches.get(j); 258 CstType exceptionClass = one.getExceptionClass(); 259 parsed(bytes, end, 0, 260 "catch " + 261 ((exceptionClass == CstType.OBJECT) ? "<any>" : 262 exceptionClass.toHuman()) + " -> " + 263 Hex.u2(one.getHandlerPc())); 264 } 265 266 changeIndent(-1); 267 byteAt = end; 268 } 269 270 int end = bytes.size(); 271 if (byteAt < end) { 272 parsed(bytes, byteAt, end - byteAt, 273 "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(end)); 274 } 275 276 suppressDump = true; 277 } 278 279 /** 280 * Does a registerizing dump. 281 * 282 * @param meth {@code non-null;} method data to dump 283 */ 284 private void ropDump(ConcreteMethod meth) { 285 TranslationAdvice advice = DexTranslationAdvice.THE_ONE; 286 BytecodeArray code = meth.getCode(); 287 ByteArray bytes = code.getBytes(); 288 RopMethod rmeth = Ropper.convert(meth, advice); 289 StringBuffer sb = new StringBuffer(2000); 290 291 if (optimize) { 292 boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags()); 293 int paramWidth = computeParamWidth(meth, isStatic); 294 rmeth = 295 Optimizer.optimize(rmeth, paramWidth, isStatic, true, advice); 296 } 297 298 BasicBlockList blocks = rmeth.getBlocks(); 299 int[] order = blocks.getLabelsInOrder(); 300 301 sb.append("first " + Hex.u2(rmeth.getFirstLabel()) + "\n"); 302 303 for (int label : order) { 304 BasicBlock bb = blocks.get(blocks.indexOfLabel(label)); 305 sb.append("block "); 306 sb.append(Hex.u2(label)); 307 sb.append("\n"); 308 309 IntList preds = rmeth.labelToPredecessors(label); 310 int psz = preds.size(); 311 for (int i = 0; i < psz; i++) { 312 sb.append(" pred "); 313 sb.append(Hex.u2(preds.get(i))); 314 sb.append("\n"); 315 } 316 317 InsnList il = bb.getInsns(); 318 int ilsz = il.size(); 319 for (int i = 0; i < ilsz; i++) { 320 Insn one = il.get(i); 321 sb.append(" "); 322 sb.append(il.get(i).toHuman()); 323 sb.append("\n"); 324 } 325 326 IntList successors = bb.getSuccessors(); 327 int ssz = successors.size(); 328 if (ssz == 0) { 329 sb.append(" returns\n"); 330 } else { 331 int primary = bb.getPrimarySuccessor(); 332 for (int i = 0; i < ssz; i++) { 333 int succ = successors.get(i); 334 sb.append(" next "); 335 sb.append(Hex.u2(succ)); 336 337 if ((ssz != 1) && (succ == primary)) { 338 sb.append(" *"); 339 } 340 341 sb.append("\n"); 342 } 343 } 344 } 345 346 suppressDump = false; 347 setAt(bytes, 0); 348 parsed(bytes, 0, bytes.size(), sb.toString()); 349 suppressDump = true; 350 } 351 } 352