diff --git a/elf/src/main/java/com/ledmington/elf/section/note/NoteSectionEntryType.java b/elf/src/main/java/com/ledmington/elf/section/note/NoteSectionEntryType.java index 8ae2f7d..8d90aa3 100644 --- a/elf/src/main/java/com/ledmington/elf/section/note/NoteSectionEntryType.java +++ b/elf/src/main/java/com/ledmington/elf/section/note/NoteSectionEntryType.java @@ -63,13 +63,13 @@ public static NoteSectionEntryType fromCode(final String owner, final int type) case 3 -> NT_GNU_BUILD_ID; case 4 -> NT_GNU_GOLD_VERSION; case 5 -> NT_GNU_PROPERTY_TYPE_0; - default -> throw new IllegalArgumentException(String.format( - "Unknown note section entry type %d (0x%08x) for owner '%s'", type, type, owner)); + default -> throw new IllegalArgumentException( + String.format("Unknown note section entry type %d (0x%08x) for owner '%s'", type, type, owner)); }; case "stapsdt" -> switch (type) { case 3 -> NT_STAPSDT; - default -> throw new IllegalArgumentException(String.format( - "Unknown note section entry type %d (0x%08x) for owner '%s'", type, type, owner)); + default -> throw new IllegalArgumentException( + String.format("Unknown note section entry type %d (0x%08x) for owner '%s'", type, type, owner)); }; default -> throw new IllegalArgumentException( String.format("Unknown note section entry owner '%s'", owner)); 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 f25a5f9..199a6de 100644 --- a/emu-cli/src/main/java/com/ledmington/emu/Main.java +++ b/emu-cli/src/main/java/com/ledmington/emu/Main.java @@ -75,8 +75,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 X86Cpu(rf, mem); + final X86Emulator cpu = new X86Cpu(mem); ELFLoader.load( elf, diff --git a/emu-gui/src/main/java/com/ledmington/view/ELFView.java b/emu-gui/src/main/java/com/ledmington/view/ELFView.java index a26594f..3b275f4 100644 --- a/emu-gui/src/main/java/com/ledmington/view/ELFView.java +++ b/emu-gui/src/main/java/com/ledmington/view/ELFView.java @@ -26,7 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import javafx.geometry.Insets; @@ -36,7 +35,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.text.Font; -import javafx.stage.Stage; import com.ledmington.elf.ELF; import com.ledmington.elf.ELFReader; @@ -56,7 +54,6 @@ import com.ledmington.elf.section.SectionHeaderFlags; import com.ledmington.elf.section.SectionHeaderType; import com.ledmington.elf.section.StringTableSection; -import com.ledmington.elf.section.X86_64_Unwind; import com.ledmington.elf.section.gnu.GnuHashSection; import com.ledmington.elf.section.gnu.GnuVersionRequirementEntry; import com.ledmington.elf.section.gnu.GnuVersionRequirementsSection; @@ -85,13 +82,8 @@ private record Range(int offset, int length) { throw new IllegalArgumentException(String.format("Invalid range [%d; %d+%d]", offset, offset, length)); } } - - public Range(final int singleByte) { - this(singleByte, MINIMUM_ALLOWED_LENGTH); - } } - private final Stage parent; private final TextArea addressArea = new TextArea(); private final TextArea hexContentArea = new TextArea(); private final TextArea asciiContentArea = new TextArea(); @@ -102,9 +94,7 @@ public Range(final int singleByte) { private File file = null; private int startByte = 0; - public ELFView(final Stage parent) { - this.parent = Objects.requireNonNull(parent); - + public ELFView() { final TreeItem root = new TreeItem<>(""); tree = new TreeView<>(root); tree.setEditable(false); @@ -179,7 +169,6 @@ public ELFView(final Stage parent) { this.setRight(grid); this.setPadding(new Insets(5)); - parent.sizeToScene(); } public void loadFile(final File elfFile) { @@ -199,8 +188,6 @@ public void loadFile(final File elfFile) { initializeTreeView(elf); updateGrid(0); - - this.parent.sizeToScene(); } private void initializeTreeView(final ELF elf) { @@ -219,56 +206,61 @@ private void initializeTreeView(final ELF elf) { root.getChildren().addAll(List.of(fileHeader, PHTRoot, SHTRoot)); } - private TreeItem getTreeItem(final String name, final Range range) { + private TreeItem getTreeItem(final String name, final int offset, final int length) { + final Range range = new Range(offset, length); final TreeItem ti = new TreeItem<>(name); ranges.put(ti, range); return ti; } + private TreeItem getTreeItem(final String name, final int offset) { + return getTreeItem(name, offset, 1); + } + private List> initializeFileHeader(final FileHeader fh) { final List> fileHeaderElements = new ArrayList<>(); final int wordSize = fh.is32Bit() ? 4 : 8; int i = 0; - fileHeaderElements.add(getTreeItem("Signature = 0x7f454c46", new Range(i, 4))); + fileHeaderElements.add(getTreeItem("Signature = 0x7f454c46", i, 4)); i += 4; - fileHeaderElements.add(getTreeItem("Class = " + (fh.is32Bit() ? "32 bit" : "64 bit"), new Range(i))); + fileHeaderElements.add(getTreeItem("Class = " + (fh.is32Bit() ? "32 bit" : "64 bit"), i)); i++; fileHeaderElements.add( - getTreeItem("Endianness = " + (fh.isLittleEndian() ? "little-endian" : "big-endian"), new Range(i))); + getTreeItem("Endianness = " + (fh.isLittleEndian() ? "little-endian" : "big-endian"), i)); i++; - fileHeaderElements.add(getTreeItem("Version = 1", new Range(i))); + fileHeaderElements.add(getTreeItem("Version = 1", i)); i++; - fileHeaderElements.add(getTreeItem("OS/ABI = " + fh.getOSABI().getName(), new Range(i))); + fileHeaderElements.add(getTreeItem("OS/ABI = " + fh.getOSABI().getName(), i)); i++; - fileHeaderElements.add(getTreeItem("ABI version = " + fh.getABIVersion(), new Range(i))); + fileHeaderElements.add(getTreeItem("ABI version = " + fh.getABIVersion(), i)); i++; fileHeaderElements.add( - getTreeItem("File type = " + fh.getFileType().name().replaceFirst("^ET_", ""), new Range(i, 2))); + getTreeItem("File type = " + fh.getFileType().name().replaceFirst("^ET_", ""), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("ISA = " + fh.getISA().getName(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("ISA = " + fh.getISA().getName(), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("Version = " + fh.getVersion(), new Range(i, 4))); + fileHeaderElements.add(getTreeItem("Version = " + fh.getVersion(), i, 4)); i += 4; - fileHeaderElements.add(getTreeItem("Entrypoint = " + fh.getEntryPointVirtualAddress(), new Range(i, wordSize))); + fileHeaderElements.add(getTreeItem("Entrypoint = " + fh.getEntryPointVirtualAddress(), i, wordSize)); i += wordSize; - fileHeaderElements.add(getTreeItem("PHT offset = " + fh.getProgramHeaderTableOffset(), new Range(i, wordSize))); + fileHeaderElements.add(getTreeItem("PHT offset = " + fh.getProgramHeaderTableOffset(), i, wordSize)); i += wordSize; - fileHeaderElements.add(getTreeItem("SHT offset = " + fh.getSectionHeaderTableOffset(), new Range(i, wordSize))); + fileHeaderElements.add(getTreeItem("SHT offset = " + fh.getSectionHeaderTableOffset(), i, wordSize)); i += wordSize; - fileHeaderElements.add(getTreeItem("Flags = " + fh.getFlags(), new Range(i, 4))); + fileHeaderElements.add(getTreeItem("Flags = " + fh.getFlags(), i, 4)); i += 4; - fileHeaderElements.add(getTreeItem("Header size = " + fh.getHeaderSize(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("Header size = " + fh.getHeaderSize(), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("PHT entry size = " + fh.getProgramHeaderTableEntrySize(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("PHT entry size = " + fh.getProgramHeaderTableEntrySize(), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("PHT entries = " + fh.getNumProgramHeaderTableEntries(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("PHT entries = " + fh.getNumProgramHeaderTableEntries(), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("SHT entry size = " + fh.getSectionHeaderTableEntrySize(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("SHT entry size = " + fh.getSectionHeaderTableEntrySize(), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("SHT entries = " + fh.getNumSectionHeaderTableEntries(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("SHT entries = " + fh.getNumSectionHeaderTableEntries(), i, 2)); i += 2; - fileHeaderElements.add(getTreeItem("shstrndx = " + fh.getSectionHeaderStringTableIndex(), new Range(i, 2))); + fileHeaderElements.add(getTreeItem("shstrndx = " + fh.getSectionHeaderStringTableIndex(), i, 2)); return fileHeaderElements; } @@ -281,35 +273,32 @@ private List> initializeProgramHeaderTable(final FileHeader fh, final long PHTEntrySize = fh.getProgramHeaderTableEntrySize(); for (int i = 0; i < pht.getProgramHeaderTableLength(); i++) { final TreeItem x = - getTreeItem(String.valueOf(i), new Range((int) (PHTOffset + i * PHTEntrySize), (int) PHTEntrySize)); + getTreeItem(String.valueOf(i), (int) (PHTOffset + i * PHTEntrySize), (int) PHTEntrySize); PHTElements.add(x); final PHTEntry phte = pht.getProgramHeader(i); final long entryOffset = PHTOffset + i * PHTEntrySize; int k = (int) entryOffset; - x.getChildren().add(getTreeItem("Type = " + phte.getType().getName(), new Range(k, 4))); + x.getChildren().add(getTreeItem("Type = " + phte.getType().getName(), k, 4)); k += 4; x.getChildren() .add(getTreeItem( "Flags = " + (phte.isReadable() ? "R" : " ") + (phte.isWriteable() ? "W" : " ") + (phte.isExecutable() ? "X" : " "), - new Range(k, 4))); + k, + 4)); k += 4; - x.getChildren().add(getTreeItem("Segment offset = " + phte.getSegmentOffset(), new Range(k, wordSize))); + x.getChildren().add(getTreeItem("Segment offset = " + phte.getSegmentOffset(), k, wordSize)); k += wordSize; - x.getChildren() - .add(getTreeItem("Segment vaddr = " + phte.getSegmentVirtualAddress(), new Range(k, wordSize))); + x.getChildren().add(getTreeItem("Segment vaddr = " + phte.getSegmentVirtualAddress(), k, wordSize)); k += wordSize; - x.getChildren() - .add(getTreeItem("Segment paddr = " + phte.getSegmentPhysicalAddress(), new Range(k, wordSize))); + x.getChildren().add(getTreeItem("Segment paddr = " + phte.getSegmentPhysicalAddress(), k, wordSize)); k += wordSize; - x.getChildren() - .add(getTreeItem("Segment file size = " + phte.getSegmentFileSize(), new Range(k, wordSize))); + x.getChildren().add(getTreeItem("Segment file size = " + phte.getSegmentFileSize(), k, wordSize)); k += wordSize; - x.getChildren() - .add(getTreeItem("Segment memory size = " + phte.getSegmentMemorySize(), new Range(k, wordSize))); + x.getChildren().add(getTreeItem("Segment memory size = " + phte.getSegmentMemorySize(), k, wordSize)); k += wordSize; - x.getChildren().add(getTreeItem("Segment alignment = " + phte.getAlignment(), new Range(k, wordSize))); + x.getChildren().add(getTreeItem("Segment alignment = " + phte.getAlignment(), k, wordSize)); } return PHTElements; @@ -317,102 +306,108 @@ private List> initializeProgramHeaderTable(final FileHeader fh, private List> initializeSectionTable(final FileHeader fh, final SectionTable st) { final List> STElements = new ArrayList<>(); - final int wordSize = fh.is32Bit() ? 4 : 8; for (int i = 0; i < st.getSectionTableLength(); i++) { final Section s = st.getSection(i); - final SectionHeader sh = s.getHeader(); - final TreeItem x = new TreeItem<>(s.getName().isEmpty() ? "(null)" : s.getName()); - STElements.add(x); - final long sectionHeaderOffset = fh.getSectionHeaderTableOffset() + (long) i * fh.getSectionHeaderTableEntrySize(); - final TreeItem shRoot = getTreeItem( - "Section Header", new Range((int) sectionHeaderOffset, fh.getSectionHeaderTableEntrySize())); - x.getChildren().add(shRoot); - { - int k = (int) sectionHeaderOffset; - shRoot.getChildren().add(getTreeItem("Name offset = " + sh.getNameOffset(), new Range(k, 4))); - k += 4; - shRoot.getChildren().add(getTreeItem("Type = " + sh.getType().getName(), new Range(k, 4))); - k += 4; - shRoot.getChildren() - .add(getTreeItem( - "Flags = " - + (sh.getFlags().isEmpty() - ? "(none)" - : sh.getFlags().stream() - .map(SectionHeaderFlags::getName) - .collect(Collectors.joining(", "))), - new Range(k, wordSize))); - k += wordSize; - shRoot.getChildren().add(getTreeItem("Vaddr = " + sh.getVirtualAddress(), new Range(k, wordSize))); - k += wordSize; - shRoot.getChildren().add(getTreeItem("File offset = " + sh.getFileOffset(), new Range(k, wordSize))); - k += wordSize; - shRoot.getChildren().add(getTreeItem("Section size = " + sh.getSectionSize(), new Range(k, wordSize))); - k += wordSize; - shRoot.getChildren() - .add(getTreeItem("Linked section = " + sh.getLinkedSectionIndex(), new Range(k, 4))); - k += 4; - shRoot.getChildren().add(getTreeItem("sh_info = " + sh.getInfo(), new Range(k, 4))); - k += 4; - shRoot.getChildren().add(getTreeItem("Alignment = " + sh.getAlignment(), new Range(k, wordSize))); - k += wordSize; - shRoot.getChildren().add(getTreeItem("Entry size = " + sh.getEntrySize(), new Range(k, wordSize))); - } + STElements.add( + initializeSection(s, sectionHeaderOffset, fh.getSectionHeaderTableEntrySize(), fh.is32Bit())); + } - if (sh.getType() == SectionHeaderType.SHT_NULL - || sh.getType() == SectionHeaderType.SHT_NOBITS - || sh.getSectionSize() == 0L) { - x.getChildren().add(new TreeItem<>("No Content")); - } else { - final TreeItem root = - getTreeItem("Section Content", new Range((int) sh.getFileOffset(), (int) sh.getSectionSize())); - - switch (s) { - case InterpreterPathSection interp -> initializeInterpreterPathSection(root, interp); - case BasicProgBitsSection pbs -> initializeProgBitsSection(root, pbs); - case DynamicSection dyn -> initializeDynamicSection(root, dyn, wordSize); - case NoteSection ns -> initializeNoteSection(root, ns); - case SymbolTable symtab -> initializeSymbolTable(root, symtab, fh.is32Bit()); - case StringTableSection strtab -> initializeStringTable(root, strtab); - case RelocationSection rs -> initializeRelocationSection(root, rs, wordSize); - case RelocationAddendSection ras -> initializeRelocationAddendSection(root, ras, wordSize); - case ConstructorsSection cs -> initializeConstructorsSection(root, cs, wordSize); - case DestructorsSection ds -> initializeDestructorsSection(root, ds, wordSize); - case HashTableSection hts -> initializeHashTableSection(root, hts); - case GnuVersionSection gvs -> initializeGnuVersionSection(root, gvs); - case GnuVersionRequirementsSection gvrs -> initializeGnuVersionRequirementsSection(root, gvrs); - case GnuHashSection ghs -> initializeGnuHashSection(root, ghs, wordSize); - case X86_64_Unwind xu -> initializeX86Unwind(root, xu); - default -> throw new IllegalArgumentException(String.format( - "Unknown section with type '%s' and name '%s'", - sh.getType().getName(), s.getName())); - } + return STElements; + } - x.getChildren().add(root); + private TreeItem initializeSection( + final Section s, + final long sectionHeaderOffset, + final long sectionHeaderTableEntrySize, + final boolean is32Bit) { + final SectionHeader sh = s.getHeader(); + final int wordSize = is32Bit ? 4 : 8; + final TreeItem x = new TreeItem<>(s.getName().isEmpty() ? "(null)" : s.getName()); + final TreeItem shRoot = + getTreeItem("Section Header", (int) sectionHeaderOffset, (int) sectionHeaderTableEntrySize); + x.getChildren().add(shRoot); + + int k = (int) sectionHeaderOffset; + shRoot.getChildren().add(getTreeItem("Name offset = " + sh.getNameOffset(), k, 4)); + k += 4; + shRoot.getChildren().add(getTreeItem("Type = " + sh.getType().getName(), k, 4)); + k += 4; + shRoot.getChildren() + .add(getTreeItem( + "Flags = " + + (sh.getFlags().isEmpty() + ? "(none)" + : sh.getFlags().stream() + .map(SectionHeaderFlags::getName) + .collect(Collectors.joining(", "))), + k, + wordSize)); + k += wordSize; + shRoot.getChildren().add(getTreeItem("Vaddr = " + sh.getVirtualAddress(), k, wordSize)); + k += wordSize; + shRoot.getChildren().add(getTreeItem("File offset = " + sh.getFileOffset(), k, wordSize)); + k += wordSize; + shRoot.getChildren().add(getTreeItem("Section size = " + sh.getSectionSize(), k, wordSize)); + k += wordSize; + shRoot.getChildren().add(getTreeItem("Linked section = " + sh.getLinkedSectionIndex(), k, 4)); + k += 4; + shRoot.getChildren().add(getTreeItem("sh_info = " + sh.getInfo(), k, 4)); + k += 4; + shRoot.getChildren().add(getTreeItem("Alignment = " + sh.getAlignment(), k, wordSize)); + k += wordSize; + shRoot.getChildren().add(getTreeItem("Entry size = " + sh.getEntrySize(), k, wordSize)); + + if (sh.getType() == SectionHeaderType.SHT_NULL + || sh.getType() == SectionHeaderType.SHT_NOBITS + || sh.getSectionSize() == 0L) { + x.getChildren().add(new TreeItem<>("No Content")); + } else { + final TreeItem root = + getTreeItem("Section Content", (int) sh.getFileOffset(), (int) sh.getSectionSize()); + + switch (s) { + case InterpreterPathSection interp -> initializeInterpreterPathSection(root, interp); + case BasicProgBitsSection pbs -> initializeProgBitsSection(root, pbs); + case DynamicSection dyn -> initializeDynamicSection(root, dyn, wordSize); + case NoteSection ns -> initializeNoteSection(root, ns); + case SymbolTable symtab -> initializeSymbolTable(root, symtab, is32Bit); + case StringTableSection strtab -> initializeStringTable(root, strtab); + case RelocationSection rs -> initializeRelocationSection(root, rs, wordSize); + case RelocationAddendSection ras -> initializeRelocationAddendSection(root, ras, wordSize); + case ConstructorsSection cs -> initializeConstructorsSection(root, cs, wordSize); + case DestructorsSection ds -> initializeDestructorsSection(root, ds, wordSize); + case HashTableSection hts -> initializeHashTableSection(root, hts); + case GnuVersionSection gvs -> initializeGnuVersionSection(root, gvs); + case GnuVersionRequirementsSection gvrs -> initializeGnuVersionRequirementsSection(root, gvrs); + case GnuHashSection ghs -> initializeGnuHashSection(root, ghs, wordSize); + default -> throw new IllegalArgumentException(String.format( + "Unknown section with type '%s' and name '%s'", + sh.getType().getName(), s.getName())); } + + x.getChildren().add(root); } - return STElements; + return x; } private void initializeInterpreterPathSection(final TreeItem root, final InterpreterPathSection interp) { root.getChildren() .add(getTreeItem( "Interpreter Path", - new Range( - (int) interp.getHeader().getFileOffset(), - interp.getInterpreterFilePath().length()))); + (int) interp.getHeader().getFileOffset(), + interp.getInterpreterFilePath().length())); } private void initializeProgBitsSection(final TreeItem root, final BasicProgBitsSection pbs) { root.getChildren() .add(getTreeItem( // TODO: change name - "Content", new Range((int) pbs.getHeader().getFileOffset(), (int) - pbs.getHeader().getSectionSize()))); + "Content", (int) pbs.getHeader().getFileOffset(), (int) + pbs.getHeader().getSectionSize())); } private void initializeDynamicSection(final TreeItem root, final DynamicSection dyn, final int wordSize) { @@ -420,10 +415,9 @@ private void initializeDynamicSection(final TreeItem root, final Dynamic for (int i = 0; i < dyn.getTableLength(); i++) { final DynamicTableEntry dte = dyn.getEntry(i); final int entryStart = (int) dyn.getHeader().getFileOffset() + entrySize * i; - final TreeItem x = - getTreeItem("#" + i + ": " + dte.getTag().getName(), new Range(entryStart, entrySize)); - x.getChildren().add(getTreeItem("tag = " + dte.getTag().getName(), new Range(entryStart, wordSize))); - x.getChildren().add(getTreeItem("content", new Range(entryStart + wordSize, wordSize))); + final TreeItem x = getTreeItem("#" + i + ": " + dte.getTag().getName(), entryStart, entrySize); + x.getChildren().add(getTreeItem("tag = " + dte.getTag().getName(), entryStart, wordSize)); + x.getChildren().add(getTreeItem("content", entryStart + wordSize, wordSize)); root.getChildren().add(x); } } @@ -433,14 +427,12 @@ private void initializeNoteSection(final TreeItem root, final NoteSectio for (int i = 0; i < ns.getNumEntries(); i++) { final NoteSectionEntry nse = ns.getEntry(i); final int nameLength = nse.getName().length() + 1; - final TreeItem entry = getTreeItem("Entry " + i, new Range(start, nse.getAlignedSize())); - entry.getChildren().add(getTreeItem("namesz = " + nameLength, new Range(start, 4))); - entry.getChildren().add(getTreeItem("descsz = " + nse.getDescriptionLength(), new Range(start + 4, 4))); - entry.getChildren() - .add(getTreeItem("type = " + nse.getType().getDescription(), new Range(start + 4 + 4, 4))); - entry.getChildren().add(getTreeItem("name = " + nse.getName(), new Range(start + 4 + 4 + 4, nameLength))); - entry.getChildren() - .add(getTreeItem("desc", new Range(start + 4 + 4 + 4 + nameLength, nse.getDescriptionLength()))); + final TreeItem entry = getTreeItem("Entry " + i, start, nse.getAlignedSize()); + entry.getChildren().add(getTreeItem("namesz = " + nameLength, start, 4)); + entry.getChildren().add(getTreeItem("descsz = " + nse.getDescriptionLength(), start + 4, 4)); + entry.getChildren().add(getTreeItem("type = " + nse.getType().getDescription(), start + 4 + 4, 4)); + entry.getChildren().add(getTreeItem("name = " + nse.getName(), start + 4 + 4 + 4, nameLength)); + entry.getChildren().add(getTreeItem("desc", start + 4 + 4 + 4 + nameLength, nse.getDescriptionLength())); root.getChildren().add(entry); start += nse.getAlignedSize(); @@ -449,50 +441,49 @@ private void initializeNoteSection(final TreeItem root, final NoteSectio private void initializeGnuHashSection(final TreeItem root, final GnuHashSection ghs, final int wordSize) { final int start = (int) ghs.getHeader().getFileOffset(); - root.getChildren().add(getTreeItem("nbuckets = " + ghs.getBucketsLength(), new Range(start, 4))); - root.getChildren().add(getTreeItem("symndx = " + ghs.getSymbolTableIndex(), new Range(start + 4, 4))); - root.getChildren().add(getTreeItem("maskwords = " + ghs.getBloomFilterLength(), new Range(start + 4 + 4, 4))); - root.getChildren().add(getTreeItem("shift2 = " + ghs.getBloomShift(), new Range(start + 4 + 4 + 4, 4))); + root.getChildren().add(getTreeItem("nbuckets = " + ghs.getBucketsLength(), start, 4)); + root.getChildren().add(getTreeItem("symndx = " + ghs.getSymbolTableIndex(), start + 4, 4)); + root.getChildren().add(getTreeItem("maskwords = " + ghs.getBloomFilterLength(), start + 4 + 4, 4)); + root.getChildren().add(getTreeItem("shift2 = " + ghs.getBloomShift(), start + 4 + 4 + 4, 4)); final int startBloom = start + 4 + 4 + 4 + 4; - final TreeItem bloom = - getTreeItem("Bloom Filter", new Range(startBloom, wordSize * ghs.getBloomFilterLength())); + final TreeItem bloom = getTreeItem("Bloom Filter", startBloom, wordSize * ghs.getBloomFilterLength()); for (int i = 0; i < ghs.getBloomFilterLength(); i++) { - bloom.getChildren().add(getTreeItem("#" + i, new Range(startBloom + wordSize * i, wordSize))); + bloom.getChildren().add(getTreeItem("#" + i, startBloom + wordSize * i, wordSize)); } root.getChildren().add(bloom); final int startBuckets = startBloom + wordSize * ghs.getBloomFilterLength(); - final TreeItem buckets = getTreeItem("Buckets", new Range(startBuckets, 4 * ghs.getBucketsLength())); + final TreeItem buckets = getTreeItem("Buckets", startBuckets, 4 * ghs.getBucketsLength()); for (int i = 0; i < ghs.getBucketsLength(); i++) { - buckets.getChildren().add(getTreeItem("#" + i, new Range(startBuckets + 4 * i, 4))); + buckets.getChildren().add(getTreeItem("#" + i, startBuckets + 4 * i, 4)); } root.getChildren().add(buckets); final int startChains = startBuckets + 4 * ghs.getChainsLength(); - final TreeItem chains = getTreeItem("Chains", new Range(startChains, 4 * ghs.getChainsLength())); + final TreeItem chains = getTreeItem("Chains", startChains, 4 * ghs.getChainsLength()); for (int i = 0; i < ghs.getChainsLength(); i++) { - chains.getChildren().add(getTreeItem("#" + i, new Range(startChains + 4 * i, 4))); + chains.getChildren().add(getTreeItem("#" + i, startChains + 4 * i, 4)); } root.getChildren().add(chains); } private void initializeHashTableSection(final TreeItem root, final HashTableSection hts) { final int start = (int) hts.getHeader().getFileOffset(); - root.getChildren().add(getTreeItem("nbuckets = " + hts.getNumBuckets(), new Range(start, 4))); - root.getChildren().add(getTreeItem("nchain = " + hts.getNumChains(), new Range(start + 4, 4))); + root.getChildren().add(getTreeItem("nbuckets = " + hts.getNumBuckets(), start, 4)); + root.getChildren().add(getTreeItem("nchain = " + hts.getNumChains(), start + 4, 4)); final int startBuckets = start + 4 + 4; - final TreeItem buckets = getTreeItem("Buckets", new Range(startBuckets, 4 * hts.getNumBuckets())); + final TreeItem buckets = getTreeItem("Buckets", startBuckets, 4 * hts.getNumBuckets()); for (int i = 0; i < hts.getNumBuckets(); i++) { - buckets.getChildren().add(getTreeItem("#" + i, new Range(startBuckets + 4 * i, 4))); + buckets.getChildren().add(getTreeItem("#" + i, startBuckets + 4 * i, 4)); } root.getChildren().add(buckets); final int startChains = start + 4 + 4 + 4 * hts.getNumBuckets(); - final TreeItem chains = getTreeItem("Chains", new Range(startChains, 4 * hts.getNumChains())); + final TreeItem chains = getTreeItem("Chains", startChains, 4 * hts.getNumChains()); for (int i = 0; i < hts.getNumChains(); i++) { - chains.getChildren().add(getTreeItem("#" + i, new Range(startChains + 4 * i, 4))); + chains.getChildren().add(getTreeItem("#" + i, startChains + 4 * i, 4)); } root.getChildren().add(chains); } @@ -503,25 +494,20 @@ private void initializeSymbolTable(final TreeItem root, final SymbolTabl final int entrySize = is32Bit ? (4 + 4 + 4 + 1 + 1 + 2) : (4 + 1 + 1 + 2 + 8 + 8); for (int i = 0; i < symtab.getSymbolTableLength(); i++) { final int entryStart = start + entrySize * i; - final TreeItem x = getTreeItem("#" + i, new Range(entryStart, entrySize)); + final TreeItem x = getTreeItem("#" + i, entryStart, entrySize); - x.getChildren().add(getTreeItem("name", new Range(entryStart, 4))); + x.getChildren().add(getTreeItem("name", entryStart, 4)); x.getChildren() - .add(getTreeItem( - "value", new Range(is32Bit ? (entryStart + 4) : (entryStart + 4 + 1 + 1 + 2), wordSize))); + .add(getTreeItem("value", is32Bit ? (entryStart + 4) : (entryStart + 4 + 1 + 1 + 2), wordSize)); x.getChildren() .add(getTreeItem( - "size", - new Range(is32Bit ? (entryStart + 4 + 4) : (entryStart + 4 + 1 + 1 + 2 + 8), wordSize))); + "size", is32Bit ? (entryStart + 4 + 4) : (entryStart + 4 + 1 + 1 + 2 + 8), wordSize)); + x.getChildren().add(getTreeItem("info", is32Bit ? (entryStart + 4 + 4 + 4) : (entryStart + 4))); x.getChildren() - .add(getTreeItem("info", new Range(is32Bit ? (entryStart + 4 + 4 + 4) : (entryStart + 4), 1))); - x.getChildren() - .add(getTreeItem( - "visibility", new Range(is32Bit ? (entryStart + 4 + 4 + 4 + 1) : (entryStart + 4 + 1), 1))); + .add(getTreeItem("visibility", is32Bit ? (entryStart + 4 + 4 + 4 + 1) : (entryStart + 4 + 1))); x.getChildren() .add(getTreeItem( - "stidx", - new Range(is32Bit ? (entryStart + 4 + 4 + 4 + 1 + 1) : (entryStart + 4 + 1 + 1), 2))); + "stidx", is32Bit ? (entryStart + 4 + 4 + 4 + 1 + 1) : (entryStart + 4 + 1 + 1), 2)); root.getChildren().add(x); } @@ -529,15 +515,11 @@ private void initializeSymbolTable(final TreeItem root, final SymbolTabl private void initializeStringTable(final TreeItem root, final StringTableSection strtab) { final int start = (int) strtab.getHeader().getFileOffset(); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < strtab.getHeader().getSectionSize(); i++) { - final char x = strtab.getChar(i); - if (x == '\0' && !sb.isEmpty()) { - root.getChildren().add(getTreeItem(sb.toString(), new Range(start + i - sb.length(), sb.length() + 1))); - sb = new StringBuilder(); - } else { - sb.append(x); - } + int i = 0; + while (i < strtab.getHeader().getSectionSize()) { + final String s = strtab.getString(i); + root.getChildren().add(getTreeItem(s, start + i, s.length())); + i += s.length() + 1; } } @@ -549,9 +531,7 @@ private void initializeConstructorsSection( } for (int i = 0; i < cs.getNumConstructors(); i++) { - root.getChildren() - .add(getTreeItem( - "#" + i, new Range((int) cs.getHeader().getFileOffset() + wordSize * i, wordSize))); + root.getChildren().add(getTreeItem("#" + i, (int) cs.getHeader().getFileOffset() + wordSize * i, wordSize)); } } @@ -563,9 +543,7 @@ private void initializeDestructorsSection( } for (int i = 0; i < ds.getNumDestructors(); i++) { - root.getChildren() - .add(getTreeItem( - "#" + i, new Range((int) ds.getHeader().getFileOffset() + wordSize * i, wordSize))); + root.getChildren().add(getTreeItem("#" + i, (int) ds.getHeader().getFileOffset() + wordSize * i, wordSize)); } } @@ -576,8 +554,7 @@ private void initializeGnuVersionSection(final TreeItem root, final GnuV } for (int i = 0; i < gvs.getVersionsLength(); i++) { - root.getChildren() - .add(getTreeItem("#" + i, new Range((int) gvs.getHeader().getFileOffset() + 2 * i, 2))); + root.getChildren().add(getTreeItem("#" + i, (int) gvs.getHeader().getFileOffset() + 2 * i, 2)); } } @@ -591,35 +568,39 @@ private void initializeGnuVersionRequirementsSection( int entryStart = (int) gvrs.getHeader().getFileOffset(); for (int i = 0; i < gvrs.getRequirementsLength(); i++) { final GnuVersionRequirementEntry gvre = gvrs.getEntry(i); - final TreeItem x = new TreeItem<>("#" + i); - - x.getChildren().add(getTreeItem("version", new Range(entryStart, 2))); - x.getChildren().add(getTreeItem("count", new Range(entryStart + 2, 2))); - x.getChildren().add(getTreeItem("file offset", new Range(entryStart + 2 + 2, 4))); - x.getChildren().add(getTreeItem("aux offset", new Range(entryStart + 2 + 2 + 4, 4))); - x.getChildren().add(getTreeItem("next offset", new Range(entryStart + 2 + 2 + 4 + 4, 4))); - - final TreeItem aux = new TreeItem<>("Auxiliary"); - int auxStart = entryStart + gvre.getAuxOffset(); - for (int j = 0; j < gvre.getAuxiliaryLength(); j++) { - final TreeItem y = getTreeItem("#" + j, new Range(auxStart, 4 + 2 + 2 + 4 + 4)); - y.getChildren().add(getTreeItem("vna_hash", new Range(auxStart, 4))); - y.getChildren().add(getTreeItem("vna_flags", new Range(auxStart + 4, 2))); - y.getChildren().add(getTreeItem("vna_other", new Range(auxStart + 4 + 2, 2))); - y.getChildren().add(getTreeItem("vna_name", new Range(auxStart + 4 + 2 + 2, 4))); - y.getChildren().add(getTreeItem("vna_next", new Range(auxStart + 4 + 2 + 2 + 4, 4))); - aux.getChildren().add(y); - - auxStart += gvre.getAuxiliary(j).nextOffset(); - } - x.getChildren().add(aux); - - root.getChildren().add(x); - + root.getChildren().add(initializeGnuVersionRequirementEntry(gvre, "#" + i, entryStart)); entryStart += gvrs.getEntry(i).getNextOffset(); } } + private TreeItem initializeGnuVersionRequirementEntry( + final GnuVersionRequirementEntry gvre, final String name, final int entryStart) { + final TreeItem x = new TreeItem<>(name); + + x.getChildren().add(getTreeItem("version", entryStart, 2)); + x.getChildren().add(getTreeItem("count", entryStart + 2, 2)); + x.getChildren().add(getTreeItem("file offset", entryStart + 2 + 2, 4)); + x.getChildren().add(getTreeItem("aux offset", entryStart + 2 + 2 + 4, 4)); + x.getChildren().add(getTreeItem("next offset", entryStart + 2 + 2 + 4 + 4, 4)); + + final TreeItem aux = new TreeItem<>("Auxiliary"); + int auxStart = entryStart + gvre.getAuxOffset(); + for (int j = 0; j < gvre.getAuxiliaryLength(); j++) { + final TreeItem y = getTreeItem("#" + j, auxStart, 4 + 2 + 2 + 4 + 4); + y.getChildren().add(getTreeItem("vna_hash", auxStart, 4)); + y.getChildren().add(getTreeItem("vna_flags", auxStart + 4, 2)); + y.getChildren().add(getTreeItem("vna_other", auxStart + 4 + 2, 2)); + y.getChildren().add(getTreeItem("vna_name", auxStart + 4 + 2 + 2, 4)); + y.getChildren().add(getTreeItem("vna_next", auxStart + 4 + 2 + 2 + 4, 4)); + aux.getChildren().add(y); + + auxStart += gvre.getAuxiliary(j).nextOffset(); + } + x.getChildren().add(aux); + + return x; + } + private void initializeRelocationSection( final TreeItem root, final RelocationSection rs, final int wordSize) { if (rs.getNumRelocationEntries() == 0) { @@ -632,9 +613,9 @@ private void initializeRelocationSection( for (int i = 0; i < rs.getNumRelocationEntries(); i++) { final int entryStart = start + entrySize * i; - final TreeItem x = getTreeItem(" #" + i, new Range(entryStart, entrySize)); - x.getChildren().add(getTreeItem("offset", new Range(entryStart, wordSize))); - x.getChildren().add(getTreeItem("info", new Range(entryStart + wordSize, wordSize))); + final TreeItem x = getTreeItem(" #" + i, entryStart, entrySize); + x.getChildren().add(getTreeItem("offset", entryStart, wordSize)); + x.getChildren().add(getTreeItem("info", entryStart + wordSize, wordSize)); root.getChildren().add(x); } } @@ -650,20 +631,16 @@ private void initializeRelocationAddendSection( final int entrySize = 3 * wordSize; for (int i = 0; i < ras.getRelocationAddendTableLength(); i++) { final int entryStart = start + entrySize * i; - final TreeItem x = getTreeItem("#" + i, new Range(entryStart, entrySize)); + final TreeItem x = getTreeItem("#" + i, entryStart, entrySize); - x.getChildren().add(getTreeItem("offset", new Range(entryStart, wordSize))); - x.getChildren().add(getTreeItem("info", new Range(entryStart + wordSize, wordSize))); - x.getChildren().add(getTreeItem("addend", new Range(entryStart + wordSize + wordSize, wordSize))); + x.getChildren().add(getTreeItem("offset", entryStart, wordSize)); + x.getChildren().add(getTreeItem("info", entryStart + wordSize, wordSize)); + x.getChildren().add(getTreeItem("addend", entryStart + wordSize + wordSize, wordSize)); root.getChildren().add(x); } } - private void initializeX86Unwind(final TreeItem root, final X86_64_Unwind xu) { - throw new Error("Not implemented"); - } - private void updateGrid(final int startByte) { if (startByte < 0) { throw new IllegalArgumentException( diff --git a/emu-gui/src/main/java/com/ledmington/view/ELFViewer.java b/emu-gui/src/main/java/com/ledmington/view/ELFViewer.java index 9237616..c6d0314 100644 --- a/emu-gui/src/main/java/com/ledmington/view/ELFViewer.java +++ b/emu-gui/src/main/java/com/ledmington/view/ELFViewer.java @@ -35,7 +35,7 @@ public final class ELFViewer extends Stage { public ELFViewer(final double width, final double height) { final BorderPane mainPane = new BorderPane(); - this.view = new ELFView(this); + this.view = new ELFView(); final FlowPane topPane = new FlowPane(); final Button load = new Button(); diff --git a/emu-gui/src/main/java/com/ledmington/view/EmulatorView.java b/emu-gui/src/main/java/com/ledmington/view/EmulatorView.java index c4ee1a5..bf40fc3 100644 --- a/emu-gui/src/main/java/com/ledmington/view/EmulatorView.java +++ b/emu-gui/src/main/java/com/ledmington/view/EmulatorView.java @@ -25,6 +25,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import javafx.geometry.Insets; import javafx.scene.Scene; @@ -50,9 +51,10 @@ import com.ledmington.elf.ELFReader; import com.ledmington.emu.ELFLoader; import com.ledmington.emu.EmulatorConstants; +import com.ledmington.emu.ImmutableRegisterFile; import com.ledmington.emu.InstructionFetcher; import com.ledmington.emu.RFlags; -import com.ledmington.emu.X86RegisterFile; +import com.ledmington.emu.RegisterFile; import com.ledmington.mem.MemoryController; import com.ledmington.utils.MiniLogger; @@ -63,13 +65,8 @@ public final class EmulatorView extends Stage { // The CPU used to emulate private X86CpuAdapter cpu; - // The register file of the CPU - private X86RegisterFile regFile; // The memory controller used by the CPU private MemoryController mem; - // An external decoder used to decode instructions in the GUI without modifying - // the state of the CPU - private InstructionDecoder decoder; private final Button stepBtn = new Button(); private final Button runBtn = new Button(); private final TextArea codeArea = new TextArea(); @@ -133,8 +130,8 @@ public EmulatorView() { for (final Register64 r : Register64.values()) { registerPane.add(LabelFactory.getDefaultLabel(r.name()), 0, row); final Label rl = LabelFactory.getDefaultLabel("0x" + "0".repeat(16)); - rl.setTooltip(new Tooltip("Click to see the memory at [" + r.name() + "]")); - rl.setOnMouseClicked(e -> updateMemory(regFile.get(r) + rl.setTooltip(TooltipFactory.getDefaultTooltip("Click to see the memory at [" + r.name() + "]")); + rl.setOnMouseClicked(e -> updateMemory(cpu.getRegisters().get(r) - ((long) AppConstants.getMaxMemoryLines() * AppConstants.getMemoryBytesPerLine()) / 2L)); registerPane.add(rl, 1, row); regLabels.put(r, rl); @@ -142,7 +139,7 @@ public EmulatorView() { } registerPane.add(LabelFactory.getDefaultLabel("RFLAGS"), 0, row); - this.rflagsLabel = LabelFactory.getDefaultLabel("-".repeat(RFlags.values().length)); + this.rflagsLabel = LabelFactory.getDefaultLabel(""); registerPane.add(rflagsLabel, 1, row); row++; @@ -189,7 +186,7 @@ public EmulatorView() { this.cpu.doExecuteOne(); updateRegisters(); updateCode(); - updateMemory(regFile.get(Register64.RIP)); + updateMemory(cpu.getRegisters().get(Register64.RIP)); }); this.stepBtn.setTooltip(new Tooltip("Step")); @@ -204,7 +201,7 @@ public EmulatorView() { this.cpu.doExecute(); updateRegisters(); updateCode(); - updateMemory(regFile.get(Register64.RIP)); + updateMemory(cpu.getRegisters().get(Register64.RIP)); }); this.runBtn.setTooltip(new Tooltip("Run")); @@ -231,9 +228,7 @@ private InputStream getResourceStream(final String name) { private void loadFile(final File file) { logger.info("Loading file '%s'", file.toString()); this.mem = new MemoryController(EmulatorConstants.getMemoryInitializer(), false); - this.regFile = new X86RegisterFile(); - this.cpu = new X86CpuAdapter(regFile, mem); - this.decoder = new InstructionDecoderV1(new InstructionFetcher(mem, regFile)); + this.cpu = new X86CpuAdapter(mem); // TODO: implement this final String[] commandLineArguments = new String[0]; @@ -252,41 +247,46 @@ private void loadFile(final File file) { updateRegisters(); updateCode(); - updateMemory(regFile.get(Register64.RIP)); + updateMemory(cpu.getRegisters().get(Register64.RIP)); } private void updateRegisters() { + final ImmutableRegisterFile regFile = this.cpu.getRegisters(); + for (final Register64 r : Register64.values()) { - this.regLabels.get(r).setText(String.format("0x%016x", this.regFile.get(r))); + this.regLabels.get(r).setText(String.format("0x%016x", regFile.get(r))); } for (final Register16 s : new Register16[] { Register16.CS, Register16.DS, Register16.SS, Register16.ES, Register16.FS, Register16.GS }) { - this.segLabels.get(s).setText(String.format("0x%04x", this.regFile.get(s))); + this.segLabels.get(s).setText(String.format("0x%04x", regFile.get(s))); } - final StringBuilder sb = new StringBuilder(); - Arrays.stream(RFlags.values()) - .sorted(Comparator.comparingInt(RFlags::bit)) - .forEach(f -> sb.append(regFile.isSet(f) ? f.getInitial() : '-')); - this.rflagsLabel.setText(sb.toString()); + this.rflagsLabel.setText("|" + + Arrays.stream(RFlags.values()) + .sorted(Comparator.comparingInt(RFlags::bit)) + .map(f -> regFile.isSet(f) ? f.getSymbol() : "") + .collect(Collectors.joining("|")) + + "|"); } private void updateCode() { + final RegisterFile regFile = (RegisterFile) cpu.getRegisters(); + final InstructionDecoder decoder = new InstructionDecoderV1(new InstructionFetcher(this.mem, regFile)); final StringBuilder sb = new StringBuilder(); final int n = AppConstants.getMaxCodeInstructions(); - final long originalRIP = this.regFile.get(Register64.RIP); + final long originalRIP = regFile.get(Register64.RIP); long rip = originalRIP; for (int i = 0; i < n; i++) { final long startRIP = rip; - this.regFile.set(Register64.RIP, rip); + regFile.set(Register64.RIP, rip); sb.append("0x").append(String.format("%0" + (2 * ADDRESS_BYTES) + "x", rip)); String inst; try { - inst = this.decoder.decode().toIntelSyntax(); - rip = this.regFile.get(Register64.RIP); + inst = decoder.decode().toIntelSyntax(); + rip = regFile.get(Register64.RIP); } catch (final UnknownOpcode | ReservedOpcode | IllegalArgumentException e) { inst = String.format(".byte 0x%02x", this.mem.readCode(rip)); rip = startRIP + 1L; @@ -294,7 +294,7 @@ private void updateCode() { sb.append(" : ").append(inst).append('\n'); } this.codeArea.setText(sb.toString()); - this.regFile.set(Register64.RIP, originalRIP); + regFile.set(Register64.RIP, originalRIP); } private void updateMemory(final long baseAddress) { diff --git a/emu-gui/src/main/java/com/ledmington/view/Main.java b/emu-gui/src/main/java/com/ledmington/view/Main.java index 0266381..bf8617e 100644 --- a/emu-gui/src/main/java/com/ledmington/view/Main.java +++ b/emu-gui/src/main/java/com/ledmington/view/Main.java @@ -73,7 +73,7 @@ public static void main(final String[] args) { case verboseFlag -> MiniLogger.setMinimumLevel(MiniLogger.LoggingLevel.INFO); case veryVerboseFlag -> MiniLogger.setMinimumLevel(MiniLogger.LoggingLevel.DEBUG); default -> { - out.printf("Unknown flag '%s'\n", arg); + out.printf("Unknown flag '%s'%n", arg); out.flush(); Platform.exit(); System.exit(-1); diff --git a/emu-gui/src/main/java/com/ledmington/view/TooltipFactory.java b/emu-gui/src/main/java/com/ledmington/view/TooltipFactory.java new file mode 100644 index 0000000..15bb888 --- /dev/null +++ b/emu-gui/src/main/java/com/ledmington/view/TooltipFactory.java @@ -0,0 +1,33 @@ +/* + * 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.view; + +import javafx.scene.control.Tooltip; +import javafx.scene.text.Font; + +public final class TooltipFactory { + + private TooltipFactory() {} + + public static Tooltip getDefaultTooltip(final String text) { + final Tooltip lbl = new Tooltip(text); + lbl.setWrapText(false); + lbl.setFont(new Font(AppConstants.getDefaultMonospaceFont(), AppConstants.getDefaultFontSize())); + return lbl; + } +} diff --git a/emu-gui/src/main/java/com/ledmington/view/X86CpuAdapter.java b/emu-gui/src/main/java/com/ledmington/view/X86CpuAdapter.java index 3be19f4..8e2f44e 100644 --- a/emu-gui/src/main/java/com/ledmington/view/X86CpuAdapter.java +++ b/emu-gui/src/main/java/com/ledmington/view/X86CpuAdapter.java @@ -22,7 +22,6 @@ import com.ledmington.cpu.x86.Instruction; import com.ledmington.emu.X86Cpu; -import com.ledmington.emu.X86RegisterFile; import com.ledmington.mem.MemoryController; // TODO: refactor avoiding inheritance (and avoiding two queues) @@ -31,8 +30,8 @@ public final class X86CpuAdapter extends X86Cpu { private final BlockingQueue execute = new ArrayBlockingQueue<>(1); private final BlockingQueue executionCompleted = new ArrayBlockingQueue<>(1); - public X86CpuAdapter(final X86RegisterFile regFile, final MemoryController mem) { - super(regFile, mem); + public X86CpuAdapter(final MemoryController mem) { + super(mem); } private void waitToStartExecution() { diff --git a/emu/src/main/java/com/ledmington/emu/ELFLoader.java b/emu/src/main/java/com/ledmington/emu/ELFLoader.java index e0a36c5..b14b2f5 100644 --- a/emu/src/main/java/com/ledmington/emu/ELFLoader.java +++ b/emu/src/main/java/com/ledmington/emu/ELFLoader.java @@ -31,12 +31,15 @@ import com.ledmington.elf.PHTEntryType; import com.ledmington.elf.ProgramHeaderTable; import com.ledmington.elf.SectionTable; +import com.ledmington.elf.section.BasicProgBitsSection; import com.ledmington.elf.section.ConstructorsSection; import com.ledmington.elf.section.LoadableSection; import com.ledmington.elf.section.NoBitsSection; import com.ledmington.elf.section.Section; import com.ledmington.elf.section.SectionHeaderFlags; import com.ledmington.elf.section.StringTableSection; +import com.ledmington.elf.section.sym.SymbolTableEntry; +import com.ledmington.elf.section.sym.SymbolTableEntryType; import com.ledmington.elf.section.sym.SymbolTableSection; import com.ledmington.mem.MemoryController; import com.ledmington.utils.BitUtils; @@ -59,6 +62,7 @@ private ELFLoader() {} * Loads the given ELF file in the emulated memory. * * @param elf The file to be loaded. + * @param cpu The CPU to be used to execute some instructions, if needed. * @param mem The emulated memory where to load the file. * @param commandLineArguments The arguments to pass to the program. * @param baseAddress The address where to start loading the file. @@ -78,8 +82,9 @@ public static void load( // We make RSP point at the last 8 bytes of allocated memory final long stackPointer = highestAddress + stackSize - 8L; - // This is a fake instruction + // These are fake instructions to setup the stack cpu.executeOne(new Instruction(Opcode.MOV, Register64.RSP, new Immediate(stackPointer))); + cpu.executeOne(new Instruction(Opcode.PUSH, new Immediate(0L))); loadCommandLineArgumentsAndEnvironmentVariables( mem, highestAddress, elf.getFileHeader().is32Bit(), commandLineArguments); @@ -96,7 +101,12 @@ public static void load( (StringTableSection) elf.getSectionByName(".strtab").orElseThrow()); } if (elf.getSectionByName(".init").isPresent()) { - runInit(); + runInit( + (BasicProgBitsSection) elf.getSectionByName(".init").orElseThrow(), + cpu, + baseAddress, + (SymbolTableSection) elf.getSectionByName(".symtab").orElseThrow(), + (StringTableSection) elf.getSectionByName(".strtab").orElseThrow()); } if (elf.getSectionByName(".ctors").isPresent()) { runCtors(); @@ -121,6 +131,11 @@ public static void unload(final ELF elf) { } } + private static void runFrom(final X86Emulator cpu, final long startAddress) { + cpu.executeOne(new Instruction(Opcode.MOV, Register64.RIP, new Immediate(startAddress))); + cpu.execute(); + } + private static void runPreInitArray() { notImplemented(); } @@ -137,13 +152,27 @@ private static void runInitArray( final String ctorName = strtab.getString(symtab.getSymbolWithValue(c).nameOffset()); logger.debug("Running .init_array[%d] = %,d (0x%016x) '%s'", i, c, c, ctorName); - cpu.executeOne(new Instruction(Opcode.MOV, Register64.RIP, new Immediate(entryPointVirtualAddress + c))); - cpu.execute(); + runFrom(cpu, entryPointVirtualAddress + c); } } - private static void runInit() { - notImplemented(); + private static void runInit( + final BasicProgBitsSection init, + final X86Emulator cpu, + final long entryPointVirtualAddress, + final SymbolTableSection symtab, + final StringTableSection strtab) { + for (int i = 0; i < symtab.getSymbolTableLength(); i++) { + final SymbolTableEntry ste = symtab.getSymbolTableEntry(i); + if (ste.info().getType() == SymbolTableEntryType.STT_FUNC + && ste.value() >= init.getHeader().getFileOffset() + && ste.value() + < init.getHeader().getFileOffset() + + init.getHeader().getSectionSize()) { + logger.debug("Running constructor '%s' from .init", strtab.getString(ste.nameOffset())); + runFrom(cpu, entryPointVirtualAddress + ste.value()); + } + } } private static void runCtors() { diff --git a/emu/src/main/java/com/ledmington/emu/ImmutableRegisterFile.java b/emu/src/main/java/com/ledmington/emu/ImmutableRegisterFile.java new file mode 100644 index 0000000..07860af --- /dev/null +++ b/emu/src/main/java/com/ledmington/emu/ImmutableRegisterFile.java @@ -0,0 +1,67 @@ +/* + * 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 com.ledmington.cpu.x86.Register16; +import com.ledmington.cpu.x86.Register32; +import com.ledmington.cpu.x86.Register64; +import com.ledmington.cpu.x86.Register8; + +/** Represents an immutable register file. */ +public interface ImmutableRegisterFile { + + /** + * Returns the value of the given 8-bit register as a byte. + * + * @param r The register to be read. + * @return The value in the register. + */ + byte get(final Register8 r); + + /** + * Returns the value of the given 16-bit register as a short. + * + * @param r The register to be read. + * @return The value in the register. + */ + short get(final Register16 r); + + /** + * Returns the value of the given 32-bit register as an int. + * + * @param r The Register to be read. + * @return The value of the register. + */ + int get(final Register32 r); + + /** + * Returns the value of the given 64-bit register as a long. + * + * @param r The Register to be read. + * @return The value of the register. + */ + long get(final Register64 r); + + /** + * Checks whether the given flag is set. + * + * @param f The flag to be checked. + * @return True if it is set, false otherwise. + */ + boolean isSet(final RFlags f); +} diff --git a/emu/src/main/java/com/ledmington/emu/InstructionFetcher.java b/emu/src/main/java/com/ledmington/emu/InstructionFetcher.java index 4370424..6626b73 100644 --- a/emu/src/main/java/com/ledmington/emu/InstructionFetcher.java +++ b/emu/src/main/java/com/ledmington/emu/InstructionFetcher.java @@ -27,7 +27,7 @@ public final class InstructionFetcher implements ReadOnlyByteBuffer { private final MemoryController mem; - private final X86RegisterFile regFile; + private final RegisterFile regFile; /** * Creates an InstructionFetcher with the given MemoryController and register file. @@ -35,7 +35,7 @@ public final class InstructionFetcher implements ReadOnlyByteBuffer { * @param mem The memory controller to retrieve instructions from. * @param regFile The register file to get and set the instruction pointer. */ - public InstructionFetcher(final MemoryController mem, final X86RegisterFile regFile) { + public InstructionFetcher(final MemoryController mem, final RegisterFile regFile) { this.mem = Objects.requireNonNull(mem); this.regFile = Objects.requireNonNull(regFile); } diff --git a/emu/src/main/java/com/ledmington/emu/RegisterFile.java b/emu/src/main/java/com/ledmington/emu/RegisterFile.java new file mode 100644 index 0000000..f3e19c4 --- /dev/null +++ b/emu/src/main/java/com/ledmington/emu/RegisterFile.java @@ -0,0 +1,67 @@ +/* + * 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 com.ledmington.cpu.x86.Register16; +import com.ledmington.cpu.x86.Register32; +import com.ledmington.cpu.x86.Register64; +import com.ledmington.cpu.x86.Register8; + +/** Represents a mutable register file. */ +public interface RegisterFile extends ImmutableRegisterFile { + + /** + * Sets the value of the given 8-bit register to given byte. This operation does not modify the other registers. + * + * @param r The Register to be overwritten. + * @param v The value to be written. + */ + void set(final Register8 r, final byte v); + + /** + * Sets the value of the given 16-bit register to given short. This operation does not modify the other registers. + * + * @param r The Register to be overwritten. + * @param v The value to be written. + */ + void set(final Register16 r, final short v); + + /** + * Sets the value of the given 32-bit register to given int. This operation does not modify the other registers. + * + * @param r The Register to be overwritten. + * @param v The value to be written. + */ + void set(final Register32 r, final int v); + + /** + * Sets the value of the given 64-bit register to given long. This operation does not modify the other registers. + * + * @param r The Register to be overwritten. + * @param v The value to be written. + */ + void set(final Register64 r, final long v); + + /** + * Sets the given flag to the given value. + * + * @param f The flag to be set. + * @param v The value to be written. + */ + void set(final RFlags f, final boolean v); +} diff --git a/emu/src/main/java/com/ledmington/emu/X86Cpu.java b/emu/src/main/java/com/ledmington/emu/X86Cpu.java index 9ef07ea..887794e 100644 --- a/emu/src/main/java/com/ledmington/emu/X86Cpu.java +++ b/emu/src/main/java/com/ledmington/emu/X86Cpu.java @@ -32,6 +32,7 @@ import com.ledmington.mem.MemoryController; import com.ledmington.utils.BitUtils; import com.ledmington.utils.MiniLogger; +import com.ledmington.utils.SuppressFBWarnings; /** Emulator of an x86 CPU. */ public class X86Cpu implements X86Emulator { @@ -43,15 +44,19 @@ private enum State { HALTED } - private final X86RegisterFile rf; + private final RegisterFile rf = new X86RegisterFile(); 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); + /** + * Creates a new x86 CPU with the given memory controller. + * + * @param mem The object to be used to access the memory. + */ + public X86Cpu(final MemoryController mem) { this.mem = Objects.requireNonNull(mem); this.instFetch = new InstructionFetcher(mem, rf); this.dec = new InstructionDecoderV1(instFetch); @@ -180,9 +185,15 @@ public void executeOne(final Instruction inst) { } } case PUSH -> { - final Register64 src = (Register64) inst.firstOperand(); + final long value = + switch (inst.firstOperand()) { + case Register64 r64 -> rf.get(r64); + case Immediate imm -> imm.asLong(); + default -> throw new IllegalArgumentException("Unexpected value: " + inst.firstOperand()); + }; + final long rsp = rf.get(Register64.RSP); - mem.write(rsp, rf.get(src)); + mem.write(rsp, value); // the stack "grows downward" rf.set(Register64.RSP, rsp - 8L); } @@ -205,15 +216,33 @@ public void executeOne(final Instruction inst) { final long result = computeIndirectOperand(rf, src); rf.set(Register64.RIP, result); } + case RET -> { + // TODO: check this + final long prev = rf.get(Register64.RSP) + 8L; + + // If we read 0x0, we have exhausted the stack + if (mem.read8(prev) == 0L) { + state = State.HALTED; + } else { + rf.set(Register64.RSP, prev); + rf.set(Register64.RIP, mem.read8(prev)); + } + } 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) { + private long computeIndirectOperand(final RegisterFile 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(); } + + @Override + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "We know that this object is immutable.") + public ImmutableRegisterFile getRegisters() { + return rf; + } } diff --git a/emu/src/main/java/com/ledmington/emu/X86Emulator.java b/emu/src/main/java/com/ledmington/emu/X86Emulator.java index 2c14cb5..402a347 100644 --- a/emu/src/main/java/com/ledmington/emu/X86Emulator.java +++ b/emu/src/main/java/com/ledmington/emu/X86Emulator.java @@ -20,6 +20,7 @@ import com.ledmington.cpu.x86.Instruction; import com.ledmington.cpu.x86.Opcode; +/** Common interface to represent an x86 Emulator. */ public interface X86Emulator { /** @@ -37,4 +38,11 @@ public interface X86Emulator { * @param inst The instruction to be executed. */ void executeOne(final Instruction inst); + + /** + * Returns an immutable view of the registers in use. + * + * @return An immutable view of the registers in use. + */ + ImmutableRegisterFile getRegisters(); } diff --git a/emu/src/main/java/com/ledmington/emu/X86RegisterFile.java b/emu/src/main/java/com/ledmington/emu/X86RegisterFile.java index 62288fb..6dbda45 100644 --- a/emu/src/main/java/com/ledmington/emu/X86RegisterFile.java +++ b/emu/src/main/java/com/ledmington/emu/X86RegisterFile.java @@ -28,7 +28,7 @@ import com.ledmington.utils.HashUtils; /** This class represents the set of registers used by an x86-64 processor during execution. */ -public final class X86RegisterFile { +public final class X86RegisterFile implements RegisterFile { // General-purpose registers private final long[] gpr = new long[16]; @@ -44,12 +44,7 @@ public final class X86RegisterFile { /** Creates the register file initializing every register to 0. */ public X86RegisterFile() {} - /** - * Returns the value of the given 8-bit register as a byte. - * - * @param r The register to be read. - * @return The value in the register. - */ + @Override public byte get(final Register8 r) { return switch (r) { case AL -> BitUtils.asByte(gpr[0]); @@ -75,12 +70,7 @@ public byte get(final Register8 r) { }; } - /** - * Sets the value of the given 8-bit register to given byte. This operation does not modify the other registers. - * - * @param r The Register to be overwritten. - * @param v The value to be written. - */ + @Override public void set(final Register8 r, final byte v) { switch (r) { case AL -> gpr[0] = (gpr[0] & 0xffffffffffffff00L) | BitUtils.asLong(v); @@ -106,12 +96,7 @@ public void set(final Register8 r, final byte v) { } } - /** - * Returns the value of the given 16-bit register as a short. - * - * @param r The register to be read. - * @return The value in the register. - */ + @Override public short get(final Register16 r) { return switch (r) { case AX -> BitUtils.asShort(gpr[0]); @@ -139,12 +124,7 @@ public short get(final Register16 r) { }; } - /** - * Sets the value of the given 16-bit register to given short. This operation does not modify the other registers. - * - * @param r The Register to be overwritten. - * @param v The value to be written. - */ + @Override public void set(final Register16 r, final short v) { switch (r) { case AX -> gpr[0] = (gpr[0] & 0xffffffffff0000L) | BitUtils.asLong(v); @@ -172,12 +152,7 @@ public void set(final Register16 r, final short v) { } } - /** - * Returns the value of the given 32-bit register as an int. - * - * @param r The Register to be read. - * @return The value of the register. - */ + @Override public int get(final Register32 r) { return switch (r) { case EAX -> BitUtils.asInt(gpr[0]); @@ -200,12 +175,7 @@ public int get(final Register32 r) { }; } - /** - * Sets the value of the given 32-bit register to given int. This operation does not modify the other registers. - * - * @param r The Register to be overwritten. - * @param v The value to be written. - */ + @Override public void set(final Register32 r, final int v) { switch (r) { case EAX -> gpr[0] = (gpr[0] & 0xffffffff00000000L) | BitUtils.asLong(v); @@ -228,12 +198,7 @@ public void set(final Register32 r, final int v) { } } - /** - * Returns the value of the given 64-bit register as a long. - * - * @param r The Register to be read. - * @return The value of the register. - */ + @Override public long get(final Register64 r) { return switch (r) { case RAX -> gpr[0]; @@ -256,12 +221,7 @@ public long get(final Register64 r) { }; } - /** - * Sets the value of the given 64-bit register to given long. This operation does not modify the other registers. - * - * @param r The Register to be overwritten. - * @param v The value to be written. - */ + @Override public void set(final Register64 r, final long v) { switch (r) { case RAX -> gpr[0] = v; @@ -284,22 +244,12 @@ public void set(final Register64 r, final long v) { } } - /** - * Checks whether the given flag is set. - * - * @param f The flag to be checked. - * @return True if it is set, false otherwise. - */ + @Override public boolean isSet(final RFlags f) { return (rflags & (1L << f.bit())) != 0L; } - /** - * Sets the given flag to the given value. - * - * @param f The flag to be set. - * @param v The value to be written. - */ + @Override public void set(final RFlags f, final boolean v) { if (v) { set(f); diff --git a/id/src/main/java/com/ledmington/cpu/x86/InstructionDecoderV1.java b/id/src/main/java/com/ledmington/cpu/x86/InstructionDecoderV1.java index 872fc38..eb48fc5 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/InstructionDecoderV1.java +++ b/id/src/main/java/com/ledmington/cpu/x86/InstructionDecoderV1.java @@ -52,7 +52,7 @@ public final class InstructionDecoderV1 implements InstructionDecoder { * * @param b The byte buffer to read bytes from. */ - @SuppressFBWarnings("EI_EXPOSE_REP2") + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "We are taking a reference to an immutable object.") public InstructionDecoderV1(final ReadOnlyByteBuffer b) { this.b = Objects.requireNonNull(b); } diff --git a/id/src/main/java/com/ledmington/cpu/x86/Register64.java b/id/src/main/java/com/ledmington/cpu/x86/Register64.java index 60b3b9a..d0dd2d1 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Register64.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Register64.java @@ -22,7 +22,7 @@ /** An x86 64-bit general-purpose register. */ public enum Register64 implements Register { - /** The register RAX. */ + /** The register RAX. Usually contains the return value of a function call. */ RAX("rax"), /** The register RBX. */ @@ -40,10 +40,10 @@ public enum Register64 implements Register { /** The register RDI. */ RDI("rdi"), - /** The register RSP. */ + /** The register RSP. Usually points to the top (the end) of the current stack frame. */ RSP("rsp"), - /** The register RBP. */ + /** The register RBP. Usually points to the base (the start) of the current stack frame. */ RBP("rbp"), /** The register R8. */ diff --git a/utils/src/main/java/com/ledmington/utils/SuppressFBWarnings.java b/utils/src/main/java/com/ledmington/utils/SuppressFBWarnings.java index 5a96edc..34f2c54 100644 --- a/utils/src/main/java/com/ledmington/utils/SuppressFBWarnings.java +++ b/utils/src/main/java/com/ledmington/utils/SuppressFBWarnings.java @@ -30,14 +30,14 @@ * The set of SpotBugs warnings that are to be suppressed in annotated element. The value can be a bug category, * kind or pattern. * - * @return The set of SpotBugs warning to be suppressed. + * @return The set of SpotBugs warnings to be suppressed. */ - String[] value() default {}; + String[] value(); /** * Optional documentation of the reason why the warning is suppressed. * * @return The reason why the warning is suppressed. */ - String justification() default ""; + String justification(); }