From 3e957c510de7a6c60e54a40cf21a1de1d9e3690a Mon Sep 17 00:00:00 2001 From: Ledmington Date: Sun, 16 Aug 2026 12:28:58 +0200 Subject: [PATCH 1/8] Added e2e objdump tests --- objdump/build.gradle | 6 +- .../com/ledmington/objdump/CheckObjdump.java | 52 +------------ .../objdump/ObjdumpSystemComparison.java | 78 +++++++++++++++++++ .../objdump/TestObjdumpAgainstSystem.java | 68 ++++++++++++++++ 4 files changed, 154 insertions(+), 50 deletions(-) create mode 100644 objdump/src/test/java/com/ledmington/objdump/ObjdumpSystemComparison.java create mode 100644 objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java diff --git a/objdump/build.gradle b/objdump/build.gradle index 95acc4b8..532ff771 100644 --- a/objdump/build.gradle +++ b/objdump/build.gradle @@ -55,7 +55,11 @@ tasks.register('fatJar', Jar) { tasks.build.dependsOn(fatJar) tasks.javadoc.enabled = false -tasks.test.enabled = false + +tasks.test { + dependsOn fatJar + systemProperty 'e2eTestFilesDir', rootProject.file(path('core', 'src', 'test', 'resources', 'generated')).absolutePath +} tasks.register('checkObjdump', JavaExec) { group = 'verification' diff --git a/objdump/src/test/java/com/ledmington/objdump/CheckObjdump.java b/objdump/src/test/java/com/ledmington/objdump/CheckObjdump.java index de59950e..6fba0b08 100644 --- a/objdump/src/test/java/com/ledmington/objdump/CheckObjdump.java +++ b/objdump/src/test/java/com/ledmington/objdump/CheckObjdump.java @@ -18,16 +18,12 @@ package com.ledmington.objdump; import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import java.util.stream.Stream; import com.ledmington.utils.ProcessUtils; import com.ledmington.utils.TerminalUtils; @@ -39,51 +35,9 @@ public final class CheckObjdump { private static final PrintWriter out = System.console() != null ? System.console().writer() : new PrintWriter(System.out, false, StandardCharsets.UTF_8); - private static final String fatJarPath; - - static { - try (Stream s = Files.find( - Path.of(".", "build").normalize().toAbsolutePath(), 999, (p, bfa) -> bfa.isRegularFile()) - .filter(p -> p.getFileName().toString().startsWith("emu-objdump") - && p.getFileName().toString().endsWith(".jar"))) { - fatJarPath = s.max(Comparator.comparingLong(a -> a.toFile().length())) - .orElseThrow() - .normalize() - .toAbsolutePath() - .toString(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } private CheckObjdump() {} - private static boolean isELF(final Path p) { - try (InputStream is = Files.newInputStream(p, StandardOpenOption.READ)) { - final int expectedBytes = 4; - final byte[] buffer = new byte[expectedBytes]; - final int bytesRead = is.read(buffer); - return bytesRead == expectedBytes - && buffer[0] == (byte) 0x7f - && buffer[1] == (byte) 0x45 - && buffer[2] == (byte) 0x4c - && buffer[3] == (byte) 0x46; - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - private static String runSystemObjdump(final Path p) { - final String systemObjdump = "/usr/bin/objdump"; - final String[] cmd = {systemObjdump, "-d", "-Mintel", p.toString()}; - return ProcessUtils.run(cmd); - } - - private static String runCustomObjdump(final Path p) { - final String[] cmd = {"java", "-jar", fatJarPath, "-d", p.toString()}; - return ProcessUtils.run(cmd); - } - private static void checkDiff(final String expected, final String actual) { if (expected.equals(actual)) { out.println(TerminalUtils.ANSI_GREEN + "OK" + TerminalUtils.ANSI_RESET); @@ -117,8 +71,8 @@ private static void checkDiff(final String expected, final String actual) { private static void test(final Path p) { out.print(p.toString() + " ... "); - final String outputSystemObjdump = runSystemObjdump(p); - final String outputCustomObjdump = runCustomObjdump(p); + final String outputSystemObjdump = ObjdumpSystemComparison.runSystemObjdump(p); + final String outputCustomObjdump = ObjdumpSystemComparison.runCustomObjdump(p); checkDiff(outputSystemObjdump, outputCustomObjdump); } @@ -145,7 +99,7 @@ public static void main(final String[] args) { out.printf("File '%s' does not exist, skipping it.%n", p); continue; } - if (!isELF(p)) { + if (!ObjdumpSystemComparison.isELF(p)) { out.printf("File '%s' is not an ELF, skipping it.%n", p); continue; } diff --git a/objdump/src/test/java/com/ledmington/objdump/ObjdumpSystemComparison.java b/objdump/src/test/java/com/ledmington/objdump/ObjdumpSystemComparison.java new file mode 100644 index 00000000..9cc3ceca --- /dev/null +++ b/objdump/src/test/java/com/ledmington/objdump/ObjdumpSystemComparison.java @@ -0,0 +1,78 @@ +/* + * emu - Processor Emulator + * Copyright (C) 2023-2026 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.objdump; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Comparator; +import java.util.stream.Stream; + +import com.ledmington.utils.ProcessUtils; + +/** Runs both this project's objdump and the system's objdump on a given ELF file, to compare their output. */ +@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") +final class ObjdumpSystemComparison { + + private static final String fatJarPath; + + static { + try (Stream s = Files.find( + Path.of(".", "build").normalize().toAbsolutePath(), 999, (p, bfa) -> bfa.isRegularFile()) + .filter(p -> p.getFileName().toString().startsWith("emu-objdump") + && p.getFileName().toString().endsWith(".jar"))) { + fatJarPath = s.max(Comparator.comparingLong(a -> a.toFile().length())) + .orElseThrow() + .normalize() + .toAbsolutePath() + .toString(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private ObjdumpSystemComparison() {} + + /* default */ static boolean isELF(final Path p) { + try (InputStream is = Files.newInputStream(p, StandardOpenOption.READ)) { + final int expectedBytes = 4; + final byte[] buffer = new byte[expectedBytes]; + final int bytesRead = is.read(buffer); + return bytesRead == expectedBytes + && buffer[0] == (byte) 0x7f + && buffer[1] == (byte) 0x45 + && buffer[2] == (byte) 0x4c + && buffer[3] == (byte) 0x46; + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + /* default */ static String runSystemObjdump(final Path p) { + final String systemObjdump = "/usr/bin/objdump"; + final String[] cmd = {systemObjdump, "-d", "-Mintel", p.toString()}; + return ProcessUtils.run(cmd); + } + + /* default */ static String runCustomObjdump(final Path p) { + final String[] cmd = {"java", "-jar", fatJarPath, "-d", p.toString()}; + return ProcessUtils.run(cmd); + } +} diff --git a/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java b/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java new file mode 100644 index 00000000..544de4ed --- /dev/null +++ b/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java @@ -0,0 +1,68 @@ +/* + * emu - Processor Emulator + * Copyright (C) 2023-2026 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.objdump; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.ledmington.utils.SuppressFBWarnings; + +/** Checks that this project's objdump produces the exact same output as the system's objdump. */ +@DisabledOnOs(OS.WINDOWS) +final class TestObjdumpAgainstSystem { + + private static final String SYSTEM_OBJDUMP_PATH = "/usr/bin/objdump"; + + private static Path e2eTestFile(final String executableName) { + final String e2eTestFilesDir = Objects.requireNonNull( + System.getProperty("e2eTestFilesDir"), "System property 'e2eTestFilesDir' was not set."); + final Path p = Path.of(e2eTestFilesDir, executableName); + if (!Files.exists(p)) { + throw new IllegalStateException(String.format( + "File '%s' not found: did you forget to run './gradlew :core:generateEndToEndTestFiles'?", p)); + } + return p; + } + + @SuppressFBWarnings( + value = "DMI_HARDCODED_ABSOLUTE_FILENAME", + justification = "This is where the system's objdump is expected to be found on Linux.") + private static void checkSystemObjdumpIsAvailable() { + Assumptions.assumeTrue(Files.exists(Path.of(SYSTEM_OBJDUMP_PATH)), "system objdump was not found, skipping."); + } + + @ParameterizedTest + @ValueSource(strings = {"do_nothing.static", "do_nothing.dynamic", "small.x"}) + void disassembly(final String executableName) { + checkSystemObjdumpIsAvailable(); + final Path p = e2eTestFile(executableName); + assertEquals( + ObjdumpSystemComparison.runSystemObjdump(p), + ObjdumpSystemComparison.runCustomObjdump(p), + () -> "objdump output for '" + p + "' did not match the system's objdump output."); + } +} From 34eb4670100bd5e68d7fe9a6b48b917b893432a3 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Sun, 16 Aug 2026 12:36:40 +0200 Subject: [PATCH 2/8] Minor fixes --- .../ledmington/cpu/InstructionDecoder.java | 15 ++++++++++++++ .../com/ledmington/cpu/x86/Vex3Prefix.java | 10 ++++++++++ .../java/com/ledmington/objdump/Main.java | 20 ++++++++++--------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java index eae28ea6..80e0a2e4 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java @@ -3877,6 +3877,21 @@ private static Instruction parseVex3Opcodes( return switch (opcodeFirstByte) { case VMOVQ_OPCODE -> { final ModRM modrm = modrm(b); + final byte MANDATORY_PREFIX_F3 = (byte) 2; + if (vex3.p() == MANDATORY_PREFIX_F3) { + // VEX.128.F3.0F 7E /r: VMOVQ xmm1, xmm2/m64 + yield Instruction.builder() + .opcode(Opcode.VMOVQ) + .op(RegisterXMM.fromByte(getByteFromReg(vex3, modrm))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.QWORD_PTR) + .build() + : RegisterXMM.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } + // VEX.128.66.0F.W1 7E /r: VMOVQ r64/m64, xmm1 yield Instruction.builder() .opcode(Opcode.VMOVQ) .op(Register64.fromByte(getByteFromRM(vex3, modrm))) diff --git a/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java b/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java index 8043a409..f0f0feb9 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java @@ -138,4 +138,14 @@ public boolean w() { public byte v() { return v; } + + /** + * Returns the value of the mandatory-prefix (pp) field in this VEX3 prefix: 0 for none, 1 for 0x66, 2 for 0xf3, 3 + * for 0xf2. + * + * @return The 2-bit mandatory-prefix field. + */ + public byte p() { + return p; + } } diff --git a/objdump/src/main/java/com/ledmington/objdump/Main.java b/objdump/src/main/java/com/ledmington/objdump/Main.java index c84b9f1b..987c5012 100644 --- a/objdump/src/main/java/com/ledmington/objdump/Main.java +++ b/objdump/src/main/java/com/ledmington/objdump/Main.java @@ -60,10 +60,7 @@ private Main() {} public static void main(final String[] args) { MiniLogger.setMinimumLevel(MiniLogger.LoggingLevel.ERROR); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - out.println(); - out.flush(); - })); + Runtime.getRuntime().addShutdownHook(new Thread(out::flush)); String filename = null; boolean disassembleExecutableSections = false; @@ -110,12 +107,17 @@ public static void main(final String[] args) { out.println(); if (disassembleExecutableSections) { + boolean isFirstSection = true; for (int i = 0; i < elf.getSectionTableLength(); i++) { final Section s = elf.getSection(i); if (!s.header().getFlags().contains(SectionHeaderFlags.SHT_EXECINSTR)) { continue; } + if (!isFirstSection) { + out.println(); + } + try { disassembleSection(elf, i); } catch (final Throwable t) { @@ -123,10 +125,10 @@ public static void main(final String[] args) { out.flush(); throw t; } + isFirstSection = false; } } - out.println(); out.flush(); System.exit(0); } @@ -213,8 +215,6 @@ private static void disassembleSection(final SectionTable st, final int sectionI out.println(); } } - - out.println(); } private static long getAsLong(final Immediate imm) { @@ -249,8 +249,10 @@ private static Map findFunctionNames(final SectionTable st) { for (int i = 0; i < symtab.getSymbolTableLength(); i++) { final SymbolTableEntry ste = symtab.getSymbolTableEntry(i); - final boolean isFunction = ste.info().getType() == SymbolTableEntryType.STT_FUNC; - if (!isFunction) { + final SymbolTableEntryType type = ste.info().getType(); + final boolean isLabelWorthy = + type == SymbolTableEntryType.STT_FUNC || type == SymbolTableEntryType.STT_NOTYPE; + if (!isLabelWorthy) { continue; } functionNames.put(ste.value(), strtab.getString(ste.nameOffset())); From 48f2ea240778bc0b34a80f162f8e200c36b0cd7e Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 17 Aug 2026 13:05:19 +0200 Subject: [PATCH 3/8] Added some missing instructions --- .../ledmington/cpu/InstructionChecker.java | 56 ++- .../ledmington/cpu/InstructionDecoder.java | 346 ++++++++++++++++-- .../com/ledmington/cpu/OperandTypeList.java | 45 +++ .../com/ledmington/cpu/x86/EvexPrefix.java | 19 + .../java/com/ledmington/cpu/x86/Opcode.java | 48 +++ .../com/ledmington/cpu/x86/Vex3Prefix.java | 9 + .../java/com/ledmington/objdump/Main.java | 19 +- 7 files changed, 490 insertions(+), 52 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java index e1330dd8..8bfaaca2 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java @@ -271,6 +271,8 @@ public final class InstructionChecker { Map.entry(Opcode.RDSEED, List.of(R16, R32, R64)), Map.entry(Opcode.RDSSPQ, List.of(R64)), Map.entry(Opcode.INCSSPQ, List.of(R64)), + Map.entry(Opcode.RSTORSSP, List.of(M64)), + Map.entry(Opcode.SAVEPREVSSP, List.of(NO_ARGS)), Map.entry(Opcode.LAHF, List.of(NO_ARGS)), Map.entry(Opcode.SAHF, List.of(NO_ARGS)), Map.entry(Opcode.SYSCALL, List.of(NO_ARGS)), @@ -285,7 +287,8 @@ public final class InstructionChecker { Map.entry(Opcode.PMINUD, List.of(RX_M128)), Map.entry(Opcode.PMAXUB, List.of(RX_RX)), Map.entry(Opcode.PALIGNR, List.of(RX_RX_I8, RX_M128_I8)), - Map.entry(Opcode.VPXOR, List.of(RX_RX_RX)), + Map.entry(Opcode.VPXOR, List.of(RX_RX_RX, RY_RY_RY, RX_RX_M128, RY_RY_M256)), + Map.entry(Opcode.VPADDB, List.of(RX_RX_RX, RY_RY_RY, RZ_RZ_RZ, RX_RX_M128, RY_RY_M256, RZ_RZ_M512)), Map.entry(Opcode.VPXORQ, List.of(RY_RY_M256)), Map.entry(Opcode.VPORQ, List.of(RY_RY_RY)), Map.entry(Opcode.PEXTRW, List.of(R32_RMM_I8)), @@ -293,34 +296,56 @@ public final class InstructionChecker { Map.entry(Opcode.VPMINUB, List.of(RY_RY_RY, RY_RY_M256)), Map.entry(Opcode.VPMINUD, List.of(RY_RY_RY, RY_RY_M256)), Map.entry(Opcode.VPMOVMSKB, List.of(R32_RX, R32_RY)), - Map.entry(Opcode.VPCMPEQB, List.of(RK_RX_RX, RK_RY_RY, RY_RY_M256, RK_RX_M128, RK_RY_M256)), + Map.entry( + Opcode.VPCMPEQB, + List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RY_RY_M256, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), Map.entry(Opcode.VPCMPLTB, List.of(RK_RY_RY)), Map.entry(Opcode.VPCMPEQD, List.of(RK_RY_RY, RY_RY_M256, RK_RY_M256)), Map.entry(Opcode.VPCMPEQQ, List.of(RX_RX_M128)), - Map.entry(Opcode.VPCMPNEQB, List.of(RK_RY_RY, RK_RY_M256)), + Map.entry(Opcode.VPCMPNEQB, List.of(RK_RY_RY, RK_RZ_RZ, RK_RY_M256, RK_RZ_M512)), Map.entry(Opcode.VZEROALL, List.of(NO_ARGS)), - Map.entry(Opcode.VMOVQ, List.of(R64_RX, RX_M64, M64_RX)), - Map.entry(Opcode.VMOVD, List.of(RX_M32)), + Map.entry(Opcode.VMOVQ, List.of(R64_RX, RX_M64, M64_RX, RX_R64)), + Map.entry(Opcode.VMOVD, List.of(RX_M32, RX_R32)), + Map.entry(Opcode.ANDN, List.of(R32_R32_R32, R32_R32_M32, R64_R64_R64, R64_R64_M64)), + Map.entry(Opcode.BLSR, List.of(R32_R32, R32_M32, R64_R64, R64_M64)), + Map.entry(Opcode.BLSMSK, List.of(R32_R32, R32_M32, R64_R64, R64_M64)), + Map.entry(Opcode.BLSI, List.of(R32_R32, R32_M32, R64_R64, R64_M64)), Map.entry(Opcode.PCMPISTRI, List.of(RX_RX_I8, RX_M128_I8)), Map.entry(Opcode.PUNPCKLBW, List.of(RX_RX)), - Map.entry(Opcode.VPBROADCASTB, List.of(RY_RX, RZ_R32)), + Map.entry(Opcode.VPBROADCASTB, List.of(RY_RX, RZ_R32, RZ_RX, RZ_M8)), Map.entry(Opcode.VPBROADCASTD, List.of(RY_RX, RZ_R32)), Map.entry(Opcode.SARX, List.of(R32_R32_R32)), Map.entry(Opcode.VPOR, List.of(RY_RY_RY)), Map.entry(Opcode.VPAND, List.of(RY_RY_RY)), - Map.entry(Opcode.VPANDN, List.of(RX_RX_RX)), + Map.entry(Opcode.VPANDN, List.of(RX_RX_RX, RY_RY_RY, RX_RX_M128, RY_RY_M256)), Map.entry(Opcode.BZHI, List.of(R32_R32_R32, R64_R64_R64)), Map.entry(Opcode.MOVBE, List.of(R32_M32)), Map.entry(Opcode.MOVNTDQ, List.of(M128_RX)), Map.entry(Opcode.MOVNTPS, List.of(M128_RX)), Map.entry(Opcode.SFENCE, List.of(NO_ARGS)), Map.entry(Opcode.VMOVUPS, List.of(RZ_M512, M512_RZ)), - Map.entry(Opcode.VMOVDQU8, List.of(RZ_M512, M512_RZ)), - Map.entry(Opcode.VMOVDQU64, List.of(RZ_M512, M512_RZ)), + Map.entry( + Opcode.VMOVDQU8, + List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), + Map.entry( + Opcode.VMOVDQU64, + List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), + Map.entry( + Opcode.VMOVDQU16, + List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), + Map.entry( + Opcode.VMOVDQU32, + List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), + Map.entry( + Opcode.VMOVDQA32, + List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), + Map.entry( + Opcode.VMOVDQA64, + List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), Map.entry(Opcode.VMOVNTDQ, List.of(M256_RY, M512_RZ)), Map.entry(Opcode.PCMPGTB, List.of(RX_RX)), - Map.entry(Opcode.VPCMPGTB, List.of(RX_RX_RX)), - Map.entry(Opcode.VPSUBB, List.of(RX_RX_RX)), + Map.entry(Opcode.VPCMPGTB, List.of(RX_RX_RX, RY_RY_RY, RX_RX_M128, RY_RY_M256)), + Map.entry(Opcode.VPSUBB, List.of(RX_RX_RX, RY_RY_RY, RZ_RZ_RZ, RX_RX_M128, RY_RY_M256, RZ_RZ_M512)), Map.entry(Opcode.VPCMPISTRI, List.of(RX_RX_I8)), Map.entry(Opcode.VPSLLDQ, List.of(RX_RX_I8)), Map.entry(Opcode.VPSRLDQ, List.of(RX_RX_I8)), @@ -337,7 +362,12 @@ public final class InstructionChecker { Map.entry(Opcode.KMOVQ, List.of(R64_RK, RK_R64)), Map.entry(Opcode.KMOVD, List.of(R32_RK, RK_R32)), Map.entry(Opcode.XTEST, List.of(NO_ARGS)), - Map.entry(Opcode.VPCMPNEQUB, List.of(RK_RX_M128, RK_RY_M256)), + Map.entry(Opcode.VPCMPEQUB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPLTUB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPLEUB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPNEQUB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPNLTUB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPNLEUB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), Map.entry(Opcode.SLDT, List.of(M16)), Map.entry(Opcode.INS, List.of(M8_R16, M32_R16)), Map.entry(Opcode.OUTS, List.of(R16_M8, R16_M32)), @@ -356,6 +386,8 @@ public final class InstructionChecker { Map.entry(Opcode.VPTERNLOGD, List.of(RY_RY_M256_I8, RY_RY_RY_I8)), Map.entry(Opcode.VPTESTMB, List.of(RK_RY_RY)), Map.entry(Opcode.KORTESTD, List.of(RK_RK)), + Map.entry(Opcode.KORTESTQ, List.of(RK_RK)), + Map.entry(Opcode.KXNORQ, List.of(RK_RK_RK)), Map.entry(Opcode.KORD, List.of(RK_RK_RK)), Map.entry(Opcode.TZCNT, List.of(R32_R32, R64_R64)), Map.entry(Opcode.KUNPCKDQ, List.of(RK_RK_RK)), diff --git a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java index 80e0a2e4..46399367 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java @@ -933,9 +933,27 @@ private static Instruction parseExtendedOpcodeGroup1( } private static Instruction parseExtendedOpcodeGroup7( - final ReadOnlyByteBuffer b, final byte opcodeFirstByte, final byte opcodeSecondByte) { + final ReadOnlyByteBuffer b, final byte opcodeFirstByte, final byte opcodeSecondByte, final Prefixes pref) { final ModRM modrm = modrm(b); + final boolean hasRepPrefix = pref.p1().isPresent() && pref.p1().orElseThrow() == LegacyPrefix.REP; + if (hasRepPrefix && modrm.reg() == (byte) 0b101) { + if (isIndirectOperandNeeded(modrm)) { + // F3 0F 01 /5: RSTORSSP m64 + return Instruction.builder() + .opcode(Opcode.RSTORSSP) + .op(parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.QWORD_PTR) + .build()) + .build(); + } + if (modrm.rm() == (byte) 0b010) { + // F3 0F 01 EA: SAVEPREVSSP + return Instruction.builder().opcode(Opcode.SAVEPREVSSP).build(); + } + throw new UnknownOpcode(opcodeFirstByte, opcodeSecondByte); + } + if (isIndirectOperandNeeded(modrm)) { notImplemented(); } @@ -1294,7 +1312,7 @@ private static Instruction parse2BytesOpcode( final byte opcodeSecondByte = b.read1(); return switch (opcodeSecondByte) { - case GROUP7_OPCODE -> parseExtendedOpcodeGroup7(b, opcodeFirstByte, opcodeSecondByte); + case GROUP7_OPCODE -> parseExtendedOpcodeGroup7(b, opcodeFirstByte, opcodeSecondByte, pref); case GROUP8_OPCODE -> parseExtendedOpcodeGroup8(b, opcodeFirstByte, opcodeSecondByte, pref); case GROUP9_OPCODE -> parseExtendedOpcodeGroup9(b, opcodeFirstByte, opcodeSecondByte, pref); case GROUP12_OPCODE -> parseExtendedOpcodeGroup12(b, opcodeFirstByte, opcodeSecondByte); @@ -3852,12 +3870,15 @@ private static Instruction parseVex3Opcodes( final ReadOnlyByteBuffer b, final byte opcodeFirstByte, final Prefixes pref) { final byte VPSHUFB_OPCODE = (byte) 0x00; final byte VPALIGNR_OPCODE = (byte) 0x0f; + final byte VPCMPGTB_OPCODE = (byte) 0x64; final byte VPCMPEQQ_OPCODE = (byte) 0x29; final byte VPMINUD_OPCODE = (byte) 0x3b; + final byte KXNORQ_OPCODE = (byte) 0x46; final byte KORD_OPCODE = (byte) 0x45; final byte KUNPCKDQ_OPCODE = (byte) 0x4b; final byte VPBROADCASTD_OPCODE = (byte) 0x58; final byte VPCMPISTRI_OPCODE = (byte) 0x63; + final byte VMOVD_VMOVQ_OPCODE = (byte) 0x6e; final byte VMOVDQU_RYMM_M256_OPCODE = (byte) 0x6f; final byte VPCMPEQB_OPCODE = (byte) 0x74; final byte VMOVQ_OPCODE = (byte) 0x7e; @@ -3869,6 +3890,9 @@ private static Instruction parseVex3Opcodes( final byte VMOVDQU_M256_RYMM_OPCODE = (byte) 0x7f; final byte VPMOVMSKB_OPCODE = (byte) 0xd7; final byte VPXOR_OPCODE = (byte) 0xef; + final byte VPADDB_OPCODE = (byte) 0xfc; + final byte ANDN_OPCODE = (byte) 0xf2; + final byte BLSR_BLSMSK_BLSI_OPCODE = (byte) 0xf3; final byte BZHI_OPCODE = (byte) 0xf5; final byte SARX_OPCODE = (byte) 0xf7; @@ -3898,6 +3922,34 @@ private static Instruction parseVex3Opcodes( .op(RegisterXMM.fromByte(getByteFromReg(vex3, modrm))) .build(); } + case VMOVD_VMOVQ_OPCODE -> { + final ModRM modrm = modrm(b); + if (vex3.w()) { + // VEX.128.66.0F.W1 6E /r: VMOVQ xmm1, r64/m64 + yield Instruction.builder() + .opcode(Opcode.VMOVQ) + .op(RegisterXMM.fromByte(getByteFromReg(vex3, modrm))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.QWORD_PTR) + .build() + : Register64.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } else { + // VEX.128.66.0F.W0 6E /r: VMOVD xmm1, r32/m32 + yield Instruction.builder() + .opcode(Opcode.VMOVD) + .op(RegisterXMM.fromByte(getByteFromReg(vex3, modrm))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.DWORD_PTR) + .build() + : Register32.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } + } case KMOVQ_RK_R64_OPCODE -> { final ModRM modrm = modrm(b); yield Instruction.builder() @@ -4013,6 +4065,67 @@ private static Instruction parseVex3Opcodes( .op(RegisterXMM.fromByte(getByteFromRM(vex3, modrm))) .build(); } + case ANDN_OPCODE -> { + final ModRM modrm = modrm(b); + if (vex3.w()) { + yield Instruction.builder() + .opcode(Opcode.ANDN) + .op(Register64.fromByte(getByteFromReg(vex3, modrm))) + .op(Register64.fromByte(getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.QWORD_PTR) + .build() + : Register64.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } else { + yield Instruction.builder() + .opcode(Opcode.ANDN) + .op(Register32.fromByte(getByteFromReg(vex3, modrm))) + .op(Register32.fromByte(getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.DWORD_PTR) + .build() + : Register32.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } + } + case BLSR_BLSMSK_BLSI_OPCODE -> { + final ModRM modrm = modrm(b); + final Opcode op = + switch (modrm.reg()) { + case (byte) 1 -> Opcode.BLSR; + case (byte) 2 -> Opcode.BLSMSK; + case (byte) 3 -> Opcode.BLSI; + default -> throw new UnknownOpcode(opcodeFirstByte); + }; + if (vex3.w()) { + yield Instruction.builder() + .opcode(op) + .op(Register64.fromByte(getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.QWORD_PTR) + .build() + : Register64.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } else { + yield Instruction.builder() + .opcode(op) + .op(Register32.fromByte(getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.DWORD_PTR) + .build() + : Register32.fromByte(getByteFromRM(vex3, modrm))) + .build(); + } + } case SARX_OPCODE -> { final ModRM modrm = modrm(b); yield Instruction.builder() @@ -4044,24 +4157,73 @@ private static Instruction parseVex3Opcodes( final ModRM modrm = modrm(b); yield Instruction.builder() .opcode(Opcode.VPANDN) - .op(RegisterXMM.fromByte(getByteFromReg(vex3, modrm))) - .op(RegisterXMM.fromByte(getByteFromV(vex3))) - .op(RegisterXMM.fromByte(getByteFromRM(vex3, modrm))) + .op(vectorRegister(vex3, getByteFromReg(vex3, modrm))) + .op(vectorRegister(vex3, getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vex3.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .build() + : vectorRegister(vex3, getByteFromRM(vex3, modrm))) .build(); } case VPXOR_OPCODE -> { final ModRM modrm = modrm(b); yield Instruction.builder() .opcode(Opcode.VPXOR) - .op(RegisterXMM.fromByte(getByteFromReg(vex3, modrm))) - .op(RegisterXMM.fromByte(getByteFromV(vex3))) - .op(RegisterXMM.fromByte(getByteFromRM(vex3, modrm))) + .op(vectorRegister(vex3, getByteFromReg(vex3, modrm))) + .op(vectorRegister(vex3, getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vex3.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .build() + : vectorRegister(vex3, getByteFromRM(vex3, modrm))) + .build(); + } + case VPCMPGTB_OPCODE -> { + final ModRM modrm = modrm(b); + yield Instruction.builder() + .opcode(Opcode.VPCMPGTB) + .op(vectorRegister(vex3, getByteFromReg(vex3, modrm))) + .op(vectorRegister(vex3, getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vex3.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .build() + : vectorRegister(vex3, getByteFromRM(vex3, modrm))) + .build(); + } + case VPADDB_OPCODE -> { + final ModRM modrm = modrm(b); + yield Instruction.builder() + .opcode(Opcode.VPADDB) + .op(vectorRegister(vex3, getByteFromReg(vex3, modrm))) + .op(vectorRegister(vex3, getByteFromV(vex3))) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vex3.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .build() + : vectorRegister(vex3, getByteFromRM(vex3, modrm))) .build(); } case KORTESTD_OPCODE -> { final ModRM modrm = modrm(b); yield new GeneralInstruction( - Opcode.KORTESTD, MaskRegister.fromByte(modrm.reg()), MaskRegister.fromByte(modrm.rm())); + vex3.w() ? Opcode.KORTESTQ : Opcode.KORTESTD, + MaskRegister.fromByte(modrm.reg()), + MaskRegister.fromByte(modrm.rm())); + } + case KXNORQ_OPCODE -> { + final ModRM modrm = modrm(b); + yield Instruction.builder() + .opcode(Opcode.KXNORQ) + .op(MaskRegister.fromByte(modrm.reg())) + .op(MaskRegister.fromByte(getByteFromV(vex3))) + .op(MaskRegister.fromByte(modrm.rm())) + .build(); } case KORD_OPCODE -> { final ModRM modrm = modrm(b); @@ -4113,6 +4275,7 @@ private static Instruction parseEvexOpcodes( final byte VMOVQ_RX_M128_OPCODE = (byte) 0x6e; final byte VMOVDQU64_RZMM_M512_OPCODE = (byte) 0x6f; final byte VPCMPEQB_OPCODE = (byte) 0x74; + final byte VPBROADCASTB_RX_M8_OPCODE = (byte) 0x78; final byte VPBROADCASTB_OPCODE = (byte) 0x7a; final byte VPBROADCASTD_OPCODE = (byte) 0x7c; final byte VMOVQ_R64_RX_OPCODE = (byte) 0x7e; @@ -4121,6 +4284,8 @@ private static Instruction parseEvexOpcodes( final byte VMOVNTDQ_OPCODE = (byte) 0xe7; final byte VPORQ_OPCODE = (byte) 0xeb; final byte VPXORQ_OPCODE = (byte) 0xef; + final byte VPSUBB_OPCODE = (byte) 0xf8; + final byte VPADDB_OPCODE = (byte) 0xfc; final EvexPrefix evex = pref.evex().orElseThrow(); @@ -4153,6 +4318,19 @@ private static Instruction parseEvexOpcodes( .op(RegisterXMM.fromByte(getByteFromRM(evex, modrm))) .build(); } + case VPBROADCASTB_RX_M8_OPCODE -> { + final ModRM modrm = modrm(b); + yield Instruction.builder() + .opcode(Opcode.VPBROADCASTB) + .op(getZmmFromReg(evex, modrm)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(PointerSize.BYTE_PTR) + .build() + : RegisterXMM.fromByte(getByteFromRM(evex, modrm))) + .build(); + } case VPBROADCASTB_OPCODE -> { final ModRM modrm = modrm(b); yield Instruction.builder() @@ -4185,11 +4363,16 @@ private static Instruction parseEvexOpcodes( if (evex.a() != (byte) 0) { ib.mask(MaskRegister.fromByte(evex.a())); } - yield ib.opcode(evex.w() ? Opcode.VMOVDQU64 : Opcode.VMOVDQU8) - .op(getZmmFromReg(evex, modrm)) - .op(parseIndirectOperand(b, pref, modrm) - .pointer(PointerSize.ZMMWORD_PTR) - .build()) + final byte r1 = or(getByteFromReg(evex, modrm), evex.r1() ? 0 : (byte) 0b00010000); + final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + yield ib.opcode(vmovdqOpcode(evex)) + .op(vectorRegister(evex, r1)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r2)) .build(); } case VMOVDQU64_M512_RZMM_OPCODE -> { @@ -4198,11 +4381,12 @@ private static Instruction parseEvexOpcodes( if (evex.a() != (byte) 0) { ib.mask(MaskRegister.fromByte(evex.a())); } - yield ib.opcode(evex.w() ? Opcode.VMOVDQU64 : Opcode.VMOVDQU8) + final byte r1 = or(getByteFromReg(evex, modrm), evex.r1() ? 0 : (byte) 0b00010000); + yield ib.opcode(vmovdqOpcode(evex)) .op(parseIndirectOperand(b, pref, modrm) - .pointer(PointerSize.ZMMWORD_PTR) + .pointer(vectorPointerSize(evex)) .build()) - .op(getZmmFromReg(evex, modrm)) + .op(vectorRegister(evex, r1)) .build(); } case VMOVNTDQ_OPCODE -> { @@ -4233,22 +4417,31 @@ yield new GeneralInstruction( } case VPCMPNEQUB_OPCODE -> { final ModRM modrm = modrm(b); - final InstructionBuilder ib = Instruction.builder(); + final byte r1 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + final InstructionBuilder ib = Instruction.builder() + .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) + .op(vectorRegister(evex, r1)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r2)); if (evex.a() != (byte) 0) { ib.mask(MaskRegister.fromByte(evex.a())); } - final byte r2 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); - final Instruction tmp = ib.opcode(Opcode.VPCMPNEQUB) - .op(MaskRegister.fromByte(modrm.reg())) - .op(evex.l() ? RegisterYMM.fromByte(r2) : RegisterXMM.fromByte(r2)) - .op(parseIndirectOperand(b, pref, modrm) - .pointer(evex.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) - .build()) - .build(); - // For some unknown (to me) reason, after VPCMPNEQUB instructions there is an extra 0x04 byte which is - // not needed... why? - b.read1(); - yield tmp; + final byte predicate = b.read1(); + switch (predicate) { + case (byte) 0x00 -> ib.opcode(Opcode.VPCMPEQUB); + case (byte) 0x01 -> ib.opcode(Opcode.VPCMPLTUB); + case (byte) 0x02 -> ib.opcode(Opcode.VPCMPLEUB); + case (byte) 0x04 -> ib.opcode(Opcode.VPCMPNEQUB); + case (byte) 0x05 -> ib.opcode(Opcode.VPCMPNLTUB); + case (byte) 0x06 -> ib.opcode(Opcode.VPCMPNLEUB); + default -> throw new UnknownOpcode(predicate); + } + yield ib.build(); } case VPCMPxxB_OPCODE -> { final ModRM modrm = modrm(b); @@ -4256,13 +4449,13 @@ yield new GeneralInstruction( final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); final InstructionBuilder ib = Instruction.builder() .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) - .op(evex.l() ? RegisterYMM.fromByte(r1) : RegisterXMM.fromByte(r1)) + .op(vectorRegister(evex, r1)) .op( isIndirectOperandNeeded(modrm) ? parseIndirectOperand(b, pref, modrm) - .pointer(evex.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .pointer(vectorPointerSize(evex)) .build() - : (evex.l() ? RegisterYMM.fromByte(r2) : RegisterXMM.fromByte(r2))); + : vectorRegister(evex, r2)); if (evex.a() != (byte) 0) { ib.mask(MaskRegister.fromByte(evex.a())); } @@ -4285,13 +4478,13 @@ yield new GeneralInstruction( final InstructionBuilder ib = Instruction.builder() .opcode(Opcode.VPCMPEQB) .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) - .op(evex.l() ? RegisterYMM.fromByte(r1) : RegisterXMM.fromByte(r1)) + .op(vectorRegister(evex, r1)) .op( isIndirectOperandNeeded(modrm) ? parseIndirectOperand(b, pref, modrm) - .pointer(evex.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .pointer(vectorPointerSize(evex)) .build() - : (evex.l() ? RegisterYMM.fromByte(r2) : RegisterXMM.fromByte(r2))); + : vectorRegister(evex, r2)); if (evex.a() != (byte) 0) { ib.mask(MaskRegister.fromByte(evex.a())); } @@ -4304,13 +4497,13 @@ yield new GeneralInstruction( final Instruction tmp = Instruction.builder() .opcode(Opcode.VPCMPEQD) .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) - .op(evex.l() ? RegisterYMM.fromByte(r1) : RegisterXMM.fromByte(r1)) + .op(vectorRegister(evex, r1)) .op( isIndirectOperandNeeded(modrm) ? parseIndirectOperand(b, pref, modrm) - .pointer(evex.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .pointer(vectorPointerSize(evex)) .build() - : (evex.l() ? RegisterYMM.fromByte(r2) : RegisterXMM.fromByte(r2))) + : vectorRegister(evex, r2)) .build(); b.read1(); yield tmp; @@ -4364,6 +4557,46 @@ yield new GeneralInstruction( : RegisterYMM.fromByte( or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)))); } + case VPSUBB_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1 = or(evex.r1() ? 0 : (byte) 0b00010000, getByteFromReg(evex, modrm)); + final byte r2 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r3 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + final InstructionBuilder ib = Instruction.builder() + .opcode(Opcode.VPSUBB) + .op(vectorRegister(evex, r1)) + .op(vectorRegister(evex, r2)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r3)); + if (evex.a() != (byte) 0) { + ib.mask(MaskRegister.fromByte(evex.a())); + } + yield ib.build(); + } + case VPADDB_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1 = or(evex.r1() ? 0 : (byte) 0b00010000, getByteFromReg(evex, modrm)); + final byte r2 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r3 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + final InstructionBuilder ib = Instruction.builder() + .opcode(Opcode.VPADDB) + .op(vectorRegister(evex, r1)) + .op(vectorRegister(evex, r2)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r3)); + if (evex.a() != (byte) 0) { + ib.mask(MaskRegister.fromByte(evex.a())); + } + yield ib.build(); + } case VPMINUD_OPCODE -> { final ModRM modrm = modrm(b); final InstructionBuilder ib = Instruction.builder() @@ -4406,6 +4639,41 @@ private static RegisterZMM getZmmFromReg(final EvexPrefix evex, final ModRM modr return RegisterZMM.fromByte(or(getByteFromReg(evex, modrm), evex.r1() ? 0 : (byte) 0b00010000)); } + private static Register vectorRegister(final Vex3Prefix vex3, final byte raw) { + return vex3.l() ? RegisterYMM.fromByte(raw) : RegisterXMM.fromByte(raw); + } + + private static Register vectorRegister(final EvexPrefix evex, final byte raw) { + if (evex.l1()) { + return RegisterZMM.fromByte(raw); + } else if (evex.l()) { + return RegisterYMM.fromByte(raw); + } else { + return RegisterXMM.fromByte(raw); + } + } + + private static Opcode vmovdqOpcode(final EvexPrefix evex) { + return switch (evex.p()) { + case (byte) 1 -> evex.w() ? Opcode.VMOVDQA64 : Opcode.VMOVDQA32; + case (byte) 2 -> evex.w() ? Opcode.VMOVDQU64 : Opcode.VMOVDQU32; + case (byte) 3 -> evex.w() ? Opcode.VMOVDQU8 : Opcode.VMOVDQU16; + default -> + throw new IllegalArgumentException( + String.format("Invalid mandatory prefix for VMOVDQ*: 0x%02x.", evex.p())); + }; + } + + private static PointerSize vectorPointerSize(final EvexPrefix evex) { + if (evex.l1()) { + return PointerSize.ZMMWORD_PTR; + } else if (evex.l()) { + return PointerSize.YMMWORD_PTR; + } else { + return PointerSize.XMMWORD_PTR; + } + } + private static void invalidValue() { throw new IllegalArgumentException("Invalid value."); } diff --git a/id/src/main/java/com/ledmington/cpu/OperandTypeList.java b/id/src/main/java/com/ledmington/cpu/OperandTypeList.java index e3039f4a..c8e3210a 100644 --- a/id/src/main/java/com/ledmington/cpu/OperandTypeList.java +++ b/id/src/main/java/com/ledmington/cpu/OperandTypeList.java @@ -131,12 +131,21 @@ public enum OperandTypeList { /** The list of operands made of a 256-bit YMM vector register and a 128-bit XMM vector register. */ RY_RX(OperandType.RY, OperandType.RX), + /** The list of operands made of a 256-bit YMM vector register and a 256-bit YMM vector register. */ + RY_RY(OperandType.RY, OperandType.RY), + /** The list of operands made of a 512-bit ZMM vector register and a 32-bit general-purpose register. */ RZ_R32(OperandType.RZ, OperandType.R32), /** The list of operands made of a 512-bit ZMM vector register and a 128-bit XMM vector register. */ RZ_RX(OperandType.RZ, OperandType.RX), + /** The list of operands made of a 512-bit ZMM vector register and a BYTE PTR indirect operand. */ + RZ_M8(OperandType.RZ, OperandType.M8), + + /** The list of operands made of a 512-bit ZMM vector register and a 512-bit ZMM vector register. */ + RZ_RZ(OperandType.RZ, OperandType.RZ), + /** The list of operands made of a 32-bit general-purpose register and a vector mask register. */ R32_RK(OperandType.R32, OperandType.RK), @@ -407,6 +416,18 @@ public enum OperandTypeList { */ R64_R64_R64(OperandType.R64, OperandType.R64, OperandType.R64), + /** + * The list of operands made of a 32-bit general-purpose register, a 32-bit general-purpose register and a DWORD PTR + * indirect operand. + */ + R32_R32_M32(OperandType.R32, OperandType.R32, OperandType.M32), + + /** + * The list of operands made of a 64-bit general-purpose register, a 64-bit general-purpose register and a QWORD PTR + * indirect operand. + */ + R64_R64_M64(OperandType.R64, OperandType.R64, OperandType.M64), + /** * The list of operands made of a vector mask register, a 128-bit XMM vector register and a XMMWORD PTR indirect * operand. @@ -419,6 +440,12 @@ public enum OperandTypeList { */ RK_RY_M256(OperandType.RK, OperandType.RY, OperandType.M256), + /** + * The list of operands made of a vector mask register, a 512-bit ZMM vector register and a ZMMWORD PTR indirect + * operand. + */ + RK_RZ_M512(OperandType.RK, OperandType.RZ, OperandType.M512), + /** * The list of operands made of a 128-bit XMM vector register, a 128-bit XMM vector register and a 128-bit XMM * vector register. @@ -449,6 +476,12 @@ public enum OperandTypeList { */ RK_RY_RY(OperandType.RK, OperandType.RY, OperandType.RY), + /** + * The list of operands made of a vector mask register, a 512-bit ZMM vector register and a 512-bit ZMM vector + * register. + */ + RK_RZ_RZ(OperandType.RK, OperandType.RZ, OperandType.RZ), + /** The list of operands made of a vector mask register, a vector mask register and a vector mask register. */ RK_RK_RK(OperandType.RK, OperandType.RK, OperandType.RK), @@ -458,6 +491,18 @@ public enum OperandTypeList { */ RY_RY_M256(OperandType.RY, OperandType.RY, OperandType.M256), + /** + * The list of operands made of a 512-bit ZMM vector register, a 512-bit ZMM vector register and a 512-bit ZMM + * vector register. + */ + RZ_RZ_RZ(OperandType.RZ, OperandType.RZ, OperandType.RZ), + + /** + * The list of operands made of a 512-bit ZMM vector register, a 512-bit ZMM vector register and a ZMMWORD PTR + * indirect operand. + */ + RZ_RZ_M512(OperandType.RZ, OperandType.RZ, OperandType.M512), + /** * The list of operands made of a 128-bit XMM vector register, a 128-bit XMM vector register, a XMMWORD PTR indirect * operand and an 8-bit immediate value. diff --git a/id/src/main/java/com/ledmington/cpu/x86/EvexPrefix.java b/id/src/main/java/com/ledmington/cpu/x86/EvexPrefix.java index bc1aacb6..9a5d281e 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/EvexPrefix.java +++ b/id/src/main/java/com/ledmington/cpu/x86/EvexPrefix.java @@ -205,6 +205,16 @@ public boolean z() { return z; } + /** + * Returns the value of the mandatory-prefix (pp) field in this EVEX prefix: 0 for none, 1 for 0x66, 2 for 0xf3, 3 + * for 0xf2. + * + * @return The 2-bit mandatory-prefix field. + */ + public byte p() { + return p; + } + /** * Returns the L bit value. * @@ -214,6 +224,15 @@ public boolean l() { return l; } + /** + * Returns the L' (L1) bit value. + * + * @return The L1 bit. + */ + public boolean l1() { + return l1; + } + /** * Returns the V' (V1) bit value. * diff --git a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java index 1cd5fd52..876d853e 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java @@ -35,9 +35,21 @@ public enum Opcode { /** Logical AND. */ AND, + /** Logical AND NOT. */ + ANDN, + /** Bitwise logical AND of packed double-precision floating-point values. */ ANDPD, + /** Extract lowest set isolated bit. */ + BLSI, + + /** Set all lower bits below the lowest set bit to 1. */ + BLSMSK, + + /** Reset lowest set bit. */ + BLSR, + /** Bounded jump. */ BND_JMP("bnd jmp"), @@ -293,12 +305,18 @@ public enum Opcode { /** OR masks and set flags. */ KORTESTD, + /** OR masks and set flags. */ + KORTESTQ, + /** Unpack for Mask Registers. */ KUNPCKBW, /** Unpack for Mask Registers. */ KUNPCKDQ, + /** Bitwise logical XNOR masks. */ + KXNORQ, + /** Load status flags into AH register. */ LAHF, @@ -581,6 +599,9 @@ public enum Opcode { /** Read shadow stack pointer. */ RDSSPQ, + /** Restore saved shadow stack pointer. */ + RSTORSSP, + /** Read time-stamp counter. */ RDTSC, @@ -605,6 +626,9 @@ public enum Opcode { /** Arithmetic shift right without affecting flags. */ SARX, + /** Save previous shadow stack pointer. */ + SAVEPREVSSP, + /** Integer subtraction with borrow. */ SBB, @@ -737,6 +761,9 @@ public enum Opcode { /** Compare packed data for equal. */ VPCMPEQB, + /** Compare packed unsigned byte values into mask. */ + VPCMPEQUB, + /** Compare packed data for equal. */ VPCMPEQD, @@ -752,9 +779,15 @@ public enum Opcode { /** Compare packed data for less than or equal. */ VPCMPLEB, + /** Compare packed unsigned byte values into mask. */ + VPCMPLEUB, + /** Compare packed data for less than. */ VPCMPLTB, + /** Compare packed unsigned byte values into mask. */ + VPCMPLTUB, + /** Compare packed data for not equal. */ VPCMPNEQB, @@ -764,9 +797,15 @@ public enum Opcode { /** Compare packed data for not less than or equal. */ VPCMPNLEB, + /** Compare packed unsigned byte values into mask. */ + VPCMPNLEUB, + /** Compare packed data for not less than. */ VPCMPNLTB, + /** Compare packed unsigned byte values into mask. */ + VPCMPNLTUB, + /** Minimum of packed unsigned byte integers. */ VPMINUB, @@ -797,6 +836,12 @@ public enum Opcode { /** Move doubleword. */ VMOVD, + /** Move aligned packed integer values. */ + VMOVDQA32, + + /** Move aligned packed integer values. */ + VMOVDQA64, + /** Move unaligned packed integer values. */ VMOVDQU, @@ -827,6 +872,9 @@ public enum Opcode { /** Logical AND NOT. */ VPANDN, + /** Add packed byte integers. */ + VPADDB, + /** Move byte mask. */ VPMOVMSKB, diff --git a/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java b/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java index f0f0feb9..e13b4a2e 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Vex3Prefix.java @@ -148,4 +148,13 @@ public byte v() { public byte p() { return p; } + + /** + * Returns the value of the L bit in this VEX3 prefix. + * + * @return True if the L bit is set (256-bit vector length), false otherwise (128-bit vector length). + */ + public boolean l() { + return l; + } } diff --git a/objdump/src/main/java/com/ledmington/objdump/Main.java b/objdump/src/main/java/com/ledmington/objdump/Main.java index 987c5012..76b54c65 100644 --- a/objdump/src/main/java/com/ledmington/objdump/Main.java +++ b/objdump/src/main/java/com/ledmington/objdump/Main.java @@ -231,9 +231,26 @@ private static boolean isJumpWithImmediate(final Instruction inst) { return inst.hasFirstOperand() && !inst.hasSecondOperand() && (inst.opcode() == Opcode.JMP + || inst.opcode() == Opcode.JA + || inst.opcode() == Opcode.JAE + || inst.opcode() == Opcode.JB + || inst.opcode() == Opcode.JBE || inst.opcode() == Opcode.JE - || inst.opcode() == Opcode.JNE + || inst.opcode() == Opcode.JG + || inst.opcode() == Opcode.JGE + || inst.opcode() == Opcode.JL || inst.opcode() == Opcode.JLE + || inst.opcode() == Opcode.JNE + || inst.opcode() == Opcode.JNS + || inst.opcode() == Opcode.JO + || inst.opcode() == Opcode.JNO + || inst.opcode() == Opcode.JNP + || inst.opcode() == Opcode.JP + || inst.opcode() == Opcode.JRCXZ + || inst.opcode() == Opcode.JS + || inst.opcode() == Opcode.LOOP + || inst.opcode() == Opcode.LOOPE + || inst.opcode() == Opcode.LOOPNE || inst.opcode() == Opcode.CALL) && inst.firstOperand() instanceof Immediate; } From b358078dee5512a4e3a20851cbebf5c158083ee3 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 17 Aug 2026 13:30:33 +0200 Subject: [PATCH 4/8] Added more encodings --- .../ledmington/cpu/InstructionChecker.java | 20 ++- .../ledmington/cpu/InstructionDecoder.java | 135 ++++++++++++++++-- .../java/com/ledmington/cpu/x86/Opcode.java | 42 ++++++ 3 files changed, 180 insertions(+), 17 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java index 8bfaaca2..f1dc1018 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java @@ -234,7 +234,9 @@ public final class InstructionChecker { Map.entry(Opcode.PXOR, List.of(RMM_RMM, RX_RX, RX_M128)), Map.entry(Opcode.POR, List.of(RX_RX, RX_M128)), Map.entry(Opcode.PAND, List.of(RX_RX, RX_M128)), + Map.entry(Opcode.PANDN, List.of(RX_RX, RX_M128)), Map.entry(Opcode.PADDQ, List.of(RX_RX, RX_M128)), + Map.entry(Opcode.PADDB, List.of(RX_RX, RX_M128)), Map.entry(Opcode.PADDD, List.of(RX_RX)), Map.entry(Opcode.PSUBQ, List.of(RX_RX, RX_M128)), Map.entry(Opcode.PSUBB, List.of(RX_RX)), @@ -289,7 +291,7 @@ public final class InstructionChecker { Map.entry(Opcode.PALIGNR, List.of(RX_RX_I8, RX_M128_I8)), Map.entry(Opcode.VPXOR, List.of(RX_RX_RX, RY_RY_RY, RX_RX_M128, RY_RY_M256)), Map.entry(Opcode.VPADDB, List.of(RX_RX_RX, RY_RY_RY, RZ_RZ_RZ, RX_RX_M128, RY_RY_M256, RZ_RZ_M512)), - Map.entry(Opcode.VPXORQ, List.of(RY_RY_M256)), + Map.entry(Opcode.VPXORQ, List.of(RX_RX_RX, RY_RY_RY, RZ_RZ_RZ, RX_RX_M128, RY_RY_M256, RZ_RZ_M512)), Map.entry(Opcode.VPORQ, List.of(RY_RY_RY)), Map.entry(Opcode.PEXTRW, List.of(R32_RMM_I8)), Map.entry(Opcode.VMOVDQU, List.of(RY_M256, M256_RY)), @@ -300,7 +302,14 @@ public final class InstructionChecker { Opcode.VPCMPEQB, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RY_RY_M256, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), Map.entry(Opcode.VPCMPLTB, List.of(RK_RY_RY)), - Map.entry(Opcode.VPCMPEQD, List.of(RK_RY_RY, RY_RY_M256, RK_RY_M256)), + Map.entry( + Opcode.VPCMPEQD, + List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RY_RY_M256, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPLTD, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPLED, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPNEQD, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPNLTD, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPCMPNLED, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), Map.entry(Opcode.VPCMPEQQ, List.of(RX_RX_M128)), Map.entry(Opcode.VPCMPNEQB, List.of(RK_RY_RY, RK_RZ_RZ, RK_RY_M256, RK_RZ_M512)), Map.entry(Opcode.VZEROALL, List.of(NO_ARGS)), @@ -344,6 +353,7 @@ public final class InstructionChecker { List.of(RX_M128, M128_RX, RX_RX, RY_M256, M256_RY, RY_RY, RZ_M512, M512_RZ, RZ_RZ)), Map.entry(Opcode.VMOVNTDQ, List.of(M256_RY, M512_RZ)), Map.entry(Opcode.PCMPGTB, List.of(RX_RX)), + Map.entry(Opcode.PCMPGTD, List.of(RMM_RMM, RX_RX, RX_M128)), Map.entry(Opcode.VPCMPGTB, List.of(RX_RX_RX, RY_RY_RY, RX_RX_M128, RY_RY_M256)), Map.entry(Opcode.VPSUBB, List.of(RX_RX_RX, RY_RY_RY, RZ_RZ_RZ, RX_RX_M128, RY_RY_M256, RZ_RZ_M512)), Map.entry(Opcode.VPCMPISTRI, List.of(RX_RX_I8)), @@ -385,8 +395,14 @@ public final class InstructionChecker { Map.entry(Opcode.OUT, List.of(I8_R8, I8_R32, R16_R8, R16_R32)), Map.entry(Opcode.VPTERNLOGD, List.of(RY_RY_M256_I8, RY_RY_RY_I8)), Map.entry(Opcode.VPTESTMB, List.of(RK_RY_RY)), + Map.entry(Opcode.VPTESTMD, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPTESTMQ, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPTESTNMD, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.VPTESTNMQ, List.of(RK_RX_RX, RK_RY_RY, RK_RZ_RZ, RK_RX_M128, RK_RY_M256, RK_RZ_M512)), + Map.entry(Opcode.KORTESTB, List.of(RK_RK)), Map.entry(Opcode.KORTESTD, List.of(RK_RK)), Map.entry(Opcode.KORTESTQ, List.of(RK_RK)), + Map.entry(Opcode.KORTESTW, List.of(RK_RK)), Map.entry(Opcode.KXNORQ, List.of(RK_RK_RK)), Map.entry(Opcode.KORD, List.of(RK_RK_RK)), Map.entry(Opcode.TZCNT, List.of(R32_R32, R64_R64)), diff --git a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java index 46399367..dbbef704 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java @@ -1179,6 +1179,7 @@ private static Instruction parse2BytesOpcode( final byte PUNPCKLWD_OPCODE = (byte) 0x61; final byte PUNPCKLDQ_OPCODE = (byte) 0x62; final byte PCMPGTB_OPCODE = (byte) 0x64; + final byte PCMPGTD_OPCODE = (byte) 0x66; final byte PUNPCKHDQ_OPCODE = (byte) 0x6a; final byte PUNPCKLQDQ_OPCODE = (byte) 0x6c; final byte PUNPCKHQDQ_OPCODE = (byte) 0x6d; @@ -1261,6 +1262,7 @@ private static Instruction parse2BytesOpcode( final byte PMINUB_OPCODE = (byte) 0xda; final byte PMAXUB_OPCODE = (byte) 0xde; final byte PAND_OPCODE = (byte) 0xdb; + final byte PANDN_OPCODE = (byte) 0xdf; final byte MOVNTDQ_OPCODE = (byte) 0xe7; final byte POR_OPCODE = (byte) 0xeb; final byte PXOR_OPCODE = (byte) 0xef; @@ -1269,6 +1271,7 @@ private static Instruction parse2BytesOpcode( final byte PSUBW_OPCODE = (byte) 0xf9; final byte PSUBD_OPCODE = (byte) 0xfa; final byte PSUBQ_OPCODE = (byte) 0xfb; + final byte PADDB_OPCODE = (byte) 0xfc; final byte PADDD_OPCODE = (byte) 0xfe; final Opcode[] cmovOpcodes = { @@ -1843,6 +1846,16 @@ yield new GeneralInstruction( .op(getXMMArgument(b, modrm, pref, r2Byte)) .build(); } + case PANDN_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1Byte = getByteFromReg(pref.rex(), modrm); + final byte r2Byte = getByteFromRM(pref, modrm); + yield Instruction.builder() + .opcode(Opcode.PANDN) + .op(RegisterXMM.fromByte(r1Byte)) + .op(getXMMArgument(b, modrm, pref, r2Byte)) + .build(); + } case PADDQ_OPCODE -> { final ModRM modrm = modrm(b); final byte r1Byte = getByteFromReg(pref.rex(), modrm); @@ -1853,6 +1866,16 @@ yield new GeneralInstruction( .op(getXMMArgument(b, modrm, pref, r2Byte)) .build(); } + case PADDB_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1Byte = getByteFromReg(pref.rex(), modrm); + final byte r2Byte = getByteFromRM(pref, modrm); + yield Instruction.builder() + .opcode(Opcode.PADDB) + .op(RegisterXMM.fromByte(r1Byte)) + .op(getXMMArgument(b, modrm, pref, r2Byte)) + .build(); + } case PADDD_OPCODE -> { final ModRM modrm = modrm(b); final byte r1Byte = getByteFromReg(pref.rex(), modrm); @@ -1975,6 +1998,29 @@ yield new GeneralInstruction( : RegisterMMX.fromByte(r2Byte)) .build(); } + case PCMPGTD_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1Byte = getByteFromReg(pref.rex(), modrm); + final byte r2Byte = getByteFromRM(pref, modrm); + yield Instruction.builder() + .opcode(Opcode.PCMPGTD) + .op( + pref.hasOperandSizeOverridePrefix() + ? RegisterXMM.fromByte(r1Byte) + : RegisterMMX.fromByte(r1Byte)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer( + pref.hasOperandSizeOverridePrefix() + ? PointerSize.XMMWORD_PTR + : PointerSize.QWORD_PTR) + .build() + : (pref.hasOperandSizeOverridePrefix() + ? RegisterXMM.fromByte(r2Byte) + : RegisterMMX.fromByte(r2Byte))) + .build(); + } case PCMPEQD_OPCODE -> { final ModRM modrm = modrm(b); final byte r1Byte = getByteFromReg(pref.rex(), modrm); @@ -4211,10 +4257,12 @@ private static Instruction parseVex3Opcodes( } case KORTESTD_OPCODE -> { final ModRM modrm = modrm(b); + final boolean hasSixtySixPrefix = vex3.p() == (byte) 1; + final Opcode kortestOpcode = vex3.w() + ? (hasSixtySixPrefix ? Opcode.KORTESTD : Opcode.KORTESTQ) + : (hasSixtySixPrefix ? Opcode.KORTESTB : Opcode.KORTESTW); yield new GeneralInstruction( - vex3.w() ? Opcode.KORTESTQ : Opcode.KORTESTD, - MaskRegister.fromByte(modrm.reg()), - MaskRegister.fromByte(modrm.rm())); + kortestOpcode, MaskRegister.fromByte(modrm.reg()), MaskRegister.fromByte(modrm.rm())); } case KXNORQ_OPCODE -> { final ModRM modrm = modrm(b); @@ -4265,9 +4313,11 @@ private static Instruction parseEvexOpcodes( final byte VMOVUPS_R512_M512_OPCODE = (byte) 0x10; final byte VMOVUPS_M512_R512_OPCODE = (byte) 0x11; final byte VBROADCASTSS_OPCODE = (byte) 0x18; - final byte VPCMPEQD_OPCODE = (byte) 0x1f; + final byte VPCMPxxD_OPCODE = (byte) 0x1f; + final byte VPCMPEQD_OPCODE = (byte) 0x76; final byte VPTERNLOGD_OPCODE = (byte) 0x25; final byte VPTESTMB_OPCODE = (byte) 0x26; + final byte VPTESTxMD_OPCODE = (byte) 0x27; final byte VMOVAPS_OPCODE = (byte) 0x29; final byte VPMINUD_OPCODE = (byte) 0x3b; final byte VPCMPNEQUB_OPCODE = (byte) 0x3e; @@ -4490,12 +4540,16 @@ yield new GeneralInstruction( } yield ib.build(); } - case VPCMPEQD_OPCODE -> { + case VPTESTxMD_OPCODE -> { final ModRM modrm = modrm(b); final byte r1 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); - final Instruction tmp = Instruction.builder() - .opcode(Opcode.VPCMPEQD) + final boolean isNegated = evex.p() == (byte) 2; + yield Instruction.builder() + .opcode( + evex.w() + ? (isNegated ? Opcode.VPTESTNMQ : Opcode.VPTESTMQ) + : (isNegated ? Opcode.VPTESTNMD : Opcode.VPTESTMD)) .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) .op(vectorRegister(evex, r1)) .op( @@ -4505,18 +4559,69 @@ yield new GeneralInstruction( .build() : vectorRegister(evex, r2)) .build(); - b.read1(); - yield tmp; + } + case VPCMPxxD_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + final InstructionBuilder ib = Instruction.builder() + .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) + .op(vectorRegister(evex, r1)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r2)); + if (evex.a() != (byte) 0) { + ib.mask(MaskRegister.fromByte(evex.a())); + } + final byte predicate = b.read1(); + switch (predicate) { + case (byte) 0x00 -> ib.opcode(Opcode.VPCMPEQD); + case (byte) 0x01 -> ib.opcode(Opcode.VPCMPLTD); + case (byte) 0x02 -> ib.opcode(Opcode.VPCMPLED); + case (byte) 0x04 -> ib.opcode(Opcode.VPCMPNEQD); + case (byte) 0x05 -> ib.opcode(Opcode.VPCMPNLTD); + case (byte) 0x06 -> ib.opcode(Opcode.VPCMPNLED); + default -> throw new UnknownOpcode(predicate); + } + yield ib.build(); + } + case VPCMPEQD_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + final InstructionBuilder ib = Instruction.builder() + .opcode(Opcode.VPCMPEQD) + .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) + .op(vectorRegister(evex, r1)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r2)); + if (evex.a() != (byte) 0) { + ib.mask(MaskRegister.fromByte(evex.a())); + } + yield ib.build(); } case VPXORQ_OPCODE -> { final ModRM modrm = modrm(b); + final byte r1 = or(evex.r1() ? 0 : (byte) 0b00010000, getByteFromReg(evex, modrm)); + final byte r2 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r3 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); yield Instruction.builder() .opcode(Opcode.VPXORQ) - .op(RegisterYMM.fromByte(or(evex.r1() ? 0 : (byte) 0b00010000, getByteFromReg(evex, modrm)))) - .op(RegisterYMM.fromByte(or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)))) - .op(parseIndirectOperand(b, pref, modrm) - .pointer(PointerSize.YMMWORD_PTR) - .build()) + .op(vectorRegister(evex, r1)) + .op(vectorRegister(evex, r2)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(vectorPointerSize(evex)) + .build() + : vectorRegister(evex, r3)) .build(); } case VPTERNLOGD_OPCODE -> { @@ -4657,7 +4762,7 @@ private static Opcode vmovdqOpcode(final EvexPrefix evex) { return switch (evex.p()) { case (byte) 1 -> evex.w() ? Opcode.VMOVDQA64 : Opcode.VMOVDQA32; case (byte) 2 -> evex.w() ? Opcode.VMOVDQU64 : Opcode.VMOVDQU32; - case (byte) 3 -> evex.w() ? Opcode.VMOVDQU8 : Opcode.VMOVDQU16; + case (byte) 3 -> evex.w() ? Opcode.VMOVDQU16 : Opcode.VMOVDQU8; default -> throw new IllegalArgumentException( String.format("Invalid mandatory prefix for VMOVDQ*: 0x%02x.", evex.p())); diff --git a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java index 876d853e..b44486d8 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java @@ -302,12 +302,18 @@ public enum Opcode { /** Bitwise logical OR masks. */ KORD, + /** OR masks and set flags. */ + KORTESTB, + /** OR masks and set flags. */ KORTESTD, /** OR masks and set flags. */ KORTESTQ, + /** OR masks and set flags. */ + KORTESTW, + /** Unpack for Mask Registers. */ KUNPCKBW, @@ -431,6 +437,9 @@ public enum Opcode { /** Output string to port. */ OUTS, + /** Add packed byte integers. */ + PADDB, + /** Add packed doubleword integers. */ PADDD, @@ -443,6 +452,9 @@ public enum Opcode { /** Logical AND . */ PAND, + /** Logical AND NOT. */ + PANDN, + /** Compare packed bytes for equal. */ PCMPEQB, @@ -455,6 +467,9 @@ public enum Opcode { /** Compare packed signed integers for greater than. */ PCMPGTB, + /** Compare packed signed integers for greater than. */ + PCMPGTD, + /** Packed compare implicit-length strings. */ PCMPISTRI, @@ -779,30 +794,45 @@ public enum Opcode { /** Compare packed data for less than or equal. */ VPCMPLEB, + /** Compare packed data for less than or equal. */ + VPCMPLED, + /** Compare packed unsigned byte values into mask. */ VPCMPLEUB, /** Compare packed data for less than. */ VPCMPLTB, + /** Compare packed data for less than. */ + VPCMPLTD, + /** Compare packed unsigned byte values into mask. */ VPCMPLTUB, /** Compare packed data for not equal. */ VPCMPNEQB, + /** Compare packed data for not equal. */ + VPCMPNEQD, + /** Compare packed byte values into mask. */ VPCMPNEQUB, /** Compare packed data for not less than or equal. */ VPCMPNLEB, + /** Compare packed data for not less than or equal. */ + VPCMPNLED, + /** Compare packed unsigned byte values into mask. */ VPCMPNLEUB, /** Compare packed data for not less than. */ VPCMPNLTB, + /** Compare packed data for not less than. */ + VPCMPNLTD, + /** Compare packed unsigned byte values into mask. */ VPCMPNLTUB, @@ -830,6 +860,18 @@ public enum Opcode { /** Logical AND and set mask. */ VPTESTMB, + /** Logical AND and set mask. */ + VPTESTMD, + + /** Logical AND and set mask. */ + VPTESTMQ, + + /** Logical NAND and set mask. */ + VPTESTNMD, + + /** Logical NAND and set mask. */ + VPTESTNMQ, + /** Invoke VM function. */ VMFUNC, From c91d1cf606a9cba5ae644d44ae4016264fbf7b4c Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 17 Aug 2026 14:36:23 +0200 Subject: [PATCH 5/8] Fixed objdump tests OOM --- .../objdump/TestObjdumpAgainstSystem.java | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java b/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java index 544de4ed..65c2fde8 100644 --- a/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java +++ b/objdump/src/test/java/com/ledmington/objdump/TestObjdumpAgainstSystem.java @@ -17,7 +17,7 @@ */ package com.ledmington.objdump; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.nio.file.Files; import java.nio.file.Path; @@ -60,9 +60,30 @@ private static void checkSystemObjdumpIsAvailable() { void disassembly(final String executableName) { checkSystemObjdumpIsAvailable(); final Path p = e2eTestFile(executableName); - assertEquals( - ObjdumpSystemComparison.runSystemObjdump(p), - ObjdumpSystemComparison.runCustomObjdump(p), - () -> "objdump output for '" + p + "' did not match the system's objdump output."); + assertLinesMatch(p, ObjdumpSystemComparison.runSystemObjdump(p), ObjdumpSystemComparison.runCustomObjdump(p)); + } + + /** + * Compares the two outputs line by line and fails at the first mismatch, instead of comparing the (potentially + * huge, e.g. hundreds of thousands of lines for statically-linked executables) full strings at once: an + * assertEquals-style comparison would embed both full outputs in the resulting failure message, which then has to + * be shipped back to the Gradle daemon for reporting and can exhaust its heap. + */ + private static void assertLinesMatch(final Path p, final String systemOutput, final String customOutput) { + final String[] systemLines = systemOutput.split("\n", -1); + final String[] customLines = customOutput.split("\n", -1); + final int commonLines = Math.min(systemLines.length, customLines.length); + for (int i = 0; i < commonLines; i++) { + if (!systemLines[i].equals(customLines[i])) { + fail(String.format( + "objdump output for '%s' did not match the system's objdump output at line %,d (system has %,d lines, custom has %,d lines).%nsystem: %s%ncustom: %s", + p, i + 1, systemLines.length, customLines.length, systemLines[i], customLines[i])); + } + } + if (systemLines.length != customLines.length) { + fail(String.format( + "objdump output for '%s' did not match the system's objdump output: system has %,d lines, custom has %,d lines.", + p, systemLines.length, customLines.length)); + } } } From 14c45dfca5ed0913e6315e1ace2897cda5312fe6 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 17 Aug 2026 14:53:51 +0200 Subject: [PATCH 6/8] Fixed do_nothing.dynamic --- .../ledmington/cpu/InstructionEncoder.java | 44 +++- .../java/com/ledmington/objdump/Main.java | 246 ++++++++++++++++-- 2 files changed, 272 insertions(+), 18 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index 77ab88e7..b2f7b960 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -179,13 +179,55 @@ private static String operandString(final Instruction inst, final Operand op, fi : Optional.empty(); yield io.toIntelSyntax(requiresExplicitPointerSize, compressedDisplacement, shortHex); } - case Immediate imm -> imm.toIntelSyntax(shortHex); + case Immediate imm -> immediateOperandString(inst, op, imm, shortHex); case Register r -> r.toIntelSyntax(); case SegmentedAddress sa -> sa.toIntelSyntax(); default -> throw new IllegalArgumentException(String.format("Unknown operand type: '%s'.", op)); }; } + private static String immediateOperandString( + final Instruction inst, final Operand op, final Immediate imm, final boolean shortHex) { + // The legacy 'shift/rotate by 1' encoding (D0/D1) has no immediate byte at all: GNU objdump always displays + // its implicit count as a bare '1', never as a hex immediate. + if (GROUP2_REG_BYTES.containsKey(inst.opcode()) && imm.bits() == 8 && imm.asByte() == (byte) 1) { + return "1"; + } + + // Immediates narrower than their destination (e.g. the imm8 forms of ADD/SUB/AND/CMP/... or the imm32 form of + // a 64-bit MOV) are sign-extended by the CPU to the destination's width, and GNU objdump displays the + // sign-extended value rather than the raw encoded bytes. + final boolean isDestinationOperand = op == inst.firstOperand(); + final boolean isSignExtendingForm = !GROUP2_REG_BYTES.containsKey(inst.opcode()); + if (!isDestinationOperand + && isSignExtendingForm + && (inst.firstOperand() instanceof Register || inst.firstOperand() instanceof IndirectOperand)) { + final int destinationBits = inst.firstOperand().bits(); + if (destinationBits > imm.bits()) { + return signExtendedIntelSyntax(imm, destinationBits, shortHex); + } + } + + return imm.toIntelSyntax(shortHex); + } + + private static String signExtendedIntelSyntax( + final Immediate imm, final int destinationBits, final boolean shortHex) { + final long signExtended = + switch (imm.bits()) { + case 8 -> imm.asByte(); + case 16 -> imm.asShort(); + case 32 -> imm.asInt(); + default -> imm.asLong(); + }; + return switch (destinationBits) { + case 8 -> String.format(shortHex ? "0x%x" : "0x%02x", (byte) signExtended); + case 16 -> String.format(shortHex ? "0x%x" : "0x%04x", (short) signExtended); + case 32 -> String.format(shortHex ? "0x%x" : "0x%08x", (int) signExtended); + default -> String.format(shortHex ? "0x%x" : "0x%016x", signExtended); + }; + } + /** * Encodes a single instruction in intel syntax. Reference obtainable through objdump -Mintel-mnemonic ... * diff --git a/objdump/src/main/java/com/ledmington/objdump/Main.java b/objdump/src/main/java/com/ledmington/objdump/Main.java index 76b54c65..8d9dd43a 100644 --- a/objdump/src/main/java/com/ledmington/objdump/Main.java +++ b/objdump/src/main/java/com/ledmington/objdump/Main.java @@ -21,7 +21,9 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; +import java.util.TreeMap; import com.ledmington.cpu.InstructionDecoder; import com.ledmington.cpu.InstructionEncoder; @@ -29,6 +31,8 @@ import com.ledmington.cpu.x86.IndirectOperand; import com.ledmington.cpu.x86.Instruction; import com.ledmington.cpu.x86.Opcode; +import com.ledmington.cpu.x86.Register64; +import com.ledmington.cpu.x86.SegmentRegister; import com.ledmington.elf.ELF; import com.ledmington.elf.ELFParser; import com.ledmington.elf.SectionTable; @@ -36,6 +40,11 @@ import com.ledmington.elf.section.Section; import com.ledmington.elf.section.SectionHeaderFlags; import com.ledmington.elf.section.StringTableSection; +import com.ledmington.elf.section.gnu.GnuVersionRequirementsSection; +import com.ledmington.elf.section.gnu.GnuVersionSection; +import com.ledmington.elf.section.rel.RelocationAddendEntry; +import com.ledmington.elf.section.rel.RelocationAddendSection; +import com.ledmington.elf.section.sym.SymbolTable; import com.ledmington.elf.section.sym.SymbolTableEntry; import com.ledmington.elf.section.sym.SymbolTableEntryType; import com.ledmington.elf.section.sym.SymbolTableSection; @@ -107,6 +116,18 @@ public static void main(final String[] args) { out.println(); if (disassembleExecutableSections) { + final Map relocatedSymbols = findRelocatedSymbols(elf); + final Map pltLabels = findPltLabels(elf, relocatedSymbols); + + final Map functionNames = new HashMap<>(findFunctionNames(elf)); + functionNames.putAll(pltLabels); + + final NavigableMap allSymbols = new TreeMap<>(findAllSymbols(elf)); + allSymbols.putAll(pltLabels); + for (final Map.Entry e : relocatedSymbols.entrySet()) { + allSymbols.put(e.getKey(), e.getValue().versionedName()); + } + boolean isFirstSection = true; for (int i = 0; i < elf.getSectionTableLength(); i++) { final Section s = elf.getSection(i); @@ -119,7 +140,7 @@ public static void main(final String[] args) { } try { - disassembleSection(elf, i); + disassembleSection(elf, i, functionNames, allSymbols); } catch (final Throwable t) { out.println(); out.flush(); @@ -134,33 +155,31 @@ public static void main(final String[] args) { } @SuppressWarnings({"PMD.AvoidLiteralsInIfCondition", "PMD.NPathComplexity"}) - private static void disassembleSection(final SectionTable st, final int sectionIndex) { + private static void disassembleSection( + final SectionTable st, + final int sectionIndex, + final Map functionNames, + final NavigableMap allSymbols) { final Section s = st.getSection(sectionIndex); out.printf("Disassembly of section %s:%n", s.getName()); out.println(); final long startOfSection = s.header().getVirtualAddress(); - final Map functionNames = findFunctionNames(st); - - final boolean hasNoFunctions = functionNames.isEmpty(); - if (hasNoFunctions) { + if (!functionNames.containsKey(startOfSection)) { out.printf("%016x <%s>:%n", startOfSection, s.getName()); } final byte[] content = ((LoadableSection) s).getLoadableContent(); final ReadOnlyByteBuffer b = new ReadOnlyByteBufferV1(content, true, 1L); - String functionName = ""; while (b.getPosition() < content.length) { final long currentPosition = startOfSection + b.getPosition(); - final boolean hasFunctionName = functionNames.containsKey(currentPosition); - if (hasFunctionName) { - functionName = functionNames.get(currentPosition); + if (functionNames.containsKey(currentPosition)) { if (b.getPosition() > 0L) { out.println(); } - out.printf("%016x <%s>:%n", currentPosition, functionName); + out.printf("%016x <%s>:%n", currentPosition, functionNames.get(currentPosition)); } final long startOfInstruction = b.getPosition(); @@ -192,15 +211,25 @@ private static void disassembleSection(final SectionTable st, final int sectionI computedOffset - gotSectionAddress); } else if (isJumpWithImmediate(inst)) { // conditional jumps and 'call' instructions need to be printed differently: instead of just the - // immediate, we need to add it to the current IP and display the name of the function it points to. + // immediate, we need to add it to the current IP and display the name of the symbol it points to. final long jumpOffset = getAsLong((Immediate) inst.firstOperand()); - final long offsetFromStartOfFunction = endOfInstruction + jumpOffset; - final long actualPointedAddress = startOfSection + offsetFromStartOfFunction; + final long actualPointedAddress = startOfSection + endOfInstruction + jumpOffset; + final String label = resolveAddressLabel(actualPointedAddress, allSymbols); + if (label == null) { + out.printf("%-6s %x%n", inst.opcode().mnemonic(), actualPointedAddress); + } else { + out.printf("%-6s %x <%s>%n", inst.opcode().mnemonic(), actualPointedAddress, label); + } + } else if (isPaddingNopWithCsPrefix(inst)) { + // GNU objdump displays a no-op CS segment override used purely for instruction-length padding as a + // leading pseudo-prefix instead of showing it as part of the memory operand. out.printf( - "%-6s %x <%s+0x%x>%n", - inst.opcode().mnemonic(), actualPointedAddress, functionName, offsetFromStartOfFunction); + "cs %s%n", + InstructionEncoder.toIntelSyntax(inst, true, 0, true).replace("cs:", "")); } else { - out.printf("%s%n", InstructionEncoder.toIntelSyntax(inst, true, 6, true)); + final String base = InstructionEncoder.toIntelSyntax(inst, true, 6, true); + final String ripComment = ripRelativeComment(inst, startOfSection, endOfInstruction, allSymbols); + out.printf("%s%s%n", base, ripComment == null ? "" : ripComment); } if (lengthOfInstruction >= 8L) { @@ -217,6 +246,189 @@ private static void disassembleSection(final SectionTable st, final int sectionI } } + private static boolean isPaddingNopWithCsPrefix(final Instruction inst) { + return inst.opcode() == Opcode.NOP + && inst.hasFirstOperand() + && inst.firstOperand() instanceof final IndirectOperand io + && io.hasSegment() + && io.getSegment() == SegmentRegister.CS; + } + + private static String ripRelativeComment( + final Instruction inst, + final long startOfSection, + final long endOfInstruction, + final NavigableMap allSymbols) { + final IndirectOperand io = findRipRelativeOperand(inst); + if (io == null) { + return null; + } + final long target = startOfSection + endOfInstruction + io.getDisplacement(); + final String label = resolveAddressLabel(target, allSymbols); + return label == null ? null : String.format(" # %x <%s>", target, label); + } + + private static IndirectOperand findRipRelativeOperand(final Instruction inst) { + if (inst.hasFirstOperand() && inst.firstOperand() instanceof final IndirectOperand io && isRipBase(io)) { + return io; + } + if (inst.hasSecondOperand() && inst.secondOperand() instanceof final IndirectOperand io && isRipBase(io)) { + return io; + } + if (inst.hasThirdOperand() && inst.thirdOperand() instanceof final IndirectOperand io && isRipBase(io)) { + return io; + } + if (inst.hasFourthOperand() && inst.fourthOperand() instanceof final IndirectOperand io && isRipBase(io)) { + return io; + } + return null; + } + + private static boolean isRipBase(final IndirectOperand io) { + return io.hasBase() && io.getBase() == Register64.RIP; + } + + /** + * Resolves an address to a symbolic label the way GNU objdump does: an exact match is printed bare, otherwise the + * nearest preceding symbol is printed with a "+0xN" offset. Returns {@code null} if no preceding symbol exists. + */ + private static String resolveAddressLabel(final long address, final NavigableMap allSymbols) { + final Map.Entry floor = allSymbols.floorEntry(address); + if (floor == null) { + return null; + } + final long offset = address - floor.getKey(); + return offset == 0L ? floor.getValue() : String.format("%s+0x%x", floor.getValue(), offset); + } + + /** + * A symbol referenced through a relocation entry (e.g. a GOT slot), together with its bare name (used for + * '@plt'-style PLT stub labels) and its version-suffixed name (used for '# addr <symbol>' comments). + */ + private record RelocatedSymbol(String bareName, String versionedName) {} + + @SuppressWarnings("PMD.UseConcurrentHashMap") + private static Map findRelocatedSymbols(final SectionTable st) { + final Map result = new HashMap<>(); + final GnuVersionSection gvs = st.getSectionByName(GnuVersionSection.getStandardName()) + .map(GnuVersionSection.class::cast) + .orElse(null); + final GnuVersionRequirementsSection gvrs = st.getSectionByName(GnuVersionRequirementsSection.getStandardName()) + .map(GnuVersionRequirementsSection.class::cast) + .orElse(null); + + for (int i = 0; i < st.getSectionTableLength(); i++) { + if (!(st.getSection(i) instanceof final RelocationAddendSection ras)) { + continue; + } + final int symtabIndex = ras.header().getLinkedSectionIndex(); + if (symtabIndex == 0) { + continue; + } + final SymbolTable symtab = (SymbolTable) st.getSection(symtabIndex); + final StringTableSection strtab = + (StringTableSection) st.getSection(symtab.header().getLinkedSectionIndex()); + + for (int j = 0; j < ras.getRelocationAddendTableLength(); j++) { + final RelocationAddendEntry rae = ras.getRelocationAddendEntry(j); + if (rae.symbolTableIndex() == 0) { + continue; + } + final SymbolTableEntry ste = symtab.getSymbolTableEntry(rae.symbolTableIndex()); + if (ste.nameOffset() == 0) { + continue; + } + final String bareName = strtab.getString(ste.nameOffset()); + final String suffix = versionSuffix(gvs, gvrs, strtab, rae.symbolTableIndex()); + result.put(rae.offset(), new RelocatedSymbol(bareName, bareName + suffix)); + } + } + return result; + } + + private static String versionSuffix( + final GnuVersionSection gvs, + final GnuVersionRequirementsSection gvrs, + final StringTableSection dynstr, + final int dynsymIndex) { + if (gvs == null) { + return ""; + } + final int masked = gvs.getVersion(dynsymIndex) & 0x7fff; + if (masked <= 1 || gvrs == null) { + return "@Base"; + } + final int nameOffset = gvrs.getVersionNameOffset((short) masked); + return nameOffset == -1 ? "@Base" : "@" + dynstr.getString(nameOffset); + } + + /** + * Finds the '<symbol@plt>'-style labels of PLT-like sections (e.g. '.plt', '.plt.got', '.plt.sec') by + * correlating each stub's RIP-relative jump/call/push target with the GOT slot addresses touched by relocations. + */ + private static Map findPltLabels( + final SectionTable st, final Map relocatedSymbols) { + final Map labels = new HashMap<>(); + for (int i = 0; i < st.getSectionTableLength(); i++) { + final Section s = st.getSection(i); + if (!s.getName().startsWith(".plt") || !(s instanceof final LoadableSection ls)) { + continue; + } + final long entrySize = s.header().getEntrySize(); + if (entrySize <= 0L) { + continue; + } + final long sectionStart = s.header().getVirtualAddress(); + final byte[] content = ls.getLoadableContent(); + final ReadOnlyByteBuffer b = new ReadOnlyByteBufferV1(content, true, 1L); + while (b.getPosition() < content.length) { + final long instructionStart = b.getPosition(); + final Instruction inst; + try { + inst = InstructionDecoder.fromHex(b); + } catch (final RuntimeException e) { + break; + } + final long instructionEnd = b.getPosition(); + final IndirectOperand io = findRipRelativeOperand(inst); + if (io == null) { + continue; + } + final long target = sectionStart + instructionEnd + io.getDisplacement(); + final RelocatedSymbol rs = relocatedSymbols.get(target); + if (rs != null) { + final long stubStart = sectionStart + (instructionStart / entrySize) * entrySize; + labels.put(stubStart, rs.bareName() + "@plt"); + } + } + } + return labels; + } + + @SuppressWarnings("PMD.UseConcurrentHashMap") + private static NavigableMap findAllSymbols(final SectionTable st) { + final NavigableMap symbols = new TreeMap<>(); + final Optional
symbolTable = st.getSectionByName(".symtab"); + if (symbolTable.isPresent()) { + final SymbolTableSection symtab = (SymbolTableSection) symbolTable.orElseThrow(); + final StringTableSection strtab = + (StringTableSection) st.getSection(symtab.header().getLinkedSectionIndex()); + + for (int i = 0; i < symtab.getSymbolTableLength(); i++) { + final SymbolTableEntry ste = symtab.getSymbolTableEntry(i); + final SymbolTableEntryType type = ste.info().getType(); + final boolean isMeaningful = type != SymbolTableEntryType.STT_FILE + && type != SymbolTableEntryType.STT_SECTION + && ste.sectionTableIndex() != 0; + if (!isMeaningful) { + continue; + } + symbols.put(ste.value(), strtab.getString(ste.nameOffset())); + } + } + return symbols; + } + private static long getAsLong(final Immediate imm) { return switch (imm.bits()) { case 8 -> imm.asByte(); From cbf8552059ec43bbe0adf27c9884e409da5158ea Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 17 Aug 2026 15:21:48 +0200 Subject: [PATCH 7/8] Various decoding fixes for do_nothing.static --- .../ledmington/cpu/InstructionChecker.java | 1 + .../ledmington/cpu/InstructionDecoder.java | 33 +++++++-- .../ledmington/cpu/InstructionEncoder.java | 54 +++++++++----- .../ledmington/cpu/x86/IndirectOperand.java | 6 ++ .../java/com/ledmington/cpu/x86/Opcode.java | 3 + .../java/com/ledmington/cpu/X64Encodings.java | 2 + .../java/com/ledmington/objdump/Main.java | 72 +++++++++++++++++-- 7 files changed, 139 insertions(+), 32 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java index f1dc1018..561eb502 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java @@ -99,6 +99,7 @@ public final class InstructionChecker { Map.entry(Opcode.POP, List.of(R16, R64, M64)), Map.entry(Opcode.CDQ, List.of(NO_ARGS)), Map.entry(Opcode.CDQE, List.of(NO_ARGS)), + Map.entry(Opcode.CQO, List.of(NO_ARGS)), Map.entry(Opcode.CWDE, List.of(NO_ARGS)), Map.entry(Opcode.LEAVE, List.of(NO_ARGS)), Map.entry(Opcode.INT3, List.of(NO_ARGS)), diff --git a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java index dbbef704..9744e2c3 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java @@ -2782,7 +2782,10 @@ private static Instruction parseSingleByteOpcode( case INT_OPCODE -> Instruction.builder().opcode(Opcode.INT).op(imm8(b)).build(); case IRET_OPCODE -> Instruction.builder().opcode(Opcode.IRET).build(); - case CDQ_OPCODE -> Instruction.builder().opcode(Opcode.CDQ).build(); + case CDQ_OPCODE -> + Instruction.builder() + .opcode(pref.rex().isOperand64Bit() ? Opcode.CQO : Opcode.CDQ) + .build(); case SAHF_OPCODE -> Instruction.builder().opcode(Opcode.SAHF).build(); case LAHF_OPCODE -> Instruction.builder().opcode(Opcode.LAHF).build(); case HLT_OPCODE -> Instruction.builder().opcode(Opcode.HLT).build(); @@ -3587,9 +3590,9 @@ private static Instruction parseSingleByteOpcode( case LEA_OPCODE -> { final ModRM modrm = modrm(b); final Register r = Registers.fromCode( - getByteFromReg(pref, modrm), + modrm.reg(), pref.rex().isOperand64Bit(), - pref.rex().b(), + pref.rex().hasModRMRegExtension(), pref.hasOperandSizeOverridePrefix()); yield Instruction.builder() .opcode(Opcode.LEA) @@ -4961,7 +4964,7 @@ private static IndirectOperandBuilder parseIndirectOperand( // ESP or RSP cannot be index registers of an indirect operand if (isSP(decodedIndex)) { - baseRegister = decodedBase; + baseRegister = (isBP(decodedBase) && modrm.mod() == (byte) 0b00) ? null : decodedBase; } else { iob.index(decodedIndex); iob.scale(1 << asInt(sib.scale())); @@ -4976,10 +4979,11 @@ private static IndirectOperandBuilder parseIndirectOperand( } } + if (isIndirectOperandNeeded(modrm)) { + segmentOverride(pref).ifPresent(iob::segment); + } + if (baseRegister != null) { - if (pref.p2().isPresent() && pref.p2().orElseThrow() == CS_SEGMENT_OVERRIDE_PREFIX) { - iob.segment(SegmentRegister.CS); - } iob.base(baseRegister); } @@ -5054,6 +5058,21 @@ private static boolean isLegacyPrefixGroup2(final byte prefix) { || prefix == BRANCH_TAKEN_PREFIX; } + private static Optional segmentOverride(final Prefixes pref) { + if (pref.p2().isEmpty()) { + return Optional.empty(); + } + return switch (pref.p2().orElseThrow()) { + case CS_SEGMENT_OVERRIDE_PREFIX -> Optional.of(SegmentRegister.CS); + case (byte) 0x36 -> Optional.of(SegmentRegister.SS); + case (byte) 0x3e -> Optional.of(SegmentRegister.DS); + case (byte) 0x26 -> Optional.of(SegmentRegister.ES); + case (byte) 0x64 -> Optional.of(SegmentRegister.FS); + case (byte) 0x65 -> Optional.of(SegmentRegister.GS); + default -> Optional.empty(); + }; + } + private static boolean isOperandSizeOverridePrefix(final byte prefix) { return prefix == OPERAND_SIZE_OVERRIDE_PREFIX; } diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index b2f7b960..6157ce1e 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -188,29 +188,44 @@ private static String operandString(final Instruction inst, final Operand op, fi private static String immediateOperandString( final Instruction inst, final Operand op, final Immediate imm, final boolean shortHex) { - // The legacy 'shift/rotate by 1' encoding (D0/D1) has no immediate byte at all: GNU objdump always displays - // its implicit count as a bare '1', never as a hex immediate. - if (GROUP2_REG_BYTES.containsKey(inst.opcode()) && imm.bits() == 8 && imm.asByte() == (byte) 1) { - return "1"; - } - - // Immediates narrower than their destination (e.g. the imm8 forms of ADD/SUB/AND/CMP/... or the imm32 form of - // a 64-bit MOV) are sign-extended by the CPU to the destination's width, and GNU objdump displays the - // sign-extended value rather than the raw encoded bytes. - final boolean isDestinationOperand = op == inst.firstOperand(); - final boolean isSignExtendingForm = !GROUP2_REG_BYTES.containsKey(inst.opcode()); - if (!isDestinationOperand - && isSignExtendingForm - && (inst.firstOperand() instanceof Register || inst.firstOperand() instanceof IndirectOperand)) { - final int destinationBits = inst.firstOperand().bits(); - if (destinationBits > imm.bits()) { - return signExtendedIntelSyntax(imm, destinationBits, shortHex); + // The two quirks below are specifically GNU objdump's disassembly display conventions (only used when + // 'shortHex' is enabled, i.e. by the objdump module): the general-purpose toIntelSyntax() API keeps showing + // the immediate exactly as encoded, at its own declared width. + if (shortHex) { + // The legacy 'shift/rotate by 1' encoding (D0/D1) has no immediate byte at all: GNU objdump always + // displays its implicit count as a bare '1', never as a hex immediate. + if (GROUP2_REG_BYTES.containsKey(inst.opcode()) && imm.bits() == 8 && imm.asByte() == (byte) 1) { + return "1"; + } + + // Immediates narrower than their destination (e.g. the imm8 forms of ADD/SUB/AND/CMP/... or the imm32 + // form of a 64-bit MOV) are sign-extended by the CPU to the destination's width, and GNU objdump + // displays the sign-extended value rather than the raw encoded bytes. This only applies to the classic + // 'arithmetic group 1'/TEST/MOV forms: e.g. AVX/SIMD instructions' trailing immediate is a + // control/selector byte unrelated to the (much wider) vector destination's width, and must never be + // sign-extended. + final boolean isDestinationOperand = op == inst.firstOperand(); + final boolean isSignExtendingForm = isSignExtendingImmediateForm(inst.opcode()); + if (!isDestinationOperand + && isSignExtendingForm + && (inst.firstOperand() instanceof Register || inst.firstOperand() instanceof IndirectOperand)) { + final int destinationBits = inst.firstOperand().bits(); + if (destinationBits > imm.bits()) { + return signExtendedIntelSyntax(imm, destinationBits, shortHex); + } } } return imm.toIntelSyntax(shortHex); } + private static boolean isSignExtendingImmediateForm(final Opcode opcode) { + return switch (opcode) { + case ADD, OR, ADC, SBB, AND, SUB, XOR, CMP, TEST, MOV -> true; + default -> false; + }; + } + private static String signExtendedIntelSyntax( final Immediate imm, final int destinationBits, final boolean shortHex) { final long signExtended = @@ -473,7 +488,7 @@ private static void encodeZeroOperandsInstruction(final WriteOnlyByteBuffer wb, case VZEROALL -> wb.write((byte) 0x77); case NOP -> wb.write((byte) 0x90); case CWDE, CDQE -> wb.write((byte) 0x98); - case CDQ -> wb.write((byte) 0x99); + case CDQ, CQO -> wb.write((byte) 0x99); case FWAIT -> wb.write((byte) 0x9b); case PUSHF -> wb.write((byte) 0x9c); case POPF -> wb.write((byte) 0x9d); @@ -1753,7 +1768,8 @@ private static void encodeRexPrefix(final WriteOnlyByteBuffer wb, final Instruct || (inst.opcode() == Opcode.MOVQ && isFirstMMX(inst)) || (inst.opcode() == Opcode.MOVQ && isFirstXMM(inst)) || (inst.opcode() == Opcode.CVTSI2SD && isSecondR64(inst)) - || (inst.opcode() == Opcode.CDQE)) + || (inst.opcode() == Opcode.CDQE) + || (inst.opcode() == Opcode.CQO)) && !(inst.opcode() == Opcode.MOVQ && isFirstM(inst)) && !(inst.opcode() == Opcode.MOVQ && isSecondM(inst)) && !(inst.opcode() == Opcode.PXOR && isFirstMMX(inst)) diff --git a/id/src/main/java/com/ledmington/cpu/x86/IndirectOperand.java b/id/src/main/java/com/ledmington/cpu/x86/IndirectOperand.java index 8fb5f29c..83717281 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/IndirectOperand.java +++ b/id/src/main/java/com/ledmington/cpu/x86/IndirectOperand.java @@ -290,6 +290,12 @@ public String toIntelSyntax( if (hasSegment()) { sb.append(segment.toIntelSyntax()).append(':'); } + if (hasSegment() && !hasBase() && !hasIndex()) { + // GNU objdump renders segment-relative absolute-displacement addressing (e.g. 'fs:0x28') without + // brackets, unlike every other addressing form. + addDisplacement(sb, compressedDisplacement, shortHex); + return sb.toString(); + } sb.append('['); if (hasBase()) { sb.append(base.toIntelSyntax()); diff --git a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java index b44486d8..167f3618 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java @@ -86,6 +86,9 @@ public enum Opcode { /** Convert doubleword to quadword. */ CDQE, + /** Convert quadword to octaword. */ + CQO, + /** Clear carry flag. */ CLC, diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index 26cec806..994456e4 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -5190,6 +5190,8 @@ private static List others() { test(new GeneralInstruction(Opcode.CWDE), "cwde", "98"), // Cdqe test(new GeneralInstruction(Opcode.CDQE), "cdqe", "48 98"), + // Cqo + test(new GeneralInstruction(Opcode.CQO), "cqo", "48 99"), // Leave test(new GeneralInstruction(Opcode.LEAVE), "leave", "c9"), // Int3 diff --git a/objdump/src/main/java/com/ledmington/objdump/Main.java b/objdump/src/main/java/com/ledmington/objdump/Main.java index 8d9dd43a..e0dff279 100644 --- a/objdump/src/main/java/com/ledmington/objdump/Main.java +++ b/objdump/src/main/java/com/ledmington/objdump/Main.java @@ -212,22 +212,50 @@ private static void disassembleSection( } else if (isJumpWithImmediate(inst)) { // conditional jumps and 'call' instructions need to be printed differently: instead of just the // immediate, we need to add it to the current IP and display the name of the symbol it points to. + // An address-size-override prefix has no effect on these (there is no memory operand to address), + // so GNU objdump shows it explicitly as a leading pseudo-prefix rather than silently dropping it. + final String addressSizeOverride = + content[BitUtils.asInt(startOfInstruction)] == (byte) 0x67 ? "addr32 " : ""; final long jumpOffset = getAsLong((Immediate) inst.firstOperand()); final long actualPointedAddress = startOfSection + endOfInstruction + jumpOffset; final String label = resolveAddressLabel(actualPointedAddress, allSymbols); + final String mnemonicFormat = addressSizeOverride.isEmpty() ? "%-6s" : "%s"; if (label == null) { - out.printf("%-6s %x%n", inst.opcode().mnemonic(), actualPointedAddress); + out.printf( + "%s" + mnemonicFormat + " %x%n", + addressSizeOverride, + inst.opcode().mnemonic(), + actualPointedAddress); } else { - out.printf("%-6s %x <%s>%n", inst.opcode().mnemonic(), actualPointedAddress, label); + out.printf( + "%s" + mnemonicFormat + " %x <%s>%n", + addressSizeOverride, + inst.opcode().mnemonic(), + actualPointedAddress, + label); } } else if (isPaddingNopWithCsPrefix(inst)) { - // GNU objdump displays a no-op CS segment override used purely for instruction-length padding as a - // leading pseudo-prefix instead of showing it as part of the memory operand. + // GNU objdump displays no-op prefixes used purely for instruction-length padding (a CS segment + // override, and any operand-size-override byte beyond the first one, which is the only one that + // actually affects the operand size) as leading pseudo-prefix words instead of folding them into the + // memory operand. + int redundantOperandSizePrefixes = -1; + for (long i = startOfInstruction; + i < content.length && content[BitUtils.asInt(i)] == (byte) 0x66; + i++) { + redundantOperandSizePrefixes++; + } + out.print("data16 ".repeat(Math.max(0, redundantOperandSizePrefixes))); out.printf( "cs %s%n", InstructionEncoder.toIntelSyntax(inst, true, 0, true).replace("cs:", "")); + } else if (hasNotrackPrefix(inst, content, startOfInstruction)) { + // A DS segment override on an indirect jmp/call is the CET 'notrack' hint, not an actual segment + // override (there is no memory operand to apply it to). + out.printf("notrack %s%n", InstructionEncoder.toIntelSyntax(inst, true, 0, true)); } else { - final String base = InstructionEncoder.toIntelSyntax(inst, true, 6, true); + // GNU objdump does not pad the mnemonic column when a legacy prefix (e.g. 'rep') is shown before it. + final String base = InstructionEncoder.toIntelSyntax(inst, true, inst.hasPrefix() ? 0 : 6, true); final String ripComment = ripRelativeComment(inst, startOfSection, endOfInstruction, allSymbols); out.printf("%s%s%n", base, ripComment == null ? "" : ripComment); } @@ -254,6 +282,13 @@ private static boolean isPaddingNopWithCsPrefix(final Instruction inst) { && io.getSegment() == SegmentRegister.CS; } + private static boolean hasNotrackPrefix( + final Instruction inst, final byte[] content, final long startOfInstruction) { + return (inst.opcode() == Opcode.JMP || inst.opcode() == Opcode.CALL) + && !(inst.firstOperand() instanceof Immediate) + && content[BitUtils.asInt(startOfInstruction)] == (byte) 0x3e; + } + private static String ripRelativeComment( final Instruction inst, final long startOfSection, @@ -408,6 +443,7 @@ private static Map findPltLabels( @SuppressWarnings("PMD.UseConcurrentHashMap") private static NavigableMap findAllSymbols(final SectionTable st) { final NavigableMap symbols = new TreeMap<>(); + final Map bindingPriority = new HashMap<>(); final Optional
symbolTable = st.getSectionByName(".symtab"); if (symbolTable.isPresent()) { final SymbolTableSection symtab = (SymbolTableSection) symbolTable.orElseThrow(); @@ -423,12 +459,36 @@ private static NavigableMap findAllSymbols(final SectionTable st) if (!isMeaningful) { continue; } - symbols.put(ste.value(), strtab.getString(ste.nameOffset())); + // When multiple symbols share the same address, GNU objdump prefers a typed symbol (OBJECT/FUNC/...) + // over an untyped boundary marker (NOTYPE); among equally-typed symbols, the strongest binding wins + // (GLOBAL over WEAK over LOCAL); among equally-typed, equally-bound symbols, the alphabetically + // first name wins. + final int priority = symbolPriority(ste); + final String candidateName = strtab.getString(ste.nameOffset()); + final Integer existingPriority = bindingPriority.get(ste.value()); + final String existingName = symbols.get(ste.value()); + if (existingPriority == null + || priority > existingPriority + || (priority == existingPriority && candidateName.compareTo(existingName) < 0)) { + symbols.put(ste.value(), candidateName); + bindingPriority.put(ste.value(), priority); + } } } return symbols; } + private static int symbolPriority(final SymbolTableEntry ste) { + final int typeRank = ste.info().getType() == SymbolTableEntryType.STT_NOTYPE ? 0 : 1; + final int bindingRank = + switch (ste.info().getBinding()) { + case STB_GLOBAL -> 2; + case STB_WEAK -> 1; + case STB_LOCAL -> 0; + }; + return typeRank * 10 + bindingRank; + } + private static long getAsLong(final Immediate imm) { return switch (imm.bits()) { case 8 -> imm.asByte(); From 2790689f4442bc888d045a76e7f59a1079a4960f Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 17 Aug 2026 15:39:24 +0200 Subject: [PATCH 8/8] PMD fixes --- id/src/main/java/com/ledmington/cpu/InstructionEncoder.java | 2 +- objdump/src/main/java/com/ledmington/objdump/Main.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index 6157ce1e..d13ec8d7 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -204,7 +204,7 @@ private static String immediateOperandString( // 'arithmetic group 1'/TEST/MOV forms: e.g. AVX/SIMD instructions' trailing immediate is a // control/selector byte unrelated to the (much wider) vector destination's width, and must never be // sign-extended. - final boolean isDestinationOperand = op == inst.firstOperand(); + final boolean isDestinationOperand = op.equals(inst.firstOperand()); final boolean isSignExtendingForm = isSignExtendingImmediateForm(inst.opcode()); if (!isDestinationOperand && isSignExtendingForm diff --git a/objdump/src/main/java/com/ledmington/objdump/Main.java b/objdump/src/main/java/com/ledmington/objdump/Main.java index e0dff279..12fe0db5 100644 --- a/objdump/src/main/java/com/ledmington/objdump/Main.java +++ b/objdump/src/main/java/com/ledmington/objdump/Main.java @@ -65,7 +65,7 @@ public final class Main { private Main() {} - @SuppressWarnings("PMD.AvoidCatchingGenericException") + @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.UseConcurrentHashMap"}) public static void main(final String[] args) { MiniLogger.setMinimumLevel(MiniLogger.LoggingLevel.ERROR); @@ -401,6 +401,7 @@ private static String versionSuffix( * Finds the '<symbol@plt>'-style labels of PLT-like sections (e.g. '.plt', '.plt.got', '.plt.sec') by * correlating each stub's RIP-relative jump/call/push target with the GOT slot addresses touched by relocations. */ + @SuppressWarnings("PMD.UseConcurrentHashMap") private static Map findPltLabels( final SectionTable st, final Map relocatedSymbols) { final Map labels = new HashMap<>();