1 /*
   2  * Copyright (c) 2009, 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.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 := "&lt;||@"
  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  &lt;||@
  79  *  HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3  &lt;||@
  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  *    &lt;||@
  86  *  OperandComment 24 {java.util.Locale.getDefault()}  &lt;||@
  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  *    &lt;||@
  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 }