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