Skip to content

Commit

Permalink
Refactored X86Emulator
Browse files Browse the repository at this point in the history
  • Loading branch information
Ledmington committed Oct 19, 2024
1 parent 8db3248 commit 7675404
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 201 deletions.
5 changes: 2 additions & 3 deletions emu-cli/src/main/java/com/ledmington/emu/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,15 @@ private static void run(final String filename, final String... commandLineArgume

final MemoryController mem = new MemoryController(EmulatorConstants.getMemoryInitializer());
final X86RegisterFile rf = new X86RegisterFile();
final X86Emulator cpu = new X86Emulator(rf, mem);
final X86Emulator cpu = new X86Cpu(rf, mem);

ELFLoader.load(
elf,
cpu,
mem,
commandLineArguments,
EmulatorConstants.getbaseAddress(),
EmulatorConstants.getStackSize(),
rf);
EmulatorConstants.getStackSize());

logger.info(" ### Execution start ### ");
cpu.setEntryPoint(elf.getFileHeader().getEntryPointVirtualAddress());
Expand Down
12 changes: 7 additions & 5 deletions emu-gui/src/main/java/com/ledmington/emu/EmulatorView.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
package com.ledmington.emu;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -42,6 +44,7 @@
import com.ledmington.cpu.x86.Register64;
import com.ledmington.cpu.x86.exc.ReservedOpcode;
import com.ledmington.cpu.x86.exc.UnknownOpcode;
import com.ledmington.elf.ELFReader;
import com.ledmington.mem.MemoryController;

public final class EmulatorView extends Stage {
Expand Down Expand Up @@ -159,24 +162,23 @@ private void loadFile(final File file) {
System.out.printf("Loading file '%s'\n", file.toString());
this.mem = new MemoryController(EmulatorConstants.getMemoryInitializer(), false);
this.regFile = new X86RegisterFile();
this.cpu = new X86Emulator(regFile, mem);
this.cpu = new X86EmulatorAdapter(regFile, mem);
this.decoder = new InstructionDecoderV1(new InstructionFetcher(mem, regFile));

// TODO: implement this
final String[] commandLineArguments = new String[0];

/*try {
try {
ELFLoader.load(
ELFReader.read(Files.readAllBytes(file.toPath())),
cpu,
mem,
commandLineArguments,
EmulatorConstants.getbaseAddress(),
EmulatorConstants.getStackSize(),
this.regFile);
EmulatorConstants.getStackSize());
} catch (IOException e) {
throw new RuntimeException(e);
}*/
}

updateRegisters();
updateCode();
Expand Down
55 changes: 55 additions & 0 deletions emu-gui/src/main/java/com/ledmington/emu/X86EmulatorAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* emu - Processor Emulator
* Copyright (C) 2023-2024 Filippo Barbari <filippo.barbari@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.ledmington.emu;

import java.util.ArrayDeque;
import java.util.Objects;
import java.util.Queue;

import com.ledmington.cpu.x86.Instruction;
import com.ledmington.mem.MemoryController;

public final class X86EmulatorAdapter implements X86Emulator {

private final X86Cpu cpu;
private final Queue<Instruction> instructions = new ArrayDeque<>();

public X86EmulatorAdapter(final X86RegisterFile regFile, final MemoryController mem) {
this.cpu = new X86Cpu(Objects.requireNonNull(regFile), Objects.requireNonNull(mem));
}

@Override
public void setEntryPoint(final long address) {
cpu.setEntryPoint(address);
}

@Override
public void execute() {
throw new Error("Not implemented");
}

@Override
public void executeOne() {
throw new Error("Not implemented");
}

@Override
public void executeOne(final Instruction inst) {
instructions.add(inst);
}
}
14 changes: 9 additions & 5 deletions emu/src/main/java/com/ledmington/emu/ELFLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import java.util.Objects;
import java.util.stream.Collectors;

import com.ledmington.cpu.x86.Immediate;
import com.ledmington.cpu.x86.Instruction;
import com.ledmington.cpu.x86.Opcode;
import com.ledmington.cpu.x86.Register64;
import com.ledmington.elf.ELF;
import com.ledmington.elf.PHTEntry;
Expand Down Expand Up @@ -60,22 +63,23 @@ private ELFLoader() {}
* @param commandLineArguments The arguments to pass to the program.
* @param baseAddress The address where to start loading the file.
* @param stackSize The size in bytes of the stack.
* @param rf Register file of the CPU.
*/
public static void load(
final ELF elf,
final X86Emulator cpu,
final MemoryController mem,
final String[] commandLineArguments,
final long baseAddress,
final long stackSize,
final X86RegisterFile rf) {
final long stackSize) {
loadSegments(elf, mem, baseAddress);
loadSections(elf, mem, baseAddress);
final long highestAddress = setupStack(elf, stackSize, mem);

// we make RSP point at the last 8 bytes of allocated memory
rf.set(Register64.RSP, highestAddress + stackSize - 8L);
// We make RSP point at the last 8 bytes of allocated memory
final long stackPointer = highestAddress + stackSize - 8L;

// This is a fake instruction
cpu.executeOne(new Instruction(Opcode.MOV, Register64.RSP, new Immediate(stackPointer)));

loadCommandLineArgumentsAndEnvironmentVariables(
mem, highestAddress, elf.getFileHeader().is32Bit(), commandLineArguments);
Expand Down
218 changes: 218 additions & 0 deletions emu/src/main/java/com/ledmington/emu/X86Cpu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* emu - Processor Emulator
* Copyright (C) 2023-2024 Filippo Barbari <filippo.barbari@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.ledmington.emu;

import java.util.Objects;

import com.ledmington.cpu.x86.Immediate;
import com.ledmington.cpu.x86.IndirectOperand;
import com.ledmington.cpu.x86.Instruction;
import com.ledmington.cpu.x86.InstructionDecoder;
import com.ledmington.cpu.x86.InstructionDecoderV1;
import com.ledmington.cpu.x86.Register;
import com.ledmington.cpu.x86.Register32;
import com.ledmington.cpu.x86.Register64;
import com.ledmington.cpu.x86.Register8;
import com.ledmington.cpu.x86.RelativeOffset;
import com.ledmington.mem.MemoryController;
import com.ledmington.utils.BitUtils;
import com.ledmington.utils.MiniLogger;

/** Emulator of an x86 CPU. */
public final class X86Cpu implements X86Emulator {

private static final MiniLogger logger = MiniLogger.getLogger("x86-emu");

private enum State {
RUNNING,
HALTED
}

private final X86RegisterFile rf;
private final MemoryController mem;
private final InstructionFetcher instFetch;
private final InstructionDecoder dec;
private State state = State.RUNNING;
private long entryPointVirtualAddress = 0L;

public X86Cpu(final X86RegisterFile rf, final MemoryController mem) {
this.rf = Objects.requireNonNull(rf);
this.mem = Objects.requireNonNull(mem);
this.instFetch = new InstructionFetcher(mem, rf);
this.dec = new InstructionDecoderV1(instFetch);
}

public void setEntryPoint(final long entryPointVirtualAddress) {
this.entryPointVirtualAddress = entryPointVirtualAddress;
this.instFetch.setPosition(entryPointVirtualAddress);
logger.debug("Entry point virtual address : 0x%x", entryPointVirtualAddress);
}

public void execute() {
while (state != State.HALTED) {
executeOne();
}
}

public void executeOne() {
executeOne(dec.decode());
}

public void executeOne(final Instruction inst) {
logger.debug(inst.toIntelSyntax());
switch (inst.opcode()) {
case SUB -> {
if (inst.firstOperand() instanceof Register64) {
final long r1 = rf.get((Register64) inst.firstOperand());
final long r2 = rf.get((Register64) inst.secondOperand());
rf.set((Register64) inst.firstOperand(), r1 - r2);
} else {
throw new IllegalArgumentException(String.format(
"Don't know what to do when SUB has %,d bits", ((Register) inst.firstOperand()).bits()));
}
}
case ADD -> {
if (inst.firstOperand() instanceof Register64) {
final long r1 = rf.get((Register64) inst.firstOperand());
final long r2 = rf.get((Register64) inst.secondOperand());
rf.set((Register64) inst.firstOperand(), r1 + r2);
} else {
throw new IllegalArgumentException(String.format(
"Don't know what to do when ADD has %,d bits", ((Register) inst.firstOperand()).bits()));
}
}
case SHR -> {
if (inst.firstOperand() instanceof Register64) {
final long r1 = rf.get((Register64) inst.firstOperand());
final byte imm = ((Immediate) inst.secondOperand()).asByte();
rf.set((Register64) inst.firstOperand(), r1 >>> imm);
} else {
throw new IllegalArgumentException(String.format(
"Don't know what to do when SHR has %,d bits", ((Register) inst.firstOperand()).bits()));
}
}
case SAR -> {
if (inst.firstOperand() instanceof Register64) {
final long r1 = rf.get((Register64) inst.firstOperand());
final byte imm = ((Immediate) inst.secondOperand()).asByte();
rf.set((Register64) inst.firstOperand(), r1 >> imm);
} else {
throw new IllegalArgumentException(String.format(
"Don't know what to do when SAR has %,d bits", ((Register) inst.firstOperand()).bits()));
}
}
case SHL -> {
if (inst.firstOperand() instanceof Register64) {
final long r1 = rf.get((Register64) inst.firstOperand());
final byte imm = rf.get((Register8) inst.secondOperand());
rf.set((Register64) inst.firstOperand(), r1 << imm);
} else {
throw new IllegalArgumentException(String.format(
"Don't know what to do when SHL has %,d bits", ((Register) inst.firstOperand()).bits()));
}
}
case XOR -> {
switch (((Register) inst.firstOperand()).bits()) {
case 8 -> {
final byte r1 = rf.get((Register8) inst.firstOperand());
final byte r2 = rf.get((Register8) inst.secondOperand());
rf.set((Register8) inst.firstOperand(), BitUtils.xor(r1, r2));
}
case 32 -> {
final int r1 = rf.get((Register32) inst.firstOperand());
final int r2 = rf.get((Register32) inst.secondOperand());
rf.set((Register32) inst.firstOperand(), r1 ^ r2);
}
default -> throw new IllegalArgumentException(String.format(
"Don't know what to do when XOR has %,d bits", ((Register) inst.firstOperand()).bits()));
}
}
case AND -> {
if (inst.firstOperand() instanceof Register64 r64) {
final long imm64 = ((Immediate) inst.secondOperand()).asLong();
rf.set(r64, rf.get(r64) & imm64);
} else {
throw new IllegalArgumentException(
String.format("Unknown type of first operand '%s'", inst.firstOperand()));
}
}
case TEST -> {
final long r1 = rf.get((Register64) inst.firstOperand());
final long r2 = rf.get((Register64) inst.secondOperand());
rf.set(RFlags.Zero, (r1 & r2) == 0L);
}
case JMP -> {
final long offset = (inst.firstOperand() instanceof Register64)
? rf.get((Register64) inst.firstOperand())
: ((RelativeOffset) inst.firstOperand()).getValue();
instFetch.setPosition(instFetch.getPosition() + offset);
}
case JE -> {
if (rf.isSet(RFlags.Zero)) {
instFetch.setPosition(instFetch.getPosition() + ((RelativeOffset) inst.firstOperand()).getValue());
}
}
case MOV -> {
final Register64 dest = (Register64) inst.firstOperand();
switch (inst.secondOperand()) {
case Register64 src -> rf.set(dest, rf.get(src));
case IndirectOperand io -> rf.set(
dest, mem.read8(entryPointVirtualAddress + computeIndirectOperand(rf, io)));
case Immediate imm -> rf.set(dest, imm.asLong());
default -> throw new IllegalArgumentException(
String.format("Unknown argument type '%s'", inst.secondOperand()));
}
}
case PUSH -> {
final Register64 src = (Register64) inst.firstOperand();
final long rsp = rf.get(Register64.RSP);
mem.write(rsp, rf.get(src));
// the stack "grows downward"
rf.set(Register64.RSP, rsp - 8L);
}
case POP -> {
final Register64 dest = (Register64) inst.firstOperand();
final long rsp = rf.get(Register64.RSP);
rf.set(dest, mem.read8(rsp));
// the stack "grows downward"
rf.set(Register64.RSP, rsp + 8L);
}
case LEA -> {
final Register64 dest = (Register64) inst.firstOperand();
final IndirectOperand src = (IndirectOperand) inst.secondOperand();
final long result = computeIndirectOperand(rf, src);
rf.set(dest, result);
}
case CALL -> {
// TODO: check this
final IndirectOperand src = (IndirectOperand) inst.firstOperand();
final long result = computeIndirectOperand(rf, src);
rf.set(Register64.RIP, result);
}
case ENDBR64 -> logger.warning("ENDBR64 not implemented");
case HLT -> state = State.HALTED;
default -> throw new IllegalStateException(String.format("Unknwon instruction %s", inst.toIntelSyntax()));
}
}

private long computeIndirectOperand(final X86RegisterFile rf, final IndirectOperand io) {
return ((io.base() == null) ? 0L : rf.get((Register64) io.base()))
+ ((io.index() == null) ? 0L : rf.get((Register64) io.index())) * io.scale()
+ io.getDisplacement();
}
}
Loading

0 comments on commit 7675404

Please sign in to comment.