1 /* 2 * Copyright (c) 2009, 2016, 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 package org.graalvm.compiler.code; 24 25 import java.io.ByteArrayOutputStream; 26 import java.io.OutputStream; 27 import java.io.PrintStream; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.TreeMap; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 35 import org.graalvm.compiler.code.CompilationResult.CodeAnnotation; 36 import org.graalvm.compiler.code.CompilationResult.CodeComment; 37 import org.graalvm.compiler.code.CompilationResult.JumpTable; 38 39 import jdk.vm.ci.code.CodeUtil; 40 41 /** 42 * A HexCodeFile is a textual format for representing a chunk of machine code along with extra 43 * information that can be used to enhance a disassembly of the code. 44 * 45 * A pseudo grammar for a HexCodeFile is given below. 46 * 47 * <pre> 48 * HexCodeFile ::= Platform Delim HexCode Delim (OptionalSection Delim)* 49 * 50 * OptionalSection ::= Comment | OperandComment | JumpTable | LookupTable 51 * 52 * Platform ::= "Platform" ISA WordWidth 53 * 54 * HexCode ::= "HexCode" StartAddress HexDigits 55 * 56 * Comment ::= "Comment" Position String 57 * 58 * OperandComment ::= "OperandComment" Position String 59 * 60 * JumpTable ::= "JumpTable" Position EntrySize Low High 61 * 62 * LookupTable ::= "LookupTable" Position NPairs KeySize OffsetSize 63 * 64 * Position, EntrySize, Low, High, NPairs KeySize OffsetSize ::= int 65 * 66 * Delim := "<||@" 67 * </pre> 68 * 69 * There must be exactly one HexCode and Platform part in a HexCodeFile. The length of HexDigits 70 * must be even as each pair of digits represents a single byte. 71 * <p> 72 * Below is an example of a valid Code input: 73 * 74 * <pre> 75 * 76 * Platform AMD64 64 <||@ 77 * HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3 <||@ 78 * Comment 24 frame-ref-map: +0 {0} 79 * at java.lang.String.toLowerCase(String.java:2496) [bci: 1] 80 * |0 81 * locals: |stack:0:a 82 * stack: |stack:0:a 83 * <||@ 84 * OperandComment 24 {java.util.Locale.getDefault()} <||@ 85 * Comment 36 frame-ref-map: +0 {0} 86 * at java.lang.String.toLowerCase(String.java:2496) [bci: 4] 87 * |0 88 * locals: |stack:0:a 89 * <||@ 90 * OperandComment 36 {java.lang.String.toLowerCase(Locale)} lt;||@ 91 * 92 * </pre> 93 */ 94 public class HexCodeFile { 95 96 public static final String NEW_LINE = CodeUtil.NEW_LINE; 97 public static final String SECTION_DELIM = " <||@"; 98 public static final String COLUMN_END = " <|@"; 99 public static final Pattern SECTION = Pattern.compile("(\\S+)\\s+(.*)", Pattern.DOTALL); 100 public static final Pattern COMMENT = Pattern.compile("(\\d+)\\s+(.*)", Pattern.DOTALL); 101 public static final Pattern OPERAND_COMMENT = COMMENT; 102 public static final Pattern JUMP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(-{0,1}\\d+)\\s+(-{0,1}\\d+)\\s*"); 103 public static final Pattern LOOKUP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*"); 104 public static final Pattern HEX_CODE = Pattern.compile("(\\p{XDigit}+)(?:\\s+(\\p{XDigit}*))?"); 105 public static final Pattern PLATFORM = Pattern.compile("(\\S+)\\s+(\\S+)", Pattern.DOTALL); 106 107 /** 108 * Delimiter placed before a HexCodeFile when embedded in a string/stream. 109 */ 110 public static final String EMBEDDED_HCF_OPEN = "<<<HexCodeFile"; 111 112 /** 113 * Delimiter placed after a HexCodeFile when embedded in a string/stream. 114 */ 115 public static final String EMBEDDED_HCF_CLOSE = "HexCodeFile>>>"; 116 117 /** 118 * Map from a machine code position to a list of comments for the position. 119 */ 120 public final Map<Integer, List<String>> comments = new TreeMap<>(); 121 122 /** 123 * Map from a machine code position to a comment for the operands of the instruction at the 124 * position. 125 */ 126 public final Map<Integer, List<String>> operandComments = new TreeMap<>(); 127 128 public final byte[] code; 129 130 public final ArrayList<JumpTable> jumpTables = new ArrayList<>(); 131 132 public final String isa; 133 134 public final int wordWidth; 135 136 public final long startAddress; 137 138 public HexCodeFile(byte[] code, long startAddress, String isa, int wordWidth) { 139 this.code = code; 140 this.startAddress = startAddress; 141 this.isa = isa; 142 this.wordWidth = wordWidth; 143 } 144 145 /** 146 * Parses a string in the format produced by {@link #toString()} to produce a 147 * {@link HexCodeFile} object. 148 */ 149 public static HexCodeFile parse(String input, int sourceOffset, String source, String sourceName) { 150 return new Parser(input, sourceOffset, source, sourceName).hcf; 151 } 152 153 /** 154 * Formats this HexCodeFile as a string that can be parsed with 155 * {@link #parse(String, int, String, String)}. 156 */ 157 @Override 158 public String toString() { 159 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 160 writeTo(baos); 161 return baos.toString(); 162 } 163 164 public String toEmbeddedString() { 165 return EMBEDDED_HCF_OPEN + NEW_LINE + toString() + EMBEDDED_HCF_CLOSE; 166 } 167 168 public void writeTo(OutputStream out) { 169 PrintStream ps = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out); 170 ps.printf("Platform %s %d %s%n", isa, wordWidth, SECTION_DELIM); 171 ps.printf("HexCode %x %s %s%n", startAddress, HexCodeFile.hexCodeString(code), SECTION_DELIM); 172 173 for (JumpTable table : jumpTables) { 174 ps.printf("JumpTable %d %d %d %d %s%n", table.position, table.entrySize, table.low, table.high, SECTION_DELIM); 175 } 176 177 for (Map.Entry<Integer, List<String>> e : comments.entrySet()) { 178 int pos = e.getKey(); 179 for (String comment : e.getValue()) { 180 ps.printf("Comment %d %s %s%n", pos, comment, SECTION_DELIM); 181 } 182 } 183 184 for (Map.Entry<Integer, List<String>> e : operandComments.entrySet()) { 185 for (String c : e.getValue()) { 186 ps.printf("OperandComment %d %s %s%n", e.getKey(), c, SECTION_DELIM); 187 } 188 } 189 ps.flush(); 190 } 191 192 /** 193 * Formats a byte array as a string of hex digits. 194 */ 195 public static String hexCodeString(byte[] code) { 196 if (code == null) { 197 return ""; 198 } else { 199 StringBuilder sb = new StringBuilder(code.length * 2); 200 for (int b : code) { 201 String hex = Integer.toHexString(b & 0xff); 202 if (hex.length() == 1) { 203 sb.append('0'); 204 } 205 sb.append(hex); 206 } 207 return sb.toString(); 208 } 209 } 210 211 /** 212 * Adds a comment to the list of comments for a given position. 213 */ 214 public void addComment(int pos, String comment) { 215 List<String> list = comments.get(pos); 216 if (list == null) { 217 list = new ArrayList<>(); 218 comments.put(pos, list); 219 } 220 list.add(encodeString(comment)); 221 } 222 223 /** 224 * Adds an operand comment for a given position. 225 */ 226 public void addOperandComment(int pos, String comment) { 227 List<String> list = comments.get(pos); 228 if (list == null) { 229 list = new ArrayList<>(1); 230 comments.put(pos, list); 231 } 232 list.add(encodeString(comment)); 233 } 234 235 /** 236 * Adds any jump tables, lookup tables or code comments from a list of code annotations. 237 */ 238 public static void addAnnotations(HexCodeFile hcf, List<CodeAnnotation> annotations) { 239 if (annotations == null || annotations.isEmpty()) { 240 return; 241 } 242 for (CodeAnnotation a : annotations) { 243 if (a instanceof JumpTable) { 244 JumpTable table = (JumpTable) a; 245 hcf.jumpTables.add(table); 246 } else if (a instanceof CodeComment) { 247 CodeComment comment = (CodeComment) a; 248 hcf.addComment(comment.position, comment.value); 249 } 250 } 251 } 252 253 /** 254 * Modifies a string to mangle any substrings matching {@link #SECTION_DELIM} and 255 * {@link #COLUMN_END}. 256 */ 257 public static String encodeString(String input) { 258 int index; 259 String s = input; 260 while ((index = s.indexOf(SECTION_DELIM)) != -1) { 261 s = s.substring(0, index) + " < |@" + s.substring(index + SECTION_DELIM.length()); 262 } 263 while ((index = s.indexOf(COLUMN_END)) != -1) { 264 s = s.substring(0, index) + " < @" + s.substring(index + COLUMN_END.length()); 265 } 266 return s; 267 } 268 269 /** 270 * Helper class to parse a string in the format produced by {@link HexCodeFile#toString()} and 271 * produce a {@link HexCodeFile} object. 272 */ 273 static class Parser { 274 275 final String input; 276 final String inputSource; 277 String isa; 278 int wordWidth; 279 byte[] code; 280 long startAddress; 281 HexCodeFile hcf; 282 283 Parser(String input, int sourceOffset, String source, String sourceName) { 284 this.input = input; 285 this.inputSource = sourceName; 286 parseSections(sourceOffset, source); 287 } 288 289 void makeHCF() { 290 if (hcf == null) { 291 if (isa != null && wordWidth != 0 && code != null) { 292 hcf = new HexCodeFile(code, startAddress, isa, wordWidth); 293 } 294 } 295 } 296 297 void checkHCF(String section, int offset) { 298 check(hcf != null, offset, section + " section must be after Platform and HexCode section"); 299 } 300 301 void check(boolean condition, int offset, String message) { 302 if (!condition) { 303 error(offset, message); 304 } 305 } 306 307 Error error(int offset, String message) { 308 throw new Error(errorMessage(offset, message)); 309 } 310 311 void warning(int offset, String message) { 312 PrintStream err = System.err; 313 err.println("Warning: " + errorMessage(offset, message)); 314 } 315 316 String errorMessage(int offset, String message) { 317 assert offset < input.length(); 318 InputPos inputPos = filePos(offset); 319 int lineEnd = input.indexOf(HexCodeFile.NEW_LINE, offset); 320 int lineStart = offset - inputPos.col; 321 String line = lineEnd == -1 ? input.substring(lineStart) : input.substring(lineStart, lineEnd); 322 return String.format("%s:%d: %s%n%s%n%" + (inputPos.col + 1) + "s", inputSource, inputPos.line, message, line, "^"); 323 } 324 325 static class InputPos { 326 327 final int line; 328 final int col; 329 330 InputPos(int line, int col) { 331 this.line = line; 332 this.col = col; 333 } 334 } 335 336 InputPos filePos(int index) { 337 assert input != null; 338 int lineStart = input.lastIndexOf(HexCodeFile.NEW_LINE, index) + 1; 339 340 String l = input.substring(lineStart, lineStart + 10); 341 PrintStream out = System.out; 342 out.println("YYY" + input.substring(index, index + 10) + "..."); 343 out.println("XXX" + l + "..."); 344 345 int pos = input.indexOf(HexCodeFile.NEW_LINE, 0); 346 int line = 1; 347 while (pos > 0 && pos < index) { 348 line++; 349 pos = input.indexOf(HexCodeFile.NEW_LINE, pos + 1); 350 } 351 return new InputPos(line, index - lineStart); 352 } 353 354 void parseSections(int offset, String source) { 355 assert input.startsWith(source, offset); 356 int index = 0; 357 int endIndex = source.indexOf(SECTION_DELIM); 358 while (endIndex != -1) { 359 while (source.charAt(index) <= ' ') { 360 index++; 361 } 362 String section = source.substring(index, endIndex).trim(); 363 parseSection(offset + index, section); 364 index = endIndex + SECTION_DELIM.length(); 365 endIndex = source.indexOf(SECTION_DELIM, index); 366 } 367 } 368 369 int parseInt(int offset, String value) { 370 try { 371 return Integer.parseInt(value); 372 } catch (NumberFormatException e) { 373 throw error(offset, "Not a valid integer: " + value); 374 } 375 } 376 377 void parseSection(int offset, String section) { 378 if (section.isEmpty()) { 379 return; 380 } 381 assert input.startsWith(section, offset); 382 Matcher m = HexCodeFile.SECTION.matcher(section); 383 check(m.matches(), offset, "Section does not match pattern " + HexCodeFile.SECTION); 384 385 String header = m.group(1); 386 String body = m.group(2); 387 int headerOffset = offset + m.start(1); 388 int bodyOffset = offset + m.start(2); 389 390 if (header.equals("Platform")) { 391 check(isa == null, bodyOffset, "Duplicate Platform section found"); 392 m = HexCodeFile.PLATFORM.matcher(body); 393 check(m.matches(), bodyOffset, "Platform does not match pattern " + HexCodeFile.PLATFORM); 394 isa = m.group(1); 395 wordWidth = parseInt(bodyOffset + m.start(2), m.group(2)); 396 makeHCF(); 397 } else if (header.equals("HexCode")) { 398 check(code == null, bodyOffset, "Duplicate Code section found"); 399 m = HexCodeFile.HEX_CODE.matcher(body); 400 check(m.matches(), bodyOffset, "Code does not match pattern " + HexCodeFile.HEX_CODE); 401 String hexAddress = m.group(1); 402 startAddress = Long.valueOf(hexAddress, 16); 403 String hexCode = m.group(2); 404 if (hexCode == null) { 405 code = new byte[0]; 406 } else { 407 check((hexCode.length() % 2) == 0, bodyOffset, "Hex code length must be even"); 408 code = new byte[hexCode.length() / 2]; 409 for (int i = 0; i < code.length; i++) { 410 String hexByte = hexCode.substring(i * 2, (i + 1) * 2); 411 code[i] = (byte) Integer.parseInt(hexByte, 16); 412 } 413 } 414 makeHCF(); 415 } else if (header.equals("Comment")) { 416 checkHCF("Comment", headerOffset); 417 m = HexCodeFile.COMMENT.matcher(body); 418 check(m.matches(), bodyOffset, "Comment does not match pattern " + HexCodeFile.COMMENT); 419 int pos = parseInt(bodyOffset + m.start(1), m.group(1)); 420 String comment = m.group(2); 421 hcf.addComment(pos, comment); 422 } else if (header.equals("OperandComment")) { 423 checkHCF("OperandComment", headerOffset); 424 m = HexCodeFile.OPERAND_COMMENT.matcher(body); 425 check(m.matches(), bodyOffset, "OperandComment does not match pattern " + HexCodeFile.OPERAND_COMMENT); 426 int pos = parseInt(bodyOffset + m.start(1), m.group(1)); 427 String comment = m.group(2); 428 hcf.addOperandComment(pos, comment); 429 } else if (header.equals("JumpTable")) { 430 checkHCF("JumpTable", headerOffset); 431 m = HexCodeFile.JUMP_TABLE.matcher(body); 432 check(m.matches(), bodyOffset, "JumpTable does not match pattern " + HexCodeFile.JUMP_TABLE); 433 int pos = parseInt(bodyOffset + m.start(1), m.group(1)); 434 int entrySize = parseInt(bodyOffset + m.start(2), m.group(2)); 435 int low = parseInt(bodyOffset + m.start(3), m.group(3)); 436 int high = parseInt(bodyOffset + m.start(4), m.group(4)); 437 hcf.jumpTables.add(new JumpTable(pos, low, high, entrySize)); 438 } else { 439 error(offset, "Unknown section header: " + header); 440 } 441 } 442 } 443 }