Skip to content

Instantly share code, notes, and snippets.

@eatonphil
Created July 20, 2019 21:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eatonphil/2d16bc3dae33bff8a8d7f2a9d13025c3 to your computer and use it in GitHub Desktop.
Save eatonphil/2d16bc3dae33bff8a8d7f2a9d13025c3 to your computer and use it in GitHub Desktop.
Emulator basics
const fs = require('fs');
function parse(program) {
const labels = {};
const instructions = [];
const lines = program.split('\n');
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim(); // Remove any trailing, leading whitespace
if (line.startsWith('.') || line.startsWith(';') || line.startsWith('#')) {
continue;
}
if (line.includes(';')) {
line = line.split(';')[0];
}
if (line.includes('#')) {
line = line.split('#')[0];
}
if (!line) {
continue;
}
if (line.includes(':')) {
const label = line.split(':')[0];
labels[label] = instructions.length;
continue;
}
const operation = line.split(/\s/)[0].toLowerCase();
const operands = line.substring(operation.length).split(',').map(t => t.trim().toUpperCase());
instructions.push({
operation,
operands,
});
}
return { labels, instructions };
}
const REGISTERS = [
'RDI', 'RSI', 'RSP', 'RBP', 'RAX', 'RBX', 'RCX', 'RDX', 'RIP', 'R8',
'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'CS', 'DS', 'FS',
'SS', 'ES', 'GS', 'CF', 'ZF', 'PF', 'AF', 'SF', 'TF', 'IF', 'DF', 'OF',
];
const SYSCALLS_BY_ID = {
1: function sys_write(process) {
const msg = BigInt(process.registers.RSI);
const bytes = Number(process.registers.RDX);
for (let i = 0; i < bytes; i++) {
const byte = readMemoryBytes(process, msg + BigInt(i), 1);
const char = String.fromCharCode(Number(byte));
process.fd[Number(process.registers.RDI)].write(char);
}
},
60: function sys_exit(process) {
global.process.exit(Number(process.registers.RDI));
},
};
function writeMemoryBytes(process, address, value, size) {
for (let i = 0n; i < size; i++) {
value >>= i * 8n;
process.memory[address + i] = value & 0xFFn;
}
}
function readMemoryBytes(process, address, size) {
let value = 0n;
for (let i = 0n; i < size; i++) {
value |= (process.memory[address + i] || 0n) << (i * 8n);
}
return value;
}
function interpretValue(process, value, { lhs } = { lhs: false }) {
if (REGISTERS.includes(value)) {
if (lhs) {
return value;
} else {
return process.registers[value];
}
}
if (value.startsWith('QWORD PTR [')) {
const offsetString = value.substring('QWORD PTR ['.length, value.length - 1).trim();
if (offsetString.includes('-')) {
const [l, r] = offsetString.split('-').map(l => interpretValue(process, l.trim()));
const address = l - r;
// qword is 8 bytes
const bytes = 8;
if (lhs) {
return { address, size: bytes };
} else {
return readMemoryBytes(process, address, bytes);
}
}
throw new Error('Unsupported offset calculation: ' + value);
}
return BigInt.asIntN(64, value);
}
function interpret(process) {
do {
const instruction = process.instructions[process.registers.RIP];
switch (instruction.operation.toLowerCase()) {
case 'mov': {
const lhs = interpretValue(process, instruction.operands[0], { lhs: true });
const rhs = interpretValue(process, instruction.operands[1]);
if (REGISTERS.includes(lhs)) {
process.registers[lhs] = rhs;
} else {
writeMemoryBytes(process, lhs.address, rhs, lhs.size);
}
process.registers.RIP++;
break;
}
case 'add': {
const lhs = interpretValue(process, instruction.operands[0], { lhs: true });
const rhs = interpretValue(process, instruction.operands[1]);
process.registers[lhs] += rhs;
process.registers.RIP++;
break;
}
case 'call': {
process.registers.RSP -= 8n;
writeMemoryBytes(process, process.registers.RSP, process.registers.RIP + 1n, 8);
const label = instruction.operands[0];
process.registers.RIP = process.labels[label];
break;
}
case 'ret': {
const value = readMemoryBytes(process, process.registers.RSP, 8);
process.registers.RSP += 8n;
process.registers.RIP = value;
break;
}
case 'push': {
const value = interpretValue(process, instruction.operands[0]);
process.registers.RSP -= 8n;
writeMemoryBytes(process, process.registers.RSP, value, 8);
process.registers.RIP++;
break;
}
case 'pop': {
const lhs = interpretValue(process, instruction.operands[0], { lhs: true });
const value = readMemoryBytes(process, process.registers.RSP, 8);
process.registers.RSP += 8n;
process.registers[lhs] = value;
process.registers.RIP++;
break;
}
case 'syscall': {
const idNumber = Number(process.registers.RAX);
SYSCALLS_BY_ID[idNumber](process);
process.registers.RIP++;
break;
}
}
} while (process.registers.RIP != BigInt(readMemoryBytes(process, BigInt(process.memory.length - 9), 8)));
}
function main(file) {
const memory = new Array(10000);
const code = fs.readFileSync(file).toString();
const { instructions, labels } = parse(code);
const registers = REGISTERS.reduce((rs, r) => ({ ...rs, [r]: 0n }), {});
registers.RIP = BigInt(labels.main === undefined ? labels._main : labels.main);
registers.RSP = BigInt(memory.length - 9);
const process = {
registers,
memory,
instructions,
labels,
fd: {
// stdout
1: global.process.stdout,
}
};
writeMemoryBytes(process, registers.RSP, BigInt(memory.length + 1), 8);
interpret(process);
return Number(process.registers.RAX);
}
process.exit(main(process.argv[2]));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment