From 81f74fcee16dcd6fb698ddeb2774e296527dc397 Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Mon, 13 Jan 2025 21:54:13 +0900 Subject: [PATCH] Add ffmasm-disassebler --- README.md | 38 +++++ examples/disas/pom.xml | 97 +++++++++++++ .../yasuenag/ffmasm/examples/disas/Main.java | 50 +++++++ tools/disas/pom.xml | 80 +++++++++++ .../ffmasmtools/disas/Disassembler.java | 133 ++++++++++++++++++ tools/disas/src/main/java/module-info.java | 22 +++ 6 files changed, 420 insertions(+) create mode 100644 examples/disas/pom.xml create mode 100644 examples/disas/src/main/java/com/yasuenag/ffmasm/examples/disas/Main.java create mode 100644 tools/disas/pom.xml create mode 100644 tools/disas/src/main/java/com/yasuenag/ffmasmtools/disas/Disassembler.java create mode 100644 tools/disas/src/main/java/module-info.java diff --git a/README.md b/README.md index 93dfad9..600e07e 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,44 @@ NOTE: [Linker.Option.critical()](https://docs.oracle.com/en/java/javase/22/docs/ int ret = (int)method.invoke(100); // "ret" should be 100 ``` +# Debugging + +[ffmasm-disassembler](tools/disas) can disassemble the code in `MemorySegment` like generated by ffmasm, and dump assembly code to stdout. + +You can download ffmasm-dissassembler Maven package from GitHub packages: https://github.com/YaSuenag/ffmasm/packages/2370043 + +See [examples/disas](examples/disas) for details. + +## Requirements + +ffmasm-disassembler requires [hsdis](https://github.com/openjdk/jdk/tree/master/src/utils/hsdis). + +## Generate hsdis + +To generate hsdis for Linux, you can use [hsdis-builder](https://github.com/YaSuenag/hsdis-builder). + +## Deploy hsdis + +It should be deployed one of following directory (it is documented as source comment in disassembler.cpp in HotSpot): + +1. `$JAVA_HOME/lib//libhsdis-.so` +2. `$JAVA_HOME/lib//hsdis-.so` +3. `$JAVA_HOME/lib/hsdis-.so` +4. `hsdis-.so` (using `LD_LIBRARY_PATH`) + +If you don't want to deploy hsdis into your JDK, you can specify `hsdis` system property like `-Dhsdis=/path/to/hsdis-amd64.so` + +## Examples + +``` +import com.yasuenag.ffmasmtools.disas.Disassembler; + + : + +MemorySegment rdtsc = createRDTSC(); // Generate machine code with ffmasm +Disassembler.dumpToStdout(rdtsc); // Dump assembly code of `rdtsc` to stdout +``` + # Play with JNI You can bind native method to `MemorySegment` of ffmasm code dynamically. diff --git a/examples/disas/pom.xml b/examples/disas/pom.xml new file mode 100644 index 0000000..37c3110 --- /dev/null +++ b/examples/disas/pom.xml @@ -0,0 +1,97 @@ + + + + + + 4.0.0 + + com.yasuenag + ffmasm-disas-example + 0.1.0 + jar + + ffmasm-disas-example + + + UTF-8 + com.yasuenag.ffmasm.examples.disas.Main + ${project.artifactId}-${project.version} + 22 + 22 + + + + + github + https://maven.pkg.github.com/YaSuenag/ffmasm + + + + + + com.yasuenag + ffmasm + 0.5.0-SNAPSHOT + + + com.yasuenag + ffmasm-disassembler + 0.1.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + -Xlint:all + + true + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + ${mainClass} + + + ALL-UNNAMED + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.5.0 + + ${mainClass} + + + + + diff --git a/examples/disas/src/main/java/com/yasuenag/ffmasm/examples/disas/Main.java b/examples/disas/src/main/java/com/yasuenag/ffmasm/examples/disas/Main.java new file mode 100644 index 0000000..7f75359 --- /dev/null +++ b/examples/disas/src/main/java/com/yasuenag/ffmasm/examples/disas/Main.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 Yasumasa Suenaga + * + * This file is part of ffmasm. + * + * ffmasm is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ffmasm 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ffmasm. If not, see . + */ +package com.yasuenag.ffmasm.examples.disas; + +import java.lang.foreign.*; +import java.util.*; + +import com.yasuenag.ffmasm.*; +import com.yasuenag.ffmasm.amd64.*; + +import com.yasuenag.ffmasmtools.disas.Disassembler; + + +public class Main{ + + public static MemorySegment createRDTSC() throws Exception{ + var seg = new CodeSegment(); + return AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg) + /* push %rbp */ .push(Register.RBP) + /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) + /* rdtsc */ .rdtsc() + /* shl $32, %rdx */ .shl(Register.RDX, (byte)32, OptionalInt.empty()) + /* or %rdx, %rax */ .orMR(Register.RDX, Register.RAX, OptionalInt.empty()) + /* leave */ .leave() + /* ret */ .ret() + .getMemorySegment(); + } + + public static void main(String[] args) throws Exception{ + var rdtsc = createRDTSC(); + Disassembler.dumpToStdout(rdtsc); + } + +} diff --git a/tools/disas/pom.xml b/tools/disas/pom.xml new file mode 100644 index 0000000..860e314 --- /dev/null +++ b/tools/disas/pom.xml @@ -0,0 +1,80 @@ + + + + + + 4.0.0 + + com.yasuenag + ffmasm-disassembler + 0.1.0 + jar + + ffmasm-disassembler + + + scm:git:git://github.com/YaSuenag/ffmasm.git + scm:git:ssh://github.com:YaSuenag/ffmasm.git + https://github.com/YaSuenag/ffmasm + + + + UTF-8 + 22 + 22 + + + + + com.yasuenag + ffmasm + 0.5.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + -Xlint:all + + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.4.1 + + + + + + + github + GitHub Packages + https://maven.pkg.github.com/YaSuenag/ffmasm + + + diff --git a/tools/disas/src/main/java/com/yasuenag/ffmasmtools/disas/Disassembler.java b/tools/disas/src/main/java/com/yasuenag/ffmasmtools/disas/Disassembler.java new file mode 100644 index 0000000..ea04228 --- /dev/null +++ b/tools/disas/src/main/java/com/yasuenag/ffmasmtools/disas/Disassembler.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2025 Yasumasa Suenaga + * + * This file is part of ffmasm. + * + * ffmasm is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ffmasm 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ffmasm. If not, see . + */ +package com.yasuenag.ffmasmtools.disas; + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.nio.file.Path; +import java.util.regex.Pattern; + +import com.yasuenag.ffmasm.UnsupportedPlatformException; + + +public class Disassembler{ + + private static final MethodHandle decode_instructions_virtual; + private static final MemorySegment disasOptions; + + private static Path getHSDISPath() throws UnsupportedPlatformException{ + var prop = System.getProperty("hsdis"); + if(prop != null){ + return Path.of(prop); + } + + String libName = "hsdis-" + System.getProperty("os.arch"); + var osName = System.getProperty("os.name"); + if(osName.equals("Linux")){ + libName += ".so"; + } + else if(osName.startsWith("Windows")){ + libName += ".dll"; + } + else{ + throw new UnsupportedPlatformException(osName); + } + + var javaHome = Path.of(System.getProperty("java.home")); + var vmMatcher = Pattern.compile("^.+ ([^ ]+) VM$").matcher(System.getProperty("java.vm.name")); + vmMatcher.find(); + var vmType = vmMatcher.group(1).toLowerCase(); + // Search order of hsdis: + // 1. /lib//libhsdis-.so + // 2. /lib//hsdis-.so + // 3. /lib/hsdis-.so + // 4. hsdis-.so (using LD_LIBRARY_PATH) + // See src/hotspot/share/compiler/disassembler.cpp in OpenJDK for details. + Path p = javaHome.resolve("lib", vmType, "lib" + libName); + if(p.toFile().exists()){ + return p; + } + else{ + p = javaHome.resolve("lib", vmType, libName); + if(p.toFile().exists()){ + return p; + } + else{ + p = javaHome.resolve("lib", libName); + if(p.toFile().exists()){ + return p; + } + } + } + return Path.of(libName); + } + + static{ + try{ + var hsdisPath = getHSDISPath(); + var sym = SymbolLookup.libraryLookup(hsdisPath, Arena.ofAuto()); + var disas = sym.find("decode_instructions_virtual").get(); + var desc = FunctionDescriptor.of(ValueLayout.ADDRESS, // return value + ValueLayout.ADDRESS, // start_va + ValueLayout.ADDRESS, // end_va + ValueLayout.ADDRESS, // buffer + ValueLayout.JAVA_LONG, // length + ValueLayout.ADDRESS, // event_callback + ValueLayout.ADDRESS, // event_stream + ValueLayout.ADDRESS, // printf_callback + ValueLayout.ADDRESS, // printf_stream + ValueLayout.ADDRESS, // options + ValueLayout.JAVA_INT // newline + ); + decode_instructions_virtual = Linker.nativeLinker() + .downcallHandle(disas, desc); + disasOptions = Arena.ofAuto() + .allocateFrom(""); + } + catch(UnsupportedPlatformException e){ + throw new RuntimeException(e); + } + } + + public static void dumpToStdout(MemorySegment code){ + try{ + decode_instructions_virtual.invoke( + code, // start_va + code.asSlice(code.byteSize()), // end_va + code, // buffer + code.byteSize(), // length + MemorySegment.NULL, // event_callback + MemorySegment.NULL, // event_stream + MemorySegment.NULL, // printf_callback + MemorySegment.NULL, // printf_stream + disasOptions, // options + 1 // newline + ); + } + catch(Throwable t){ + throw new RuntimeException(t); + } + } + +} diff --git a/tools/disas/src/main/java/module-info.java b/tools/disas/src/main/java/module-info.java new file mode 100644 index 0000000..a0d4cad --- /dev/null +++ b/tools/disas/src/main/java/module-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 Yasumasa Suenaga + * + * This file is part of ffmasm. + * + * ffmasm is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ffmasm 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ffmasm. If not, see . + */ +module com.yasuenag.ffmasmtools.disas { + requires com.yasuenag.ffmasm; + exports com.yasuenag.ffmasmtools.disas; +}