/*
JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
Copyright (C) 2012-2013 Ian Preston
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
This program 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 for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Details (including contact information) can be found at:
jpc.sourceforge.net
or the developer website
sourceforge.net/projects/jpc/
End of licence header
*/
package tools;
import java.util.*;
import org.jpc.emulator.execution.decoder.*;
public class DecoderGenerator
{
public static String args = "blockStart, eip, prefices, input";
public static String argsDef = "int blockStart, int eip, int prefices, PeekableInputStream input";
public static byte[] EMPTY = new byte[28];
public static class OpcodeHolder
{
Map<Instruction, byte[]> myops = new HashMap();
List<String> names = new ArrayList();
Set<String> namesSet = new HashSet();
private int modeType;
private int copyOf = -1;
public OpcodeHolder(int modeType)
{
this.modeType = modeType;
}
public void addOpcode(Instruction in, byte[] raw)
{
String name = Disassembler.getExecutableName(modeType, in);
//try {
// if (names.contains(name) && !name.contains("Unimplemented")&& !name.contains("Illegal"))
// return;
//} catch (Exception s) {return;}
names.add(name);
namesSet.add(name);
myops.put(in, raw);
}
public boolean equals(Object o)
{
if (!(o instanceof OpcodeHolder))
return false;
OpcodeHolder other = (OpcodeHolder)o;
if (myops.size() != other.myops.size())
return false;
if (!namesSet.equals(other.namesSet))
return false;
if (!names.equals(other.names))
return false;
return true;
}
public void setDuplicateOf(int index)
{
copyOf = index;
}
public Map<Instruction, byte[]> getReps()
{
Map<Instruction, byte[]> reps = new HashMap();
for (Instruction in: myops.keySet())
if (myops.get(in)[0] == (byte)0xF3)
reps.put(in, myops.get(in));
return reps;
}
public Map<Instruction, byte[]> getRepnes()
{
Map<Instruction, byte[]> reps = new HashMap();
for (Instruction in: myops.keySet())
if (myops.get(in)[0] == (byte)0xF2)
reps.put(in, myops.get(in));
return reps;
}
public Map<Instruction, byte[]> getNonreps()
{
Map<Instruction, byte[]> reps = new HashMap();
for (Instruction in: myops.keySet())
if ((myops.get(in)[0] != (byte)0xF2) && (myops.get(in)[0] != (byte)0xF3))
reps.put(in, myops.get(in));
return reps;
}
public boolean hasReps()
{
for (String opname: namesSet)
if (opname.contains("rep"))
return true;
return false;
}
public boolean hasUnimplemented()
{
for (String name: names)
if (name.contains("Unimplemented"))
return true;
return false;
}
public boolean allUnimplemented()
{
for (String name: names)
if (!name.contains("Unimplemented"))
return false;
return true;
}
public boolean isMem()
{
if (namesSet.size() > 2)
return false;
String name = null;
for (String s: namesSet)
{
if (name == null)
name = s;
else if ((name + "_mem").equals(s))
return true;
else if ((s+"_mem").equals(name))
return true;
}
return false;
}
public String toString()
{
if (namesSet.size() == 0)
return "null;";
if (copyOf != -1)
{
return String.format("ops[0x%x];\n", copyOf);
}
StringBuilder b = new StringBuilder();
if (namesSet.size() == 1)
{
b.append(new SingleOpcode(names.get(0)));
}
else if (isMem())
{
String name = null;
for (String n: namesSet)
{
if (name == null)
name = n;
else if (name.length() > n.length())
name = n;
}
b.append(new MemoryChooser(name));
}
else
{
if (allUnimplemented())
{
b.append(new SingleOpcode(names.get(0)));
}
else
{
b.append(new RepChooser(getReps(), getRepnes(), getNonreps(), modeType));
}
}
return b.toString().trim();
}
}
public static class DecoderTemplate
{
public void writeStart(StringBuilder b)
{
b.append("new OpcodeDecoder() {\n public Executable decodeOpcode("+argsDef+") {\n");
}
public void writeBody(StringBuilder b)
{
b.append("throw new IllegalStateException(\"Unimplemented Opcode\");");
}
public void writeEnd(StringBuilder b)
{
b.append(" }\n};\n");
}
public String toString()
{
StringBuilder b = new StringBuilder();
writeStart(b);
writeBody(b);
writeEnd(b);
return b.toString();
}
}
public static class SingleOpcode extends DecoderTemplate
{
String classname;
public SingleOpcode(String name)
{
this.classname = name;
}
public void writeBody(StringBuilder b)
{
b.append(" return new "+classname + "("+args+");\n");
}
}
public static class RepChooser extends DecoderTemplate
{
Map<Instruction, byte[]> reps;
Map<Instruction, byte[]> repnes;
Map<Instruction, byte[]> normals;
int mode;
public RepChooser(Map<Instruction, byte[]> reps, Map<Instruction, byte[]> repnes, Map<Instruction, byte[]> normals, int mode)
{
this.reps = reps;
this.repnes = repnes;
this.normals = normals;
this.mode = mode;
}
public void writeBody(StringBuilder b)
{
Set<String> repNames = new HashSet<String>();
for (Instruction in: reps.keySet())
repNames.add(Disassembler.getExecutableName(mode, in));
Set<String> repneNames = new HashSet<String>();
for (Instruction in: repnes.keySet())
repneNames.add(Disassembler.getExecutableName(mode, in));
Set<String> normalNames = new HashSet<String>();
for (Instruction in: normals.keySet())
normalNames.add(Disassembler.getExecutableName(mode, in));
// only add rep clauses if rep name sets are different to normal name set
if (!normalNames.containsAll(repneNames))
if (repnes.size() > 0)
{
b.append(" if (Prefices.isRepne(prefices))\n {\n");
genericChooser(repnes, mode, b);
b.append(" }\n");
}
if (!normalNames.containsAll(repNames))
if (reps.size() > 0)
{
b.append(" if (Prefices.isRep(prefices))\n {\n");
genericChooser(reps, mode, b);
b.append(" }\n");
}
genericChooser(normals, mode, b);
}
}
public static void genericChooser(Map<Instruction, byte[]> ops, int mode, StringBuilder b)
{
if (ops.size() == 0)
return;
if (ops.size() == 1)
{
for (Instruction in: ops.keySet())
{
String name = Disassembler.getExecutableName(mode, in);
b.append(" return new "+name+"("+args+");\n");
}
return;
}
int differentIndex = 0;
byte[][] bs = new byte[ops.size()][];
int index = 0;
for (byte[] bytes: ops.values())
bs[index++] = bytes;
boolean same = true;
while (same)
{
byte elem = bs[0][differentIndex];
for (int i=1; i < bs.length; i++)
if (bs[i][differentIndex] != elem)
{
same = false;
break;
}
if (same)
differentIndex++;
}
// if all names are the same, collapse to 1
String prevname = null;
boolean allSameName = true;
for (Instruction in: ops.keySet())
{
String name = Disassembler.getExecutableName(mode, in);
if (prevname == null)
prevname = name;
else if (prevname.equals(name))
continue;
else
{
allSameName = false;
break;
}
}
if (allSameName)
{
b.append(" return new "+prevname+"("+args+");\n");
}
else if (isSimpleModrmSplit(ops, mode, differentIndex, b))
{
}
else if (almostIsSimpleModrmSplit(ops, mode, differentIndex, b))
{
}
else
{
String[] cases = new String[ops.size()];
int i = 0;
for (Instruction in: ops.keySet())
{
String name = Disassembler.getExecutableName(mode, in);
cases[i++] = getConstructorLine(name, ops.get(in)[differentIndex]);
}
b.append(" switch (input.peek()) {\n");
Arrays.sort(cases);
for (String line: cases)
b.append(line);
b.append(" }\n return null;\n");
}
}
public static String getConstructorLine(String name, int modrm)
{
modrm &= 0xff;
String[] argTypes;
if (name.contains("_"))
argTypes = name.substring(name.indexOf("_")+1).split("_");
else
argTypes = new String[0];
boolean consumesModrm = false;
for (String arg: argTypes)
if (!Operand.segs.containsKey(arg) && !Operand.reg8.containsKey(arg) && !Operand.reg16.containsKey(arg) && !Operand.reg16only.containsKey(arg))
consumesModrm = true;
if (!consumesModrm && !name.contains("Unimplemented") && !name.contains("Illegal")) // has zero args, but uses modrm as opcode extension
return String.format(" case 0x%02x", modrm)+": input.read8(); return new "+name+"("+args+");\n";
else
return String.format(" case 0x%02x", modrm)+": return new "+name+"("+args+");\n";
}
public static boolean isSimpleModrmSplit(Map<Instruction, byte[]> ops, int mode, int differentIndex, StringBuilder b)
{
String[] names = new String[256];
for (Instruction in: ops.keySet())
names[ops.get(in)[differentIndex] & 0xff] = Disassembler.getExecutableName(mode, in);
for (int i=0; i < 8; i++)
for (int k=0; k < 0xC0; k += 0x40)
for (int j=0; j<8; j++)
if (!names[j + k+(i << 3)].equals(names[i << 3]))
return false;
for (int i=0; i < 8; i++)
for (int j=0; j<8; j++)
if (!names[j + 0xC0 +(i << 3)].equals(names[0xC0 + (i << 3)]))
return false;
// write out code
b.append(" int modrm = input.peek() & 0xFF;\n");
b.append(" int reg = (modrm >> 3) & 7;\n");
b.append(" if (modrm < 0xC0)\n {\n");
b.append(" switch (reg) {\n");
for (int i=0; i < 8; i++)
if ((i+1 < 8) && names[i*8].equals(names[i*8+8]))
b.append(String.format(" case 0x%02x:\n", i));
else
b.append(getConstructorLine(names[i*8], i));
b.append(" }\n");
b.append(" }\n");
b.append(" else\n {\n");
b.append(" switch (reg) {\n");
for (int i=0; i < 8; i++)
if ((i+1 < 8) && names[0xc0+i*8].equals(names[0xc0+i*8+8]))
b.append(String.format(" case 0x%02x:\n", i));
else
b.append(getConstructorLine(names[0xc0+i*8], i));
b.append(" }\n");
b.append(" }\n");
b.append(" return null;\n");
return true;
}
public static boolean almostIsSimpleModrmSplit(Map<Instruction, byte[]> ops, int mode, int differentIndex, StringBuilder b)
{
String[] names = new String[256];
for (Instruction in: ops.keySet())
names[ops.get(in)[differentIndex] & 0xff] = Disassembler.getExecutableName(mode, in);
boolean subC0Simple = true;
for (int i=0; i < 8; i++)
for (int k=0; k < 0xC0; k += 0x40)
for (int j=0; j<8; j++)
if (!names[j + k+(i << 3)].equals(names[i << 3]))
subC0Simple = false;
boolean postC0Simple = true;
for (int i=0; i < 8; i++)
for (int j=0; j<8; j++)
if (!names[j + 0xC0 +(i << 3)].equals(names[0xC0 + (i << 3)]))
postC0Simple = false;
if (subC0Simple)
{
b.append(" int modrm = input.peek() & 0xFF;\n");
b.append(" int reg = (modrm >> 3) & 7;\n");
b.append(" if (modrm < 0xC0)\n {\n");
b.append(" switch (reg) {\n");
for (int i=0; i < 8; i++)
b.append(getConstructorLine(names[i*8], i));
b.append(" }\n");
b.append(" }\n");
// post must be false otherwise IsSimpleModrmSplit would be true
b.append(" switch (modrm) {\n");
for (int i=0xc0; i < 0x100; i++)
if ((i+1 < 0x100) && names[i].equals(names[i+1]))
b.append(String.format(" case 0x%02x:\n", i));
else
b.append(getConstructorLine(names[i], i));
b.append(" }\n");
b.append(" return null;\n");
return true;
}
else
return false;
}
public static class MemoryChooser extends DecoderTemplate
{
String name;
public MemoryChooser(String name)
{
this.name = name;
}
public void writeBody(StringBuilder b)
{
b.append(" if (Modrm.isMem(input.peek()))\n return new "+name+"_mem("+args+");\n else\n return new "+name+"("+args+");\n");
}
}
public static void removeDuplicates(OpcodeHolder[] ops)
{
for (int i=0x200; i < 0x800; i++)
if (ops[i].equals(ops[i % 0x200]))
ops[i].setDuplicateOf(i % 0x200);
}
public static void generate()
{
System.out.println(Opcode.HEADER);
System.out.println("package org.jpc.emulator.execution.decoder;\n");
System.out.println("import org.jpc.emulator.execution.*;");
System.out.println("import org.jpc.emulator.execution.opcodes.rm.*;");
System.out.println("import org.jpc.emulator.execution.opcodes.pm.*;");
System.out.println("import org.jpc.emulator.execution.opcodes.vm.*;\n");
System.out.println("public class ExecutableTables {");
generateMode(1, "RM");
generateMode(2, "PM");
generateMode(3, "VM");
System.out.println("}\n");
}
public static void generateMode(int modeType, String mode)
{
System.out.println(" public static void populate"+mode+"Opcodes(OpcodeDecoder[] ops) {\n");
OpcodeHolder[] ops = new OpcodeHolder[0x800];
for (int i=0; i < ops.length; i++)
ops[i] = new OpcodeHolder(modeType);
generateRep(16, ops);
removeDuplicates(ops);
for (int i=0; i < ops.length; i++)
System.out.printf("ops[0x%02x] = "+ops[i]+"\n", i);
System.out.println("}\n\n");
}
public static void generateRep(int mode, OpcodeHolder[] ops)
{
byte[] x86 = new byte[28];
generateAll(mode, x86, 0, ops);
x86[0] = (byte)0xF2;
generateAll(mode, x86, 1, ops);
x86[0] = (byte)0xF3;
generateAll(mode, x86, 1, ops);
}
public static void generateAll(int mode, byte[] x86, int opbyte, OpcodeHolder[] ops)
{
Disassembler.ByteArrayPeekStream input = new Disassembler.ByteArrayPeekStream(x86);
int originalOpbyte = opbyte;
int base = 0;
for (int k=0; k <2; k++) // addr
{
for (int j=0; j <2; j++) // op size
{
for (int i=0; i < 2; i++) // 0F opcode start
{
for (int opcode = 0; opcode < 256; opcode++)
{
if (Prefices.isPrefix(opcode))
continue;
if ((opcode == 0x0f) && ((base & 0x100) == 0))
continue;
// fill x86 with appropriate bytes
x86[opbyte] = (byte)opcode;
input.resetCounter();
// decode prefices
Instruction in = new Instruction();
Disassembler.get_prefixes(mode, input, in);
int preficesLength = input.getCounter();
int opcodeLength;
try {
// decode opcode part
Disassembler.search_table(mode, input, in);
Disassembler.do_mode(mode, in);
opcodeLength = input.getCounter() - preficesLength;
// decode operands
Disassembler.disasm_operands(mode, input, in);
Disassembler.resolve_operator(mode, input, in);
} catch (IllegalStateException s) {continue;}
int argumentsLength = input.getCounter()-opcodeLength-preficesLength;
String[] args = in.getArgsTypes();
if ((args.length == 1) && (immediates.contains(args[0])))
{
// don't enumerate immediates
ops[base + opcode].addOpcode(in, x86.clone());
}
else
{
// enumerate modrm
for (int modrm = 0; modrm < 256; modrm++)
{
input.resetCounter();
x86[opbyte+1] = (byte)modrm;
Instruction modin = new Instruction();
try {
Disassembler.get_prefixes(mode, input, modin);
Disassembler.search_table(mode, input, modin);
Disassembler.do_mode(mode, modin);
Disassembler.disasm_operands(mode, input, modin);
Disassembler.resolve_operator(mode, input, modin);
} catch (IllegalStateException s)
{
// add the illegals
ops[base + opcode].addOpcode(modin, x86.clone());
x86[opbyte+1] = 0;
continue;
}
ops[base + opcode].addOpcode(modin, x86.clone());
}
x86[opbyte+1] = 0;
}
}
System.arraycopy(EMPTY, opbyte, x86, opbyte, x86.length-opbyte);
x86[opbyte++] = 0x0f;
base += 0x100; // now do the 0x0f opcodes (2 byte opcodes)
}
if (x86[originalOpbyte] == (byte)0x67)
opbyte = originalOpbyte + 1;
else
opbyte = originalOpbyte;
System.arraycopy(EMPTY, opbyte, x86, opbyte, x86.length-opbyte);
x86[opbyte++] = 0x66;
}
System.arraycopy(EMPTY, originalOpbyte, x86, originalOpbyte, x86.length -originalOpbyte);
x86[originalOpbyte] = 0x67;
opbyte = originalOpbyte + 1;
}
}
public static List<String> immediates = Arrays.asList(new String[]{"Jb", "Jw", "Jd", "Ib", "Iw", "Id"});
}