Skip to content

Instantly share code, notes, and snippets.

@tadeuzagallo
Last active February 12, 2024 17:18
Show Gist options
  • Save tadeuzagallo/3853299f033bf9b746e4 to your computer and use it in GitHub Desktop.
Save tadeuzagallo/3853299f033bf9b746e4 to your computer and use it in GitHub Desktop.
Writing an x86 emulator in JavaScript
<!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>
'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];
}
'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