-
-
Save tadeuzagallo/3853299f033bf9b746e4 to your computer and use it in GitHub Desktop.
Writing an x86 emulator in JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title></title> | |
<script type="text/javascript" src="x86.js"></script> | |
<script type="text/javascript" src="mach-o.js"></script> | |
</head> | |
<body> | |
<input id="binary" type="file" onchange="loadBinary(this.files[0])"> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var LC_SEGMENT = 0x00000001; | |
var LC_UNIXTHREAD = 0x00000005; | |
var Memory = new ArrayBuffer(1048576); | |
var Registers = new Uint32Array(8); | |
var PC = -1; | |
function loadBinary(file) { | |
var reader = new FileReader(); | |
reader.addEventListener("loadend", function() { | |
readBinary(new Uint32Array(reader.result)); | |
}); | |
reader.readAsArrayBuffer(file); | |
} | |
function readBinary(buffer) { | |
var header = buffer.subarray(0, 7); | |
if (header[0] !== 0xfeedface) { | |
throw new Error("Invalid Mach magic"); | |
} | |
// var cpuType = header[1]; | |
// var cpuSubtype = header[2]; | |
// var fileType = header[3]; | |
var numCommands = header[4]; | |
// var commandsSize = header[5]; | |
// var flags = header[6]; | |
var currentCommandStart = 7; | |
for (var i = 0; i < numCommands; i++) { | |
var commandSize = buffer[currentCommandStart + 1]; | |
var commandEnd = currentCommandStart + commandSize / 4; | |
var commandBuffer = buffer.subarray(currentCommandStart, commandEnd); | |
handleCommand(buffer, commandBuffer); | |
currentCommandStart = commandEnd; | |
}; | |
if (PC !== -1) { | |
run(Memory, Registers, PC); | |
} | |
PC = -1; | |
} | |
function handleCommand(buffer, commandBuffer) { | |
var command = commandBuffer[0]; | |
switch (command) { | |
case LC_SEGMENT: | |
handleSegmentCommand(buffer, commandBuffer); | |
break; | |
case LC_UNIXTHREAD: | |
handleUnixThreadCommand(buffer, commandBuffer); | |
break; | |
default: | |
// Unhandled command | |
} | |
} | |
function handleSegmentCommand(buffer, commandBuffer) { | |
var vmAddress = commandBuffer[6] >> 2; | |
var vmSize = commandBuffer[7] >> 2; | |
var fileOffset = commandBuffer[8] >> 2; | |
var fileSize = commandBuffer[9] >> 2; | |
var fileEnd = fileOffset + fileSize; | |
var view = new Uint32Array(Memory); | |
for (var i = 0; i < vmSize; i++) { | |
if (fileOffset + i >= fileEnd) { | |
break; | |
} | |
view[vmAddress + i] = buffer[fileOffset + i]; | |
} | |
} | |
function handleUnixThreadCommand(buffer, commandBuffer) { | |
var offset = 4; | |
Registers[0] = commandBuffer[offset + 0]; // eax | |
Registers[1] = commandBuffer[offset + 2]; // ecx | |
Registers[2] = commandBuffer[offset + 3]; // edx | |
Registers[3] = commandBuffer[offset + 1]; // ebx | |
Registers[4] = commandBuffer[offset + 7]; // esp | |
Registers[5] = commandBuffer[offset + 6]; // ebp | |
Registers[6] = commandBuffer[offset + 5]; // esi | |
Registers[7] = commandBuffer[offset + 4]; // edi | |
//var ss = commandBuffer[offset + 8]; | |
//var eflags = commandBuffer[offset + 9]; | |
PC = commandBuffer[offset + 10]; | |
//var cs = commandBuffer[offset + 11]; | |
//var ds = commandBuffer[offset + 12]; | |
//var es = commandBuffer[offset + 13]; | |
//var fs = commandBuffer[offset + 14]; | |
//var gs = commandBuffer[offset + 15]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var EAX = 0; | |
var ECX = 1; | |
var EDX = 2; | |
var EBX = 3; | |
var ESP = 4; | |
var EBP = 5; | |
var ESI = 6; | |
var EDI = 7; | |
var NG = 0; | |
var NZ = 0; | |
var Program; | |
var Registers; | |
var PC; | |
var Stack = new Uint32Array(1024); | |
var read = function (size) { | |
var value; | |
switch (size) { | |
case 1: | |
value = Program[PC]; | |
break; | |
case 2: | |
value = Program[PC+1] << 8 | Program[PC]; | |
break; | |
case 4: | |
value = Program[PC+3] << 24 | Program[PC+2] << 16 | Program[PC+1] << 8 | Program[PC]; | |
break; | |
case -1: | |
// http://blog.vjeux.com/2013/javascript/conversion-from-uint8-to-int8-x-24.html | |
value = Program[PC] << 24 >> 24; | |
break; | |
default: | |
throw new Error('Invalid read size'); | |
} | |
PC += Math.abs(size) // & 127; // abs | |
return value; | |
} | |
var Functions = {}; | |
var pop = function () { | |
return Stack[Registers[ESP]++]; | |
} | |
// pop reg | |
var popr = function (reg) { | |
return function () { | |
Registers[reg] = pop(); | |
}; | |
}; | |
Functions[0x58] = popr(EAX); | |
Functions[0x59] = popr(ECX); | |
Functions[0x5a] = popr(EDX); | |
Functions[0x5b] = popr(EBX); | |
Functions[0x5c] = popr(ESP); | |
Functions[0x5d] = popr(EBP); | |
Functions[0x5e] = popr(ESI); | |
Functions[0x5f] = popr(EDI); | |
var push = function (value) { | |
Stack[--Registers[ESP]] = value; | |
}; | |
var pushr = function (register) { | |
return function () { | |
push(Registers[register]) | |
}; | |
}; | |
// Push registers | |
Functions[0x50] = pushr(EAX); | |
Functions[0x51] = pushr(ECX); | |
Functions[0x52] = pushr(EDX); | |
Functions[0x53] = pushr(EBX); | |
Functions[0x54] = pushr(ESP); | |
Functions[0x55] = pushr(EBP); | |
Functions[0x56] = pushr(ESI); | |
Functions[0x57] = pushr(EDI); | |
// Push immediate | |
Functions[0x6a] = function () { | |
push(read(1)); | |
}; | |
var mov = function (register) { | |
return function () { | |
Registers[register] = read(4); | |
}; | |
}; | |
// mov imm8 reg | |
Functions[0xb8] = mov(EAX); | |
Functions[0xb9] = mov(ECX); | |
Functions[0xba] = mov(EDX); | |
Functions[0xbb] = mov(EBX); | |
Functions[0xbc] = mov(ESP); | |
// mov reg reg | |
Functions[0x89] = function () { | |
var rm = read(1); | |
var r0 = rm >> 3 & 7; | |
var r1 = rm & 7; | |
if (!(rm & (1 << 7))) { | |
var offset = read(-1) >> 2; | |
if (rm & 4 && offset == 9) { | |
offset = 0; | |
} | |
Stack[Registers[r1] + offset] = Registers[r0]; | |
} else { | |
Registers[r1] = Registers[r0]; | |
} | |
}; | |
// yam | |
Functions[0x8b] = function () { | |
var rm = read(1); | |
var r0 = rm >> 3 & 7; | |
var r1 = rm & 7; | |
var offset = read(-1) >> 2; | |
Registers[r0] = Stack[Registers[r1] + offset]; | |
} | |
// movl | |
Functions[0xc7] = function () { | |
var op1 = read(1); | |
var reg = op1 & 7; | |
var offset = read(-1) >> 2; | |
if (!(op1 & 1<<6)) { | |
offset = 0; | |
} | |
var sp = Registers[reg] + offset; | |
var value = read(4); | |
Stack[sp] = value; | |
}; | |
// lea | |
Functions[0x8d] = function () { | |
var op1 = read(1); | |
var reg0 = op1 >> 3 & 7; | |
var reg1 = op1 & 7; | |
var offset = read(-1); | |
if (!(op1 & 1<<6)) { | |
offset = 0; | |
} | |
Registers[reg0] = Registers[reg1] + offset; | |
}; | |
// add reg | |
Functions[0x01] = function () { | |
var rm = read(1) - 0xc0; | |
var r0 = rm >> 3; | |
var r1 = rm & 7; | |
Registers[r1] += Registers[r0]; | |
}; | |
// add reg pad | |
Functions[0x03] = function () { | |
var rm = read(1); | |
var r0 = rm >> 3 & 7; | |
var r1 = rm & 7; | |
var offset = read(-1) >> 2; | |
Registers[r0] += Stack[Registers[r1] + offset]; | |
}; | |
// add EAX | |
Functions[0x05] = function () { | |
Registers[EAX] += read(4); | |
}; | |
// sub rax | |
Functions[0x2d] = function () { | |
Registers[EAX] -= read(4); | |
} | |
var x83 = { | |
// add | |
0x00: function (reg, op2) { | |
Registers[reg] += op2 >> 2; | |
}, | |
// sub | |
0x05: function (reg, op2) { | |
Registers[reg] -= op2 >> 2; | |
}, | |
// cmp | |
0x07: function (reg, op2) { | |
var tmp = Registers[reg] - op2; | |
NZ = tmp != 0; | |
NG = tmp < 0; | |
} | |
}; | |
Functions[0x83] = function () { | |
var op1 = read(1); | |
var fn = op1 >> 3 & 7; | |
var reg = op1 & 7; | |
var op2 = read(1); | |
x83[fn](reg, op2); | |
}; | |
var Fn0x81 = { | |
// sub | |
0x05: function (reg, value) { | |
Registers[reg] -= read(4); | |
}, | |
// cmpl | |
0x07: function (reg) { | |
var offset = read(-1) >> 2; | |
var value = read(4); | |
var tmp = Stack[Registers[reg] + offset] - value; | |
NZ = tmp != 0; | |
NG = tmp < 0; | |
}, | |
}; | |
Functions[0x81] = function () { | |
var op1 = read(1); | |
var fn = op1 >> 3 & 7; | |
var reg = op1 & 7; | |
Fn0x81[fn](reg); | |
}; | |
// Prefix 0x0F | |
var Fn0x0f = { | |
// jge | |
0x8d: function () { | |
var dist = read(4); | |
if (!NG) { | |
PC += dist; | |
} | |
}, | |
// jle short jump | |
0x8e: function () { | |
var dist = read(4); | |
if (NG || !NZ) { | |
PC += dist; | |
} | |
}, | |
// imul | |
0xaf: function () { | |
var rm = read(1) - 0xc0; | |
var r0 = rm >> 3; | |
var r1 = rm & 7; | |
Registers[r0] = Registers[r0] * Registers[r1]; | |
}, | |
// noop | |
0x1f: function () {}, | |
}; | |
Functions[0x0f] = function () { | |
// Call the actual function inside the prefix | |
var fn = read(1); | |
Fn0x0f[fn](); | |
}; | |
// jmp imm32 | |
var jmp32 = Functions[0xe9] = function () { | |
var dst = read(4); | |
PC += dst; | |
} | |
// jmp imm8 | |
Functions[0xeb] = function () { | |
var dst = read(1); | |
PC += dst; | |
} | |
// jge | |
Functions[0x7d] = function () { | |
var dist = read(-1); | |
if (!NG) { | |
PC += dist; | |
} | |
} | |
// dec | |
var dec = function (register) { | |
return function () { | |
Registers[register]--; | |
}; | |
}; | |
Functions[0x48] = dec(EAX); | |
Functions[0x49] = dec(ECX); | |
Functions[0x4a] = dec(EDX); | |
Functions[0x4b] = dec(EBX); | |
Functions[0x4c] = dec(ESP); | |
Functions[0x4d] = dec(EBP); | |
Functions[0x4e] = dec(ESI); | |
Functions[0x4f] = dec(EDI); | |
// call | |
Functions[0xe8] = function () { | |
var dst = read(4); | |
push(PC); | |
PC += dst; | |
}; | |
// ret | |
Functions[0xc3] = function () { | |
PC = pop(); | |
}; | |
// int | |
Functions[0xcd] = function () { | |
Interrupts[read(1)](); | |
}; | |
var Interrupts = {}; | |
// syscall | |
Interrupts[0x80] = function () { | |
Syscalls[Registers[EAX]](); | |
}; | |
var Syscalls = {}; | |
// exit | |
Syscalls[0x1] = function () { | |
console.log('Program returned %s', Stack[Registers[ESP+1]]); | |
PC = -1; | |
}; | |
function run(memory, registers, pc) { | |
Program = new Uint8Array(memory); | |
Registers = registers; | |
PC = pc; | |
Registers[ESP] = 1023; | |
var op; | |
while (PC !== -1 && (op = read(1))) { | |
Functions[op](); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment