/*
* Copyright (c) 2013-2017 Chris Newland.
* Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
* Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
*/
package org.adoptopenjdk.jitwatch.ui.triview.assembly;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SPACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_AT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_CLOSE_PARENTHESES;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOLLAR;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_OPEN_PARENTHESES;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SPACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_HEX_PREFIX;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_HEX_POSTFIX;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import org.adoptopenjdk.jitwatch.model.assembly.AssemblyBlock;
import org.adoptopenjdk.jitwatch.model.assembly.AssemblyInstruction;
import org.adoptopenjdk.jitwatch.model.assembly.AssemblyMethod;
import org.adoptopenjdk.jitwatch.model.assembly.AssemblyReference;
import org.adoptopenjdk.jitwatch.model.assembly.AssemblyUtil;
import org.adoptopenjdk.jitwatch.model.assembly.IAssemblyParser;
import org.adoptopenjdk.jitwatch.model.bytecode.BytecodeInstruction;
import org.adoptopenjdk.jitwatch.ui.main.IStageAccessProxy;
import org.adoptopenjdk.jitwatch.ui.triview.ILineListener;
import org.adoptopenjdk.jitwatch.ui.triview.ILineListener.LineType;
import org.adoptopenjdk.jitwatch.ui.triview.Viewer;
import org.adoptopenjdk.jitwatch.util.StringUtil;
// https://github.com/yasm/yasm/wiki/GasSyntax
// http://x86asm.net/articles/what-i-dislike-about-gas/
// http://vaskoz.wordpress.com/2013/07/14/hotspot-disassembler/
// https://sourceware.org/binutils/docs/as/i386_002dRegs.html#i386_002dRegs
public class ViewerAssembly extends Viewer
{
private DecimalFormat formatThousandsUnderscore;
private IAssemblyParser parser; //TODO choose parser
public ViewerAssembly(IStageAccessProxy stageAccessProxy, ILineListener lineListener, LineType lineType)
{
super(stageAccessProxy, lineListener, lineType, true);
formatThousandsUnderscore = (DecimalFormat) NumberFormat.getInstance();
DecimalFormatSymbols symbols = formatThousandsUnderscore.getDecimalFormatSymbols();
symbols.setGroupingSeparator('_');
formatThousandsUnderscore.setDecimalFormatSymbols(symbols);
}
public void setAssemblyMethod(AssemblyMethod asmMethod, boolean showLocalLabels)
{
parser = AssemblyUtil.getParserForArchitecture(asmMethod.getArchitecture());
lastScrollIndex = -1;
List<Label> labels = new ArrayList<>();
int annoWidth = asmMethod.getMaxAnnotationWidth();
String annoPad = StringUtil.repeat(C_SPACE, annoWidth);
String header = asmMethod.getHeader();
if (header != null)
{
String[] headerLines = header.split(S_NEWLINE);
for (String headerLine : headerLines)
{
labels.add(createLabel(annoPad + headerLine));
}
}
for (AssemblyBlock block : asmMethod.getBlocks())
{
String title = block.getTitle();
if (title != null)
{
labels.add(createLabel(annoPad + title));
}
for (final AssemblyInstruction instr : block.getInstructions())
{
List<String> commentLines = instr.getCommentLines();
if (commentLines.size() == 0)
{
Label lblLine = createLabel(instr, annoWidth, 0, showLocalLabels);
lblLine.setTooltip(new Tooltip(getToolTip(instr)));
labels.add(lblLine);
}
else
{
for (int i = 0; i < commentLines.size(); i++)
{
Label lblLine = createLabel(instr, annoWidth, i, showLocalLabels);
lblLine.setTooltip(new Tooltip(getToolTip(instr)));
labels.add(lblLine);
}
}
}
}
setContent(labels);
}
private String getToolTip(AssemblyInstruction instruction)
{
StringBuilder builder = new StringBuilder();
String mnemonic = instruction.getMnemonic();
String ref = AssemblyReference.lookupMnemonic(mnemonic);
if (ref == null)
{
ref = "Unknown instruction. Assembly reference loaded?";
}
builder.append(ref).append(S_NEWLINE);
List<String> operands = instruction.getOperands();
// AT&T = source, dest
// Intel = dest, source
int pos = 1;
for (String operand : operands)
{
builder.append("operand ").append(pos).append(": ");
decodeOperand(mnemonic, operand, builder);
builder.append(S_NEWLINE);
pos++;
}
return builder.toString();
}
private void decodeOperand(String mnemonic, String operand, StringBuilder builder)
{
if (parser.isRegister(mnemonic, operand))
{
builder.append(decodeRegister(operand));
}
else if (parser.isConstant(mnemonic, operand))
{
builder.append(getConstantLabel(operand));
}
else if (parser.isAddress(mnemonic, operand))
{
builder.append("Address ");
builder.append(operand);
}
}
private String getConstantLabel(String operand)
{
StringBuilder builder = new StringBuilder();
builder.append("Constant ");
builder.append(operand);
try
{
if (operand.startsWith(S_DOLLAR))
{
operand = operand.substring(1);
}
if (operand.endsWith(S_HEX_POSTFIX))
{
operand = S_HEX_PREFIX + operand.substring(0, operand.length()-1);
}
long decimal = Long.decode(operand);
builder.append(S_SPACE).append(S_OPEN_PARENTHESES).append("Decimal: ").append(formatThousandsUnderscore.format(decimal))
.append(S_CLOSE_PARENTHESES);
}
catch (NumberFormatException nfe)
{
// e.g. $0xffffffffffffffff - ignore it
}
return builder.toString();
}
private String decodeRegister(String input)
{
StringBuilder builder = new StringBuilder();
// https://sourceware.org/binutils/docs/as/i386_002dRegs.html#i386_002dRegs
// http://www.x86-64.org/documentation/assembly.html
String regName = parser.extractRegisterName(input);
if (regName.startsWith("e"))
{
builder.append("32-bit register ").append(regName);
}
else if (regName.startsWith("r"))
{
builder.append("64-bit register ").append(regName);
}
else if (regName.startsWith("db"))
{
builder.append("debug register ").append(regName);
}
else if (regName.startsWith("cr"))
{
builder.append("processor control register ").append(regName);
}
else if (regName.startsWith("tr"))
{
builder.append("test register ").append(regName);
}
else if (regName.startsWith("st"))
{
builder.append("floating point register stack ").append(regName);
}
else if (regName.startsWith("mm"))
{
builder.append("MMX register ").append(regName);
}
else if (regName.startsWith("xmm"))
{
builder.append("SSE register ").append(regName);
}
else if (regName.endsWith("s"))
{
builder.append("section register ").append(regName);
}
else
{
builder.append("Register ").append(regName);
}
if (regName.endsWith("ax"))
{
builder.append(" (accumulator)");
}
else if (regName.endsWith("bp"))
{
builder.append(" (frame pointer)");
}
else if (regName.endsWith("sp"))
{
builder.append(" (stack pointer)");
}
if (input.startsWith("*"))
{
builder.append(" (indirect)");
}
return builder.toString();
}
private Label createLabel(String text)
{
Label lbl = new Label(text);
lbl.setStyle(STYLE_UNHIGHLIGHTED);
return lbl;
}
private AssemblyLabel createLabel(AssemblyInstruction instruction, int annoWidth, int line, boolean showLocalLabels)
{
AssemblyLabel lbl = new AssemblyLabel(instruction, annoWidth, line, showLocalLabels);
lbl.setStyle(lbl.getUnhighlightedStyle());
return lbl;
}
public String getClassNameFromLabel(Label label)
{
String result = null;
if (label != null)
{
String line = label.getText();
result = StringUtil.getSubstringBetween(line, "; - ", "::");
}
return result;
}
public String getSourceLineFromLabel(Label label)
{
String result = null;
if (label != null)
{
String line = label.getText();
result = StringUtil.getSubstringBetween(line, "(line ", S_CLOSE_PARENTHESES);
}
return result;
}
public String getBytecodeOffsetFromLabel(Label label)
{
String result = null;
if (label != null)
{
String line = label.getText();
result = StringUtil.getSubstringBetween(line, S_AT, S_SPACE);
}
return result;
}
public int getIndexForSourceLine(String memberClassName, int sourceIndex)
{
int result = -1;
int pos = 0;
for (Node node : vBoxRows.getChildren())
{
Label label = (Label) node;
String className = getClassNameFromLabel(label);
if (className != null && className.equals(memberClassName))
{
String labelSourceLine = getSourceLineFromLabel(label);
if (labelSourceLine != null && labelSourceLine.equals(Integer.toString(sourceIndex)))
{
result = pos;
break;
}
}
pos++;
}
return result;
}
public int getIndexForBytecodeOffset(String memberClassName, BytecodeInstruction bcInstruction)
{
int result = -1;
int pos = 0;
boolean isInvoke = bcInstruction.getOpcode().isInvoke();
int bytecodeOffset = bcInstruction.getOffset();
boolean lastAssemblyIsCall = false;
for (Node node : vBoxRows.getChildren())
{
Label label = (Label) node;
if (label instanceof AssemblyLabel)
{
AssemblyInstruction lastAssemblyInstruction = ((AssemblyLabel) label).getInstruction();
String mnemonic = lastAssemblyInstruction.getMnemonic();
if (mnemonic != null && mnemonic.toLowerCase().startsWith("call"))
{
lastAssemblyIsCall = true;
}
else
{
lastAssemblyIsCall = false;
}
}
String className = getClassNameFromLabel(label);
if (className != null && className.equals(memberClassName))
{
String labelOffsetLine = getBytecodeOffsetFromLabel(label);
if (labelOffsetLine != null && labelOffsetLine.equals(Integer.toString(bytecodeOffset)))
{
if (isInvoke && lastAssemblyIsCall)
{
result = pos;
break;
}
else if (result == -1)
{
result = pos;
}
}
}
pos++;
}
return result;
}
}