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 := "&lt;||@"
  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  &lt;||@
  77  *  HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3  &lt;||@
  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  *    &lt;||@
  84  *  OperandComment 24 {java.util.Locale.getDefault()}  &lt;||@
  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  *    &lt;||@
  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 }