001/*
002 * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.code;
024
025import java.io.*;
026import java.util.*;
027import java.util.regex.*;
028
029import jdk.internal.jvmci.code.*;
030import jdk.internal.jvmci.code.CompilationResult.*;
031
032/**
033 * A HexCodeFile is a textual format for representing a chunk of machine code along with extra
034 * information that can be used to enhance a disassembly of the code.
035 *
036 * A pseudo grammar for a HexCodeFile is given below.
037 *
038 * <pre>
039 *     HexCodeFile ::= Platform Delim HexCode Delim (OptionalSection Delim)*
040 *
041 *     OptionalSection ::= Comment | OperandComment | JumpTable | LookupTable
042 *
043 *     Platform ::= "Platform" ISA WordWidth
044 *
045 *     HexCode ::= "HexCode" StartAddress HexDigits
046 *
047 *     Comment ::= "Comment" Position String
048 *
049 *     OperandComment ::= "OperandComment" Position String
050 *
051 *     JumpTable ::= "JumpTable" Position EntrySize Low High
052 *
053 *     LookupTable ::= "LookupTable" Position NPairs KeySize OffsetSize
054 *
055 *     Position, EntrySize, Low, High, NPairs KeySize OffsetSize ::= int
056 *
057 *     Delim := "&lt;||@"
058 * </pre>
059 *
060 * There must be exactly one HexCode and Platform part in a HexCodeFile. The length of HexDigits
061 * must be even as each pair of digits represents a single byte.
062 * <p>
063 * Below is an example of a valid Code input:
064 *
065 * <pre>
066 *
067 *  Platform AMD64 64  &lt;||@
068 *  HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3  &lt;||@
069 *  Comment 24 frame-ref-map: +0 {0}
070 *  at java.lang.String.toLowerCase(String.java:2496) [bci: 1]
071 *              |0
072 *     locals:  |stack:0:a
073 *     stack:   |stack:0:a
074 *    &lt;||@
075 *  OperandComment 24 {java.util.Locale.getDefault()}  &lt;||@
076 *  Comment 36 frame-ref-map: +0 {0}
077 *  at java.lang.String.toLowerCase(String.java:2496) [bci: 4]
078 *              |0
079 *     locals:  |stack:0:a
080 *    &lt;||@
081 *  OperandComment 36 {java.lang.String.toLowerCase(Locale)}  lt;||@
082 *
083 * </pre>
084 */
085public class HexCodeFile {
086
087    public static final String NEW_LINE = CodeUtil.NEW_LINE;
088    public static final String SECTION_DELIM = " <||@";
089    public static final String COLUMN_END = " <|@";
090    public static final Pattern SECTION = Pattern.compile("(\\S+)\\s+(.*)", Pattern.DOTALL);
091    public static final Pattern COMMENT = Pattern.compile("(\\d+)\\s+(.*)", Pattern.DOTALL);
092    public static final Pattern OPERAND_COMMENT = COMMENT;
093    public static final Pattern JUMP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(-{0,1}\\d+)\\s+(-{0,1}\\d+)\\s*");
094    public static final Pattern LOOKUP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*");
095    public static final Pattern HEX_CODE = Pattern.compile("(\\p{XDigit}+)(?:\\s+(\\p{XDigit}*))?");
096    public static final Pattern PLATFORM = Pattern.compile("(\\S+)\\s+(\\S+)", Pattern.DOTALL);
097
098    /**
099     * Delimiter placed before a HexCodeFile when embedded in a string/stream.
100     */
101    public static final String EMBEDDED_HCF_OPEN = "<<<HexCodeFile";
102
103    /**
104     * Delimiter placed after a HexCodeFile when embedded in a string/stream.
105     */
106    public static final String EMBEDDED_HCF_CLOSE = "HexCodeFile>>>";
107
108    /**
109     * Map from a machine code position to a list of comments for the position.
110     */
111    public final Map<Integer, List<String>> comments = new TreeMap<>();
112
113    /**
114     * Map from a machine code position to a comment for the operands of the instruction at the
115     * position.
116     */
117    public final Map<Integer, String> operandComments = new TreeMap<>();
118
119    public final byte[] code;
120
121    public final ArrayList<JumpTable> jumpTables = new ArrayList<>();
122
123    public final String isa;
124
125    public final int wordWidth;
126
127    public final long startAddress;
128
129    public HexCodeFile(byte[] code, long startAddress, String isa, int wordWidth) {
130        this.code = code;
131        this.startAddress = startAddress;
132        this.isa = isa;
133        this.wordWidth = wordWidth;
134    }
135
136    /**
137     * Parses a string in the format produced by {@link #toString()} to produce a
138     * {@link HexCodeFile} object.
139     */
140    public static HexCodeFile parse(String input, int sourceOffset, String source, String sourceName) {
141        return new Parser(input, sourceOffset, source, sourceName).hcf;
142    }
143
144    /**
145     * Formats this HexCodeFile as a string that can be parsed with
146     * {@link #parse(String, int, String, String)}.
147     */
148    @Override
149    public String toString() {
150        ByteArrayOutputStream baos = new ByteArrayOutputStream();
151        writeTo(baos);
152        return baos.toString();
153    }
154
155    public String toEmbeddedString() {
156        return EMBEDDED_HCF_OPEN + NEW_LINE + toString() + EMBEDDED_HCF_CLOSE;
157    }
158
159    public void writeTo(OutputStream out) {
160        PrintStream ps = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out);
161        ps.printf("Platform %s %d %s%n", isa, wordWidth, SECTION_DELIM);
162        ps.printf("HexCode %x %s %s%n", startAddress, HexCodeFile.hexCodeString(code), SECTION_DELIM);
163
164        for (JumpTable table : jumpTables) {
165            ps.printf("JumpTable %d %d %d %d %s%n", table.position, table.entrySize, table.low, table.high, SECTION_DELIM);
166        }
167
168        for (Map.Entry<Integer, List<String>> e : comments.entrySet()) {
169            int pos = e.getKey();
170            for (String comment : e.getValue()) {
171                ps.printf("Comment %d %s %s%n", pos, comment, SECTION_DELIM);
172            }
173        }
174
175        for (Map.Entry<Integer, String> e : operandComments.entrySet()) {
176            ps.printf("OperandComment %d %s %s%n", e.getKey(), e.getValue(), SECTION_DELIM);
177        }
178        ps.flush();
179    }
180
181    /**
182     * Formats a byte array as a string of hex digits.
183     */
184    public static String hexCodeString(byte[] code) {
185        if (code == null) {
186            return "";
187        } else {
188            StringBuilder sb = new StringBuilder(code.length * 2);
189            for (int b : code) {
190                String hex = Integer.toHexString(b & 0xff);
191                if (hex.length() == 1) {
192                    sb.append('0');
193                }
194                sb.append(hex);
195            }
196            return sb.toString();
197        }
198    }
199
200    /**
201     * Adds a comment to the list of comments for a given position.
202     */
203    public void addComment(int pos, String comment) {
204        List<String> list = comments.get(pos);
205        if (list == null) {
206            list = new ArrayList<>();
207            comments.put(pos, list);
208        }
209        list.add(encodeString(comment));
210    }
211
212    /**
213     * Sets an operand comment for a given position.
214     *
215     * @return the previous operand comment for {@code pos}
216     */
217    public String addOperandComment(int pos, String comment) {
218        return operandComments.put(pos, encodeString(comment));
219    }
220
221    /**
222     * Adds any jump tables, lookup tables or code comments from a list of code annotations.
223     */
224    public static void addAnnotations(HexCodeFile hcf, List<CodeAnnotation> annotations) {
225        if (annotations == null || annotations.isEmpty()) {
226            return;
227        }
228        for (CodeAnnotation a : annotations) {
229            if (a instanceof JumpTable) {
230                JumpTable table = (JumpTable) a;
231                hcf.jumpTables.add(table);
232            } else if (a instanceof CodeComment) {
233                CodeComment comment = (CodeComment) a;
234                hcf.addComment(comment.position, comment.value);
235            }
236        }
237    }
238
239    /**
240     * Modifies a string to mangle any substrings matching {@link #SECTION_DELIM} and
241     * {@link #COLUMN_END}.
242     */
243    public static String encodeString(String input) {
244        int index;
245        String s = input;
246        while ((index = s.indexOf(SECTION_DELIM)) != -1) {
247            s = s.substring(0, index) + " < |@" + s.substring(index + SECTION_DELIM.length());
248        }
249        while ((index = s.indexOf(COLUMN_END)) != -1) {
250            s = s.substring(0, index) + " < @" + s.substring(index + COLUMN_END.length());
251        }
252        return s;
253    }
254
255    /**
256     * Helper class to parse a string in the format produced by {@link HexCodeFile#toString()} and
257     * produce a {@link HexCodeFile} object.
258     */
259    static class Parser {
260
261        final String input;
262        final String inputSource;
263        String isa;
264        int wordWidth;
265        byte[] code;
266        long startAddress;
267        HexCodeFile hcf;
268
269        Parser(String input, int sourceOffset, String source, String sourceName) {
270            this.input = input;
271            this.inputSource = sourceName;
272            parseSections(sourceOffset, source);
273        }
274
275        void makeHCF() {
276            if (hcf == null) {
277                if (isa != null && wordWidth != 0 && code != null) {
278                    hcf = new HexCodeFile(code, startAddress, isa, wordWidth);
279                }
280            }
281        }
282
283        void checkHCF(String section, int offset) {
284            check(hcf != null, offset, section + " section must be after Platform and HexCode section");
285        }
286
287        void check(boolean condition, int offset, String message) {
288            if (!condition) {
289                error(offset, message);
290            }
291        }
292
293        Error error(int offset, String message) {
294            throw new Error(errorMessage(offset, message));
295        }
296
297        void warning(int offset, String message) {
298            PrintStream err = System.err;
299            err.println("Warning: " + errorMessage(offset, message));
300        }
301
302        String errorMessage(int offset, String message) {
303            assert offset < input.length();
304            InputPos inputPos = filePos(offset);
305            int lineEnd = input.indexOf(HexCodeFile.NEW_LINE, offset);
306            int lineStart = offset - inputPos.col;
307            String line = lineEnd == -1 ? input.substring(lineStart) : input.substring(lineStart, lineEnd);
308            return String.format("%s:%d: %s%n%s%n%" + (inputPos.col + 1) + "s", inputSource, inputPos.line, message, line, "^");
309        }
310
311        static class InputPos {
312
313            final int line;
314            final int col;
315
316            public InputPos(int line, int col) {
317                this.line = line;
318                this.col = col;
319            }
320        }
321
322        InputPos filePos(int index) {
323            assert input != null;
324            int lineStart = input.lastIndexOf(HexCodeFile.NEW_LINE, index) + 1;
325
326            String l = input.substring(lineStart, lineStart + 10);
327            PrintStream out = System.out;
328            out.println("YYY" + input.substring(index, index + 10) + "...");
329            out.println("XXX" + l + "...");
330
331            int pos = input.indexOf(HexCodeFile.NEW_LINE, 0);
332            int line = 1;
333            while (pos > 0 && pos < index) {
334                line++;
335                pos = input.indexOf(HexCodeFile.NEW_LINE, pos + 1);
336            }
337            return new InputPos(line, index - lineStart);
338        }
339
340        void parseSections(int offset, String source) {
341            assert input.startsWith(source, offset);
342            int index = 0;
343            int endIndex = source.indexOf(SECTION_DELIM);
344            while (endIndex != -1) {
345                while (source.charAt(index) <= ' ') {
346                    index++;
347                }
348                String section = source.substring(index, endIndex).trim();
349                parseSection(offset + index, section);
350                index = endIndex + SECTION_DELIM.length();
351                endIndex = source.indexOf(SECTION_DELIM, index);
352            }
353        }
354
355        int parseInt(int offset, String value) {
356            try {
357                return Integer.parseInt(value);
358            } catch (NumberFormatException e) {
359                throw error(offset, "Not a valid integer: " + value);
360            }
361        }
362
363        void parseSection(int offset, String section) {
364            if (section.isEmpty()) {
365                return;
366            }
367            assert input.startsWith(section, offset);
368            Matcher m = HexCodeFile.SECTION.matcher(section);
369            check(m.matches(), offset, "Section does not match pattern " + HexCodeFile.SECTION);
370
371            String header = m.group(1);
372            String body = m.group(2);
373            int headerOffset = offset + m.start(1);
374            int bodyOffset = offset + m.start(2);
375
376            if (header.equals("Platform")) {
377                check(isa == null, bodyOffset, "Duplicate Platform section found");
378                m = HexCodeFile.PLATFORM.matcher(body);
379                check(m.matches(), bodyOffset, "Platform does not match pattern " + HexCodeFile.PLATFORM);
380                isa = m.group(1);
381                wordWidth = parseInt(bodyOffset + m.start(2), m.group(2));
382                makeHCF();
383            } else if (header.equals("HexCode")) {
384                check(code == null, bodyOffset, "Duplicate Code section found");
385                m = HexCodeFile.HEX_CODE.matcher(body);
386                check(m.matches(), bodyOffset, "Code does not match pattern " + HexCodeFile.HEX_CODE);
387                String hexAddress = m.group(1);
388                startAddress = Long.valueOf(hexAddress, 16);
389                String hexCode = m.group(2);
390                if (hexCode == null) {
391                    code = new byte[0];
392                } else {
393                    check((hexCode.length() % 2) == 0, bodyOffset, "Hex code length must be even");
394                    code = new byte[hexCode.length() / 2];
395                    for (int i = 0; i < code.length; i++) {
396                        String hexByte = hexCode.substring(i * 2, (i + 1) * 2);
397                        code[i] = (byte) Integer.parseInt(hexByte, 16);
398                    }
399                }
400                makeHCF();
401            } else if (header.equals("Comment")) {
402                checkHCF("Comment", headerOffset);
403                m = HexCodeFile.COMMENT.matcher(body);
404                check(m.matches(), bodyOffset, "Comment does not match pattern " + HexCodeFile.COMMENT);
405                int pos = parseInt(bodyOffset + m.start(1), m.group(1));
406                String comment = m.group(2);
407                hcf.addComment(pos, comment);
408            } else if (header.equals("OperandComment")) {
409                checkHCF("OperandComment", headerOffset);
410                m = HexCodeFile.OPERAND_COMMENT.matcher(body);
411                check(m.matches(), bodyOffset, "OperandComment does not match pattern " + HexCodeFile.OPERAND_COMMENT);
412                int pos = parseInt(bodyOffset + m.start(1), m.group(1));
413                String comment = m.group(2);
414                hcf.addOperandComment(pos, comment);
415            } else if (header.equals("JumpTable")) {
416                checkHCF("JumpTable", headerOffset);
417                m = HexCodeFile.JUMP_TABLE.matcher(body);
418                check(m.matches(), bodyOffset, "JumpTable does not match pattern " + HexCodeFile.JUMP_TABLE);
419                int pos = parseInt(bodyOffset + m.start(1), m.group(1));
420                int entrySize = parseInt(bodyOffset + m.start(2), m.group(2));
421                int low = parseInt(bodyOffset + m.start(3), m.group(3));
422                int high = parseInt(bodyOffset + m.start(4), m.group(4));
423                hcf.jumpTables.add(new JumpTable(pos, low, high, entrySize));
424            } else {
425                error(offset, "Unknown section header: " + header);
426            }
427        }
428    }
429}