From 7675404197a728d32d46e1c59d00233bbb8b8ce9 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Sat, 19 Oct 2024 12:08:46 +0200 Subject: [PATCH] Refactored X86Emulator --- .../main/java/com/ledmington/emu/Main.java | 5 +- .../java/com/ledmington/emu/EmulatorView.java | 12 +- .../ledmington/emu/X86EmulatorAdapter.java | 55 +++++ .../java/com/ledmington/emu/ELFLoader.java | 14 +- .../main/java/com/ledmington/emu/X86Cpu.java | 218 ++++++++++++++++++ .../java/com/ledmington/emu/X86Emulator.java | 203 ++-------------- .../com/ledmington/mem/MemoryController.java | 2 +- 7 files changed, 308 insertions(+), 201 deletions(-) create mode 100644 emu-gui/src/main/java/com/ledmington/emu/X86EmulatorAdapter.java create mode 100644 emu/src/main/java/com/ledmington/emu/X86Cpu.java diff --git a/emu-cli/src/main/java/com/ledmington/emu/Main.java b/emu-cli/src/main/java/com/ledmington/emu/Main.java index cc18c7c6..16597360 100644 --- a/emu-cli/src/main/java/com/ledmington/emu/Main.java +++ b/emu-cli/src/main/java/com/ledmington/emu/Main.java @@ -72,7 +72,7 @@ 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, @@ -80,8 +80,7 @@ private static void run(final String filename, final String... commandLineArgume mem, commandLineArguments, EmulatorConstants.getbaseAddress(), - EmulatorConstants.getStackSize(), - rf); + EmulatorConstants.getStackSize()); logger.info(" ### Execution start ### "); cpu.setEntryPoint(elf.getFileHeader().getEntryPointVirtualAddress()); diff --git a/emu-gui/src/main/java/com/ledmington/emu/EmulatorView.java b/emu-gui/src/main/java/com/ledmington/emu/EmulatorView.java index da6e02ed..6bc8f0c7 100644 --- a/emu-gui/src/main/java/com/ledmington/emu/EmulatorView.java +++ b/emu-gui/src/main/java/com/ledmington/emu/EmulatorView.java @@ -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; @@ -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 { @@ -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(); diff --git a/emu-gui/src/main/java/com/ledmington/emu/X86EmulatorAdapter.java b/emu-gui/src/main/java/com/ledmington/emu/X86EmulatorAdapter.java new file mode 100644 index 00000000..908600c3 --- /dev/null +++ b/emu-gui/src/main/java/com/ledmington/emu/X86EmulatorAdapter.java @@ -0,0 +1,55 @@ +/* + * emu - Processor Emulator + * Copyright (C) 2023-2024 Filippo Barbari + * + * 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 . + */ +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 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); + } +} diff --git a/emu/src/main/java/com/ledmington/emu/ELFLoader.java b/emu/src/main/java/com/ledmington/emu/ELFLoader.java index bca34857..71e44966 100644 --- a/emu/src/main/java/com/ledmington/emu/ELFLoader.java +++ b/emu/src/main/java/com/ledmington/emu/ELFLoader.java @@ -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; @@ -60,7 +63,6 @@ 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, @@ -68,14 +70,16 @@ public static void load( 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); diff --git a/emu/src/main/java/com/ledmington/emu/X86Cpu.java b/emu/src/main/java/com/ledmington/emu/X86Cpu.java new file mode 100644 index 00000000..77b8d4dd --- /dev/null +++ b/emu/src/main/java/com/ledmington/emu/X86Cpu.java @@ -0,0 +1,218 @@ +/* + * emu - Processor Emulator + * Copyright (C) 2023-2024 Filippo Barbari + * + * 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 . + */ +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(); + } +} diff --git a/emu/src/main/java/com/ledmington/emu/X86Emulator.java b/emu/src/main/java/com/ledmington/emu/X86Emulator.java index 893f22c0..79cc290c 100644 --- a/emu/src/main/java/com/ledmington/emu/X86Emulator.java +++ b/emu/src/main/java/com/ledmington/emu/X86Emulator.java @@ -17,197 +17,26 @@ */ 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 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; +import com.ledmington.cpu.x86.Opcode; - public X86Emulator(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 interface X86Emulator { - public void setEntryPoint(final long entryPointVirtualAddress) { - this.entryPointVirtualAddress = entryPointVirtualAddress; - this.instFetch.setPosition(entryPointVirtualAddress); - logger.debug("Entry point virtual address : 0x%x", entryPointVirtualAddress); - } + void setEntryPoint(final long address); - /** Automatically fetches instruction from the emulated memory and executes them. */ - public void execute() { - while (state != State.HALTED) { - executeOne(); - } - } + /** + * Automatically fetches instruction from the emulated memory and executes them. Continues indefinitely until it + * encounters a {@link Opcode#HLT} instruction. + */ + void execute(); - public void executeOne() { - final Instruction inst = dec.decode(); - 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(); - final long src = (inst.secondOperand() instanceof Register) - ? rf.get((Register64) inst.secondOperand()) - : mem.read8(entryPointVirtualAddress - + computeIndirectOperand(rf, (IndirectOperand) inst.secondOperand())); - rf.set(dest, src); - } - 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())); - } - } + /** Fetches next instruction and executes it. */ + void executeOne(); - 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(); - } + /** + * Executes the given instruction without modifying the instruction pointer. + * + * @param inst The instruction to be executed. + */ + void executeOne(final Instruction inst); } diff --git a/mem/src/main/java/com/ledmington/mem/MemoryController.java b/mem/src/main/java/com/ledmington/mem/MemoryController.java index 1d6778a2..f750b743 100644 --- a/mem/src/main/java/com/ledmington/mem/MemoryController.java +++ b/mem/src/main/java/com/ledmington/mem/MemoryController.java @@ -31,7 +31,7 @@ public final class MemoryController implements Memory { private final IntervalArray canWrite = new IntervalArray(); private final IntervalArray canExecute = new IntervalArray(); - // TODO: this does seem like a poor design choice + // TODO: this seems like a poor design choice private final boolean breakOnWrongPermissions; /**