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 */
23package org.graalvm.compiler.code;
24
25import java.io.ByteArrayOutputStream;
26import java.io.OutputStream;
27import java.io.PrintStream;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.Map;
31import java.util.TreeMap;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34
35import org.graalvm.compiler.code.CompilationResult.CodeAnnotation;
36import org.graalvm.compiler.code.CompilationResult.CodeComment;
37import org.graalvm.compiler.code.CompilationResult.JumpTable;
38
39import 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 */
94public 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}
444