/*
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.io.ByteArrayOutputStream;
import java.io.IOException;
public abstract class EmulatorControl
{
public static int ESP_INDEX = 4;
public static int EIP_INDEX = 8;
public static int EFLAGS_INDEX = 9;
public static int VM86_FLAG = 1 << 17;
public static int OF_FLAG = 1 << 11;
public static int SF_FLAG = 1 << 7;
public static int ZF_FLAG = 1 << 6;
public static int AF_FLAG = 1 << 4;
public static int PF_FLAG = 1 << 2;
public static int CF_FLAG = 1;
public static int CRO_INDEX = 36;
public static int GDT_BASE_INDEX = 24;
public static int IDT_BASE_INDEX = 26;
public static int GDT_LIMIT_INDEX = 25;
public static int IDT_LIMIT_INDEX = 27;
public static int ES_LIMIT_INDEX = 17;
public static int CS_LIMIT_INDEX = 18;
public static int SS_LIMIT_INDEX = 19;
public static int DS_LIMIT_INDEX = 20;
public static int FS_LIMIT_INDEX = 21;
public static int GS_LIMIT_INDEX = 22;
public static int SEG_PROP_INDEX = 23;
public static int ES_BASE_INDEX = 30;
public static int CS_BASE_INDEX = 31;
public static int SS_BASE_INDEX = 32;
public static int DS_BASE_INDEX = 33;
public static int FS_BASE_INDEX = 34;
public static int GS_BASE_INDEX = 35;
public static String[] names = new String[]
{
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi","eip", "flags",
/*10*/"es", "cs", "ss", "ds", "fs", "gs", "ticks",
/*17*/"es-lim", "cs-lim", "ss-lim", "ds-lim", "fs-lim", "gs-lim", "ss-cs-prop",
/*24*/"gdtrbase", "gdtr-lim", "idtrbase", "idtr-lim", "ldtrbase", "ldtr-lim",
/*30*/"es-base", "cs-base", "ss-base", "ds-base", "fs-base", "gs-base",
/*36*/"cr0",
/*37*/"ST0H", "ST0L","ST1H", "ST1L","ST2H", "ST2L","ST3H", "ST3L",
/*45*/"ST4H", "ST4L","ST5H", "ST5L","ST6H", "ST6L","ST7H", "ST7L",
//"expiry"
};
public abstract String disam(byte[] code, Integer ops, Boolean is32Bit);
public abstract int x86Length(byte[] code, Boolean is32Bit);
// return disam of next instruction
public abstract String executeInstruction() throws IOException;
public abstract int[] getState() throws IOException;
public abstract byte[] getCMOS() throws IOException;
public abstract int[] getPit() throws IOException;
public abstract int getPITIntTargetEIP() throws IOException;
public abstract Integer getPhysicalPage(Integer page, byte[] data) throws IOException;
public abstract Integer getLinearPage(Integer page, byte[] data) throws IOException;
public abstract void setPhysicalMemory(int addr, byte[] data) throws IOException;
public abstract void keysDown(String keys);
public abstract void keysUp(String keys);
public abstract void sendMouse(Integer dx, Integer dy, Integer dz, Integer buttons);
public abstract void destroy();
public void setState(int[] state, int currentCSEIP) throws IOException
{
boolean targetPM = (state[EmulatorControl.CRO_INDEX] & 1) != 0;
if (targetPM)
{
if ((state[EFLAGS_INDEX] & VM86_FLAG) != 0)
setVM86State(state, currentCSEIP);
else
setPMState(state, currentCSEIP);
return;
}
// get to cs:eip = 0:2000
// Assumes we are currently in real mode
int codeAddress16 = 0x2000;
int dataAddress16 = 0x3000;
setPhysicalMemory(currentCSEIP, new byte[]{(byte) 0xea, (byte) codeAddress16, (byte) (codeAddress16 >> 8), (byte) 0, (byte) 0});
executeInstruction();
// zero what we just wrote
setPhysicalMemory(currentCSEIP, new byte[5]);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
// assume we are starting in real mode
int intCount = 0;
for (int i=1; i < 8; i++)
{
// mov reg, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0+i);
if (i == 4)
{
int esp = 0x1000;
bout.write(esp);
bout.write(esp >> 8);
bout.write(esp >> 16);
bout.write(esp >> 24);
}
else
{
bout.write(state[i]);
bout.write(state[i] >> 8);
bout.write(state[i] >> 16);
bout.write(state[i] >> 24);
}
intCount++;
}
// set segments
// 8e c0 = mov es, ax
// 8e d0 = mov ss, ax
// 8e d8 = mov ds, ax
// 8e e0 = mov fs, ax
// 8e e8 = mov gs, ax
for (int seg = 0; seg < 6; seg++)
{
if (seg == 1) // can't load CS like this
continue;
bout.write(0xc7);
bout.write(0xc0);
if (seg == 3) // ds
{
bout.write(0);
bout.write(0);
}
else if (seg == 2) // ss
{
bout.write(0);
bout.write(0);
}
else
{
bout.write(state[seg + 10]);
bout.write(state[seg + 10] >> 8);
}
bout.write(0x8e);
bout.write(0xc0 + (seg << 3));
intCount += 2;
}
// reset FPU
bout.write(0xdb);
bout.write(0xe3);
intCount++;
// set FPU stack (relies on ds base being 0)
int nextDataAddress = dataAddress16;
for (int i=7; i >=0 ; i--)
{
byte[] value = new byte[8];
for (int j=0; j < 4; j++)
value[7-j] = (byte)(state[37 + 2*i] >> (8*(3-j)));
for (int j=4; j < 8; j++)
value[7-j] = (byte)(state[37 + 2*i +1] >> (8*(7-j)));
// put value in mem at dataAddress
setPhysicalMemory(nextDataAddress, value);
// fld that mem section
bout.write(0xdd);
bout.write(0x06);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 8;
intCount++;
}
// set eflags
bout.write(0x66); // push ID
bout.write(0x68);
bout.write(state[9]);
bout.write(state[9] >> 8);
bout.write(state[9] >> 16);
bout.write(state[9] >> 24);
bout.write(0x66); // popfd
bout.write(0x9d);
intCount += 2;
// set CR0
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[36]);
bout.write(state[36] >> 8);
bout.write(state[36] >> 16);
bout.write(state[36] >> 24);
bout.write(0x0f);
bout.write(0x22);
bout.write(0xc0);
intCount += 2;
// set ds (FPU set needed it zero)
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[3 + 10]);
bout.write(state[3 + 10] >> 8);
bout.write(0x8e);
bout.write(0xc0 + (3 << 3));
intCount += 2;
// set ss (mov cr0,eax needed it zero)
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[3 + 10]);
bout.write(state[3 + 10] >> 8);
bout.write(0x8e);
bout.write(0xc0 + (2 << 3));
intCount += 2;
// set eax: mov reg, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[0]);
bout.write(state[0] >> 8);
bout.write(state[0] >> 16);
bout.write(state[0] >> 24);
intCount++;
// set esp (push and popf needed it different): mov reg, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc4);
bout.write(state[4]);
bout.write(state[4] >> 8);
bout.write(state[4] >> 16);
bout.write(state[4] >> 24);
intCount++;
// set cs:eip with far jmp
bout.write(0xea);
bout.write(state[8]);
bout.write(state[8] >> 8);
bout.write(state[11]);
bout.write(state[11] >> 8);
intCount++;
setPhysicalMemory(codeAddress16, bout.toByteArray());
for (int i = 0; i < intCount-2; i++)
executeInstruction();
// account for mov ss, X executing 2 instructions in JPC
for (int i: new int[]{0, 1})
{
int[] stateNow = getState();
if (stateNow[8] != state[8])
executeInstruction();
}
// zero what we just wrote
setPhysicalMemory(codeAddress16, new byte[bout.size()]);
// and the data too
setPhysicalMemory(dataAddress16, new byte[8 * 8]);
}
private void setPMState(int[] state, int currentCSEIP) throws IOException
{
// get to cs:eip = 0:2000
// Assumes we are currently in real mode
int codeAddress16 = 0x2000;
int dataAddress16 = 0x3000;
int nextDataAddress = dataAddress16;
setPhysicalMemory(currentCSEIP, new byte[]{(byte) 0xea, (byte) codeAddress16, (byte) (codeAddress16 >> 8), (byte) 0, (byte) 0});
executeInstruction();
// zero what we just wrote
setPhysicalMemory(currentCSEIP, new byte[5]);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
// assume we are starting in real mode
int intCount = 0;
// create GDTR
setPhysicalMemory(nextDataAddress, new byte[]{(byte)state[GDT_LIMIT_INDEX], (byte)(state[GDT_LIMIT_INDEX] >> 8),
(byte)state[GDT_BASE_INDEX], (byte)(state[GDT_BASE_INDEX] >> 8), (byte)(state[GDT_BASE_INDEX] >> 16),
(byte)(state[GDT_BASE_INDEX] >> 24)});
bout.write(0x0f); // LGDT ds:IW
bout.write(0x01);
bout.write(0x16);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 6;
intCount++;
byte[] gdt = toBytes(new long[] {0L, getDataDescriptor(state[ES_BASE_INDEX], state[ES_LIMIT_INDEX]),
getCodeDescriptor(state[CS_BASE_INDEX], state[CS_LIMIT_INDEX]), getDataDescriptor(state[SS_BASE_INDEX], state[SS_LIMIT_INDEX]),
getDataDescriptor(state[DS_BASE_INDEX], state[DS_LIMIT_INDEX]), getDataDescriptor(state[FS_BASE_INDEX], state[FS_LIMIT_INDEX]),
getDataDescriptor(state[GS_BASE_INDEX], state[GS_LIMIT_INDEX]), getCodeDescriptor(0, 0xffffffff)});
setPhysicalMemory(state[GDT_BASE_INDEX], gdt);
// create IDTR
setPhysicalMemory(nextDataAddress, new byte[]{(byte)state[IDT_LIMIT_INDEX], (byte)(state[IDT_LIMIT_INDEX] >> 8),
(byte)state[IDT_BASE_INDEX], (byte)(state[IDT_BASE_INDEX] >> 8), (byte)(state[IDT_BASE_INDEX] >> 16),
(byte)(state[IDT_BASE_INDEX] >> 24)});
bout.write(0x0f); // LIDT ds:IW
bout.write(0x01);
bout.write(0x1e);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 6;
intCount++;
for (int i=1; i < 8; i++)
{
// mov reg, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0+i);
bout.write(state[i]);
bout.write(state[i] >> 8);
bout.write(state[i] >> 16);
bout.write(state[i] >> 24);
intCount++;
}
// reset FPU
bout.write(0xdb);
bout.write(0xe3);
intCount++;
// set FPU stack (relies on ds base being 0)
for (int i=7; i >=0 ; i--)
{
byte[] value = new byte[8];
for (int j=0; j < 4; j++)
value[7-j] = (byte)(state[37 + 2*i] >> (8*(3-j)));
for (int j=4; j < 8; j++)
value[7-j] = (byte)(state[37 + 2*i +1] >> (8*(7-j)));
// put value in mem at dataAddress
setPhysicalMemory(nextDataAddress, value);
// fld that mem section
bout.write(0xdd);
bout.write(0x06);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 8;
intCount++;
}
// set ds (FPU set needed it zero)
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[3 + 10]);
bout.write(state[3 + 10] >> 8);
bout.write(0x8e);
bout.write(0xc0 + (3 << 3));
intCount += 2;
// set eflags
bout.write(0x66); // push ID
bout.write(0x68);
bout.write(state[9]);
bout.write(state[9] >> 8);
bout.write(state[9] >> 16);
bout.write(state[9] >> 24);
bout.write(0x66); // popfd
bout.write(0x9d);
intCount += 2;
// set CR0 and switch to protected mode
bout.write(0x0f); // mov eax, cr0
bout.write(0x20);
bout.write(0xc0);
bout.write(0x0c); // or al, 1
bout.write(0x01);
bout.write(0x0f); // mov cr0, eax
bout.write(0x22);
bout.write(0xc0);
intCount += 3;
// far jump to where we are (to load cs)
int currentEIP = codeAddress16 + bout.size() + 8;
bout.write(0x66);
bout.write(0xea);
bout.write(currentEIP);
bout.write(currentEIP >> 8);
bout.write(currentEIP >> 16);
bout.write(currentEIP >> 24);
bout.write(7 << 3); // the last GDT entry pointing to base 0, limit 0xffffffff
bout.write(0);
intCount++;
// set segments
// 8e c0 = mov es, ax
// 8e d0 = mov ss, ax
// 8e d8 = mov ds, ax
// 8e e0 = mov fs, ax
// 8e e8 = mov gs, ax
for (int seg = 0; seg < 6; seg++)
{
if (seg == 1) // can't load CS like this
continue;
bout.write(0xc7);
bout.write(0xc0);
bout.write((seg +1) << 3); // gdt index is seg +1
bout.write(0);
bout.write(0x8e); // mov S, ax
bout.write(0xc0 + (seg << 3));
intCount += 2;
}
// set eax: mov reg, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[0]);
bout.write(state[0] >> 8);
bout.write(state[0] >> 16);
bout.write(state[0] >> 24);
intCount++;
// set cs:eip with far jmp
bout.write(0x66);
bout.write(0xea);
bout.write(state[8]);
bout.write(state[8] >> 8);
bout.write(state[8] >> 16);
bout.write(state[8] >> 24);
bout.write(2 << 3);
bout.write(0);
intCount++;
setPhysicalMemory(codeAddress16, bout.toByteArray());
for (int i = 0; i < intCount-1; i++)
executeInstruction();
// account for mov ss, X executing 2 instructions in JPC
int[] stateNow = getState();
if (stateNow[8] != state[8])
executeInstruction();
// zero what we just wrote
setPhysicalMemory(codeAddress16, new byte[bout.size()]);
// and the data too
setPhysicalMemory(dataAddress16, new byte[8 * 8]);
}
private void setVM86State(int[] state, int currentCSEIP) throws IOException
{
// need to have [EIP, CS, EFLAGS, ESP, SS, ES, DS, FS, GS] on stack, then do iret_o32_a32
int[] stack = new int[9];
stack[0] = state[EIP_INDEX];
stack[1] = state[CS_BASE_INDEX] >> 4;
stack[2] = state[EFLAGS_INDEX];
stack[3] = state[ESP_INDEX];
stack[4] = state[SS_BASE_INDEX] >> 4;
stack[5] = state[ES_BASE_INDEX] >> 4;
stack[6] = state[DS_BASE_INDEX] >> 4;
stack[7] = state[FS_BASE_INDEX] >> 4;
stack[8] = state[GS_BASE_INDEX] >> 4;
// get to cs:eip = 0:2000
// Assumes we are currently in real mode
int codeAddress16 = 0x2000;
int dataAddress16 = 0x3000;
int ssespAddress16 = 0x4000;
int nextDataAddress = dataAddress16;
setPhysicalMemory(currentCSEIP, new byte[]{(byte) 0xea, (byte) codeAddress16, (byte) (codeAddress16 >> 8), (byte) 0, (byte) 0});
executeInstruction();
// zero what we just wrote
setPhysicalMemory(currentCSEIP, new byte[5]);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
// assume we are starting in real mode
int intCount = 0;
// create GDTR
setPhysicalMemory(nextDataAddress, new byte[]{(byte)state[GDT_LIMIT_INDEX], (byte)(state[GDT_LIMIT_INDEX] >> 8),
(byte)state[GDT_BASE_INDEX], (byte)(state[GDT_BASE_INDEX] >> 8), (byte)(state[GDT_BASE_INDEX] >> 16),
(byte)(state[GDT_BASE_INDEX] >> 24)});
bout.write(0x0f); // LGDT ds:IW
bout.write(0x01);
bout.write(0x16);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 6;
intCount++;
byte[] gdt = toBytes(new long[] {0L, getDataDescriptor(state[ES_BASE_INDEX], state[ES_LIMIT_INDEX]),
getCodeDescriptor(state[CS_BASE_INDEX], state[CS_LIMIT_INDEX]), getDataDescriptor(state[SS_BASE_INDEX], state[SS_LIMIT_INDEX]),
getDataDescriptor(state[DS_BASE_INDEX], state[DS_LIMIT_INDEX]), getDataDescriptor(state[FS_BASE_INDEX], state[FS_LIMIT_INDEX]),
getDataDescriptor(state[GS_BASE_INDEX], state[GS_LIMIT_INDEX]), getCodeDescriptor(0, 0xffffffff), getDataDescriptor(0, 0xffffffff)});
setPhysicalMemory(state[GDT_BASE_INDEX], gdt);
// create IDTR
setPhysicalMemory(nextDataAddress, new byte[]{(byte)state[IDT_LIMIT_INDEX], (byte)(state[IDT_LIMIT_INDEX] >> 8),
(byte)state[IDT_BASE_INDEX], (byte)(state[IDT_BASE_INDEX] >> 8), (byte)(state[IDT_BASE_INDEX] >> 16),
(byte)(state[IDT_BASE_INDEX] >> 24)});
bout.write(0x0f); // LIDT ds:IW
bout.write(0x01);
bout.write(0x1e);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 6;
intCount++;
for (int i=1; i < 8; i++)
{
// mov reg, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0+i);
bout.write(state[i]);
bout.write(state[i] >> 8);
bout.write(state[i] >> 16);
bout.write(state[i] >> 24);
intCount++;
}
// reset FPU
bout.write(0xdb);
bout.write(0xe3);
intCount++;
// set FPU stack (relies on ds base being 0)
for (int i=7; i >=0 ; i--)
{
byte[] value = new byte[8];
for (int j=0; j < 4; j++)
value[7-j] = (byte)(state[37 + 2*i] >> (8*(3-j)));
for (int j=4; j < 8; j++)
value[7-j] = (byte)(state[37 + 2*i +1] >> (8*(7-j)));
// put value in mem at dataAddress
setPhysicalMemory(nextDataAddress, value);
// fld that mem section
bout.write(0xdd);
bout.write(0x06);
bout.write(nextDataAddress);
bout.write(nextDataAddress >> 8);
nextDataAddress += 8;
intCount++;
}
// set esp for iret: mov esp, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc4);
bout.write(ssespAddress16);
bout.write(ssespAddress16 >> 8);
bout.write(ssespAddress16 >> 16);
bout.write(ssespAddress16 >> 24);
intCount++;
// set CR0 and switch to 16 bit protected mode
bout.write(0x0f); // mov eax, cr0
bout.write(0x20);
bout.write(0xc0);
bout.write(0x0c); // or al, 1
bout.write(0x01);
bout.write(0x0f); // mov cr0, eax
bout.write(0x22);
bout.write(0xc0);
intCount += 3;
// set ss base to 0
bout.write(0xc7);
bout.write(0xc0);
bout.write(8 << 3); // gdt index is 8 = base 0
bout.write(0);
bout.write(0x8e); // mov S, ax
bout.write(0xd0);
intCount += 2;
// set eax: mov eax, ID
bout.write(0x66);
bout.write(0xc7);
bout.write(0xc0);
bout.write(state[0]);
bout.write(state[0] >> 8);
bout.write(state[0] >> 16);
bout.write(state[0] >> 24);
intCount++;
// iretd to VM86 (loads eip, esp, segments and eflags)
bout.write(0x66);
bout.write(0xcf);
intCount++;
setPhysicalMemory(ssespAddress16, toBytes(stack));
setPhysicalMemory(codeAddress16, bout.toByteArray());
for (int i = 0; i < intCount-1; i++)
executeInstruction();
// account for mov ss, X executing 2 instructions in JPC
int[] stateNow = getState();
if (stateNow[8] != state[8])
executeInstruction();
// zero what we just wrote
setPhysicalMemory(codeAddress16, new byte[bout.size()]);
// and the data too
setPhysicalMemory(dataAddress16, new byte[8 * 8]);
setPhysicalMemory(ssespAddress16, new byte[4 * stack.length]);
}
public static long getInterruptGateDescriptor(int gdt_index, int offset)
{
long d = ((gdt_index << 3) & 0xffffL) << 16;
d |= offset & 0xffffL;
d |= (offset & 0xffff0000L) << 32;
d |= (0x86L << 40); // present, 16-bit gate
return d;
}
public static long getCodeDescriptor(int base, int limit)
{
long d = (base & 0xffffL) << 16;
d |= (base & 0xff0000L) << 16;
d |= (base & 0xff000000L) << 32;
d |= limit & 0xffffL;
d |= (limit & 0xf0000L) << 32;
d |= (0x9aL << 40); // present, execute read conforming code segment
return d;
}
public static long getDataDescriptor(int base, int limit)
{
long d = (base & 0xffffL) << 16;
d |= (base & 0xff0000L) << 16;
d |= (base & 0xff000000L) << 32;
d |= limit & 0xffffL;
d |= (limit & 0xf0000L) << 32;
d |= (0x93L << 40); // present, read/write data segment
return d;
}
public static byte[] toBytes(int[] d)
{
byte[] b = new byte[d.length*8];
for (int i=0; i < d.length; i++)
{
b[4*i] = (byte) d[i];
b[4*i + 1] = (byte) (d[i] >> 8);
b[4*i + 2] = (byte) (d[i] >> 16);
b[4*i + 3] = (byte) (d[i] >> 24);
}
return b;
}
public static byte[] toBytes(long[] d)
{
byte[] b = new byte[d.length*8];
for (int i=0; i < d.length; i++)
{
b[8*i] = (byte) d[i];
b[8*i + 1] = (byte) (d[i] >> 8);
b[8*i + 2] = (byte) (d[i] >> 16);
b[8*i + 3] = (byte) (d[i] >> 24);
b[8*i + 4] = (byte) (d[i] >> 32);
b[8*i + 5] = (byte) (d[i] >> 40);
b[8*i + 6] = (byte) (d[i] >> 48);
b[8*i + 7] = (byte) (d[i] >> 56);
}
return b;
}
}