/*
* Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.code;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.graalvm.compiler.code.CompilationResult.CodeAnnotation;
import org.graalvm.compiler.code.CompilationResult.CodeComment;
import org.graalvm.compiler.code.CompilationResult.JumpTable;
import jdk.vm.ci.code.CodeUtil;
A HexCodeFile is a textual format for representing a chunk of machine code along with extra
information that can be used to enhance a disassembly of the code.
A pseudo grammar for a HexCodeFile is given below.
HexCodeFile ::= Platform Delim HexCode Delim (OptionalSection Delim)*
OptionalSection ::= Comment | OperandComment | JumpTable | LookupTable
Platform ::= "Platform" ISA WordWidth
HexCode ::= "HexCode" StartAddress HexDigits
Comment ::= "Comment" Position String
OperandComment ::= "OperandComment" Position String
JumpTable ::= "JumpTable" Position EntrySize Low High
LookupTable ::= "LookupTable" Position NPairs KeySize OffsetSize
Position, EntrySize, Low, High, NPairs KeySize OffsetSize ::= int
Delim := "<||@"
There must be exactly one HexCode and Platform part in a HexCodeFile. The length of HexDigits
must be even as each pair of digits represents a single byte.
Below is an example of a valid Code input:
Platform AMD64 64 <||@
HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3 <||@
Comment 24 frame-ref-map: +0 {0}
at java.lang.String.toLowerCase(String.java:2496) [bci: 1]
|0
locals: |stack:0:a
stack: |stack:0:a
<||@
OperandComment 24 {java.util.Locale.getDefault()} <||@
Comment 36 frame-ref-map: +0 {0}
at java.lang.String.toLowerCase(String.java:2496) [bci: 4]
|0
locals: |stack:0:a
<||@
OperandComment 36 {java.lang.String.toLowerCase(Locale)} lt;||@
/**
* A HexCodeFile is a textual format for representing a chunk of machine code along with extra
* information that can be used to enhance a disassembly of the code.
*
* A pseudo grammar for a HexCodeFile is given below.
*
* <pre>
* HexCodeFile ::= Platform Delim HexCode Delim (OptionalSection Delim)*
*
* OptionalSection ::= Comment | OperandComment | JumpTable | LookupTable
*
* Platform ::= "Platform" ISA WordWidth
*
* HexCode ::= "HexCode" StartAddress HexDigits
*
* Comment ::= "Comment" Position String
*
* OperandComment ::= "OperandComment" Position String
*
* JumpTable ::= "JumpTable" Position EntrySize Low High
*
* LookupTable ::= "LookupTable" Position NPairs KeySize OffsetSize
*
* Position, EntrySize, Low, High, NPairs KeySize OffsetSize ::= int
*
* Delim := "<||@"
* </pre>
*
* There must be exactly one HexCode and Platform part in a HexCodeFile. The length of HexDigits
* must be even as each pair of digits represents a single byte.
* <p>
* Below is an example of a valid Code input:
*
* <pre>
*
* Platform AMD64 64 <||@
* HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3 <||@
* Comment 24 frame-ref-map: +0 {0}
* at java.lang.String.toLowerCase(String.java:2496) [bci: 1]
* |0
* locals: |stack:0:a
* stack: |stack:0:a
* <||@
* OperandComment 24 {java.util.Locale.getDefault()} <||@
* Comment 36 frame-ref-map: +0 {0}
* at java.lang.String.toLowerCase(String.java:2496) [bci: 4]
* |0
* locals: |stack:0:a
* <||@
* OperandComment 36 {java.lang.String.toLowerCase(Locale)} lt;||@
*
* </pre>
*/
public class HexCodeFile {
public static final String NEW_LINE = CodeUtil.NEW_LINE;
public static final String SECTION_DELIM = " <||@";
public static final String COLUMN_END = " <|@";
public static final Pattern SECTION = Pattern.compile("(\\S+)\\s+(.*)", Pattern.DOTALL);
public static final Pattern COMMENT = Pattern.compile("(\\d+)\\s+(.*)", Pattern.DOTALL);
public static final Pattern OPERAND_COMMENT = COMMENT;
public static final Pattern JUMP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(-{0,1}\\d+)\\s+(-{0,1}\\d+)\\s*");
public static final Pattern LOOKUP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*");
public static final Pattern HEX_CODE = Pattern.compile("(\\p{XDigit}+)(?:\\s+(\\p{XDigit}*))?");
public static final Pattern PLATFORM = Pattern.compile("(\\S+)\\s+(\\S+)", Pattern.DOTALL);
Delimiter placed before a HexCodeFile when embedded in a string/stream.
/**
* Delimiter placed before a HexCodeFile when embedded in a string/stream.
*/
public static final String EMBEDDED_HCF_OPEN = "<<<HexCodeFile";
Delimiter placed after a HexCodeFile when embedded in a string/stream.
/**
* Delimiter placed after a HexCodeFile when embedded in a string/stream.
*/
public static final String EMBEDDED_HCF_CLOSE = "HexCodeFile>>>";
Map from a machine code position to a list of comments for the position.
/**
* Map from a machine code position to a list of comments for the position.
*/
public final Map<Integer, List<String>> comments = new TreeMap<>();
Map from a machine code position to a comment for the operands of the instruction at the
position.
/**
* Map from a machine code position to a comment for the operands of the instruction at the
* position.
*/
public final Map<Integer, List<String>> operandComments = new TreeMap<>();
public final byte[] code;
public final ArrayList<JumpTable> jumpTables = new ArrayList<>();
public final String isa;
public final int wordWidth;
public final long startAddress;
public HexCodeFile(byte[] code, long startAddress, String isa, int wordWidth) {
this.code = code;
this.startAddress = startAddress;
this.isa = isa;
this.wordWidth = wordWidth;
}
Parses a string in the format produced by toString()
to produce a HexCodeFile
object. /**
* Parses a string in the format produced by {@link #toString()} to produce a
* {@link HexCodeFile} object.
*/
public static HexCodeFile parse(String input, int sourceOffset, String source, String sourceName) {
return new Parser(input, sourceOffset, source, sourceName).hcf;
}
Formats this HexCodeFile as a string that can be parsed with parse(String, int, String, String)
. /**
* Formats this HexCodeFile as a string that can be parsed with
* {@link #parse(String, int, String, String)}.
*/
@Override
public String toString() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeTo(baos);
return baos.toString();
}
public String toEmbeddedString() {
return EMBEDDED_HCF_OPEN + NEW_LINE + toString() + EMBEDDED_HCF_CLOSE;
}
public void writeTo(OutputStream out) {
PrintStream ps = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out);
ps.printf("Platform %s %d %s%n", isa, wordWidth, SECTION_DELIM);
ps.printf("HexCode %x %s %s%n", startAddress, HexCodeFile.hexCodeString(code), SECTION_DELIM);
for (JumpTable table : jumpTables) {
ps.printf("JumpTable %d %d %d %d %s%n", table.position, table.entrySize, table.low, table.high, SECTION_DELIM);
}
for (Map.Entry<Integer, List<String>> e : comments.entrySet()) {
int pos = e.getKey();
for (String comment : e.getValue()) {
ps.printf("Comment %d %s %s%n", pos, comment, SECTION_DELIM);
}
}
for (Map.Entry<Integer, List<String>> e : operandComments.entrySet()) {
for (String c : e.getValue()) {
ps.printf("OperandComment %d %s %s%n", e.getKey(), c, SECTION_DELIM);
}
}
ps.flush();
}
Formats a byte array as a string of hex digits.
/**
* Formats a byte array as a string of hex digits.
*/
public static String hexCodeString(byte[] code) {
if (code == null) {
return "";
} else {
StringBuilder sb = new StringBuilder(code.length * 2);
for (int b : code) {
String hex = Integer.toHexString(b & 0xff);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
Adds a comment to the list of comments for a given position.
/**
* Adds a comment to the list of comments for a given position.
*/
public void addComment(int pos, String comment) {
List<String> list = comments.get(pos);
if (list == null) {
list = new ArrayList<>();
comments.put(pos, list);
}
list.add(encodeString(comment));
}
Adds an operand comment for a given position.
/**
* Adds an operand comment for a given position.
*/
public void addOperandComment(int pos, String comment) {
List<String> list = comments.get(pos);
if (list == null) {
list = new ArrayList<>(1);
comments.put(pos, list);
}
list.add(encodeString(comment));
}
Adds any jump tables, lookup tables or code comments from a list of code annotations.
/**
* Adds any jump tables, lookup tables or code comments from a list of code annotations.
*/
public static void addAnnotations(HexCodeFile hcf, List<CodeAnnotation> annotations) {
if (annotations == null || annotations.isEmpty()) {
return;
}
for (CodeAnnotation a : annotations) {
if (a instanceof JumpTable) {
JumpTable table = (JumpTable) a;
hcf.jumpTables.add(table);
} else if (a instanceof CodeComment) {
CodeComment comment = (CodeComment) a;
hcf.addComment(comment.position, comment.value);
}
}
}
Modifies a string to mangle any substrings matching SECTION_DELIM
and COLUMN_END
. /**
* Modifies a string to mangle any substrings matching {@link #SECTION_DELIM} and
* {@link #COLUMN_END}.
*/
public static String encodeString(String input) {
int index;
String s = input;
while ((index = s.indexOf(SECTION_DELIM)) != -1) {
s = s.substring(0, index) + " < |@" + s.substring(index + SECTION_DELIM.length());
}
while ((index = s.indexOf(COLUMN_END)) != -1) {
s = s.substring(0, index) + " < @" + s.substring(index + COLUMN_END.length());
}
return s;
}
Helper class to parse a string in the format produced by HexCodeFile.toString()
and produce a HexCodeFile
object. /**
* Helper class to parse a string in the format produced by {@link HexCodeFile#toString()} and
* produce a {@link HexCodeFile} object.
*/
static class Parser {
final String input;
final String inputSource;
String isa;
int wordWidth;
byte[] code;
long startAddress;
HexCodeFile hcf;
Parser(String input, int sourceOffset, String source, String sourceName) {
this.input = input;
this.inputSource = sourceName;
parseSections(sourceOffset, source);
}
void makeHCF() {
if (hcf == null) {
if (isa != null && wordWidth != 0 && code != null) {
hcf = new HexCodeFile(code, startAddress, isa, wordWidth);
}
}
}
void checkHCF(String section, int offset) {
check(hcf != null, offset, section + " section must be after Platform and HexCode section");
}
void check(boolean condition, int offset, String message) {
if (!condition) {
error(offset, message);
}
}
Error error(int offset, String message) {
throw new Error(errorMessage(offset, message));
}
void warning(int offset, String message) {
PrintStream err = System.err;
err.println("Warning: " + errorMessage(offset, message));
}
String errorMessage(int offset, String message) {
assert offset < input.length();
InputPos inputPos = filePos(offset);
int lineEnd = input.indexOf(HexCodeFile.NEW_LINE, offset);
int lineStart = offset - inputPos.col;
String line = lineEnd == -1 ? input.substring(lineStart) : input.substring(lineStart, lineEnd);
return String.format("%s:%d: %s%n%s%n%" + (inputPos.col + 1) + "s", inputSource, inputPos.line, message, line, "^");
}
static class InputPos {
final int line;
final int col;
InputPos(int line, int col) {
this.line = line;
this.col = col;
}
}
InputPos filePos(int index) {
assert input != null;
int lineStart = input.lastIndexOf(HexCodeFile.NEW_LINE, index) + 1;
String l = input.substring(lineStart, lineStart + 10);
PrintStream out = System.out;
out.println("YYY" + input.substring(index, index + 10) + "...");
out.println("XXX" + l + "...");
int pos = input.indexOf(HexCodeFile.NEW_LINE, 0);
int line = 1;
while (pos > 0 && pos < index) {
line++;
pos = input.indexOf(HexCodeFile.NEW_LINE, pos + 1);
}
return new InputPos(line, index - lineStart);
}
void parseSections(int offset, String source) {
assert input.startsWith(source, offset);
int index = 0;
int endIndex = source.indexOf(SECTION_DELIM);
while (endIndex != -1) {
while (source.charAt(index) <= ' ') {
index++;
}
String section = source.substring(index, endIndex).trim();
parseSection(offset + index, section);
index = endIndex + SECTION_DELIM.length();
endIndex = source.indexOf(SECTION_DELIM, index);
}
}
int parseInt(int offset, String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw error(offset, "Not a valid integer: " + value);
}
}
void parseSection(int offset, String section) {
if (section.isEmpty()) {
return;
}
assert input.startsWith(section, offset);
Matcher m = HexCodeFile.SECTION.matcher(section);
check(m.matches(), offset, "Section does not match pattern " + HexCodeFile.SECTION);
String header = m.group(1);
String body = m.group(2);
int headerOffset = offset + m.start(1);
int bodyOffset = offset + m.start(2);
if (header.equals("Platform")) {
check(isa == null, bodyOffset, "Duplicate Platform section found");
m = HexCodeFile.PLATFORM.matcher(body);
check(m.matches(), bodyOffset, "Platform does not match pattern " + HexCodeFile.PLATFORM);
isa = m.group(1);
wordWidth = parseInt(bodyOffset + m.start(2), m.group(2));
makeHCF();
} else if (header.equals("HexCode")) {
check(code == null, bodyOffset, "Duplicate Code section found");
m = HexCodeFile.HEX_CODE.matcher(body);
check(m.matches(), bodyOffset, "Code does not match pattern " + HexCodeFile.HEX_CODE);
String hexAddress = m.group(1);
startAddress = Long.valueOf(hexAddress, 16);
String hexCode = m.group(2);
if (hexCode == null) {
code = new byte[0];
} else {
check((hexCode.length() % 2) == 0, bodyOffset, "Hex code length must be even");
code = new byte[hexCode.length() / 2];
for (int i = 0; i < code.length; i++) {
String hexByte = hexCode.substring(i * 2, (i + 1) * 2);
code[i] = (byte) Integer.parseInt(hexByte, 16);
}
}
makeHCF();
} else if (header.equals("Comment")) {
checkHCF("Comment", headerOffset);
m = HexCodeFile.COMMENT.matcher(body);
check(m.matches(), bodyOffset, "Comment does not match pattern " + HexCodeFile.COMMENT);
int pos = parseInt(bodyOffset + m.start(1), m.group(1));
String comment = m.group(2);
hcf.addComment(pos, comment);
} else if (header.equals("OperandComment")) {
checkHCF("OperandComment", headerOffset);
m = HexCodeFile.OPERAND_COMMENT.matcher(body);
check(m.matches(), bodyOffset, "OperandComment does not match pattern " + HexCodeFile.OPERAND_COMMENT);
int pos = parseInt(bodyOffset + m.start(1), m.group(1));
String comment = m.group(2);
hcf.addOperandComment(pos, comment);
} else if (header.equals("JumpTable")) {
checkHCF("JumpTable", headerOffset);
m = HexCodeFile.JUMP_TABLE.matcher(body);
check(m.matches(), bodyOffset, "JumpTable does not match pattern " + HexCodeFile.JUMP_TABLE);
int pos = parseInt(bodyOffset + m.start(1), m.group(1));
int entrySize = parseInt(bodyOffset + m.start(2), m.group(2));
int low = parseInt(bodyOffset + m.start(3), m.group(3));
int high = parseInt(bodyOffset + m.start(4), m.group(4));
hcf.jumpTables.add(new JumpTable(pos, low, high, entrySize));
} else {
error(offset, "Unknown section header: " + header);
}
}
}
}