1 /* 2 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.bytecode; 26 27 import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD; 28 import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY; 29 import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE; 30 import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH; 31 import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST; 32 import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD; 33 import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE; 34 import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD; 35 import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE; 36 import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD; 37 import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC; 38 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO; 39 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W; 40 import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ; 41 import static org.graalvm.compiler.bytecode.Bytecodes.IFGE; 42 import static org.graalvm.compiler.bytecode.Bytecodes.IFGT; 43 import static org.graalvm.compiler.bytecode.Bytecodes.IFLE; 44 import static org.graalvm.compiler.bytecode.Bytecodes.IFLT; 45 import static org.graalvm.compiler.bytecode.Bytecodes.IFNE; 46 import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL; 47 import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL; 48 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ; 49 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE; 50 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ; 51 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE; 52 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT; 53 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE; 54 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT; 55 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE; 56 import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD; 57 import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF; 58 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC; 59 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE; 60 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL; 61 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC; 62 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL; 63 import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE; 64 import static org.graalvm.compiler.bytecode.Bytecodes.JSR; 65 import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W; 66 import static org.graalvm.compiler.bytecode.Bytecodes.LDC; 67 import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W; 68 import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W; 69 import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD; 70 import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH; 71 import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE; 72 import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY; 73 import static org.graalvm.compiler.bytecode.Bytecodes.NEW; 74 import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY; 75 import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD; 76 import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC; 77 import static org.graalvm.compiler.bytecode.Bytecodes.RET; 78 import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH; 79 import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH; 80 81 import jdk.vm.ci.meta.ConstantPool; 82 import jdk.vm.ci.meta.JavaConstant; 83 import jdk.vm.ci.meta.JavaField; 84 import jdk.vm.ci.meta.JavaMethod; 85 import jdk.vm.ci.meta.JavaType; 86 import jdk.vm.ci.meta.ResolvedJavaMethod; 87 88 /** 89 * Utility for producing a {@code javap}-like disassembly of bytecode. 90 */ 91 public class BytecodeDisassembler { 92 93 /** 94 * Specifies if the disassembly for a single instruction can span multiple lines. 95 */ 96 private final boolean multiline; 97 98 private final boolean newLine; 99 BytecodeDisassembler(boolean multiline, boolean newLine)100 public BytecodeDisassembler(boolean multiline, boolean newLine) { 101 this.multiline = multiline; 102 this.newLine = newLine; 103 } 104 BytecodeDisassembler(boolean multiline)105 public BytecodeDisassembler(boolean multiline) { 106 this(multiline, true); 107 } 108 BytecodeDisassembler()109 public BytecodeDisassembler() { 110 this(true, true); 111 } 112 disassembleOne(ResolvedJavaMethod method, int bci)113 public static String disassembleOne(ResolvedJavaMethod method, int bci) { 114 return new BytecodeDisassembler(false, false).disassemble(method, bci, bci); 115 } 116 117 /** 118 * Disassembles the bytecode of a given method in a {@code javap}-like format. 119 * 120 * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract) 121 */ disassemble(ResolvedJavaMethod method)122 public String disassemble(ResolvedJavaMethod method) { 123 return disassemble(method, 0, Integer.MAX_VALUE); 124 } 125 126 /** 127 * Disassembles the bytecode of a given method in a {@code javap}-like format. 128 * 129 * @return {@code null} if {@code method} has no bytecode (e.g., it is native or abstract) 130 */ disassemble(ResolvedJavaMethod method, int startBci, int endBci)131 public String disassemble(ResolvedJavaMethod method, int startBci, int endBci) { 132 return disassemble(new ResolvedJavaMethodBytecode(method), startBci, endBci); 133 } 134 135 /** 136 * Disassembles {@code code} in a {@code javap}-like format. 137 */ disassemble(Bytecode code)138 public String disassemble(Bytecode code) { 139 return disassemble(code, 0, Integer.MAX_VALUE); 140 } 141 142 /** 143 * Disassembles {@code code} in a {@code javap}-like format. 144 */ disassemble(Bytecode code, int startBci, int endBci)145 public String disassemble(Bytecode code, int startBci, int endBci) { 146 if (code.getCode() == null) { 147 return null; 148 } 149 ResolvedJavaMethod method = code.getMethod(); 150 ConstantPool cp = code.getConstantPool(); 151 BytecodeStream stream = new BytecodeStream(code.getCode()); 152 StringBuilder buf = new StringBuilder(); 153 int opcode = stream.currentBC(); 154 try { 155 while (opcode != Bytecodes.END) { 156 int bci = stream.currentBCI(); 157 if (bci >= startBci && bci <= endBci) { 158 String mnemonic = Bytecodes.nameOf(opcode); 159 buf.append(String.format("%4d: %-14s", bci, mnemonic)); 160 if (stream.nextBCI() > bci + 1) { 161 decodeOperand(buf, stream, cp, method, bci, opcode); 162 } 163 if (newLine) { 164 buf.append(String.format("%n")); 165 } 166 } 167 stream.next(); 168 opcode = stream.currentBC(); 169 } 170 } catch (Throwable e) { 171 throw new RuntimeException(String.format("Error disassembling %s%nPartial disassembly:%n%s", method.format("%H.%n(%p)"), buf.toString()), e); 172 } 173 return buf.toString(); 174 } 175 decodeOperand(StringBuilder buf, BytecodeStream stream, ConstantPool cp, ResolvedJavaMethod method, int bci, int opcode)176 private void decodeOperand(StringBuilder buf, BytecodeStream stream, ConstantPool cp, ResolvedJavaMethod method, int bci, int opcode) { 177 // @formatter:off 178 switch (opcode) { 179 case BIPUSH : buf.append(stream.readByte()); break; 180 case SIPUSH : buf.append(stream.readShort()); break; 181 case NEW : 182 case CHECKCAST : 183 case INSTANCEOF : 184 case ANEWARRAY : { 185 int cpi = stream.readCPI(); 186 JavaType type = cp.lookupType(cpi, opcode); 187 buf.append(String.format("#%-10d // %s", cpi, type.toJavaName())); 188 break; 189 } 190 case GETSTATIC : 191 case PUTSTATIC : 192 case GETFIELD : 193 case PUTFIELD : { 194 int cpi = stream.readCPI(); 195 JavaField field = cp.lookupField(cpi, method, opcode); 196 String fieldDesc = field.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? field.format("%n:%T") : field.format("%H.%n:%T"); 197 buf.append(String.format("#%-10d // %s", cpi, fieldDesc)); 198 break; 199 } 200 case INVOKEVIRTUAL : 201 case INVOKESPECIAL : 202 case INVOKESTATIC : { 203 int cpi = stream.readCPI(); 204 JavaMethod callee = cp.lookupMethod(cpi, opcode); 205 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R"); 206 buf.append(String.format("#%-10d // %s", cpi, calleeDesc)); 207 break; 208 } 209 case INVOKEINTERFACE: { 210 int cpi = stream.readCPI(); 211 JavaMethod callee = cp.lookupMethod(cpi, opcode); 212 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R"); 213 buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), calleeDesc)); 214 break; 215 } 216 case INVOKEDYNAMIC: { 217 int cpi = stream.readCPI4(); 218 JavaMethod callee = cp.lookupMethod(cpi, opcode); 219 String calleeDesc = callee.getDeclaringClass().getName().equals(method.getDeclaringClass().getName()) ? callee.format("%n:(%P)%R") : callee.format("%H.%n:(%P)%R"); 220 buf.append(String.format("#%-10d // %s", cpi, calleeDesc)); 221 break; 222 } 223 case LDC : 224 case LDC_W : 225 case LDC2_W : { 226 int cpi = stream.readCPI(); 227 Object constant = cp.lookupConstant(cpi); 228 String desc = null; 229 if (constant instanceof JavaConstant) { 230 JavaConstant c = ((JavaConstant) constant); 231 desc = c.toValueString(); 232 } else { 233 desc = constant.toString(); 234 } 235 if (!multiline) { 236 desc = desc.replaceAll("\\n", ""); 237 } 238 buf.append(String.format("#%-10d // %s", cpi, desc)); 239 break; 240 } 241 case RET : 242 case ILOAD : 243 case LLOAD : 244 case FLOAD : 245 case DLOAD : 246 case ALOAD : 247 case ISTORE : 248 case LSTORE : 249 case FSTORE : 250 case DSTORE : 251 case ASTORE : { 252 buf.append(String.format("%d", stream.readLocalIndex())); 253 break; 254 } 255 case IFEQ : 256 case IFNE : 257 case IFLT : 258 case IFGE : 259 case IFGT : 260 case IFLE : 261 case IF_ICMPEQ : 262 case IF_ICMPNE : 263 case IF_ICMPLT : 264 case IF_ICMPGE : 265 case IF_ICMPGT : 266 case IF_ICMPLE : 267 case IF_ACMPEQ : 268 case IF_ACMPNE : 269 case GOTO : 270 case JSR : 271 case IFNULL : 272 case IFNONNULL : 273 case GOTO_W : 274 case JSR_W : { 275 buf.append(String.format("%d", stream.readBranchDest())); 276 break; 277 } 278 case LOOKUPSWITCH : 279 case TABLESWITCH : { 280 BytecodeSwitch bswitch = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(stream, bci) : new BytecodeTableSwitch(stream, bci); 281 if (multiline) { 282 buf.append("{ // " + bswitch.numberOfCases()); 283 for (int i = 0; i < bswitch.numberOfCases(); i++) { 284 buf.append(String.format("%n %7d: %d", bswitch.keyAt(i), bswitch.targetAt(i))); 285 } 286 buf.append(String.format("%n default: %d", bswitch.defaultTarget())); 287 buf.append(String.format("%n }")); 288 } else { 289 buf.append("[" + bswitch.numberOfCases()).append("] {"); 290 for (int i = 0; i < bswitch.numberOfCases(); i++) { 291 buf.append(String.format("%d: %d", bswitch.keyAt(i), bswitch.targetAt(i))); 292 if (i != bswitch.numberOfCases() - 1) { 293 buf.append(", "); 294 } 295 } 296 buf.append(String.format("} default: %d", bswitch.defaultTarget())); 297 } 298 break; 299 } 300 case NEWARRAY : { 301 int typecode = stream.readLocalIndex(); 302 // Checkstyle: stop 303 switch (typecode) { 304 case 4: buf.append("boolean"); break; 305 case 5: buf.append("char"); break; 306 case 6: buf.append("float"); break; 307 case 7: buf.append("double"); break; 308 case 8: buf.append("byte"); break; 309 case 9: buf.append("short"); break; 310 case 10: buf.append("int"); break; 311 case 11: buf.append("long"); break; 312 } 313 // Checkstyle: resume 314 315 break; 316 } 317 case MULTIANEWARRAY : { 318 int cpi = stream.readCPI(); 319 JavaType type = cp.lookupType(cpi, opcode); 320 buf.append(String.format("#%-10s // %s", cpi + ", " + stream.readUByte(bci + 3), type.toJavaName())); 321 break; 322 } 323 } 324 // @formatter:on 325 } 326 getInvokedMethodAt(ResolvedJavaMethod method, int invokeBci)327 public static JavaMethod getInvokedMethodAt(ResolvedJavaMethod method, int invokeBci) { 328 if (method.getCode() == null) { 329 return null; 330 } 331 ConstantPool cp = method.getConstantPool(); 332 BytecodeStream stream = new BytecodeStream(method.getCode()); 333 int opcode = stream.currentBC(); 334 while (opcode != Bytecodes.END) { 335 int bci = stream.currentBCI(); 336 if (bci == invokeBci) { 337 if (stream.nextBCI() > bci + 1) { 338 switch (opcode) { 339 case INVOKEVIRTUAL: 340 case INVOKESPECIAL: 341 case INVOKESTATIC: { 342 int cpi = stream.readCPI(); 343 JavaMethod callee = cp.lookupMethod(cpi, opcode); 344 return callee; 345 } 346 case INVOKEINTERFACE: { 347 int cpi = stream.readCPI(); 348 JavaMethod callee = cp.lookupMethod(cpi, opcode); 349 return callee; 350 } 351 case INVOKEDYNAMIC: { 352 int cpi = stream.readCPI4(); 353 JavaMethod callee = cp.lookupMethod(cpi, opcode); 354 return callee; 355 } 356 default: 357 throw new InternalError(BytecodeDisassembler.disassembleOne(method, invokeBci)); 358 } 359 } 360 } 361 stream.next(); 362 opcode = stream.currentBC(); 363 } 364 return null; 365 } 366 getBytecodeAt(ResolvedJavaMethod method, int invokeBci)367 public static int getBytecodeAt(ResolvedJavaMethod method, int invokeBci) { 368 if (method.getCode() == null) { 369 return -1; 370 } 371 BytecodeStream stream = new BytecodeStream(method.getCode()); 372 int opcode = stream.currentBC(); 373 while (opcode != Bytecodes.END) { 374 int bci = stream.currentBCI(); 375 if (bci == invokeBci) { 376 return opcode; 377 } 378 stream.next(); 379 opcode = stream.currentBC(); 380 } 381 return -1; 382 } 383 } 384