├── .github └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── push-main.yaml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarks ├── funccall │ ├── Makefile │ ├── README.md │ ├── measure-single-benchmark.sh │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ └── benchmark │ │ │ └── funccall │ │ │ └── FuncCallComparison.java │ │ └── native │ │ └── rdtsc.S └── vectorapi │ ├── README.md │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── yasuenag │ └── ffmasm │ └── benchmark │ └── vectorapi │ └── VectorOpComparison.java ├── examples ├── cpumodel │ ├── README.md │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ ├── com │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ └── examples │ │ │ └── cpumodel │ │ │ └── Main.java │ │ └── module-info.java ├── disas │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── yasuenag │ │ └── ffmasm │ │ └── examples │ │ └── disas │ │ └── Main.java └── perf │ ├── README.md │ ├── perf.sh │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── yasuenag │ └── ffmasm │ └── examples │ └── perf │ └── PerfJit.java ├── pom.xml ├── src ├── main │ └── java │ │ ├── com │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ ├── CodeSegment.java │ │ │ ├── JitDump.java │ │ │ ├── NativeRegister.java │ │ │ ├── PlatformException.java │ │ │ ├── UnsupportedPlatformException.java │ │ │ ├── amd64 │ │ │ ├── AMD64AsmBuilder.java │ │ │ ├── AVXAsmBuilder.java │ │ │ ├── Register.java │ │ │ └── SSEAsmBuilder.java │ │ │ └── internal │ │ │ ├── ExecMemory.java │ │ │ ├── JavaVM.java │ │ │ ├── JniEnv.java │ │ │ ├── JvmtiEnv.java │ │ │ ├── amd64 │ │ │ ├── AMD64NativeRegister.java │ │ │ └── CallingRegisters.java │ │ │ ├── linux │ │ │ ├── LinuxExecMemory.java │ │ │ └── PerfJitDump.java │ │ │ └── windows │ │ │ └── WindowsExecMemory.java │ │ └── module-info.java └── test │ └── java │ └── com │ └── yasuenag │ └── ffmasm │ └── test │ ├── amd64 │ ├── AVXAsmTest.java │ ├── AsmTest.java │ ├── NativeRegisterTest.java │ ├── SSEAsmTest.java │ └── TestBase.java │ ├── common │ ├── CodeSegmentTest.java │ └── JitDumpTest.java │ └── linux │ └── CodeSegmentTest.java └── tools └── disas ├── pom.xml └── src └── main └── java ├── com └── yasuenag │ └── ffmasmtools │ └── disas │ └── Disassembler.java └── module-info.java /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Maven Build & Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: [ ubuntu-latest, windows-latest ] 14 | 15 | runs-on: ${{ matrix.os }} 16 | 17 | name: Run Maven on ${{ matrix.os }} 18 | 19 | steps: 20 | - name: 'Checkout repository' 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup Java 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: oracle 27 | java-version: 22 28 | cache: maven 29 | 30 | - name: 'Run Maven' 31 | run: mvn -B test 32 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ 'java' ] 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup Java 28 | uses: actions/setup-java@v4 29 | with: 30 | distribution: oracle 31 | java-version: 22 32 | cache: maven 33 | 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v3 36 | with: 37 | languages: ${{ matrix.language }} 38 | queries: security-extended,security-and-quality 39 | 40 | - name: Build 41 | run: mvn -B -DskipTests package 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v3 45 | -------------------------------------------------------------------------------- /.github/workflows/push-main.yaml: -------------------------------------------------------------------------------- 1 | name: Publish snapshot library 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: push-snapshot 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | publish-jar: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | packages: write 18 | contents: read 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | - name: Setup Java 23 | uses: actions/setup-java@v4 24 | with: 25 | distribution: oracle 26 | java-version: 22 27 | cache: maven 28 | - name: Deploy snapshot library to GitHub Packages by Maven 29 | run: mvn -B deploy 30 | env: 31 | GITHUB_TOKEN: ${{ github.token }} 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish new release 2 | on: 3 | release: 4 | types: [published] 5 | 6 | concurrency: 7 | group: "publish" 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | upload: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | packages: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Setup Java 18 | uses: actions/setup-java@v4 19 | with: 20 | distribution: oracle 21 | java-version: 22 22 | cache: maven 23 | - name: Publish to GitHub Packages via Apache Maven 24 | run: mvn -B -DskipTests deploy javadoc:javadoc 25 | env: 26 | GITHUB_TOKEN: ${{ github.token }} 27 | - name: Upload artifact 28 | uses: actions/upload-pages-artifact@v3 29 | with: 30 | path: 'target/site/apidocs' 31 | 32 | deploy_page: 33 | needs: upload 34 | permissions: 35 | pages: write 36 | id-token: write 37 | runs-on: ubuntu-latest 38 | environment: 39 | name: github-pages 40 | url: ${{ steps.deployment.outputs.page_url }} 41 | steps: 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | jit-*.dump 3 | jitted-*.so 4 | perf.*data* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ffmasm 2 | =================== 3 | 4 | ![CI result](../../actions/workflows/ci.yml/badge.svg) 5 | ![CodeQL](../../actions/workflows/codeql-analysis.yml/badge.svg) 6 | 7 | ffmasm is an assembler for hand-assembling from Java. 8 | It uses Foreign Function & Memory API, so the application can call assembled code via [MethodHandle](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/invoke/MethodHandle.html). 9 | 10 | * Javadoc: https://yasuenag.github.io/ffmasm/ 11 | * Maven package: https://github.com/YaSuenag/ffmasm/packages/ 12 | * Supported instructions: See builder classes in [com.yasuenag.ffmasm.amd64](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/amd64/package-summary.html). 13 | 14 | # Requirements 15 | 16 | Java 22 17 | 18 | # Supported platform 19 | 20 | * Linux AMD64 21 | * Windows AMD64 22 | 23 | # How to build 24 | 25 | ``` 26 | $ mvn package 27 | ``` 28 | 29 | ## Test for ffmasm 30 | 31 | ```bash 32 | $ mvn test 33 | ``` 34 | 35 | # How to use 36 | 37 | See [Javadoc](https://yasuenag.github.io/ffmasm/) and [cpumodel](examples/cpumodel) examples. 38 | 39 | ## 1. Create `CodeSegment` 40 | 41 | `CodeSegment` is a storage for assembled code. In Linux, it would be allocated by `mmap(2)` with executable bit. 42 | It implements [AutoCloseable](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/AutoCloseable.html), so you can use try-with-resources in below: 43 | 44 | ```java 45 | try(var seg = new CodeSegment()){ 46 | ... 47 | } 48 | ``` 49 | 50 | ## 2. Create `MethodHandle` via `AMD64AsmBuilder` 51 | 52 | You can assemble the code via `AMD64AsmBuilder`. It would be instanciated via `create()`, and it should be passed both `CodeSegment` and [FunctionDescriptor](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/FunctionDescriptor.html). 53 | 54 | In following example, the method is defined as `(I)I` (JNI signature) in `FunctionDescriptor`. 55 | `AMD64AsmBuilder` is builder pattern, so you can add instruction in below. Following example shows method argument (`int`) would be returned straightly. 56 | 57 | You can get `MethodHandle` in result of `build()`. 58 | 59 | ```java 60 | var desc = FunctionDescriptor.of( 61 | ValueLayout.JAVA_INT, // return value 62 | ValueLayout.JAVA_INT // 1st argument 63 | ); 64 | 65 | var method = AMD64AsmBuilder.create(seg, desc) 66 | /* push %rbp */ .push(Register.RBP) 67 | /* mov %rsp, %rbp */ .movRM(Register.RSP, Register.RBP, OptionalInt.empty()) 68 | /* mov %rdi, %rax */ .movRM(Register.RDI, Register.RAX, OptionalInt.empty()) 69 | /* leave */ .leave() 70 | /* ret */ .ret() 71 | .build(Linker.Option.critical(false)); 72 | ``` 73 | 74 | NOTE: [Linker.Option.critical()](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/Linker.Option.html#critical(boolean)) is recommended to pass `build()` method due to performance, but it might be cause of some issues in JVM (time to synchronize safepoint, memory corruption, etc). See Javadoc of `critical()`. 75 | 76 | ## 3. Method call 77 | 78 | ```java 79 | int ret = (int)method.invoke(100); // "ret" should be 100 80 | ``` 81 | 82 | # Debugging 83 | 84 | [ffmasm-disassembler](tools/disas) can disassemble the code in `MemorySegment` like generated by ffmasm, and dump assembly code to stdout. 85 | 86 | You can download ffmasm-dissassembler Maven package from GitHub packages: https://github.com/YaSuenag/ffmasm/packages/2370043 87 | 88 | See [examples/disas](examples/disas) for details. 89 | 90 | ## Requirements 91 | 92 | ffmasm-disassembler requires [hsdis](https://github.com/openjdk/jdk/tree/master/src/utils/hsdis). 93 | 94 | ## Generate hsdis 95 | 96 | To generate hsdis for Linux, you can use [hsdis-builder](https://github.com/YaSuenag/hsdis-builder). 97 | 98 | ## Deploy hsdis 99 | 100 | It should be deployed one of following directory (it is documented as source comment in disassembler.cpp in HotSpot): 101 | 102 | 1. `$JAVA_HOME/lib//libhsdis-.so` 103 | 2. `$JAVA_HOME/lib//hsdis-.so` 104 | 3. `$JAVA_HOME/lib/hsdis-.so` 105 | 4. `hsdis-.so` (using `LD_LIBRARY_PATH`) 106 | 107 | If you don't want to deploy hsdis into your JDK, you can specify `hsdis` system property like `-Dhsdis=/path/to/hsdis-amd64.so` 108 | 109 | ## Examples 110 | 111 | ``` 112 | import com.yasuenag.ffmasmtools.disas.Disassembler; 113 | 114 | : 115 | 116 | MemorySegment rdtsc = createRDTSC(); // Generate machine code with ffmasm 117 | Disassembler.dumpToStdout(rdtsc); // Dump assembly code of `rdtsc` to stdout 118 | ``` 119 | 120 | # Play with JNI 121 | 122 | You can bind native method to `MemorySegment` of ffmasm code dynamically. 123 | 124 | You have to construct `MemorySegment` of the machine code with `AMD64AsmBuilder`, and you have to get it from `getMemorySegment()`. Then you can bind it via `NativeRegister`. 125 | 126 | Following example shows native method `test` is binded to the code made by ffmasm. Note that 1st argument in Java is located at arg3 in native function because this is native function (1st arg is `JNIEnv*`, and 2nd arg is `jobject` or `jclass`). 127 | 128 | ```java 129 | public native int test(int arg); 130 | 131 | 132 | 133 | try(var seg = new CodeSegment()){ 134 | var desc = FunctionDescriptor.of( 135 | ValueLayout.JAVA_INT, // return value 136 | ValueLayout.JAVA_INT, // 1st arg (JNIEnv *) 137 | ValueLayout.JAVA_INT, // 2nd arg (jobject) 138 | ValueLayout.JAVA_INT // 3rd arg (arg1 of caller) 139 | ); 140 | var stub = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc) 141 | /* push %rbp */ .push(Register.RBP) 142 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty()) 143 | /* mov %arg3, retReg */ .movMR(argReg.arg3(), argReg.returnReg(), OptionalInt.empty()) // arg1 in Java is arg3 in native 144 | /* leave */ .leave() 145 | /* ret */ .ret() 146 | .getMemorySegment(); 147 | 148 | var method = this.getClass() 149 | .getMethod("test", int.class); 150 | 151 | var methodMap = Map.of(method, stub); 152 | var register = NativeRegister.create(this.getClass()); 153 | register.registerNatives(methodMap); 154 | 155 | final int expected = 100; 156 | int actual = test(expected); 157 | Assertions.assertEquals(expected, actual); 158 | } 159 | ``` 160 | 161 | # Play with perf tool 162 | 163 | You can record both function name and entry point address as a perf map file. 164 | 165 | ## Record function 166 | 167 | You can pass function name into `build()` method: 168 | 169 | ```java 170 | .build("GeneratedFunc", Linker.Option.critical(true)); 171 | ``` 172 | 173 | Function name would be set to `` if you do not pass function name (includes calling `build(Linker.Option)`). 174 | 175 | ## Write to map file 176 | 177 | perf map file would be written at [shutdown hook](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/Runtime.html#addShutdownHook(java.lang.Thread)) when `CodeSegment` lives. All of functions in all of `CodeSegment`s which are lives would be dumped at the time of shutdown hook. 178 | 179 | You need to enable perf map dumper via `CodeSegment::enablePerfMapDumper`. Call `CodeSegment::disablePerfMapDumper` if you want to cancel the dumper. 180 | 181 | ## Generate jitdump 182 | 183 | perf tool on Linux supports JIT-generated code. ffmasm can dump generated code as a jitdump. See an [example](examples/perf) for details. 184 | 185 | ### Record assembled code as a JIT'ed code 186 | 187 | Pass `JitDump` insntace to `build` method. 188 | 189 | ```java 190 | jitdump = JitDump.getInstance(Path.of(".")); 191 | 192 | : 193 | 194 | .build("GeneratedFunc", jitdump); 195 | ``` 196 | 197 | Then you can run `perf record`. Note that you have to set monotonic clock with `-k` option. 198 | 199 | ``` 200 | perf record -k 1 $JAVA_HOME/bin/java ... 201 | ``` 202 | 203 | As a result, you would get `jit-.dump` which includes JIT information. You should keep until run `perf inject`. 204 | 205 | ### Inject JIT'ed code into recording file 206 | 207 | `perf.data` generated by `perf record` would not include JIT'ed code, so you need to inject them via `perf inject` as following. 208 | 209 | ``` 210 | perf inject --jit -i perf.data -o perf.jit.data 211 | ``` 212 | 213 | You will get some `.so` file and `perf.jit.data` as an injected file as a result. 214 | 215 | ### Check with `perf report` 216 | 217 | ``` 218 | perf report -i perf.jit.data 219 | ``` 220 | 221 | # License 222 | 223 | The GNU Lesser General Public License, version 3.0 224 | -------------------------------------------------------------------------------- /benchmarks/funccall/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | AS = as 3 | ASFLAGS = -g -c --noexecstack 4 | LDFLAGS = -Wl,-z,noexecstack 5 | 6 | TARGET = librdtsc.so 7 | OBJS = target/rdtsc.o 8 | SRCS = src/main/native/rdtsc.S 9 | 10 | all: $(TARGET) 11 | 12 | $(TARGET): $(OBJS) 13 | $(CC) -shared -o $@ $^ $(LDFLAGS) 14 | mv $(TARGET) target/$(TARGET) 15 | 16 | $(OBJS): $(SRCS) 17 | $(AS) $(ASFLAGS) $< -o $@ 18 | -------------------------------------------------------------------------------- /benchmarks/funccall/README.md: -------------------------------------------------------------------------------- 1 | Benchmark for calling function 2 | =================== 3 | 4 | [JMH](https://github.com/openjdk/jmh) benchmark to comparison between JNI and ffmasm. 5 | 6 | This benchmark runs RDTSC instruction. JNI function is written in assembly code in [rdtsc.S](src/main/native/rdtsc.S), and same code is assembled by ffmasm in [FFMComparison.java](src/main/java/com/yasuenag/ffmasm/benchmark/FFMComparison.java). So you can see calling performacne JNI and Foreign Function & Memory API. 7 | 8 | # Requirements 9 | 10 | * Java 22 11 | * Maven 12 | * GNU Make 13 | * GNU assembler 14 | 15 | # How to build 16 | 17 | ``` 18 | cd /path/to/ffasm 19 | mvn install 20 | cd benchmark/funccall 21 | mvn package 22 | ``` 23 | 24 | # Check result from RDTSC 25 | 26 | ``` 27 | mvn exec:exec@single-run 28 | ``` 29 | 30 | # Run benchmark 31 | 32 | ``` 33 | cd target 34 | $JAVA_HOME/bin/java -jar ffmasm-benchmark-funccall-1.0.3.jar 35 | ``` 36 | 37 | JIT log ( `-Xlog:jit+compilation=debug,jit+inlining=debug` ) would be collected into `target` dir with PID. 38 | 39 | # Run single benchmark 40 | 41 | You can use [measure-single-benchmark.sh](measure-single-benchmark.sh) to run single benchmark. 42 | Benchmark should be set from following list: 43 | 44 | * FFM 45 | * FFMCritical 46 | * RegisterNatives 47 | * JNI 48 | 49 | JIT log ( `-Xlog:jit+compilation=debug,jit+inlining=debug` ) would be collected into `target` dir with benchmark name. 50 | 51 | ## Measures iterations or execution time 52 | 53 | ``` 54 | ./measure-single-benchmark.sh run [benchmark] 55 | ``` 56 | 57 | ## Measure iterations or execution time with `perf` 58 | 59 | You have to install perf tool before running. 60 | 61 | ``` 62 | ./measure-single-benchmark.sh perf [benchmark] 63 | ``` 64 | 65 | `perf.data` would be stored into working directory. 66 | -------------------------------------------------------------------------------- /benchmarks/funccall/measure-single-benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MODE=$1 4 | BENCH=$2 5 | BASEDIR=$(dirname $0) 6 | 7 | EXECUTABLE=$JAVA_HOME/bin/java 8 | FUNCCALL_JAR=`ls $BASEDIR/target/original-ffmasm-benchmark-funccall-*.jar` 9 | FFMASM_JAR=`ls $HOME/.m2/repository/com/yasuenag/ffmasm/*-SNAPSHOT/ffmasm-*-SNAPSHOT.jar | tail -n 1` 10 | 11 | JVM_OPTS="-cp $FUNCCALL_JAR:$FFMASM_JAR -Djava.library.path=$BASEDIR/target --enable-native-access=ALL-UNNAMED -Xms4g -Xmx4g -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+AlwaysPreTouch -XX:+PreserveFramePointer -Xlog:jit+compilation=debug,jit+inlining=debug:file=$BASEDIR/target/$BENCH-jit.log::filesize=0" 12 | APP_OPTS="com.yasuenag.ffmasm.benchmark.funccall.FuncCallComparison iterate $BENCH" 13 | 14 | if [ "$MODE" = "perf" ]; then 15 | JVM_OPTS="$JVM_OPTS -XX:+UnlockDiagnosticVMOptions -XX:+DumpPerfMapAtExit" 16 | exec perf record -g -F 99 -- $EXECUTABLE $JVM_OPTS $APP_OPTS 17 | elif [ "$MODE" = "run" ]; then 18 | exec $EXECUTABLE $JVM_OPTS $APP_OPTS 19 | else 20 | echo 'Unknown mode' 21 | fi 22 | -------------------------------------------------------------------------------- /benchmarks/funccall/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | com.yasuenag 28 | ffmasm-benchmark-funccall 29 | jar 30 | 1.0.3 31 | 32 | 33 | UTF-8 34 | 22 35 | 22 36 | 1.37 37 | 38 | 39 | 40 | 41 | org.openjdk.jmh 42 | jmh-core 43 | ${jmh.version} 44 | 45 | 46 | com.yasuenag 47 | ffmasm 48 | 0.6.0-SNAPSHOT 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.10.1 58 | 59 | 60 | -Xlint:all 61 | 62 | true 63 | 64 | 65 | org.openjdk.jmh 66 | jmh-generator-annprocess 67 | ${jmh.version} 68 | 69 | 70 | 71 | 72 | 73 | org.codehaus.mojo 74 | exec-maven-plugin 75 | 3.1.0 76 | 77 | 78 | make 79 | compile 80 | 81 | exec 82 | 83 | 84 | make 85 | 86 | 87 | 88 | single-run 89 | exec 90 | 91 | exec 92 | 93 | 94 | ${java.home}/bin/java 95 | ${project.build.directory} 96 | 97 | -classpath 98 | 99 | -Djava.library.path=. 100 | --enable-native-access=ALL-UNNAMED 101 | com.yasuenag.ffmasm.benchmark.funccall.FuncCallComparison 102 | single 103 | 104 | 105 | 106 | 107 | 108 | 109 | maven-shade-plugin 110 | 3.5.1 111 | 112 | false 113 | 114 | 115 | 116 | package 117 | 118 | shade 119 | 120 | 121 | 122 | 123 | org.openjdk.jmh.Main 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /benchmarks/funccall/src/main/java/com/yasuenag/ffmasm/benchmark/funccall/FuncCallComparison.java: -------------------------------------------------------------------------------- 1 | package com.yasuenag.ffmasm.benchmark.funccall; 2 | 3 | import java.lang.foreign.*; 4 | import java.lang.invoke.*; 5 | import java.lang.ref.*; 6 | import java.util.*; 7 | import java.util.concurrent.*; 8 | import java.util.concurrent.atomic.*; 9 | 10 | import com.yasuenag.ffmasm.*; 11 | import com.yasuenag.ffmasm.amd64.*; 12 | 13 | import org.openjdk.jmh.annotations.*; 14 | 15 | 16 | @State(Scope.Benchmark) 17 | @BenchmarkMode(Mode.Throughput) 18 | @Fork(value = 1, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "-Djava.library.path=.", "-Xms4g", "-Xmx4g", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC", "-XX:+AlwaysPreTouch", "-XX:+PreserveFramePointer", "-Xlog:jit+compilation=debug,jit+inlining=debug:file=jit%p.log::filesize=0"}) 19 | @Warmup(iterations = 1, time = 10, timeUnit = TimeUnit.SECONDS) 20 | @Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS) 21 | public class FuncCallComparison{ 22 | 23 | private static final CodeSegment seg; 24 | 25 | private static final MethodHandle ffmRDTSC; 26 | private static final MethodHandle ffmRDTSCCritical; 27 | 28 | static{ 29 | System.loadLibrary("rdtsc"); 30 | 31 | try{ 32 | seg = new CodeSegment(); 33 | var action = new CodeSegment.CleanerAction(seg); 34 | Cleaner.create() 35 | .register(FuncCallComparison.class, action); 36 | 37 | var desc = FunctionDescriptor.of(ValueLayout.JAVA_LONG); 38 | var mem = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc) 39 | /* push %rbp */ .push(Register.RBP) 40 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 41 | /* rdtsc */ .rdtsc() 42 | /* shl $32, %rdx */ .shl(Register.RDX, (byte)32, OptionalInt.empty()) 43 | /* or %rdx, %rax */ .orMR(Register.RDX, Register.RAX, OptionalInt.empty()) 44 | /* leave */ .leave() 45 | /* ret */ .ret() 46 | .getMemorySegment(); 47 | 48 | ffmRDTSC = Linker.nativeLinker().downcallHandle(mem, desc); 49 | ffmRDTSCCritical = Linker.nativeLinker().downcallHandle(mem, desc, Linker.Option.critical(false)); 50 | 51 | var register = NativeRegister.create(FuncCallComparison.class); 52 | register.registerNatives(Map.of(FuncCallComparison.class.getMethod("invokeFFMRDTSCRegisterNatives"), mem)); 53 | } 54 | catch(Throwable t){ 55 | throw new RuntimeException(t); 56 | } 57 | } 58 | 59 | @Benchmark 60 | public native long invokeJNI(); 61 | 62 | @Benchmark 63 | public long invokeFFMRDTSC(){ 64 | try{ 65 | return (long)ffmRDTSC.invokeExact(); 66 | } 67 | catch(Throwable t){ 68 | throw new RuntimeException(t); 69 | } 70 | } 71 | 72 | @Benchmark 73 | public long invokeFFMRDTSCCritical(){ 74 | try{ 75 | return (long)ffmRDTSCCritical.invokeExact(); 76 | } 77 | catch(Throwable t){ 78 | throw new RuntimeException(t); 79 | } 80 | } 81 | 82 | @Benchmark 83 | public native long invokeFFMRDTSCRegisterNatives(); 84 | 85 | public void singleRun(){ 86 | long nativeVal = invokeJNI(); 87 | long ffmVal = invokeFFMRDTSC(); 88 | long ffmCriticalVal = invokeFFMRDTSCCritical(); 89 | long ffmRegisterNativesVal = invokeFFMRDTSCRegisterNatives(); 90 | 91 | System.out.println(" JNI: " + nativeVal); 92 | System.out.println(" FFM: " + ffmVal); 93 | System.out.println(" FFM (Critical): " + ffmCriticalVal); 94 | System.out.println("FFM (RegisterNatives): " + ffmRegisterNativesVal); 95 | } 96 | 97 | public void iterate(String benchmark){ 98 | Runnable runner = switch(benchmark){ 99 | case "JNI" -> this::invokeJNI; 100 | case "FFM" -> this::invokeFFMRDTSC; 101 | case "FFMCritical" -> this::invokeFFMRDTSCCritical; 102 | case "RegisterNatives" -> this::invokeFFMRDTSCRegisterNatives; 103 | default -> throw new IllegalArgumentException("Unknown benchmark"); 104 | }; 105 | 106 | final AtomicLong counter = new AtomicLong(); 107 | 108 | var task = new TimerTask(){ 109 | @Override 110 | public void run(){ 111 | System.out.printf("Interrupted: counter = %d\n", counter.get()); 112 | System.exit(0); 113 | } 114 | }; 115 | var timer = new Timer("Benchmark Finisher"); 116 | timer.schedule(task, 30_000); 117 | 118 | long startTime = System.nanoTime(); 119 | while(true){ 120 | runner.run(); 121 | if(counter.incrementAndGet() == Long.MAX_VALUE){ 122 | timer.cancel(); 123 | long endTime = System.nanoTime(); 124 | long elapsedTime = endTime - startTime; 125 | System.out.printf("Finished: %d ns\n", elapsedTime); 126 | } 127 | } 128 | } 129 | 130 | public static void main(String[] args){ 131 | if(args.length < 1){ 132 | System.err.println("You should specify the mode."); 133 | System.exit(1); 134 | } 135 | String mode = args[0]; 136 | 137 | var inst = new FuncCallComparison(); 138 | 139 | if(mode.equals("single")){ 140 | inst.singleRun(); 141 | } 142 | else if(mode.equals("iterate")){ 143 | if(args.length != 2){ 144 | System.err.println("You should specify the benchmark."); 145 | System.exit(2); 146 | } 147 | inst.iterate(args[1]); 148 | } 149 | else{ 150 | System.err.println("Unknown mode."); 151 | System.exit(3); 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /benchmarks/funccall/src/main/native/rdtsc.S: -------------------------------------------------------------------------------- 1 | .text 2 | 3 | # jlong Java_com_yasuenag_ffmasm_benchmark_funccall.FuncCallComparison_invokeJNI(JNIEnv *env) 4 | .global Java_com_yasuenag_ffmasm_benchmark_funccall_FuncCallComparison_invokeJNI 5 | .type Java_com_yasuenag_ffmasm_benchmark_funccall_FuncCallComparison_invokeJNI, @function 6 | Java_com_yasuenag_ffmasm_benchmark_funccall_FuncCallComparison_invokeJNI: 7 | push %rbp 8 | mov %rsp, %rbp 9 | rdtsc 10 | shl $32, %rdx 11 | or %rdx, %rax 12 | leave 13 | ret 14 | -------------------------------------------------------------------------------- /benchmarks/vectorapi/README.md: -------------------------------------------------------------------------------- 1 | Benchmark for vector operation 2 | =================== 3 | 4 | [JMH](https://github.com/openjdk/jmh) benchmark to comparison Pure Java code, [Vector API](https://openjdk.org/jeps/426), and ffmasm. 5 | 6 | This benchmark adds with 256bit packed int. Pure Java and Vector API are expected to get benefit from C2. 7 | 8 | # Requirements 9 | 10 | * Java 22 11 | * Maven 12 | 13 | # How to build 14 | 15 | ```sh 16 | $ cd /path/to/ffasm 17 | $ mvn install 18 | $ cd benchmark/vectorapi 19 | $ mvn package 20 | ``` 21 | 22 | # Run benchmark 23 | 24 | ```sh 25 | $ $JAVA_HOME/bin/java -jar target/ffmasm-benchmark-vectorapi-1.0.4.jar 26 | ``` 27 | -------------------------------------------------------------------------------- /benchmarks/vectorapi/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | com.yasuenag 28 | ffmasm-benchmark-vectorapi 29 | jar 30 | 1.0.4 31 | 32 | 33 | UTF-8 34 | 22 35 | 22 36 | 1.37 37 | 38 | 39 | 40 | 41 | org.openjdk.jmh 42 | jmh-core 43 | ${jmh.version} 44 | 45 | 46 | com.yasuenag 47 | ffmasm 48 | 0.6.0-SNAPSHOT 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.10.1 58 | 59 | 60 | -Xlint:all 61 | --add-modules 62 | jdk.incubator.vector 63 | --add-exports 64 | jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED 65 | 66 | 67 | 68 | org.openjdk.jmh 69 | jmh-generator-annprocess 70 | ${jmh.version} 71 | 72 | 73 | 74 | 75 | 76 | maven-shade-plugin 77 | 3.4.0 78 | 79 | false 80 | 81 | 82 | 83 | package 84 | 85 | shade 86 | 87 | 88 | 89 | 90 | org.openjdk.jmh.Main 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /benchmarks/vectorapi/src/main/java/com/yasuenag/ffmasm/benchmark/vectorapi/VectorOpComparison.java: -------------------------------------------------------------------------------- 1 | package com.yasuenag.ffmasm.benchmark.vector; 2 | 3 | import java.lang.foreign.*; 4 | import java.lang.invoke.*; 5 | import java.lang.ref.*; 6 | import java.util.*; 7 | import java.util.concurrent.*; 8 | 9 | import jdk.incubator.vector.*; 10 | 11 | import com.yasuenag.ffmasm.*; 12 | import com.yasuenag.ffmasm.amd64.*; 13 | 14 | import org.openjdk.jmh.annotations.*; 15 | 16 | 17 | @State(Scope.Benchmark) 18 | @BenchmarkMode(Mode.Throughput) 19 | @Fork(value = 1, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules", "jdk.incubator.vector", "--add-exports", "jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED", "-Xms6g", "-Xmx6g"}) 20 | @Warmup(iterations = 1, time = 3, timeUnit = TimeUnit.SECONDS) 21 | @Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS) 22 | public class VectorOpComparison{ 23 | 24 | private static int[] randArray; 25 | private static int[] result; 26 | 27 | private static final CodeSegment seg; 28 | private static final MethodHandle ffm; 29 | private static final MemorySegment srcSeg; 30 | private static final MemorySegment destSeg; 31 | private MemorySegment heapSrcSeg; 32 | private MemorySegment heapDestSeg; 33 | private static final MethodHandle ffmHeap; 34 | 35 | private IntVector vectorSrc; 36 | private IntVector vectorDest; 37 | 38 | static{ 39 | 40 | try{ 41 | seg = new CodeSegment(); 42 | var action = new CodeSegment.CleanerAction(seg); 43 | Cleaner.create() 44 | .register(VectorOpComparison.class, action); 45 | 46 | var desc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS); 47 | var ffmSeg = AMD64AsmBuilder.create(AVXAsmBuilder.class, seg, desc) 48 | /* push %rbp */ .push(Register.RBP) 49 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 50 | .cast(AVXAsmBuilder.class) 51 | /* vmovdqu (%rdi), %ymm0 */ .vmovdquRM(Register.YMM0, Register.RDI, OptionalInt.of(0)) 52 | /* vpaddd (%rsi), %ymm0, %ymm0 */ .vpaddd(Register.YMM0, Register.RSI, Register.YMM0, OptionalInt.of(0)) 53 | /* vmovdqu %ymm0, (%rdi) */ .vmovdquMR(Register.YMM0, Register.RDI, OptionalInt.of(0)) 54 | /* leave */ .leave() 55 | /* ret */ .ret() 56 | .getMemorySegment(); 57 | 58 | var linker = Linker.nativeLinker(); 59 | ffm = linker.downcallHandle(ffmSeg, desc); 60 | ffmHeap = linker.downcallHandle(ffmSeg, desc, Linker.Option.critical(true)); 61 | } 62 | catch(PlatformException | UnsupportedPlatformException e){ 63 | throw new RuntimeException(e); 64 | } 65 | 66 | var arena = Arena.ofAuto(); 67 | srcSeg = arena.allocate(32, 32); 68 | destSeg = arena.allocate(32, 32); 69 | } 70 | 71 | @Setup(Level.Iteration) 72 | public void paramSetup(){ 73 | randArray = (new Random()).ints() 74 | .limit(8) 75 | .toArray(); 76 | result = new int[]{0, 0, 0, 0, 0, 0, 0, 0}; 77 | 78 | MemorySegment.copy(randArray, 0, srcSeg, ValueLayout.JAVA_INT, 0, 8); 79 | MemorySegment.copy(result, 0, destSeg, ValueLayout.JAVA_INT, 0, 8); 80 | 81 | heapSrcSeg = MemorySegment.ofArray(randArray); 82 | heapDestSeg = MemorySegment.ofArray(result); 83 | 84 | vectorSrc = IntVector.fromArray(IntVector.SPECIES_256, randArray, 0); 85 | vectorDest = IntVector.fromArray(IntVector.SPECIES_256, result, 0); 86 | } 87 | 88 | @Benchmark 89 | public int[] invokeJava(){ 90 | for(int i = 0; i < 8; i++){ 91 | result[i] += randArray[i]; 92 | } 93 | return result; 94 | } 95 | 96 | @Benchmark 97 | public int[] invokeFFM(){ 98 | try{ 99 | ffm.invoke(destSeg, srcSeg); 100 | return destSeg.toArray(ValueLayout.JAVA_INT); 101 | } 102 | catch(Throwable t){ 103 | throw new RuntimeException(t); 104 | } 105 | } 106 | 107 | @Benchmark 108 | public int[] invokeFFMHeap(){ 109 | try{ 110 | ffmHeap.invoke(heapDestSeg, heapSrcSeg); 111 | return result; 112 | } 113 | catch(Throwable t){ 114 | throw new RuntimeException(t); 115 | } 116 | } 117 | 118 | @Benchmark 119 | public int[] invokeVector(){ 120 | vectorDest.add(vectorSrc).intoArray(result, 0); 121 | return result; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /examples/cpumodel/README.md: -------------------------------------------------------------------------------- 1 | ffmasm examples - cpumodel 2 | =================== 3 | 4 | This is an example of ffmasm to obtain CPU model from `CPUID` instruction in AMD64. 5 | 6 | # Requirements 7 | 8 | * Java 24 9 | * AMD64 Linux or Windows 10 | * Maven 11 | 12 | # How to run 13 | 14 | ## 1. Install ffmasm 15 | 16 | ```bash 17 | $ mvn clean install 18 | ``` 19 | 20 | ## 2. Build cpumodel 21 | 22 | ```bash 23 | $ cd examples/cpumodel 24 | $ mvn clean package 25 | ``` 26 | 27 | ## 3. Run via Maven 28 | 29 | ``` 30 | $ mvn exec:java 31 | ``` 32 | 33 | ## 4. [EXTRA] check assembly in GDB 34 | 35 | Run GDB if you want to check assembled code. 36 | 37 | You can see PID and address of `CodeSegment` on the console when you run `java -jar cpumodel-0.1.3.jar --stop` in below. 38 | 39 | ``` 40 | PID: 928 41 | Addr: 0x7f4c42704000 42 | Intel(R) Core(TM) i3-8145U CPU @ 2.10GHz 43 | ``` 44 | 45 | Attach to JVM process (928 in this case), and run `disas` on GDB. 46 | 47 | ``` 48 | $ gdb -p 928 49 | 50 | : 51 | 52 | (gdb) disas 0x7f4c42704000, 0x7f4c42704020 53 | Dump of assembler code from 0x7f4c42704000 to 0x7f4c42704020: 54 | 0x00007f4c42704000: push %rbp 55 | 0x00007f4c42704001: mov %rsp,%rbp 56 | 0x00007f4c42704004: push %rbx 57 | 0x00007f4c42704005: mov %rdi,%rax 58 | 0x00007f4c42704008: mov %rsi,%r11 59 | 0x00007f4c4270400b: cpuid 60 | 0x00007f4c4270400d: mov %eax,(%r11) 61 | 0x00007f4c42704010: mov %ebx,0x4(%r11) 62 | 0x00007f4c42704014: mov %ecx,0x8(%r11) 63 | 0x00007f4c42704018: mov %edx,0xc(%r11) 64 | 0x00007f4c4270401c: pop %rbx 65 | 0x00007f4c4270401e: leave 66 | 0x00007f4c4270401f: ret 67 | End of assembler dump. 68 | ``` 69 | 70 | # Calling convention 71 | 72 | Calling convention is different between Linux AMD64 and Windows x64. Linux AMD64 conform to [System V Application Binary Interface](https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf), on the other hand Windows conform to [here](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention). You have to conform to them when you assemble. 73 | 74 | # Expected assembly code in this example on Linux 75 | 76 | ```assembly 77 | # prologue 78 | push %rbp 79 | mov %rsp, %rbp 80 | 81 | # Evacuate callee-saved register 82 | push %rbx 83 | 84 | # Copy arguments to temporary registers 85 | mov %rdi,%rax 86 | mov %rsi,%r11 87 | 88 | # Call CPUID 89 | cpuid 90 | 91 | # Store result to given memory 92 | mov %eax, (%r11) 93 | mov %ebx, 4(%r11) 94 | mov %ecx, 8(%r11) 95 | mov %edx, 12(%r11) 96 | 97 | # Restore callee-saved register 98 | pop %rbx 99 | 100 | # Epilogue 101 | leave 102 | ret 103 | ``` 104 | 105 | # Play with AOT 106 | 107 | Java 24 introduced [JEP 483: Ahead-of-Time Class Loading & Linking](https://openjdk.org/jeps/483). It can cache InvokeDynamic link, so first call in FFM could be faster. 108 | 109 | [pom.xml](pom.xml) generates AOT cache in `package` phase, so you can feel acceralation with AOT as following. Note that you have to move `target` directory because AOT identifies class path. 110 | 111 | ``` 112 | $ cd target 113 | 114 | 115 | $ time $JAVA_HOME/bin/java -jar cpumodel-0.1.3.jar 116 | PID: 3147 117 | Addr: 0x7f8d1fc37000 118 | AMD Ryzen 3 3300X 4-Core Processor 119 | 120 | real 0m0.140s 121 | user 0m0.196s 122 | sys 0m0.049s 123 | 124 | 125 | $ time $JAVA_HOME/bin/java -XX:AOTCache=ffmasm-cpumodel.aot -jar cpumodel-0.1.3.jar 126 | PID: 3167 127 | Addr: 0x7f9000001000 128 | AMD Ryzen 3 3300X 4-Core Processor 129 | 130 | real 0m0.095s 131 | user 0m0.121s 132 | sys 0m0.041s 133 | ``` 134 | -------------------------------------------------------------------------------- /examples/cpumodel/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | cpumodel 27 | 0.1.3 28 | jar 29 | 30 | cpumodel 31 | 32 | 33 | UTF-8 34 | com.yasuenag.ffmasm.examples.cpumodel 35 | com.yasuenag.ffmasm.examples.cpumodel.Main 36 | 24 37 | 24 38 | 39 | 40 | 41 | 42 | com.yasuenag 43 | ffmasm 44 | 0.6.0-SNAPSHOT 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 3.10.1 54 | 55 | 56 | -Xlint:all 57 | 58 | true 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-jar-plugin 64 | 3.4.2 65 | 66 | 67 | 68 | ALL-UNNAMED 69 | 70 | 71 | 72 | 73 | 74 | maven-shade-plugin 75 | 3.6.0 76 | 77 | false 78 | 79 | 80 | 81 | package 82 | 83 | shade 84 | 85 | 86 | 87 | 88 | ${mainClass} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.codehaus.mojo 97 | exec-maven-plugin 98 | 3.5.0 99 | 100 | 101 | aot-record 102 | package 103 | 104 | exec 105 | 106 | 107 | ${project.build.directory} 108 | ${java.home}/bin/java 109 | 110 | --enable-native-access=ALL-UNNAMED 111 | -XX:AOTMode=record 112 | -XX:AOTConfiguration=ffmasm-cpumodel.aotconf 113 | -jar 114 | ${project.build.finalName}.jar 115 | 116 | 117 | 118 | 119 | aot-create 120 | package 121 | 122 | exec 123 | 124 | 125 | ${project.build.directory} 126 | ${java.home}/bin/java 127 | 128 | -XX:AOTMode=create 129 | -XX:AOTConfiguration=ffmasm-cpumodel.aotconf 130 | -XX:AOTCache=ffmasm-cpumodel.aot 131 | -cp 132 | ${project.build.finalName}.jar 133 | 134 | 135 | 136 | 137 | 138 | 139 | org.codehaus.mojo 140 | exec-maven-plugin 141 | 3.5.0 142 | 143 | ${mainClass} 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /examples/cpumodel/src/main/java/com/yasuenag/ffmasm/examples/cpumodel/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.examples.cpumodel; 20 | 21 | import java.lang.foreign.*; 22 | import java.nio.charset.*; 23 | import java.util.*; 24 | 25 | import com.yasuenag.ffmasm.*; 26 | import com.yasuenag.ffmasm.amd64.*; 27 | 28 | 29 | public class Main{ 30 | 31 | public static void main(String[] args) throws Throwable{ 32 | System.out.println("PID: " + ProcessHandle.current().pid()); 33 | 34 | var mem = Arena.ofAuto().allocate(4 * 4 * 3); // 32bit * 4 regs (eax - edx) * 3 calls 35 | var desc = FunctionDescriptor.ofVoid( 36 | ValueLayout.JAVA_INT, // eax 37 | ValueLayout.ADDRESS // memory for eax - edx 38 | ); 39 | try(var codeSegment = new CodeSegment()){ 40 | System.out.println("Addr: 0x" + Long.toHexString(codeSegment.getAddr().address())); 41 | 42 | Register arg1, arg2; 43 | String osName = System.getProperty("os.name"); 44 | if(osName.equals("Linux")){ 45 | arg1 = Register.RDI; 46 | arg2 = Register.RSI; 47 | } 48 | else if(osName.startsWith("Windows")){ 49 | arg1 = Register.RCX; 50 | arg2 = Register.RDX; 51 | } 52 | else{ 53 | throw new RuntimeException("Unsupported OS: " + osName); 54 | } 55 | 56 | var cpuid = AMD64AsmBuilder.create(AMD64AsmBuilder.class, codeSegment, desc) 57 | /* push %rbp */ .push(Register.RBP) 58 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 59 | /* push %rbx */ .push(Register.RBX) 60 | /* mov , %rax */ .movMR(arg1, Register.RAX, OptionalInt.empty()) 61 | /* mov , %r11 */ .movMR(arg2, Register.R11, OptionalInt.empty()) 62 | /* cpuid */ .cpuid() 63 | /* mov %eax, (%r11) */ .movMR(Register.EAX, Register.R11, OptionalInt.of(0)) 64 | /* mov %ebx, 4(%r11) */ .movMR(Register.EBX, Register.R11, OptionalInt.of(4)) 65 | /* mov %ecx, 8(%r11) */ .movMR(Register.ECX, Register.R11, OptionalInt.of(8)) 66 | /* mov %edx, 12(%r11) */ .movMR(Register.EDX, Register.R11, OptionalInt.of(12)) 67 | /* pop %rbx */ .pop(Register.RBX, OptionalInt.empty()) 68 | /* leave */ .leave() 69 | /* ret */ .ret() 70 | .build(Linker.Option.critical(false)); 71 | 72 | cpuid.invoke(0x80000002, mem); 73 | cpuid.invoke(0x80000003, mem.asSlice(4 * 4)); 74 | cpuid.invoke(0x80000004, mem.asSlice(4 * 4 * 2)); 75 | 76 | String model = mem.getString(0L); 77 | System.out.println(model); 78 | 79 | if((args.length > 0) && args[0].equals("--stop")){ 80 | System.out.println(); 81 | System.out.print("Press any key to exit..."); 82 | System.out.flush(); 83 | System.in.read(); 84 | } 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/cpumodel/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | module com.yasuenag.ffmasm.examples.cpumodel { 20 | requires com.yasuenag.ffmasm; 21 | } 22 | -------------------------------------------------------------------------------- /examples/disas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 4.0.0 23 | 24 | com.yasuenag 25 | ffmasm-disas-example 26 | 0.1.0 27 | jar 28 | 29 | ffmasm-disas-example 30 | 31 | 32 | UTF-8 33 | com.yasuenag.ffmasm.examples.disas.Main 34 | ${project.artifactId}-${project.version} 35 | 22 36 | 22 37 | 38 | 39 | 40 | 41 | github 42 | https://maven.pkg.github.com/YaSuenag/ffmasm 43 | 44 | 45 | 46 | 47 | 48 | com.yasuenag 49 | ffmasm 50 | 0.6.0-SNAPSHOT 51 | 52 | 53 | com.yasuenag 54 | ffmasm-disassembler 55 | 0.1.1 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 3.13.0 65 | 66 | 67 | -Xlint:all 68 | 69 | true 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-jar-plugin 75 | 3.4.2 76 | 77 | 78 | 79 | ${mainClass} 80 | 81 | 82 | ALL-UNNAMED 83 | 84 | 85 | 86 | 87 | 88 | org.codehaus.mojo 89 | exec-maven-plugin 90 | 3.5.0 91 | 92 | ${mainClass} 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/disas/src/main/java/com/yasuenag/ffmasm/examples/disas/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.examples.disas; 20 | 21 | import java.lang.foreign.*; 22 | import java.util.*; 23 | 24 | import com.yasuenag.ffmasm.*; 25 | import com.yasuenag.ffmasm.amd64.*; 26 | 27 | import com.yasuenag.ffmasmtools.disas.Disassembler; 28 | 29 | 30 | public class Main{ 31 | 32 | public static MemorySegment createRDTSC() throws Exception{ 33 | var seg = new CodeSegment(); 34 | return AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg) 35 | /* push %rbp */ .push(Register.RBP) 36 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 37 | /* rdtsc */ .rdtsc() 38 | /* shl $32, %rdx */ .shl(Register.RDX, (byte)32, OptionalInt.empty()) 39 | /* or %rdx, %rax */ .orMR(Register.RDX, Register.RAX, OptionalInt.empty()) 40 | /* leave */ .leave() 41 | /* ret */ .ret() 42 | .getMemorySegment(); 43 | } 44 | 45 | public static void main(String[] args) throws Exception{ 46 | var rdtsc = createRDTSC(); 47 | Disassembler.dumpToStdout(rdtsc); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /examples/perf/README.md: -------------------------------------------------------------------------------- 1 | Example of profiling with perf tool 2 | =================== 3 | 4 | This example shows how to use `JitDump` to profile your assembly code generated by ffmasm. [PerfJit.java](src/main/java/com/yasuenag/ffmasm/examples/perf/PerfJit.java) issues `RDRAND` instruction in 10,000,000 times. 5 | 6 | # How to build 7 | 8 | ``` 9 | cd /path/to/ffasm 10 | mvn install 11 | cd examples/perf 12 | mvn package 13 | ``` 14 | 15 | # Run test 16 | 17 | [perf.sh](perf.sh) runs the example with `perf` and injects jitted code (it means assembly code generated by ffmasm) with `perf inject`. 18 | 19 | ``` 20 | ./perf.sh 21 | ``` 22 | 23 | You can see `jit-.dump`, `jitted-*.so`, and `perf.*data*` on working directory of `perf.sh`. Please keep them until you run `perf report`. 24 | 25 | # Profiling with `perf report` 26 | 27 | You need to specify `perf.jit.data` with `-i` option to load injected data. 28 | 29 | ``` 30 | perf report -i perf.jit.data 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/perf/perf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=$(dirname $0) 4 | 5 | perf record -k 1 $JAVA_HOME/bin/java -jar $BASEDIR/target/perf-example-0.1.0.jar && \ 6 | perf inject --jit -i perf.data -o perf.jit.data && \ 7 | echo 'Completed. run "perf report -i perf.jit.data"' 8 | -------------------------------------------------------------------------------- /examples/perf/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | com.yasuenag 28 | perf-example 29 | jar 30 | 0.1.0 31 | 32 | 33 | UTF-8 34 | 22 35 | 22 36 | 37 | 38 | 39 | 40 | com.yasuenag 41 | ffmasm 42 | 0.6.0-SNAPSHOT 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-compiler-plugin 51 | 3.13.0 52 | 53 | 54 | -Xlint:all 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-jar-plugin 61 | 3.4.2 62 | 63 | 64 | 65 | ALL-UNNAMED 66 | 67 | 68 | 69 | 70 | 71 | maven-shade-plugin 72 | 3.6.0 73 | 74 | false 75 | 76 | 77 | 78 | package 79 | 80 | shade 81 | 82 | 83 | 84 | 85 | com.yasuenag.ffmasm.examples.perf.PerfJit 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /examples/perf/src/main/java/com/yasuenag/ffmasm/examples/perf/PerfJit.java: -------------------------------------------------------------------------------- 1 | package com.yasuenag.ffmasm.examples.perf; 2 | 3 | import java.lang.foreign.*; 4 | import java.lang.invoke.*; 5 | import java.nio.file.*; 6 | import java.util.*; 7 | 8 | import com.yasuenag.ffmasm.*; 9 | import com.yasuenag.ffmasm.amd64.*; 10 | 11 | 12 | public class PerfJit{ 13 | 14 | private static final CodeSegment seg; 15 | 16 | private static final MethodHandle rdtsc; 17 | 18 | private static final JitDump jitdump; 19 | 20 | static{ 21 | try{ 22 | seg = new CodeSegment(); 23 | jitdump = JitDump.getInstance(Path.of(".")); 24 | var desc = FunctionDescriptor.of(ValueLayout.JAVA_LONG); 25 | rdtsc = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc) 26 | /* .align 16 */ .alignTo16BytesWithNOP() 27 | /* retry: */ .label("retry") 28 | /* rdrand %rax */ .rdrand(Register.RAX) 29 | /* jae retry */ .jae("retry") 30 | /* ret */ .ret() 31 | .build("ffm_rdtsc", jitdump, Linker.Option.critical(false)); 32 | } 33 | catch(Throwable t){ 34 | throw new RuntimeException(t); 35 | } 36 | } 37 | 38 | public static void main(String[] args) throws Throwable{ 39 | try(jitdump; seg){ 40 | for(int i = 0; i < 10_000_000; i++){ 41 | long _ = (long)rdtsc.invokeExact(); 42 | } 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | ffmasm 27 | 0.6.0-SNAPSHOT 28 | jar 29 | 30 | ffmasm 31 | 32 | 33 | scm:git:git://github.com/YaSuenag/ffmasm.git 34 | scm:git:ssh://github.com:YaSuenag/ffmasm.git 35 | https://github.com/YaSuenag/ffmasm 36 | 37 | 38 | 39 | UTF-8 40 | 22 41 | 22 42 | 43 | 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter-api 48 | 5.9.1 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 3.10.1 59 | 60 | 61 | -Xlint:all 62 | 63 | true 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-javadoc-plugin 69 | 3.4.1 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-surefire-plugin 74 | 3.5.2 75 | 76 | ${java.home}/bin/java 77 | --enable-native-access=ALL-UNNAMED 78 | --enable-native-access=com.yasuenag.ffmasm 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | github 87 | GitHub Packages 88 | https://maven.pkg.github.com/YaSuenag/ffmasm 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/CodeSegment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | import java.io.IOException; 22 | import java.io.PrintWriter; 23 | import java.io.UncheckedIOException; 24 | import java.lang.foreign.MemorySegment; 25 | import java.lang.invoke.MethodHandle; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.util.Comparator; 29 | import java.util.Set; 30 | import java.util.HashSet; 31 | 32 | import com.yasuenag.ffmasm.internal.ExecMemory; 33 | import com.yasuenag.ffmasm.internal.linux.LinuxExecMemory; 34 | import com.yasuenag.ffmasm.internal.windows.WindowsExecMemory; 35 | 36 | 37 | /** 38 | * Memory segment for executables. 39 | * 40 | * @author Yasumasa Suenaga 41 | */ 42 | public class CodeSegment implements AutoCloseable{ 43 | 44 | /** 45 | * Default size of code segment. 46 | */ 47 | public static final long DEFAULT_CODE_SEGMENT_SIZE = 4096L; 48 | 49 | /** 50 | * Holder for method information. This is used for perfmap dumping. 51 | */ 52 | public static record MethodInfo(String name, long address, int size){ 53 | @Override 54 | public String toString(){ 55 | return String.format("0x%x 0x%x %s", address, size, name); 56 | } 57 | } 58 | 59 | private final ExecMemory mem; 60 | 61 | private final MemorySegment addr; 62 | 63 | private final long size; 64 | 65 | private final Set methods; 66 | 67 | private long tail; 68 | 69 | private Thread perfMapDumper; 70 | 71 | /** 72 | * Allocate memory for this code segment with default size (4096 bytes). 73 | * @throws PlatformException thrown when native function call failed. 74 | * @throws UnsupportedPlatformException thrown when the platform is not supported. 75 | */ 76 | public CodeSegment() throws PlatformException, UnsupportedPlatformException{ 77 | this(DEFAULT_CODE_SEGMENT_SIZE); 78 | } 79 | 80 | /** 81 | * Allocate memory for this code segment. 82 | * @param size size of code segment. 83 | * @throws PlatformException thrown when native function call failed. 84 | * @throws UnsupportedPlatformException thrown when the platform is not supported. 85 | */ 86 | public CodeSegment(long size) throws PlatformException, UnsupportedPlatformException{ 87 | String osName = System.getProperty("os.name"); 88 | if(osName.equals("Linux")){ 89 | mem = new LinuxExecMemory(); 90 | } 91 | else if(osName.startsWith("Windows")){ 92 | mem = new WindowsExecMemory(); 93 | } 94 | else{ 95 | throw new UnsupportedPlatformException(osName + " is unsupported."); 96 | } 97 | 98 | this.size = size; 99 | this.addr = mem.allocate(size); 100 | this.methods = new HashSet<>(); 101 | this.tail = 0L; 102 | this.perfMapDumper = null; 103 | } 104 | 105 | /** 106 | * Release memory for this code segment. 107 | */ 108 | @Override 109 | public void close() throws Exception{ 110 | if(perfMapDumper != null){ 111 | disablePerfMapDumper(); 112 | } 113 | mem.deallocate(addr, size); 114 | } 115 | 116 | /** 117 | * Class to register calling close() as Cleaner action. 118 | */ 119 | public static class CleanerAction implements Runnable{ 120 | 121 | private final CodeSegment seg; 122 | 123 | /** 124 | * @param seg CodeSegment instance to close. 125 | */ 126 | public CleanerAction(CodeSegment seg){ 127 | this.seg = seg; 128 | } 129 | 130 | /** 131 | * Close associated CodeSegment. 132 | * All of exceptions are ignored during close() operation. 133 | */ 134 | @Override 135 | public void run(){ 136 | try{ 137 | seg.close(); 138 | } 139 | catch(Exception e){ 140 | // ignore 141 | } 142 | } 143 | 144 | } 145 | 146 | /** 147 | * Get slice of this segment from the tail. 148 | * 149 | * @return Slice of this segment from the tail. 150 | */ 151 | public MemorySegment getTailOfMemorySegment(){ 152 | return addr.asSlice(tail); 153 | } 154 | 155 | /** 156 | * Align the tail to 16 bytes 157 | */ 158 | public void alignTo16Bytes(){ 159 | if((tail & 0xf) > 0){ // not aligned 160 | tail = (tail + 0x10) & 0xfffffffffffffff0L; 161 | } 162 | } 163 | 164 | /** 165 | * Get the tail of this segment. 166 | * 167 | * @return the tail of this segment. 168 | */ 169 | public long getTail(){ 170 | return tail; 171 | } 172 | 173 | /** 174 | * Increment the tail with given size. 175 | * @param size value to increment 176 | */ 177 | public void incTail(long size){ 178 | this.tail += size; 179 | } 180 | 181 | /** 182 | * Get MemorySegment which relates to this segment. 183 | * 184 | * @return MemorySegment of this segment. 185 | */ 186 | public MemorySegment getAddr(){ 187 | return addr; 188 | } 189 | 190 | /** 191 | * Add method info. It will be dumped to perf map as related method of this CodeSegment. 192 | * @param name Method name 193 | * @param address Address of the method 194 | * @param size Size of the method (machine code) 195 | * @return MethodInfo of the method info. 196 | * @throws IllegalArgumentException if the address is out of range from this CodeSegment. 197 | */ 198 | public MethodInfo addMethodInfo(String name, long address, int size){ 199 | if((address < addr.address()) || ((addr.address() + this.size) < (address + size))){ 200 | throw new IllegalArgumentException("Address is out of range from CodeSegment."); 201 | } 202 | var methodInfo = new MethodInfo(name, address, size); 203 | methods.add(methodInfo); 204 | return methodInfo; 205 | } 206 | 207 | private void dumpPerfMap(Path path){ 208 | try(var writer = new PrintWriter(Files.newOutputStream(path))){ 209 | methods.stream() 210 | .sorted(Comparator.comparing(MethodInfo::address)) 211 | .map(MethodInfo::toString) 212 | .forEachOrdered(writer::println); 213 | } 214 | catch(IOException e){ 215 | throw new UncheckedIOException(e); 216 | } 217 | } 218 | 219 | /** 220 | * Enable perf map dumper at shutdown hook for dumping all of functions in this CodeSegment. 221 | * @param path Path to dumpfile 222 | */ 223 | public synchronized void enablePerfMapDumper(Path path){ 224 | perfMapDumper = new Thread(() -> dumpPerfMap(path)); 225 | Runtime.getRuntime().addShutdownHook(perfMapDumper); 226 | } 227 | 228 | /** 229 | * Disable perf map dumper 230 | * @throws IllegalStateException if enablePerfMapDumper() is not called before. 231 | */ 232 | public synchronized void disablePerfMapDumper(){ 233 | if(perfMapDumper == null){ 234 | throw new IllegalStateException("PerfMapDumper is not enabled."); 235 | } 236 | Runtime.getRuntime().removeShutdownHook(perfMapDumper); 237 | perfMapDumper = null; 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/JitDump.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | import java.io.IOException; 22 | import java.nio.file.Path; 23 | 24 | import com.yasuenag.ffmasm.internal.linux.PerfJitDump; 25 | 26 | 27 | /** 28 | * Interface of jitdump for perf command on Linux. 29 | * 30 | * @author Yasumasa Suenaga 31 | */ 32 | public interface JitDump extends AutoCloseable{ 33 | 34 | /** 35 | * Get instance of JitDump. 36 | * 37 | * @param dir Base directory which jitdump is generated. 38 | * @throws UnsupportedPlatformException if the call happens on unsupported platform. 39 | */ 40 | public static JitDump getInstance(Path dir) throws UnsupportedPlatformException, PlatformException, IOException{ 41 | var osName = System.getProperty("os.name"); 42 | if(osName.equals("Linux")){ 43 | return new PerfJitDump(dir); 44 | } 45 | 46 | throw new UnsupportedPlatformException("This platform is not supported in JitDump"); 47 | } 48 | 49 | /** 50 | * Write method info to jitdump. 51 | * 52 | * @param method MethodInfo should be written. 53 | */ 54 | public void writeFunction(CodeSegment.MethodInfo method); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/NativeRegister.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.Linker; 25 | import java.lang.foreign.MemoryLayout; 26 | import java.lang.foreign.MemorySegment; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.reflect.Method; 29 | import java.util.Map; 30 | 31 | import com.yasuenag.ffmasm.internal.JniEnv; 32 | import com.yasuenag.ffmasm.internal.JvmtiEnv; 33 | import com.yasuenag.ffmasm.internal.amd64.AMD64NativeRegister; 34 | 35 | 36 | /** 37 | * Dynamic native method register. 38 | * 39 | * This class binds memory address to arbitrary native methods dynamically. 40 | * Note that 1st and 2nd arguments of JNI function are reserved. 41 | * (1st is JNIEnv*, 2nd is jclass or jobject) 42 | * You need to get instance of this class from {@link #create(Class)}. 43 | * 44 | * We can get JNIEnv* via GetEnv() JNI function. 45 | * However we have to get JavaVM* to call it. 46 | * So we call JNI_GetCreatedJavaVMs() which is exported function 47 | * from JVM at first, then we get a pointer of GetEnv() from 48 | * JavaVM function table. 49 | * 50 | * To register native functions dynamically, we need to call 51 | * RegisterNatives() JNI function, we need to know jclass of 52 | * the holder class to do it. 53 | * We can use FindClass() JNI function normally for this purpose, 54 | * but we cannot do that because FindClass() checks caller, and 55 | * it attempts to find only from system class loader when the caller is 56 | * MethodHandle. 57 | * (See JavaThread::security_get_caller_class() in HotSpot) 58 | * ffmasm would be loaded from application class loader, so we need to get 59 | * jclass by another method. 60 | * 61 | * We can get jclasses of all of loaded classes via 62 | * GetLoadedClasses() JVMTI function. Get jvmtiEnv* 63 | * with same procedure with JNIEnv*, and get a pointer of 64 | * GetLoadedClasses() from the function table. 65 | * However lifecycle of jclasses is JNI local, it means they will 66 | * be invalid when function call (via ffmasm) are ended (return to the app). 67 | * So we need to create stub code to call GetLoadedClasses() and 68 | * to find out a target jclass at once. 69 | * 70 | * Stub code calls GetLoadedClasses() at first, then passes 71 | * jclasses and number of them to callback function. 72 | * Callback function iterates jclasses, and compare class signature 73 | * from GetClassSignature() JVMTI function with 74 | * strcmp(). The reason of using it is that we do not need to 75 | * create new Java instance for String, and don't need to 76 | * reinterpret MemorySegment of class signature with its length. 77 | * We would release class signature with Deallocate() JVMTI 78 | * function of course when it can be dispose. 79 | * 80 | * When we find a target jclass at the callback, we register 81 | * executable memory of native method via RegisterNatives() JNI 82 | * function. Then callback would be finished. 83 | * 84 | * Finally, stub code would release memory of jclasses, then 85 | * all of register processes are done! 86 | * 87 | * @author Yasumasa Suenaga 88 | */ 89 | public abstract class NativeRegister{ 90 | 91 | private static final MemoryLayout layoutCallbackParam; 92 | private static final MemoryLayout.PathElement peClassSig; 93 | private static final MemoryLayout.PathElement peMethods; 94 | private static final MemoryLayout.PathElement peNumMethods; 95 | 96 | private static final MethodHandle strcmp; 97 | 98 | private final Class klass; 99 | 100 | static{ 101 | layoutCallbackParam = MemoryLayout.structLayout(ValueLayout.ADDRESS.withName("class_sig"), 102 | ValueLayout.ADDRESS.withName("methods"), 103 | ValueLayout.JAVA_INT.withName("num_methods")); 104 | peClassSig = MemoryLayout.PathElement.groupElement("class_sig"); 105 | peMethods = MemoryLayout.PathElement.groupElement("methods"); 106 | peNumMethods = MemoryLayout.PathElement.groupElement("num_methods"); 107 | 108 | var linker = Linker.nativeLinker(); 109 | strcmp = linker.downcallHandle(linker.defaultLookup() 110 | .find("strcmp") 111 | .get(), 112 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 113 | ValueLayout.ADDRESS, 114 | ValueLayout.ADDRESS)); 115 | } 116 | 117 | protected static void callback(MemorySegment classes, int class_count, int resultGetLoadedClasses, MemorySegment callbackParam){ 118 | if(resultGetLoadedClasses != JvmtiEnv.JVMTI_ERROR_NONE){ 119 | throw new RuntimeException("GetLoadedClasses() returns " + resultGetLoadedClasses); 120 | } 121 | 122 | classes = classes.reinterpret(ValueLayout.ADDRESS.byteSize() * class_count); 123 | callbackParam = callbackParam.reinterpret(layoutCallbackParam.byteSize()); 124 | try(var arena = Arena.ofConfined()){ 125 | var sigPtr = arena.allocate(ValueLayout.ADDRESS); 126 | var targetSig = (MemorySegment)layoutCallbackParam.varHandle(peClassSig) 127 | .get(callbackParam, 0L); 128 | var jvmtiEnv = JvmtiEnv.getInstance(); 129 | for(int idx = 0; idx < class_count; idx++){ 130 | var clazz = classes.getAtIndex(ValueLayout.ADDRESS, idx); 131 | int result = jvmtiEnv.getClassSignature(clazz, sigPtr, MemorySegment.NULL); 132 | if(result != JvmtiEnv.JVMTI_ERROR_NONE){ 133 | throw new RuntimeException("GetClassSignature() returns " + result); 134 | } 135 | 136 | var sig = sigPtr.get(ValueLayout.ADDRESS, 0); 137 | int cmp = (int)strcmp.invoke(sig, targetSig); 138 | jvmtiEnv.deallocate(sig); 139 | if(cmp == 0){ 140 | MemorySegment methods = (MemorySegment)layoutCallbackParam.varHandle(peMethods) 141 | .get(callbackParam, 0L); 142 | int nMethods = (int)layoutCallbackParam.varHandle(peNumMethods) 143 | .get(callbackParam, 0L); 144 | result = JniEnv.getInstance() 145 | .registerNatives(clazz, methods, nMethods); 146 | if(result != JniEnv.JNI_OK){ 147 | throw new RuntimeException("RegisterNatives() returns " + result); 148 | } 149 | return; 150 | } 151 | } 152 | } 153 | catch(Throwable t){ 154 | throw new RuntimeException(t); 155 | } 156 | 157 | } 158 | 159 | protected NativeRegister(Class klass){ 160 | this.klass = klass; 161 | } 162 | 163 | private String convertToJNITypeSignature(Class clazz){ 164 | String signature = clazz.isArray() ? "[" : ""; 165 | if(clazz == boolean.class){ 166 | signature += "Z"; 167 | } 168 | else if(clazz == byte.class){ 169 | signature += "B"; 170 | } 171 | else if(clazz == char.class){ 172 | signature += "C"; 173 | } 174 | else if(clazz == short.class){ 175 | signature += "S"; 176 | } 177 | else if(clazz == int.class){ 178 | signature += "I"; 179 | } 180 | else if(clazz == long.class){ 181 | signature += "J"; 182 | } 183 | else if(clazz == float.class){ 184 | signature += "F"; 185 | } 186 | else if(clazz == double.class){ 187 | signature += "D"; 188 | } 189 | else if(clazz == void.class){ 190 | signature += "V"; 191 | } 192 | else{ 193 | signature += "L" + clazz.getCanonicalName().replace('.', '/') + ";"; 194 | }; 195 | return signature; 196 | } 197 | 198 | private String getJNIMethodSignature(Method method){ 199 | String signature = "("; 200 | for(var argType : method.getParameterTypes()){ 201 | signature += convertToJNITypeSignature(argType); 202 | } 203 | signature += ")" + convertToJNITypeSignature(method.getReturnType()); 204 | 205 | return signature; 206 | } 207 | 208 | protected abstract void callRegisterStub(MemorySegment callbackParam) throws Throwable; 209 | 210 | /** 211 | * Register executable memory to native methods. 212 | * 213 | * @param methods Map of Method and MemorySegment. You have to set Method object what you want to set to key of map. 214 | * @throws Throwable when some error happened. 215 | */ 216 | public void registerNatives(Map methods) throws Throwable{ 217 | var classSig = "L" + klass.getCanonicalName().replace('.', '/') + ";"; 218 | var peNameHandle = JniEnv.layoutJNINativeMethod.arrayElementVarHandle(JniEnv.peName); 219 | var peSignatureHandle = JniEnv.layoutJNINativeMethod.arrayElementVarHandle(JniEnv.peSignature); 220 | var peFnPtrHandle = JniEnv.layoutJNINativeMethod.arrayElementVarHandle(JniEnv.peFnPtr); 221 | 222 | try(var arena = Arena.ofConfined()){ 223 | var nativeMethods = arena.allocate(JniEnv.layoutJNINativeMethod, methods.size()); 224 | int idx = 0; 225 | for(var es : methods.entrySet()){ 226 | Method method = es.getKey(); 227 | MemorySegment mem = es.getValue(); 228 | 229 | peNameHandle.set(nativeMethods, 0L, (long)idx, arena.allocateFrom(method.getName())); 230 | peSignatureHandle.set(nativeMethods, 0L, (long)idx, arena.allocateFrom(getJNIMethodSignature(method))); 231 | peFnPtrHandle.set(nativeMethods, 0L, (long)idx, mem); 232 | 233 | idx++; 234 | } 235 | 236 | var callbackParam = arena.allocate(layoutCallbackParam); 237 | layoutCallbackParam.varHandle(peClassSig).set(callbackParam, 0L, arena.allocateFrom(classSig)); 238 | layoutCallbackParam.varHandle(peMethods).set(callbackParam, 0L, nativeMethods); 239 | layoutCallbackParam.varHandle(peNumMethods).set(callbackParam, 0L, methods.size()); 240 | 241 | callRegisterStub(callbackParam); 242 | } 243 | } 244 | 245 | /** 246 | * Create new instance of NativeRegister. 247 | * 248 | * @param klass Class what you want to set native methods. 249 | * @throws UnsupportedPlatformException when this method is called on unsupported platform. 250 | */ 251 | public static NativeRegister create(Class klass) throws UnsupportedPlatformException{ 252 | var arch = System.getProperty("os.arch"); 253 | if(arch.equals("amd64")){ 254 | return new AMD64NativeRegister(klass); 255 | } 256 | else{ 257 | throw new UnsupportedPlatformException(arch + " is not supported"); 258 | } 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/PlatformException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | 22 | /** 23 | * Thrown when platform functions fail. 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public class PlatformException extends Exception{ 28 | 29 | private final int errcode; 30 | 31 | /** 32 | * Constructs a new exception with the caused Throwable. 33 | * The cause is not initialized. 34 | * @param cause caused Throwable 35 | */ 36 | public PlatformException(Throwable cause){ 37 | super(cause); 38 | this.errcode = 0; 39 | } 40 | 41 | /** 42 | * Constructs a new exception with the specified detail message and error code from platform. 43 | * The cause is not initialized. 44 | * @param message exception message 45 | * @param errcode error code from platform 46 | */ 47 | public PlatformException(String message, int errcode){ 48 | super(message); 49 | this.errcode = errcode; 50 | } 51 | 52 | /** 53 | * Returns error code which relates to this exception. 54 | * 0 means OS did not return any error (e.g. errcode = 0) - it means some error happen 55 | * in foreign function call in Java. 56 | * 57 | * @return error code which relates to this exception. 58 | */ 59 | public int getErrCode(){ 60 | return errcode; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/UnsupportedPlatformException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | 22 | /** 23 | * Thrown when the platform is unsupported. 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public class UnsupportedPlatformException extends Exception{ 28 | 29 | /** 30 | * Constructs a new exception with the message. 31 | * @param message exception message 32 | */ 33 | public UnsupportedPlatformException(String message){ 34 | super(message); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/amd64/AVXAsmBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.amd64; 20 | 21 | import java.lang.foreign.FunctionDescriptor; 22 | import java.util.OptionalInt; 23 | 24 | import com.yasuenag.ffmasm.CodeSegment; 25 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 26 | 27 | 28 | /** 29 | * Builder for AVX hand-assembling 30 | * 31 | * @author Yasumasa Suenaga 32 | */ 33 | public class AVXAsmBuilder extends SSEAsmBuilder{ 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param seg CodeSegment which is used by this builder. 39 | * @param desc FunctionDescriptor for this builder. It will be used by build(). 40 | */ 41 | protected AVXAsmBuilder(CodeSegment seg, FunctionDescriptor desc){ 42 | super(seg, desc); 43 | } 44 | 45 | private static enum PP{ 46 | None((byte)0b00), 47 | H66((byte)0b01), 48 | HF3((byte)0b10), 49 | HF2((byte)0b11); 50 | 51 | private final byte prefix; 52 | 53 | private PP(byte prefix){ 54 | this.prefix = prefix; 55 | } 56 | 57 | public byte prefix(){ 58 | return prefix; 59 | } 60 | } 61 | 62 | private static enum LeadingBytes{ 63 | H0F((byte)0b00001), 64 | H0F38((byte)0b00010), 65 | H0F3A((byte)0b00011); 66 | 67 | private final byte bytes; 68 | 69 | private LeadingBytes(byte bytes){ 70 | this.bytes = bytes; 71 | } 72 | 73 | public byte bytes(){ 74 | return bytes; 75 | } 76 | } 77 | 78 | private void emit2ByteVEXPrefix(Register src1, PP simdPrefix){ 79 | byte VEXvvvv = (byte)((~src1.encoding()) & 0b1111); 80 | emit2ByteVEXPrefixWithVVVV(VEXvvvv, src1.width() == 256, simdPrefix); 81 | } 82 | 83 | private void emit2ByteVEXPrefixWithVVVV(byte VEXvvvv, boolean is256bit, PP simdPrefix){ 84 | byte rexr = (byte)((VEXvvvv >> 3) & 1); 85 | byte vecLength = is256bit ? (byte)1 : (byte)0; 86 | byteBuf.put((byte)0xC5); // 2-byte VEX 87 | byteBuf.put((byte)( (rexr << 7) | // REX.R 88 | (VEXvvvv << 3) | // VEX.vvvv 89 | (vecLength << 2) | // Vector Length 90 | simdPrefix.prefix() // opcode extension (SIMD prefix) 91 | )); 92 | } 93 | 94 | private void emit3ByteVEXPrefix(Register r, Register m, PP simdPrefix, LeadingBytes bytes){ 95 | byte VEXvvvv = (byte)((~r.encoding()) & 0b1111); 96 | byte invMem = (byte)((~m.encoding()) & 0b1111); 97 | byte rexr = (byte)((VEXvvvv >> 3) & 1); 98 | byte rexb = (byte)((invMem >> 3) & 1); 99 | byte is256Bit = (r.width() == 256) ? (byte)1 : (byte)0; 100 | byteBuf.put((byte)0xC4); // 3-byte VEX 101 | byteBuf.put((byte)( (rexr << 7) | // REX.R 102 | ( 1 << 6) | // inverse of REX.X 103 | (rexb << 5) | // REX.B 104 | bytes.bytes() // leading opcode bytes 105 | )); 106 | byteBuf.put((byte)( (VEXvvvv << 3) | // VEX.vvvv 107 | (is256Bit << 2) | // Vector Length 108 | simdPrefix.prefix() // opcode extension (SIMD prefix) 109 | )); 110 | } 111 | 112 | private AVXAsmBuilder vmovdq(Register r, Register m, OptionalInt disp, PP pp, byte opcode){ 113 | if(m.encoding() > 7){ 114 | emit3ByteVEXPrefix(Register.YMM0 /* unused */, m, pp, LeadingBytes.H0F); 115 | } 116 | else{ 117 | emit2ByteVEXPrefix(Register.YMM0 /* unused */, pp); 118 | } 119 | 120 | byteBuf.put(opcode); // MOVDQA 121 | 122 | byte mode = emitModRM(r, m, disp); 123 | if(mode == 0b01){ // reg-mem disp8 124 | byteBuf.put((byte)disp.getAsInt()); 125 | } 126 | else if(mode == 0b10){ // reg-mem disp32 127 | byteBuf.putInt(disp.getAsInt()); 128 | } 129 | 130 | return this; 131 | } 132 | 133 | /** 134 | * Move aligned packed integer values from r/m to r. 135 | * NOTES: This method supports YMM register only now. 136 | * Opcode: VEX.256.66.0F.WIG 6F /r (256 bit) 137 | * Instruction: VMOVDQA r, r/m 138 | * Op/En: A 139 | * 140 | * @param r "r" register 141 | * @param m "r/m" register 142 | * @param disp Displacement. Set "empty" if this operation is reg-reg 143 | * then "r/m" have to be a SIMD register. 144 | * Otherwise it has to be 64 bit GPR because it have to be 145 | * a memory operand.. 146 | * @return This instance 147 | */ 148 | public AVXAsmBuilder vmovdqaRM(Register r, Register m, OptionalInt disp){ 149 | return vmovdq(r, m, disp, PP.H66, (byte)0x6f); 150 | } 151 | 152 | /** 153 | * Move aligned packed integer values from r to r/m. 154 | * NOTES: This method supports YMM register only now. 155 | * Opcode: VEX.256.66.0F.WIG 7F /r (256 bit) 156 | * Instruction: VMOVDQA r/m, r 157 | * Op/En: B 158 | * 159 | * @param r "r" register 160 | * @param m "r/m" register 161 | * @param disp Displacement. Set "empty" if this operation is reg-reg 162 | * then "r/m" have to be a SIMD register. 163 | * Otherwise it has to be 64 bit GPR because it have to be 164 | * a memory operand.. 165 | * @return This instance 166 | */ 167 | public AVXAsmBuilder vmovdqaMR(Register r, Register m, OptionalInt disp){ 168 | return vmovdq(r, m, disp, PP.H66, (byte)0x7f); 169 | } 170 | 171 | /** 172 | * Move unaligned packed integer values from r/m to r. 173 | * NOTES: This method supports YMM register only now. 174 | * Opcode: VEX.256.F3.0F.WIG 6F /r (256 bit) 175 | * Instruction: VMOVDQU r, r/m 176 | * Op/En: A 177 | * 178 | * @param r "r" register 179 | * @param m "r/m" register 180 | * @param disp Displacement. Set "empty" if this operation is reg-reg 181 | * then "r/m" have to be a SIMD register. 182 | * Otherwise it has to be 64 bit GPR because it have to be 183 | * a memory operand.. 184 | * @return This instance 185 | */ 186 | public AVXAsmBuilder vmovdquRM(Register r, Register m, OptionalInt disp){ 187 | return vmovdq(r, m, disp, PP.HF3, (byte)0x6f); 188 | } 189 | 190 | /** 191 | * Move unaligned packed integer values from r to r/m. 192 | * NOTES: This method supports YMM register only now. 193 | * Opcode: VEX.256.F3.0F.WIG 7F /r (256 bit) 194 | * Instruction: VMOVDQU r/m, r 195 | * Op/En: B 196 | * 197 | * @param r "r" register 198 | * @param m "r/m" register 199 | * @param disp Displacement. Set "empty" if this operation is reg-reg 200 | * then "r/m" have to be a SIMD register. 201 | * Otherwise it has to be 64 bit GPR because it have to be 202 | * a memory operand.. 203 | * @return This instance 204 | */ 205 | public AVXAsmBuilder vmovdquMR(Register r, Register m, OptionalInt disp){ 206 | return vmovdq(r, m, disp, PP.HF3, (byte)0x7f); 207 | } 208 | 209 | /** 210 | * Bitwise XOR of r and r/m. 211 | * NOTES: This method supports YMM register only now. 212 | * Opcode: VEX.256.66.0F.WIG EF /r (256 bit) 213 | * Instruction: VPXOR dest, r, r/m 214 | * Op/En: B 215 | * 216 | * @param r "r" register 217 | * @param m "r/m" register 218 | * @param dest "dest" register 219 | * @param disp Displacement. Set "empty" if this operation is reg-reg 220 | * then "r/m" have to be a SIMD register. 221 | * Otherwise it has to be 64 bit GPR because it have to be 222 | * a memory operand. 223 | * @return This instance 224 | */ 225 | public AVXAsmBuilder vpxor(Register r, Register m, Register dest, OptionalInt disp){ 226 | if(m.encoding() > 7){ 227 | emit3ByteVEXPrefix(r, m, PP.H66, LeadingBytes.H0F); 228 | } 229 | else{ 230 | emit2ByteVEXPrefix(r, PP.H66); 231 | } 232 | 233 | byteBuf.put((byte)0xef); // VPXOR 234 | 235 | byte mode = emitModRM(dest, m, disp); 236 | if(mode == 0b01){ // reg-mem disp8 237 | byteBuf.put((byte)disp.getAsInt()); 238 | } 239 | else if(mode == 0b10){ // reg-mem disp32 240 | byteBuf.putInt(disp.getAsInt()); 241 | } 242 | 243 | return this; 244 | } 245 | 246 | /** 247 | * Add packed doubleword integers from r/m, r and store in dest. 248 | * NOTES: This method supports YMM register only now. 249 | * Opcode: VEX.256.66.0F.WIG FE /r (256 bit) 250 | * Instruction: VPADDD dest, r, r/m 251 | * Op/En: B 252 | * 253 | * @param r "r" register 254 | * @param m "r/m" register 255 | * @param dest "dest" register 256 | * @param disp Displacement. Set "empty" if this operation is reg-reg 257 | * then "r/m" have to be a SIMD register. 258 | * Otherwise it has to be 64 bit GPR because it have to be 259 | * a memory operand. 260 | * @return This instance 261 | */ 262 | public AVXAsmBuilder vpaddd(Register r, Register m, Register dest, OptionalInt disp){ 263 | if(m.encoding() > 7){ 264 | emit3ByteVEXPrefix(r, m, PP.H66, LeadingBytes.H0F); 265 | } 266 | else{ 267 | emit2ByteVEXPrefix(r, PP.H66); 268 | } 269 | 270 | byteBuf.put((byte)0xfe); // VPADDD 271 | 272 | byte mode = emitModRM(dest, m, disp); 273 | if(mode == 0b01){ // reg-mem disp8 274 | byteBuf.put((byte)disp.getAsInt()); 275 | } 276 | else if(mode == 0b10){ // reg-mem disp32 277 | byteBuf.putInt(disp.getAsInt()); 278 | } 279 | 280 | return this; 281 | } 282 | 283 | /** 284 | * Logical compare r with r/m. 285 | * NOTES: This method supports YMM register only now. 286 | * Opcode: VEX.256.66.0F38.WIG 17 /r (256 bit) 287 | * Instruction: VPTEST r, r/m 288 | * Op/En: RM 289 | * 290 | * @param r "r" register 291 | * @param m "r/m" register 292 | * @param disp Displacement. Set "empty" if this operation is reg-reg 293 | * then "r/m" have to be a SIMD register. 294 | * Otherwise it has to be 64 bit GPR because it have to be 295 | * a memory operand. 296 | * @return This instance 297 | */ 298 | public AVXAsmBuilder vptest(Register r, Register m, OptionalInt disp){ 299 | emit3ByteVEXPrefix(Register.YMM0 /* unused */, m, PP.H66, LeadingBytes.H0F38); 300 | byteBuf.put((byte)0x17); // PTEST 301 | byte mode = emitModRM(r, m, disp); 302 | if(mode == 0b01){ // reg-mem disp8 303 | byteBuf.put((byte)disp.getAsInt()); 304 | } 305 | else if(mode == 0b10){ // reg-mem disp32 306 | byteBuf.putInt(disp.getAsInt()); 307 | } 308 | 309 | return this; 310 | } 311 | 312 | /** 313 | * Zero bits in positions 128 and higher of some YMM and ZMM registers. 314 | * Opcode: VEX.128.0F.WIG 77 315 | * Instruction: VZEROUPPER 316 | * Op/En: ZO 317 | * 318 | * @return This instance 319 | */ 320 | public AVXAsmBuilder vzeroupper(){ 321 | emit2ByteVEXPrefixWithVVVV((byte)0b1111, false, PP.None); 322 | byteBuf.put((byte)0x77); // VZEROUPPER 323 | return this; 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/amd64/Register.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.amd64; 20 | 21 | 22 | /** 23 | * Enum for AMD64 CPU register 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public enum Register{ 28 | 29 | AL(0, 8), 30 | CL(1, 8), 31 | DL(2, 8), 32 | BL(3, 8), 33 | AH(4, 8), 34 | CH(5, 8), 35 | DH(6, 8), 36 | BH(7, 8), 37 | 38 | AX(0, 16), 39 | CX(1, 16), 40 | DX(2, 16), 41 | BX(3, 16), 42 | SP(4, 16), 43 | BP(5, 16), 44 | SI(6, 16), 45 | DI(7, 16), 46 | 47 | EAX(0, 32), 48 | ECX(1, 32), 49 | EDX(2, 32), 50 | EBX(3, 32), 51 | ESP(4, 32), 52 | EBP(5, 32), 53 | ESI(6, 32), 54 | EDI(7, 32), 55 | R8D(8, 32), 56 | R9D(9, 32), 57 | R10D(10, 32), 58 | R11D(11, 32), 59 | R12D(12, 32), 60 | R13D(13, 32), 61 | R14D(14, 32), 62 | R15D(15, 32), 63 | 64 | RAX(0, 64), 65 | RCX(1, 64), 66 | RDX(2, 64), 67 | RBX(3, 64), 68 | RSP(4, 64), 69 | RBP(5, 64), 70 | RSI(6, 64), 71 | RDI(7, 64), 72 | R8(8, 64), 73 | R9(9, 64), 74 | R10(10, 64), 75 | R11(11, 64), 76 | R12(12, 64), 77 | R13(13, 64), 78 | R14(14, 64), 79 | R15(15, 64), 80 | 81 | XMM0(0, 128), 82 | XMM1(1, 128), 83 | XMM2(2, 128), 84 | XMM3(3, 128), 85 | XMM4(4, 128), 86 | XMM5(5, 128), 87 | XMM6(6, 128), 88 | XMM7(7, 128), 89 | XMM8(8, 128), 90 | XMM9(9, 128), 91 | XMM10(10, 128), 92 | XMM11(11, 128), 93 | XMM12(12, 128), 94 | XMM13(13, 128), 95 | XMM14(14, 128), 96 | XMM15(15, 128), 97 | 98 | YMM0(0, 256), 99 | YMM1(1, 256), 100 | YMM2(2, 256), 101 | YMM3(3, 256), 102 | YMM4(4, 256), 103 | YMM5(5, 256), 104 | YMM6(6, 256), 105 | YMM7(7, 256), 106 | YMM8(8, 256), 107 | YMM9(9, 256), 108 | YMM10(10, 256), 109 | YMM11(11, 256), 110 | YMM12(12, 256), 111 | YMM13(13, 256), 112 | YMM14(14, 256), 113 | YMM15(15, 256); 114 | 115 | private final int encoding; 116 | 117 | private final int width; 118 | 119 | private Register(int encoding, int width){ 120 | this.encoding = encoding; 121 | this.width = width; 122 | } 123 | 124 | /** 125 | * Register encoding 126 | * @return Register encoding 127 | */ 128 | public int encoding(){ 129 | return encoding; 130 | } 131 | 132 | /** 133 | * Register width in bits 134 | * @return Register width 135 | */ 136 | public int width(){ 137 | return width; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/amd64/SSEAsmBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.amd64; 20 | 21 | import java.lang.foreign.FunctionDescriptor; 22 | import java.util.OptionalInt; 23 | 24 | import com.yasuenag.ffmasm.CodeSegment; 25 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 26 | 27 | 28 | /** 29 | * Builder for SSE hand-assembling 30 | * 31 | * @author Yasumasa Suenaga 32 | */ 33 | public class SSEAsmBuilder extends AMD64AsmBuilder{ 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param seg CodeSegment which is used by this builder. 39 | * @param desc FunctionDescriptor for this builder. It will be used by build(). 40 | */ 41 | protected SSEAsmBuilder(CodeSegment seg, FunctionDescriptor desc){ 42 | super(seg, desc); 43 | } 44 | 45 | private SSEAsmBuilder movdq(Register r, Register m, OptionalInt disp, byte prefix, byte secondOpcode){ 46 | byteBuf.put(prefix); 47 | emitREXOp(r, m); 48 | byteBuf.put((byte)0x0f); // escape opcode 49 | byteBuf.put(secondOpcode); 50 | var mode = emitModRM(r, m, disp); 51 | emitDisp(mode, disp, m); 52 | 53 | return this; 54 | } 55 | 56 | /** 57 | * Move aligned packed integer values from xmm2/mem to xmm1. 58 | * Opcode: 66 0F 6F /r 59 | * Instruction: MOVDQA xmm1, xmm2/m128 60 | * Op/En: A 61 | * 62 | * @param r "r" register 63 | * @param m "r/m" register 64 | * @param disp Displacement. Set "empty" if this operation is reg-reg 65 | * then "r/m" have to be a SIMD register. 66 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 67 | * @return This instance 68 | */ 69 | public SSEAsmBuilder movdqaRM(Register r, Register m, OptionalInt disp){ 70 | return movdq(r, m, disp, (byte)0x66, (byte)0x6f); 71 | } 72 | 73 | /** 74 | * Move aligned packed integer values from xmm1 to xmm2/mem. 75 | * Opcode: 66 0F 7F /r 76 | * Instruction: MOVDQA xmm2/m128, xmm1 77 | * Op/En: B 78 | * 79 | * @param r "r" register 80 | * @param m "r/m" register 81 | * @param disp Displacement. Set "empty" if this operation is reg-reg 82 | * then "r/m" have to be a SIMD register. 83 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 84 | * @return This instance 85 | */ 86 | public SSEAsmBuilder movdqaMR(Register r, Register m, OptionalInt disp){ 87 | return movdq(r, m, disp, (byte)0x66, (byte)0x7f); 88 | } 89 | 90 | /** 91 | * Move unaligned packed integer values from xmm2/mem128 to xmm1. 92 | * Opcode: F3 0F 6F /r 93 | * Instruction: MOVDQU xmm1, xmm2/m128 94 | * Op/En: A 95 | * 96 | * @param r "r" register 97 | * @param m "r/m" register 98 | * @param disp Displacement. Set "empty" if this operation is reg-reg 99 | * then "r/m" have to be a SIMD register. 100 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 101 | * @return This instance 102 | */ 103 | public SSEAsmBuilder movdquRM(Register r, Register m, OptionalInt disp){ 104 | return movdq(r, m, disp, (byte)0xf3, (byte)0x6f); 105 | } 106 | 107 | /** 108 | * Move unaligned packed integer values from xmm1 to xmm2/mem128. 109 | * Opcode: F3 0F 7F /r 110 | * Instruction: MOVDQU xmm2/m128, xmm1 111 | * Op/En: B 112 | * 113 | * @param r "r" register 114 | * @param m "r/m" register 115 | * @param disp Displacement. Set "empty" if this operation is reg-reg 116 | * then "r/m" have to be a SIMD register. 117 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 118 | * @return This instance 119 | */ 120 | public SSEAsmBuilder movdquMR(Register r, Register m, OptionalInt disp){ 121 | return movdq(r, m, disp, (byte)0xf3, (byte)0x7f); 122 | } 123 | 124 | private SSEAsmBuilder movDInternal(Register r, Register m, OptionalInt disp, byte secondOpcode){ 125 | return movDorQ(r, m, disp, secondOpcode, false); 126 | } 127 | 128 | private SSEAsmBuilder movQInternal(Register r, Register m, OptionalInt disp, byte secondOpcode){ 129 | return movDorQ(r, m, disp, secondOpcode, true); 130 | } 131 | 132 | private SSEAsmBuilder movDorQ(Register r, Register m, OptionalInt disp, byte secondOpcode, boolean isQWORD){ 133 | byteBuf.put((byte)0x66); // prefix 134 | emitREXOp(r, m, isQWORD); 135 | byteBuf.put((byte)0x0f); // escape opcode 136 | byteBuf.put(secondOpcode); 137 | var mode = emitModRM(r, m, disp); 138 | emitDisp(mode, disp, m); 139 | 140 | return this; 141 | } 142 | 143 | /** 144 | * Move doubleword from r/m32 to xmm. 145 | * Opcode: 66 0F 6E /r 146 | * Instruction: MOVD xmm, r/m32 147 | * Op/En: A 148 | * 149 | * @param r "r" register 150 | * @param m "r/m" register 151 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 152 | * @return This instance 153 | */ 154 | public SSEAsmBuilder movdRM(Register r, Register m, OptionalInt disp){ 155 | return movDInternal(r, m, disp, (byte)0x6e); 156 | } 157 | 158 | /** 159 | * Move doubleword from xmm register to r/m32. 160 | * Opcode: 66 0F 7E /r 161 | * Instruction: MOVD r/m32, xmm 162 | * Op/En: B 163 | * 164 | * @param r "r" register 165 | * @param m "r/m" register 166 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 167 | * @return This instance 168 | */ 169 | public SSEAsmBuilder movdMR(Register r, Register m, OptionalInt disp){ 170 | return movDInternal(r, m, disp, (byte)0x7e); 171 | } 172 | 173 | /** 174 | * Move quadword from r/m64 to xmm. 175 | * Opcode: 66 REX.W 0F 6E /r 176 | * Instruction: MOVQ xmm, r/m64 177 | * Op/En: A 178 | * 179 | * @param r "r" register 180 | * @param m "r/m" register 181 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 182 | * @return This instance 183 | */ 184 | public SSEAsmBuilder movqRM(Register r, Register m, OptionalInt disp){ 185 | return movQInternal(r, m, disp, (byte)0x6e); 186 | } 187 | 188 | /** 189 | * Move quadword from xmm register to r/m64. 190 | * Opcode: 66 REX.W 0F 7E /r 191 | * Instruction: MOVQ r/m64, xmm 192 | * Op/En: B 193 | * 194 | * @param r "r" register 195 | * @param m "r/m" register 196 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 197 | * @return This instance 198 | */ 199 | public SSEAsmBuilder movqMR(Register r, Register m, OptionalInt disp){ 200 | return movQInternal(r, m, disp, (byte)0x7e); 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/ExecMemory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2023, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.foreign.MemorySegment; 22 | 23 | import com.yasuenag.ffmasm.PlatformException; 24 | 25 | 26 | /** 27 | * Interface for acquiring / releasing memory for execution code. 28 | * Implemented class should manage platform-dependent memory which can execute code in it. 29 | * 30 | * @author Yasumasa Suenaga 31 | */ 32 | public interface ExecMemory{ 33 | 34 | /** 35 | * Allocate memory which can execute code in it. 36 | * 37 | * @param size required size 38 | * @return platform memory address 39 | * @throws PlatformMemoryException thrown when memory allocation fails. 40 | */ 41 | public MemorySegment allocate(long size) throws PlatformException; 42 | 43 | /** 44 | * Deallocate memory which is pointed addr. 45 | * 46 | * @param addr platform memory address 47 | * @param size required size 48 | * @throws PlatformMemoryException thrown when memory deallocation fails. 49 | */ 50 | public void deallocate(MemorySegment addr, long size) throws PlatformException; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/JavaVM.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.MemorySegment; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.SymbolLookup; 27 | import java.lang.foreign.ValueLayout; 28 | import java.nio.file.Path; 29 | 30 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 31 | 32 | 33 | public class JavaVM{ 34 | 35 | /* Indices from API doc */ 36 | private static final int JavaVM_GetEnv_INDEX = 6; 37 | private static final int JavaVM_AttachCurrentThreadAsDaemon_INDEX = 7; 38 | private static final int JavaVM_MAX_INDEX = JavaVM_AttachCurrentThreadAsDaemon_INDEX; 39 | 40 | /* Keep instance of this class as a singleton */ 41 | private static JavaVM instance; 42 | 43 | private final Arena arena; 44 | private final MemorySegment vm; 45 | private final MemorySegment functionTable; 46 | 47 | /* function handles */ 48 | private MethodHandle GetEnv; 49 | 50 | private SymbolLookup getJvmLookup() throws UnsupportedPlatformException{ 51 | String osName = System.getProperty("os.name"); 52 | Path jvmPath; 53 | if(osName.equals("Linux")){ 54 | jvmPath = Path.of(System.getProperty("java.home"), "lib", "server", "libjvm.so"); 55 | } 56 | else if(osName.startsWith("Windows")){ 57 | jvmPath = Path.of(System.getProperty("java.home"), "bin", "server", "jvm.dll"); 58 | } 59 | else{ 60 | throw new UnsupportedPlatformException(osName + " is unsupported."); 61 | } 62 | 63 | return SymbolLookup.libraryLookup(jvmPath, arena); 64 | } 65 | 66 | private JavaVM() throws Throwable{ 67 | arena = Arena.ofAuto(); 68 | 69 | var JNI_GetCreatedJavaVMs = Linker.nativeLinker() 70 | .downcallHandle(getJvmLookup().find("JNI_GetCreatedJavaVMs").get(), 71 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 72 | ValueLayout.ADDRESS, 73 | ValueLayout.JAVA_INT, 74 | ValueLayout.ADDRESS)); 75 | var vms = arena.allocate(ValueLayout.ADDRESS, 1); 76 | int result = (int)JNI_GetCreatedJavaVMs.invoke(vms, 1, MemorySegment.NULL); 77 | if(result != JniEnv.JNI_OK){ 78 | throw new RuntimeException("JNI_GetCreatedJavaVMs() returns " + result); 79 | } 80 | vm = vms.getAtIndex(ValueLayout.ADDRESS, 0) 81 | .reinterpret(ValueLayout.ADDRESS.byteSize()); 82 | 83 | functionTable = vm.getAtIndex(ValueLayout.ADDRESS, 0) 84 | .reinterpret(ValueLayout.ADDRESS.byteSize() * (JavaVM_MAX_INDEX + 1)); 85 | } 86 | 87 | public static JavaVM getInstance() throws Throwable{ 88 | if(instance == null){ 89 | instance = new JavaVM(); 90 | } 91 | return instance; 92 | } 93 | 94 | public int getEnv(MemorySegment penv, int version) throws Throwable{ 95 | if(GetEnv == null){ 96 | GetEnv = Linker.nativeLinker() 97 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JavaVM_GetEnv_INDEX), 98 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 99 | ValueLayout.ADDRESS, 100 | ValueLayout.ADDRESS, 101 | ValueLayout.JAVA_INT)); 102 | } 103 | 104 | return (int)GetEnv.invoke(vm, penv, version); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/JniEnv.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.Linker; 25 | import java.lang.foreign.MemoryLayout; 26 | import java.lang.foreign.MemorySegment; 27 | import java.lang.foreign.StructLayout; 28 | import java.lang.foreign.ValueLayout; 29 | 30 | 31 | public class JniEnv{ 32 | 33 | /* from jni.h */ 34 | public static final int JNI_OK = 0; 35 | private static final int JNI_VERSION_21 = 0x00150000; 36 | 37 | /* Indices from API doc */ 38 | private static final int JNI_RegisterNatives_INDEX = 215; 39 | private static final int JNI_GetPrimitiveArrayCritical_INDEX = 222; 40 | private static final int JNI_ReleasePrimitiveArrayCritical_INDEX = 223; 41 | private static final int JNI_IsVirtualThread_INDEX = 234; 42 | private static final int JNI_MAX_INDEX = JNI_IsVirtualThread_INDEX; 43 | 44 | /* MemoryLayout for JNINativeMethod */ 45 | public static final StructLayout layoutJNINativeMethod; 46 | public static final MemoryLayout.PathElement peName; 47 | public static final MemoryLayout.PathElement peSignature; 48 | public static final MemoryLayout.PathElement peFnPtr; 49 | 50 | /* 51 | * Keep instance of this class. 52 | * JNIEnv* is associated with JavaThread in HotSpot. 53 | * So keep this insntance with ThreadLocal. 54 | */ 55 | private static ThreadLocal instance = new ThreadLocal<>(){ 56 | @Override 57 | protected JniEnv initialValue(){ 58 | try{ 59 | return new JniEnv(); 60 | } 61 | catch(Throwable t){ 62 | throw new RuntimeException(t); 63 | } 64 | } 65 | }; 66 | 67 | private final Arena arena; 68 | private final MemorySegment jniEnv; 69 | private final MemorySegment functionTable; 70 | 71 | /* function addresses / handles */ 72 | private MethodHandle RegisterNatives; 73 | private MemorySegment GetPrimitiveArrayCriticalAddr; 74 | private MemorySegment ReleasePrimitiveArrayCriticalAddr; 75 | 76 | static{ 77 | layoutJNINativeMethod = MemoryLayout.structLayout(ValueLayout.ADDRESS.withName("name"), 78 | ValueLayout.ADDRESS.withName("signature"), 79 | ValueLayout.ADDRESS.withName("fnPtr")); 80 | peName = MemoryLayout.PathElement.groupElement("name"); 81 | peSignature = MemoryLayout.PathElement.groupElement("signature"); 82 | peFnPtr = MemoryLayout.PathElement.groupElement("fnPtr"); 83 | } 84 | 85 | private JniEnv() throws Throwable{ 86 | arena = Arena.ofAuto(); 87 | 88 | var vm = JavaVM.getInstance(); 89 | var env = arena.allocate(ValueLayout.ADDRESS); 90 | int result = vm.getEnv(env, JNI_VERSION_21); 91 | if(result != JniEnv.JNI_OK){ 92 | throw new RuntimeException("GetEnv() returns " + result); 93 | } 94 | jniEnv = env.get(ValueLayout.ADDRESS, 0) 95 | .reinterpret(ValueLayout.ADDRESS.byteSize()); 96 | 97 | functionTable = jniEnv.get(ValueLayout.ADDRESS, 0) // JNIEnv = JNINativeInterface_* 98 | .reinterpret(ValueLayout.ADDRESS.byteSize() * (JNI_MAX_INDEX + 1)); 99 | } 100 | 101 | public static JniEnv getInstance() throws Throwable{ 102 | return instance.get(); 103 | } 104 | 105 | public int registerNatives(MemorySegment clazz, MemorySegment methods, int nMethods) throws Throwable{ 106 | if(RegisterNatives == null){ 107 | RegisterNatives = Linker.nativeLinker() 108 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JNI_RegisterNatives_INDEX), 109 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 110 | ValueLayout.ADDRESS, 111 | ValueLayout.ADDRESS, 112 | ValueLayout.ADDRESS, 113 | ValueLayout.JAVA_INT)); 114 | } 115 | return (int)RegisterNatives.invoke(jniEnv, clazz, methods, nMethods); 116 | } 117 | 118 | public MemorySegment getPrimitiveArrayCriticalAddr(){ 119 | if(GetPrimitiveArrayCriticalAddr == null){ 120 | GetPrimitiveArrayCriticalAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JNI_GetPrimitiveArrayCritical_INDEX); 121 | } 122 | return GetPrimitiveArrayCriticalAddr; 123 | } 124 | 125 | public MemorySegment releasePrimitiveArrayCriticalAddr(){ 126 | if(ReleasePrimitiveArrayCriticalAddr == null){ 127 | ReleasePrimitiveArrayCriticalAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JNI_ReleasePrimitiveArrayCritical_INDEX); 128 | } 129 | return ReleasePrimitiveArrayCriticalAddr; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/JvmtiEnv.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.Linker; 25 | import java.lang.foreign.MemorySegment; 26 | import java.lang.foreign.ValueLayout; 27 | 28 | 29 | public class JvmtiEnv{ 30 | 31 | /* from jvmti.h */ 32 | public static final int JVMTI_ERROR_NONE = 0; 33 | private static final int JVMTI_VERSION_21 = 0x30150000; 34 | 35 | /* indices / positions are from API doc */ 36 | private static final int JVMTI_Deallocate_POSITION = 47; 37 | private static final int JVMTI_GetClassSignature_POSITION = 48; 38 | private static final int JVMTI_GetLoadedClasses_POSITION = 78; 39 | private static final int JVMTI_SetHeapSamplingInterval_POSITION = 156; 40 | private static final int JVMTI_MAX_POSITION = JVMTI_SetHeapSamplingInterval_POSITION; 41 | 42 | /* Keep instance of this class as a singleton */ 43 | private static JvmtiEnv instance; 44 | 45 | private final Arena arena; 46 | private final MemorySegment jvmtiEnv; 47 | private final MemorySegment functionTable; 48 | 49 | /* function addresses / handles */ 50 | private MemorySegment GetLoadedClassesAddr; 51 | private MemorySegment DeallocateAddr; 52 | private MethodHandle Deallocate; 53 | private MethodHandle GetClassSignature; 54 | 55 | private JvmtiEnv() throws Throwable{ 56 | arena = Arena.ofAuto(); 57 | 58 | var vm = JavaVM.getInstance(); 59 | var env = arena.allocate(ValueLayout.ADDRESS); 60 | int result = vm.getEnv(env, JVMTI_VERSION_21); 61 | if(result != JniEnv.JNI_OK){ 62 | throw new RuntimeException("GetEnv() returns " + result); 63 | } 64 | jvmtiEnv = env.get(ValueLayout.ADDRESS, 0) 65 | .reinterpret(ValueLayout.ADDRESS.byteSize()); 66 | 67 | functionTable = jvmtiEnv.get(ValueLayout.ADDRESS, 0) 68 | .reinterpret(ValueLayout.ADDRESS.byteSize() * JVMTI_MAX_POSITION); 69 | } 70 | 71 | public static JvmtiEnv getInstance() throws Throwable{ 72 | if(instance == null){ 73 | instance = new JvmtiEnv(); 74 | } 75 | return instance; 76 | } 77 | 78 | public long getRawAddress(){ 79 | return jvmtiEnv.address(); 80 | } 81 | 82 | public MemorySegment getLoadedClassesAddr(){ 83 | if(GetLoadedClassesAddr == null){ 84 | GetLoadedClassesAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_GetLoadedClasses_POSITION - 1); 85 | } 86 | return GetLoadedClassesAddr; 87 | } 88 | 89 | public MemorySegment deallocateAddr(){ 90 | if(DeallocateAddr == null){ 91 | DeallocateAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_Deallocate_POSITION - 1); 92 | } 93 | return DeallocateAddr; 94 | } 95 | 96 | public int deallocate(MemorySegment mem) throws Throwable{ 97 | if(Deallocate == null){ 98 | Deallocate = Linker.nativeLinker() 99 | .downcallHandle(deallocateAddr(), 100 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 101 | ValueLayout.ADDRESS, 102 | ValueLayout.ADDRESS)); 103 | } 104 | return (int)Deallocate.invoke(jvmtiEnv, mem); 105 | } 106 | 107 | public int getClassSignature(MemorySegment klass, MemorySegment signature_ptr, MemorySegment generic_ptr) throws Throwable{ 108 | if(GetClassSignature == null){ 109 | GetClassSignature = Linker.nativeLinker() 110 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_GetClassSignature_POSITION - 1), 111 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 112 | ValueLayout.ADDRESS, 113 | ValueLayout.ADDRESS, 114 | ValueLayout.ADDRESS, 115 | ValueLayout.ADDRESS)); 116 | } 117 | return (int)GetClassSignature.invoke(jvmtiEnv, klass, signature_ptr, generic_ptr); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/amd64/AMD64NativeRegister.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.amd64; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodHandles; 23 | import java.lang.foreign.Arena; 24 | import java.lang.foreign.FunctionDescriptor; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.MemorySegment; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.ref.Cleaner; 29 | import java.util.OptionalInt; 30 | 31 | import com.yasuenag.ffmasm.CodeSegment; 32 | import com.yasuenag.ffmasm.NativeRegister; 33 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder; 34 | import com.yasuenag.ffmasm.amd64.Register; 35 | import com.yasuenag.ffmasm.internal.JvmtiEnv; 36 | 37 | 38 | public final class AMD64NativeRegister extends NativeRegister{ 39 | 40 | private static final Arena arena; 41 | private static final CodeSegment seg; 42 | 43 | private static MethodHandle registerStub; 44 | 45 | private static void setupRegisterStub() throws Throwable{ 46 | var targetMethod = NativeRegister.class 47 | .getDeclaredMethod("callback", 48 | MemorySegment.class, 49 | int.class, 50 | int.class, 51 | MemorySegment.class); 52 | var hndCallback = MethodHandles.lookup() 53 | .unreflect(targetMethod); 54 | var cbStub = Linker.nativeLinker() 55 | .upcallStub(hndCallback, 56 | FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, 57 | ValueLayout.JAVA_INT, 58 | ValueLayout.JAVA_INT, 59 | ValueLayout.ADDRESS), 60 | arena); 61 | 62 | var jvmtiEnv = JvmtiEnv.getInstance(); 63 | var regs = CallingRegisters.getRegs(); 64 | var desc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS); // callbackParam 65 | 66 | /* S T A C K A L L O C A T I O N 67 | ------------------------------------------------- ---- RSP (RBP-64) 68 | | Shadow space for arg4 | 69 | |-------------------------------------------------| 70 | | Shadow space for arg3 | 71 | |-------------------------------------------------| 72 | | Shadow space for arg2 | 73 | |-------------------------------------------------| 74 | | Shadow space for arg1 | 75 | |-------------------------------------------------| 76 | | Stack alignment | 77 | |-------------------------------------------------| 78 | | Number of jclasses (from GetLoadedClasses()) | 79 | |-------------------------------------------------| 80 | | Pointer to jclass array from GetLoadedClasses() | 81 | |-------------------------------------------------| 82 | | Saved Register 1 | 83 | |-------------------------------------------------| ---- RBP 84 | | Previous RBP | 85 | |-------------------------------------------------| 86 | | Return Address | 87 | ------------------------------------------------- 88 | */ 89 | registerStub = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc) 90 | // prologue 91 | /* push %rbp */ .push(Register.RBP) 92 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 93 | /* sub $64, %rsp */ .sub(Register.RSP, 64, OptionalInt.empty()) 94 | /* mov savedReg1, -8(%rbp) */ .movMR(regs.savedReg1(), Register.RBP, OptionalInt.of(-8)) 95 | /* mov arg1, savedReg1 */ .movMR(regs.arg1(), regs.savedReg1(), OptionalInt.empty()) 96 | 97 | // call GetLoadedClasses() 98 | /* lea -24(%rbp), arg2 */ .lea(regs.arg2(), Register.RBP, -24) // count 99 | /* lea -16(%rbp), arg3 */ .lea(regs.arg3(), Register.RBP, -16) // classes 100 | /* mov addr, arg1 */ .movImm(regs.arg1(), jvmtiEnv.getRawAddress()) // address of jvmtiEnv 101 | /* mov addr, tmpReg1 */ .movImm(regs.tmpReg1(), jvmtiEnv.getLoadedClassesAddr().address()) // address of GetLoadedClasses() 102 | /* call tmpReg1 */ .call(regs.tmpReg1()) 103 | 104 | // call callback(jclass *classes, jint class_count) 105 | /* mov -24(%rbp), arg2 */ .movRM(regs.arg2(), Register.RBP, OptionalInt.of(-24)) // count 106 | /* mov -16(%rbp), arg1 */ .movRM(regs.arg1(), Register.RBP, OptionalInt.of(-16)) // classes 107 | /* mov returnReg, arg3 */ .movMR(regs.returnReg(), regs.arg3(), OptionalInt.empty()) // result of GetLoadedClasses() 108 | /* mov savedReg1, arg4 */ .movMR(regs.savedReg1(), regs.arg4(), OptionalInt.empty()) // callbackParam 109 | /* mov addr, tmpReg1 */ .movImm(regs.tmpReg1(), cbStub.address()) // address of callback 110 | /* call tmpReg1 */ .call(regs.tmpReg1()) 111 | 112 | // call Deallocate() 113 | /* mov addr, arg1 */ .movImm(regs.arg1(), jvmtiEnv.getRawAddress()) // address of jvmtiEnv 114 | /* mov -16(%rbp), arg1 */ .movRM(regs.arg1(), Register.RBP, OptionalInt.of(-16)) // classes 115 | /* mov addr, tmpReg1 */ .movImm(regs.tmpReg1(), jvmtiEnv.deallocateAddr().address()) // address of Deallocate() 116 | /* call tmpReg1 */ .call(regs.tmpReg1()) 117 | 118 | // epilogue 119 | /* mov -8(%rbp), savedReg1 */ .movRM(regs.savedReg1(), Register.RBP, OptionalInt.of(-8)) 120 | /* leave */ .leave() 121 | /* ret */ .ret() 122 | .build(); 123 | } 124 | 125 | static{ 126 | arena = Arena.ofAuto(); 127 | try{ 128 | seg = new CodeSegment(); 129 | var action = new CodeSegment.CleanerAction(seg); 130 | Cleaner.create() 131 | .register(NativeRegister.class, action); 132 | } 133 | catch(Throwable t){ 134 | throw new RuntimeException(t); 135 | } 136 | } 137 | 138 | public AMD64NativeRegister(Class klass){ 139 | super(klass); 140 | } 141 | 142 | @Override 143 | protected void callRegisterStub(MemorySegment callbackParam) throws Throwable{ 144 | if(registerStub == null){ 145 | setupRegisterStub(); 146 | } 147 | registerStub.invoke(callbackParam); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/amd64/CallingRegisters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.amd64; 20 | 21 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 22 | import com.yasuenag.ffmasm.amd64.Register; 23 | 24 | 25 | public record CallingRegisters( 26 | Register arg1, 27 | Register arg2, 28 | Register arg3, 29 | Register arg4, 30 | Register returnReg, 31 | Register savedReg1, 32 | Register tmpReg1){ 33 | 34 | public static CallingRegisters getRegs() throws UnsupportedPlatformException{ 35 | var arch = System.getProperty("os.arch"); 36 | if(!arch.equals("amd64")){ 37 | throw new UnsupportedPlatformException(arch + " is not supported."); 38 | } 39 | 40 | String osName = System.getProperty("os.name"); 41 | if(osName.equals("Linux")){ 42 | return new CallingRegisters(Register.RDI, 43 | Register.RSI, 44 | Register.RDX, 45 | Register.RCX, 46 | Register.RAX, 47 | Register.R12, 48 | Register.R10); 49 | } 50 | else if(osName.startsWith("Windows")){ 51 | return new CallingRegisters(Register.RCX, 52 | Register.RDX, 53 | Register.R8, 54 | Register.R9, 55 | Register.RAX, 56 | Register.R12, 57 | Register.R10); 58 | } 59 | else{ 60 | throw new UnsupportedPlatformException(osName + " is not supported."); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/linux/LinuxExecMemory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.linux; 20 | 21 | import java.lang.foreign.Arena; 22 | import java.lang.foreign.FunctionDescriptor; 23 | import java.lang.foreign.Linker; 24 | import java.lang.foreign.MemoryLayout; 25 | import java.lang.foreign.MemorySegment; 26 | import java.lang.foreign.SymbolLookup; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.invoke.MethodHandle; 29 | import java.lang.invoke.VarHandle; 30 | import java.util.Map; 31 | 32 | import com.yasuenag.ffmasm.internal.ExecMemory; 33 | import com.yasuenag.ffmasm.PlatformException; 34 | 35 | 36 | /** 37 | * Aquiring / releasing memory for execution code for Linux. 38 | * This class uses mmap(2) and munmap(2) for it. 39 | * 40 | * @author Yasumasa Suenaga 41 | */ 42 | public class LinuxExecMemory implements ExecMemory{ 43 | 44 | private static final Linker nativeLinker; 45 | 46 | private static final SymbolLookup sym; 47 | 48 | private static final Map canonicalLayouts; 49 | 50 | private static final Linker.Option errnoState; 51 | 52 | private static final MemorySegment errnoSeg; 53 | 54 | private static MethodHandle hndMmap = null; 55 | 56 | private static MethodHandle hndMunmap = null; 57 | 58 | private static VarHandle hndErrno = null; 59 | 60 | /** 61 | * page can be read 62 | */ 63 | public static final int PROT_READ = 0x1; 64 | 65 | /** 66 | * page can be written 67 | */ 68 | public static final int PROT_WRITE = 0x2; 69 | 70 | /** 71 | * page can be executed 72 | */ 73 | public static final int PROT_EXEC = 0x4; 74 | 75 | /** 76 | * Changes are private 77 | */ 78 | public static final int MAP_PRIVATE = 0x02; 79 | 80 | /** 81 | * don't use a file 82 | */ 83 | public static final int MAP_ANONYMOUS = 0x20; 84 | 85 | static{ 86 | nativeLinker = Linker.nativeLinker(); 87 | sym = nativeLinker.defaultLookup(); 88 | canonicalLayouts = nativeLinker.canonicalLayouts(); 89 | errnoState = Linker.Option.captureCallState("errno"); 90 | errnoSeg = Arena.global().allocate(Linker.Option.captureStateLayout()); 91 | } 92 | 93 | /** 94 | * Call mmap(2) via FFM. See manpage of mmap(2) for details. 95 | * 96 | * @throws PlatformException if mmap(2) or FFM call failed. 97 | */ 98 | public static MemorySegment mmap(MemorySegment addr, long length, int prot, int flags, int fd, long offset) throws PlatformException{ 99 | if(hndMmap == null){ 100 | var func = sym.find("mmap").get(); 101 | var desc = FunctionDescriptor.of( 102 | ValueLayout.ADDRESS, // return value 103 | ValueLayout.ADDRESS, // addr 104 | canonicalLayouts.get("size_t"), // length 105 | ValueLayout.JAVA_INT, // prot 106 | ValueLayout.JAVA_INT, // flags 107 | ValueLayout.JAVA_INT, // fd 108 | ValueLayout.JAVA_LONG // offset 109 | ); 110 | hndMmap = nativeLinker.downcallHandle(func, desc, errnoState); 111 | } 112 | 113 | try{ 114 | MemorySegment mem = (MemorySegment)hndMmap.invoke(errnoSeg, addr, length, prot, flags, fd, offset); 115 | if(mem.address() == -1L){ // MAP_FAILED 116 | if(hndErrno == null){ 117 | hndErrno = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("errno")); 118 | } 119 | throw new PlatformException("mmap() failed", (int)hndErrno.get(errnoSeg, 0L)); 120 | } 121 | return mem.reinterpret(length); 122 | } 123 | catch(Throwable t){ 124 | throw new PlatformException(t); 125 | } 126 | } 127 | 128 | /** 129 | * Call munmap(2) via FFM. See manpage of munmap(2) for details. 130 | * 131 | * @throws PlatformException if munmap(2) or FFM call failed. 132 | */ 133 | public static int munmap(MemorySegment addr, long length) throws PlatformException{ 134 | if(hndMunmap == null){ 135 | var func = sym.find("munmap").get(); 136 | var desc = FunctionDescriptor.of( 137 | ValueLayout.JAVA_INT, // return value 138 | ValueLayout.ADDRESS, // addr 139 | canonicalLayouts.get("size_t") // length 140 | ); 141 | hndMunmap = nativeLinker.downcallHandle(func, desc, Linker.Option.critical(false)); 142 | } 143 | 144 | try{ 145 | int retval = (int)hndMunmap.invoke(addr, length); 146 | if(retval == -1){ 147 | if(hndErrno == null){ 148 | hndErrno = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("errno")); 149 | } 150 | throw new PlatformException("munmap() failed", (int)hndErrno.get(errnoSeg, 0L)); 151 | } 152 | return retval; // it should be 0 153 | } 154 | catch(Throwable t){ 155 | throw new PlatformException(t); 156 | } 157 | } 158 | 159 | /** 160 | * {@inheritDoc} 161 | */ 162 | @Override 163 | public MemorySegment allocate(long size) throws PlatformException{ 164 | return mmap(MemorySegment.NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 165 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 166 | } 167 | 168 | /** 169 | * {@inheritDoc} 170 | */ 171 | @Override 172 | public void deallocate(MemorySegment addr, long size) throws PlatformException{ 173 | munmap(addr, size); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/linux/PerfJitDump.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.linux; 20 | 21 | import java.io.IOException; 22 | import java.io.UncheckedIOException; 23 | import java.lang.foreign.Arena; 24 | import java.lang.foreign.FunctionDescriptor; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.MemoryLayout; 27 | import java.lang.foreign.MemorySegment; 28 | import java.lang.foreign.StructLayout; 29 | import java.lang.foreign.ValueLayout; 30 | import java.lang.invoke.MethodHandle; 31 | import java.lang.invoke.VarHandle; 32 | import java.nio.ByteBuffer; 33 | import java.nio.ByteOrder; 34 | import java.nio.channels.FileChannel; 35 | import java.nio.file.Files; 36 | import java.nio.file.NoSuchFileException; 37 | import java.nio.file.Path; 38 | import java.nio.file.StandardOpenOption; 39 | 40 | import com.yasuenag.ffmasm.CodeSegment; 41 | import com.yasuenag.ffmasm.JitDump; 42 | import com.yasuenag.ffmasm.PlatformException; 43 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 44 | 45 | 46 | /** 47 | * Generate jitdump for perf tool on Linux. 48 | * 49 | * @author Yasumasa Suenaga 50 | */ 51 | public class PerfJitDump implements JitDump{ 52 | 53 | // These constants come from tools/perf/util/jitdump.h in Linux Kernel 54 | private static final int JITHEADER_MAGIC = 0x4A695444; 55 | private static final int JITHEADER_VERSION = 1; 56 | private static final int JIT_CODE_LOAD = 0; 57 | private static final int JIT_CODE_CLOSE = 3; 58 | 59 | // from bits/time.h 60 | private static final int CLOCK_MONOTONIC = 1; 61 | 62 | private static final long PAGE_SIZE = 4096; 63 | 64 | 65 | private static final MethodHandle mhGetTid; 66 | private static final StructLayout structTimespec; 67 | private static final VarHandle hndSec; 68 | private static final VarHandle hndNSec; 69 | private static final MethodHandle mhClockGettime; 70 | 71 | 72 | private final FileChannel ch; 73 | 74 | private final int fd; 75 | 76 | private final MemorySegment jitdump; 77 | 78 | private long codeIndex; 79 | 80 | 81 | static{ 82 | var linker = Linker.nativeLinker(); 83 | var lookup = linker.defaultLookup(); 84 | FunctionDescriptor desc; 85 | 86 | desc = FunctionDescriptor.of(ValueLayout.JAVA_INT); 87 | mhGetTid = linker.downcallHandle(lookup.find("gettid").get(), desc); 88 | 89 | structTimespec = MemoryLayout.structLayout( 90 | ValueLayout.JAVA_LONG.withName("tv_sec"), 91 | ValueLayout.JAVA_LONG.withName("tv_nsec") 92 | ); 93 | hndSec = structTimespec.varHandle(MemoryLayout.PathElement.groupElement("tv_sec")); 94 | hndNSec = structTimespec.varHandle(MemoryLayout.PathElement.groupElement("tv_nsec")); 95 | // __CLOCKID_T_TYPE is defined as __S32_TYPE in bits/typesizes.h 96 | desc = FunctionDescriptor.of(ValueLayout.JAVA_INT, 97 | ValueLayout.JAVA_INT, 98 | ValueLayout.ADDRESS); 99 | mhClockGettime = linker.downcallHandle(lookup.find("clock_gettime").get(), desc); 100 | } 101 | 102 | private static int getTid(){ 103 | try{ 104 | return (int)mhGetTid.invokeExact(); 105 | } 106 | catch(Throwable t){ 107 | throw new RuntimeException("Exception happened at gettid() call.", t); 108 | } 109 | } 110 | 111 | private static long getTimestamp(){ 112 | try(var arena = Arena.ofConfined()){ 113 | var timespec = arena.allocate(structTimespec); 114 | int _ = (int)mhClockGettime.invokeExact(CLOCK_MONOTONIC, timespec); 115 | 116 | return ((long)hndSec.get(timespec, 0L) * 1_000_000_000) + (long)hndNSec.get(timespec, 0L); 117 | } 118 | catch(Throwable t){ 119 | throw new RuntimeException("Exception happened at gettid() call.", t); 120 | } 121 | } 122 | 123 | // from tools/perf/util/jitdump.h in Linux Kernel 124 | // struct jitheader { 125 | // uint32_t magic; /* characters "jItD" */ 126 | // uint32_t version; /* header version */ 127 | // uint32_t total_size; /* total size of header */ 128 | // uint32_t elf_mach; /* elf mach target */ 129 | // uint32_t pad1; /* reserved */ 130 | // uint32_t pid; /* JIT process id */ 131 | // uint64_t timestamp; /* timestamp */ 132 | // uint64_t flags; /* flags */ 133 | // }; 134 | private void writeHeader() throws IOException{ 135 | final int headerSize = 40; // sizeof(struct jitheader) 136 | var buf = ByteBuffer.allocate(headerSize).order(ByteOrder.nativeOrder()); 137 | 138 | short elfMach = -1; 139 | try(var chExe = FileChannel.open(Path.of("/proc/self/exe"), StandardOpenOption.READ)){ 140 | var buf2 = ByteBuffer.allocate(2).order(ByteOrder.nativeOrder()); 141 | chExe.read(buf2, 18); // id (16 bytes) + e_type (2 bytes) 142 | buf2.flip(); 143 | elfMach = buf2.getShort(); 144 | } 145 | 146 | // magic 147 | buf.putInt(JITHEADER_MAGIC); 148 | // version 149 | buf.putInt(JITHEADER_VERSION); 150 | // total_size 151 | buf.putInt(headerSize); 152 | // elf_mach 153 | buf.putInt(elfMach); 154 | // pad1 155 | buf.putInt(0); 156 | // pid 157 | buf.putInt((int)ProcessHandle.current().pid()); 158 | // timestamp 159 | buf.putLong(getTimestamp()); 160 | // flags 161 | buf.putLong(0L); 162 | 163 | buf.flip(); 164 | ch.write(buf); 165 | } 166 | 167 | /** 168 | * Constructor of PerfJitDump. 169 | * This constructer creates dump file named with "jit-.dump" 170 | * into specified directory. Top of page (1 page: 4096 bytes) 171 | * will be mapped as a executable memory - it is mandatory for 172 | * recording in perf tool. 173 | * And also jitdump header will be written at this time. 174 | * 175 | * @param dir Path to base directory to dump. 176 | */ 177 | public PerfJitDump(Path dir) throws UnsupportedPlatformException, PlatformException, IOException{ 178 | // According to jit_detect() in tools/perf/util/jitdump.c in Linux Kernel, 179 | // dump file should be named "jit-.dump". 180 | var jitdumpPath = dir.resolve(String.format("jit-%d.dump", ProcessHandle.current().pid())); 181 | ch = FileChannel.open(jitdumpPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE); 182 | 183 | // find FD of jitdump file 184 | int fdWork = -1; 185 | try(var links = Files.newDirectoryStream(Path.of("/proc/self/fd"))){ 186 | for(var link : links){ 187 | try{ 188 | if(Files.isSameFile(jitdumpPath, Files.readSymbolicLink(link))){ 189 | fdWork = Integer.parseInt(link.getFileName().toString()); 190 | break; 191 | } 192 | } 193 | catch(NoSuchFileException e){ 194 | // ignore 195 | } 196 | } 197 | } 198 | if(fdWork == -1){ 199 | throw new IllegalStateException("FD of jitdump is not found."); 200 | } 201 | fd = fdWork; 202 | 203 | // from tools/perf/jvmti/jvmti_agent.c in Linux Kernel 204 | jitdump = LinuxExecMemory.mmap(MemorySegment.NULL, PAGE_SIZE, LinuxExecMemory.PROT_READ | LinuxExecMemory.PROT_EXEC, LinuxExecMemory.MAP_PRIVATE, fd, 0); 205 | codeIndex = 0; 206 | 207 | writeHeader(); 208 | } 209 | 210 | // from tools/perf/util/jitdump.h in Linux Kernel 211 | // struct jr_prefix { 212 | // uint32_t id; 213 | // uint32_t total_size; 214 | // uint64_t timestamp; 215 | // }; 216 | // 217 | // struct jr_code_load { 218 | // struct jr_prefix p; 219 | // 220 | // uint32_t pid; 221 | // uint32_t tid; 222 | // uint64_t vma; 223 | // uint64_t code_addr; 224 | // uint64_t code_size; 225 | // uint64_t code_index; 226 | // }; 227 | /** 228 | * {@inheritDoc} 229 | */ 230 | @Override 231 | public synchronized void writeFunction(CodeSegment.MethodInfo method){ 232 | // sizeof(jr_code_load) == 56, null char of method name should be included. 233 | final int totalSize = 56 + method.name().length() + 1 + method.size(); 234 | var buf = ByteBuffer.allocate(totalSize).order(ByteOrder.nativeOrder()); 235 | 236 | // id 237 | buf.putInt(JIT_CODE_LOAD); 238 | // total_size 239 | buf.putInt(totalSize); 240 | // timestamp 241 | buf.putLong(getTimestamp()); 242 | // pid 243 | buf.putInt((int)ProcessHandle.current().pid()); 244 | // tid 245 | buf.putInt(getTid()); 246 | // vma 247 | buf.putLong(method.address()); 248 | // code_addr 249 | buf.putLong(method.address()); 250 | // code_size 251 | buf.putLong(method.size()); 252 | // code_index 253 | buf.putLong(codeIndex++); 254 | 255 | // method name 256 | buf.put(method.name().getBytes()); 257 | buf.put((byte)0); // NUL 258 | 259 | // code 260 | var seg = MemorySegment.ofAddress(method.address()).reinterpret(method.size()); 261 | buf.put(seg.toArray(ValueLayout.JAVA_BYTE)); 262 | 263 | buf.flip(); 264 | try{ 265 | ch.write(buf); 266 | } 267 | catch(IOException e){ 268 | throw new UncheckedIOException(e); 269 | } 270 | } 271 | 272 | // from tools/perf/util/jitdump.h in Linux Kernel 273 | // struct jr_prefix { 274 | // uint32_t id; 275 | // uint32_t total_size; 276 | // uint64_t timestamp; 277 | // }; 278 | // 279 | // struct jr_code_close { 280 | // struct jr_prefix p; 281 | // }; 282 | /** 283 | * {@inheritDoc} 284 | */ 285 | @Override 286 | public synchronized void close() throws Exception{ 287 | final int headerSize = 16; // sizeof(jr_code_close) 288 | var buf = ByteBuffer.allocate(headerSize).order(ByteOrder.nativeOrder()); 289 | 290 | // id 291 | buf.putInt(JIT_CODE_CLOSE); 292 | // total_size 293 | buf.putInt(headerSize); 294 | // timestamp 295 | buf.putLong(getTimestamp()); 296 | 297 | buf.flip(); 298 | ch.write(buf); 299 | 300 | LinuxExecMemory.munmap(jitdump, PAGE_SIZE); 301 | ch.close(); 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/windows/WindowsExecMemory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.windows; 20 | 21 | import java.lang.foreign.Arena; 22 | import java.lang.foreign.FunctionDescriptor; 23 | import java.lang.foreign.Linker; 24 | import java.lang.foreign.MemoryLayout; 25 | import java.lang.foreign.MemorySegment; 26 | import java.lang.foreign.SymbolLookup; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.invoke.MethodHandle; 29 | import java.lang.invoke.VarHandle; 30 | import java.util.Map; 31 | 32 | import com.yasuenag.ffmasm.internal.ExecMemory; 33 | import com.yasuenag.ffmasm.PlatformException; 34 | 35 | 36 | /** 37 | * Aquiring / releasing memory for execution code for Windows. 38 | * This class uses VirtualAlloc and VirtualFree for it. 39 | * 40 | * @author Yasumasa Suenaga 41 | */ 42 | public class WindowsExecMemory implements ExecMemory{ 43 | 44 | private static final Linker nativeLinker; 45 | 46 | private static final SymbolLookup sym; 47 | 48 | private static final Map canonicalLayouts; 49 | 50 | private static final Linker.Option getLastErrorState; 51 | 52 | private static final MemorySegment getLastErrorSeg; 53 | 54 | private MethodHandle hndVirtualAlloc = null; 55 | 56 | private MethodHandle hndVirtualFree = null; 57 | 58 | private VarHandle hndGetLastError = null; 59 | 60 | public static final int MEM_COMMIT = 0x00001000; 61 | 62 | public static final int MEM_RESERVE = 0x00002000; 63 | 64 | public static final int MEM_RELEASE = 0x00008000; 65 | 66 | public static final int PAGE_EXECUTE_READWRITE = 0x40; 67 | 68 | static{ 69 | sym = SymbolLookup.libraryLookup("Kernel32", Arena.global()); 70 | nativeLinker = Linker.nativeLinker(); 71 | canonicalLayouts = nativeLinker.canonicalLayouts(); 72 | getLastErrorState = Linker.Option.captureCallState("GetLastError"); 73 | getLastErrorSeg = Arena.global().allocate(Linker.Option.captureStateLayout()); 74 | } 75 | 76 | private MemorySegment virtualAlloc(long lpAddress, long dwSize, int flAllocationType, int flProtect) throws PlatformException{ 77 | if(hndVirtualAlloc == null){ 78 | var func = sym.find("VirtualAlloc").get(); 79 | var desc = FunctionDescriptor.of( 80 | ValueLayout.ADDRESS, // return value 81 | ValueLayout.JAVA_LONG, // lpAddress 82 | canonicalLayouts.get("long"), // dwSize 83 | ValueLayout.JAVA_INT, // flAllocationType 84 | ValueLayout.JAVA_INT // flProtect 85 | ); 86 | hndVirtualAlloc = nativeLinker.downcallHandle(func, desc, getLastErrorState); 87 | } 88 | 89 | try{ 90 | MemorySegment mem = (MemorySegment)hndVirtualAlloc.invoke(getLastErrorSeg, 91 | lpAddress, 92 | (int)dwSize, // "long" is 32bit in LLP64 93 | flAllocationType, 94 | flProtect); 95 | if(mem.equals(MemorySegment.NULL)){ 96 | if(hndGetLastError == null){ 97 | hndGetLastError = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("GetLastError")); 98 | } 99 | throw new PlatformException("VirtualAlloc() failed", (int)hndGetLastError.get(getLastErrorSeg, 0L)); 100 | } 101 | return mem.reinterpret(dwSize); 102 | } 103 | catch(Throwable t){ 104 | throw new PlatformException(t); 105 | } 106 | } 107 | 108 | /** 109 | * VirtualFree returns BOOL, it is defined in int. 110 | * https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types 111 | */ 112 | private int virtualFree(MemorySegment lpAddress, long dwSize, int dwFreeType) throws PlatformException{ 113 | if(hndVirtualFree == null){ 114 | var func = sym.find("VirtualFree").get(); 115 | var desc = FunctionDescriptor.of( 116 | ValueLayout.JAVA_INT, // return value 117 | ValueLayout.ADDRESS, // addr 118 | canonicalLayouts.get("long"), // dwSize 119 | ValueLayout.JAVA_INT // dwFreeType 120 | ); 121 | hndVirtualFree = nativeLinker.downcallHandle(func, desc, getLastErrorState); 122 | } 123 | 124 | try{ 125 | int result = (int)hndVirtualFree.invoke(getLastErrorSeg, 126 | lpAddress, 127 | (int)dwSize, // "long" is 32bit in LLP64 128 | dwFreeType); 129 | if(result == 0){ 130 | if(hndGetLastError == null){ 131 | hndGetLastError = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("GetLastError")); 132 | } 133 | throw new PlatformException("VirtualFree() failed", (int)hndGetLastError.get(getLastErrorSeg, 0L)); 134 | } 135 | return result; // it should be true 136 | } 137 | catch(Throwable t){ 138 | throw new PlatformException(t); 139 | } 140 | } 141 | 142 | /** 143 | * {@inheritDoc} 144 | */ 145 | @Override 146 | public MemorySegment allocate(long size) throws PlatformException{ 147 | return virtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 148 | } 149 | 150 | /** 151 | * {@inheritDoc} 152 | */ 153 | @Override 154 | public void deallocate(MemorySegment addr, long size) throws PlatformException{ 155 | virtualFree(addr, 0, MEM_RELEASE); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | 20 | /** 21 | * Core module of ffmasm. 22 | */ 23 | module com.yasuenag.ffmasm { 24 | exports com.yasuenag.ffmasm; 25 | exports com.yasuenag.ffmasm.amd64; 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/amd64/NativeRegisterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.amd64; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.lang.foreign.FunctionDescriptor; 27 | import java.lang.foreign.ValueLayout; 28 | import java.util.Map; 29 | import java.util.OptionalInt; 30 | 31 | import com.yasuenag.ffmasm.CodeSegment; 32 | import com.yasuenag.ffmasm.NativeRegister; 33 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder; 34 | import com.yasuenag.ffmasm.amd64.Register; 35 | 36 | 37 | @EnabledOnOs(architectures = {"amd64"}) 38 | public class NativeRegisterTest extends TestBase{ 39 | 40 | public native int test(int arg); 41 | 42 | @Test 43 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 44 | public void testNativeRegister(){ 45 | try(var seg = new CodeSegment()){ 46 | var desc = FunctionDescriptor.of( 47 | ValueLayout.JAVA_INT, // return value 48 | ValueLayout.JAVA_INT, // 1st arg (JNIEnv *) 49 | ValueLayout.JAVA_INT, // 2nd arg (jobject) 50 | ValueLayout.JAVA_INT // 3rd arg (arg1 of caller) 51 | ); 52 | var stub = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc) 53 | /* push %rbp */ .push(Register.RBP) 54 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty()) 55 | /* mov %arg3, retReg */ .movMR(argReg.arg3(), argReg.returnReg(), OptionalInt.empty()) // arg1 in Java is arg3 in native 56 | /* leave */ .leave() 57 | /* ret */ .ret() 58 | .getMemorySegment(); 59 | 60 | var method = this.getClass() 61 | .getMethod("test", int.class); 62 | 63 | var methodMap = Map.of(method, stub); 64 | var register = NativeRegister.create(this.getClass()); 65 | register.registerNatives(methodMap); 66 | 67 | final int expected = 100; 68 | int actual = test(expected); 69 | Assertions.assertEquals(expected, actual); 70 | } 71 | catch(Throwable t){ 72 | Assertions.fail(t); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/amd64/SSEAsmTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.amd64; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.lang.foreign.Arena; 27 | import java.lang.foreign.FunctionDescriptor; 28 | import java.lang.foreign.MemorySegment; 29 | import java.lang.foreign.ValueLayout; 30 | import java.util.OptionalInt; 31 | 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder; 34 | import com.yasuenag.ffmasm.amd64.Register; 35 | import com.yasuenag.ffmasm.amd64.SSEAsmBuilder; 36 | 37 | 38 | public class SSEAsmTest extends TestBase{ 39 | 40 | /** 41 | * Tests MOVDQA A/B 42 | */ 43 | @Test 44 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 45 | public void testMOVDQA(){ 46 | try(var seg = new CodeSegment()){ 47 | var desc = FunctionDescriptor.ofVoid( 48 | ValueLayout.ADDRESS, // 1st argument 49 | ValueLayout.ADDRESS // 2nd argument 50 | ); 51 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) 52 | /* push %rbp */ .push(Register.RBP) 53 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 54 | .cast(SSEAsmBuilder.class) 55 | /* movdqa (arg1), %xmm0 */ .movdqaRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 56 | /* movdqa %xmm0, (arg2) */ .movdqaMR(Register.XMM0, argReg.arg2(), OptionalInt.of(0)) 57 | /* leave */ .leave() 58 | /* ret */ .ret() 59 | .build(); 60 | 61 | long[] expected = new long[]{1, 2}; // 64 * 2 = 128 bit 62 | var arena = Arena.ofAuto(); 63 | MemorySegment src = arena.allocate(16, 16); // 128 bit 64 | MemorySegment dest = arena.allocate(16, 16); // 128 bit 65 | MemorySegment.copy(expected, 0, src, ValueLayout.JAVA_LONG, 0, expected.length); 66 | 67 | method.invoke(src, dest); 68 | 69 | Assertions.assertArrayEquals(expected, src.toArray(ValueLayout.JAVA_LONG)); 70 | Assertions.assertArrayEquals(expected, dest.toArray(ValueLayout.JAVA_LONG)); 71 | } 72 | catch(Throwable t){ 73 | Assertions.fail(t); 74 | } 75 | } 76 | 77 | /** 78 | * Tests MOVDQU A/B 79 | */ 80 | @Test 81 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 82 | public void testMOVDQU(){ 83 | try(var seg = new CodeSegment()){ 84 | var desc = FunctionDescriptor.ofVoid( 85 | ValueLayout.ADDRESS, // 1st argument 86 | ValueLayout.ADDRESS // 2nd argument 87 | ); 88 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) 89 | /* push %rbp */ .push(Register.RBP) 90 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 91 | .cast(SSEAsmBuilder.class) 92 | /* movdqu (arg1), %xmm0 */ .movdquRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 93 | /* movdqu %xmm0, (arg2) */ .movdquMR(Register.XMM0, argReg.arg2(), OptionalInt.of(0)) 94 | /* leave */ .leave() 95 | /* ret */ .ret() 96 | .build(); 97 | 98 | long[] expected = new long[]{1, 2}; // 64 * 2 = 128 bit 99 | var arena = Arena.ofAuto(); 100 | MemorySegment src = arena.allocate(16, 16); // 128 bit 101 | MemorySegment dest = arena.allocate(16, 16); // 128 bit 102 | MemorySegment.copy(expected, 0, src, ValueLayout.JAVA_LONG, 0, expected.length); 103 | 104 | method.invoke(src, dest); 105 | 106 | Assertions.assertArrayEquals(expected, src.toArray(ValueLayout.JAVA_LONG)); 107 | Assertions.assertArrayEquals(expected, dest.toArray(ValueLayout.JAVA_LONG)); 108 | } 109 | catch(Throwable t){ 110 | Assertions.fail(t); 111 | } 112 | } 113 | 114 | /** 115 | * Tests MOVD A 116 | */ 117 | @Test 118 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 119 | public void testMOVD_A(){ 120 | try(var seg = new CodeSegment()){ 121 | var desc = FunctionDescriptor.of( 122 | ValueLayout.JAVA_FLOAT, // return value 123 | ValueLayout.ADDRESS // 1st argument 124 | ); 125 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) 126 | /* push %rbp */ .push(Register.RBP) 127 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 128 | .cast(SSEAsmBuilder.class) 129 | /* movd (arg1), %xmm0 */ .movdRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 130 | /* leave */ .leave() 131 | /* ret */ .ret() 132 | .build(); 133 | 134 | float expected = 1.1f; 135 | var arena = Arena.ofAuto(); 136 | MemorySegment src = arena.allocate(ValueLayout.JAVA_FLOAT); 137 | src.set(ValueLayout.JAVA_FLOAT, 0, expected); 138 | 139 | float actual = (float)method.invoke(src); 140 | 141 | Assertions.assertEquals(expected, actual); 142 | } 143 | catch(Throwable t){ 144 | Assertions.fail(t); 145 | } 146 | } 147 | 148 | /** 149 | * Tests MOVD B 150 | */ 151 | @Test 152 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 153 | public void testMOVD_B(){ 154 | try(var seg = new CodeSegment()){ 155 | var arena = Arena.ofAuto(); 156 | MemorySegment dest = arena.allocate(ValueLayout.JAVA_FLOAT); 157 | 158 | var desc = FunctionDescriptor.ofVoid( 159 | ValueLayout.JAVA_FLOAT // 1st argument 160 | ); 161 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) 162 | /* push %rbp */ .push(Register.RBP) 163 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 164 | // Mixed argument order (int, fp) is different between Windows and Linux. 165 | // Thus address is loaded from immediate value. 166 | /* mov addr, %rax */ .movImm(Register.RAX, dest.address()) 167 | .cast(SSEAsmBuilder.class) 168 | /* movd %xmm0, (%rax) */ .movdMR(Register.XMM0, Register.RAX, OptionalInt.of(0)) 169 | /* leave */ .leave() 170 | /* ret */ .ret() 171 | .build(); 172 | 173 | float expected = 1.1f; 174 | 175 | method.invoke(expected); 176 | float actual = dest.get(ValueLayout.JAVA_FLOAT, 0); 177 | 178 | Assertions.assertEquals(expected, actual); 179 | } 180 | catch(Throwable t){ 181 | Assertions.fail(t); 182 | } 183 | } 184 | 185 | /** 186 | * Tests MOVQ A 187 | */ 188 | @Test 189 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 190 | public void testMOVQ_A(){ 191 | try(var seg = new CodeSegment()){ 192 | var desc = FunctionDescriptor.of( 193 | ValueLayout.JAVA_DOUBLE, // return value 194 | ValueLayout.ADDRESS // 1st argument 195 | ); 196 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) 197 | /* push %rbp */ .push(Register.RBP) 198 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 199 | .cast(SSEAsmBuilder.class) 200 | /* movq (arg1), %xmm0 */ .movqRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 201 | /* leave */ .leave() 202 | /* ret */ .ret() 203 | .build(); 204 | 205 | double expected = 1.1d; 206 | var arena = Arena.ofAuto(); 207 | MemorySegment src = arena.allocate(ValueLayout.JAVA_DOUBLE); 208 | src.set(ValueLayout.JAVA_DOUBLE, 0, expected); 209 | 210 | double actual = (double)method.invoke(src); 211 | 212 | Assertions.assertEquals(expected, actual); 213 | } 214 | catch(Throwable t){ 215 | Assertions.fail(t); 216 | } 217 | } 218 | 219 | /** 220 | * Tests MOVQ B 221 | */ 222 | @Test 223 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 224 | public void testMOVQ_B(){ 225 | try(var seg = new CodeSegment()){ 226 | var arena = Arena.ofAuto(); 227 | MemorySegment dest = arena.allocate(ValueLayout.JAVA_DOUBLE); 228 | 229 | var desc = FunctionDescriptor.ofVoid( 230 | ValueLayout.JAVA_DOUBLE // 1st argument 231 | ); 232 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) 233 | /* push %rbp */ .push(Register.RBP) 234 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 235 | // Mixed argument order (int, fp) is different between Windows and Linux. 236 | // Thus address is loaded from immediate value. 237 | /* mov addr, %rax */ .movImm(Register.RAX, dest.address()) 238 | .cast(SSEAsmBuilder.class) 239 | /* movq %xmm0, (%rax) */ .movqMR(Register.XMM0, Register.RAX, OptionalInt.of(0)) 240 | /* leave */ .leave() 241 | /* ret */ .ret() 242 | .build(); 243 | 244 | double expected = 1.1d; 245 | 246 | method.invoke(expected); 247 | double actual = dest.get(ValueLayout.JAVA_DOUBLE, 0); 248 | 249 | Assertions.assertEquals(expected, actual); 250 | } 251 | catch(Throwable t){ 252 | Assertions.fail(t); 253 | } 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/amd64/TestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.amd64; 20 | 21 | import java.io.IOException; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.ValueLayout; 25 | import java.lang.invoke.MethodHandle; 26 | import java.util.OptionalInt; 27 | 28 | import org.junit.jupiter.api.BeforeAll; 29 | 30 | import com.yasuenag.ffmasm.CodeSegment; 31 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 32 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder; 33 | import com.yasuenag.ffmasm.amd64.Register; 34 | 35 | 36 | public class TestBase{ 37 | 38 | public static record ArgRegister( 39 | Register arg1, 40 | Register arg2, 41 | Register arg3, 42 | Register arg4, 43 | Register returnReg 44 | ){} 45 | 46 | protected static ArgRegister argReg; 47 | 48 | private static boolean isAVX; 49 | 50 | private static boolean isCLFLUSHOPT; 51 | 52 | private static MethodHandle generateCPUID(CodeSegment seg) throws UnsupportedPlatformException{ 53 | var desc = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT, /* eax */ 54 | ValueLayout.JAVA_INT, /* ecx */ 55 | ValueLayout.ADDRESS /* result */ 56 | ); 57 | return AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc) 58 | /* push %rbp */ .push(Register.RBP) 59 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty()) 60 | /* push %rbx */ .push(Register.RBX) 61 | /* mov arg1, %rax */ .movRM(Register.RAX, argReg.arg1(), OptionalInt.empty()) 62 | /* mov arg2, %rcx */ .movRM(Register.RCX, argReg.arg2(), OptionalInt.empty()) 63 | /* mov arg3, %r11 */ .movRM(Register.R11, argReg.arg3(), OptionalInt.empty()) 64 | /* cpuid */ .cpuid() 65 | /* mov %eax, (r11) */ .movMR(Register.EAX, Register.R11, OptionalInt.of(0)) 66 | /* mov %ebx, 4(r11) */ .movMR(Register.EBX, Register.R11, OptionalInt.of(4)) 67 | /* mov %ecx, 8(r11) */ .movMR(Register.ECX, Register.R11, OptionalInt.of(8)) 68 | /* mov %ecx, 12(r11) */ .movMR(Register.EDX, Register.R11, OptionalInt.of(12)) 69 | /* pop %rbx */ .pop(Register.RBX, OptionalInt.empty()) 70 | /* leave */ .leave() 71 | /* ret */ .ret() 72 | .build(); 73 | } 74 | 75 | @BeforeAll 76 | public static void init(){ 77 | String osName = System.getProperty("os.name"); 78 | if(osName.equals("Linux")){ 79 | argReg = new ArgRegister(Register.RDI, Register.RSI, Register.RDX, Register.RCX, Register.RAX); 80 | } 81 | else if(osName.startsWith("Windows")){ 82 | argReg = new ArgRegister(Register.RCX, Register.RDX, Register.R8, Register.R9, Register.RAX); 83 | } 84 | else{ 85 | throw new RuntimeException(new UnsupportedPlatformException(osName)); 86 | }; 87 | 88 | try(var seg = new CodeSegment(); 89 | var arena = Arena.ofConfined();){ 90 | var cpuid = generateCPUID(seg); 91 | var cpuidVals = arena.allocate(ValueLayout.JAVA_INT, 4); 92 | 93 | // check AVX 94 | cpuid.invokeExact(1, 0, cpuidVals); 95 | isAVX = ((cpuidVals.getAtIndex(ValueLayout.JAVA_INT, 2) >>> 28) & 0x1) == 1; // ecx 96 | 97 | // check CLFLUSHOPT 98 | cpuid.invokeExact(7, 0, cpuidVals); 99 | isCLFLUSHOPT = ((cpuidVals.getAtIndex(ValueLayout.JAVA_INT, 1) >>> 23) & 0x1) == 1; // ebx 100 | } 101 | catch(Throwable t){ 102 | throw new RuntimeException(t); 103 | } 104 | } 105 | 106 | public static boolean supportAVX(){ 107 | return isAVX; 108 | } 109 | 110 | public static boolean supportCLFLUSHOPT(){ 111 | return isCLFLUSHOPT; 112 | } 113 | 114 | /** 115 | * Show PID, address of CodeSegment, then waits stdin input. 116 | */ 117 | public void showDebugMessage(CodeSegment seg) throws IOException{ 118 | System.out.println("PID: " + ProcessHandle.current().pid()); 119 | System.out.println("Addr: 0x" + Long.toHexString(seg.getAddr().address())); 120 | System.in.read(); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/common/CodeSegmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.common; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import com.yasuenag.ffmasm.CodeSegment; 25 | 26 | 27 | public class CodeSegmentTest{ 28 | 29 | @Test 30 | public void testAlignment(){ 31 | try(var seg = new CodeSegment()){ 32 | seg.alignTo16Bytes(); 33 | Assertions.assertEquals((byte)0, (byte)(seg.getTail() & 0xf), "Memory is not aligned: " + Long.toHexString(seg.getAddr().address() + seg.getTail())); 34 | } 35 | catch(Throwable t){ 36 | Assertions.fail(t); 37 | } 38 | } 39 | 40 | @Test 41 | public void testMethodInfoString(){ 42 | var info = new CodeSegment.MethodInfo("func", 0x1234, 0xff); 43 | Assertions.assertEquals("0x1234 0xff func", info.toString()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/common/JitDumpTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.common; 20 | 21 | import java.io.File; 22 | import java.nio.ByteBuffer; 23 | import java.nio.ByteOrder; 24 | import java.nio.channels.FileChannel; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.StandardOpenOption; 28 | 29 | import org.junit.jupiter.api.Assertions; 30 | import org.junit.jupiter.api.Test; 31 | import org.junit.jupiter.api.condition.EnabledOnOs; 32 | import org.junit.jupiter.api.condition.OS; 33 | 34 | import com.yasuenag.ffmasm.CodeSegment; 35 | import com.yasuenag.ffmasm.JitDump; 36 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 37 | import com.yasuenag.ffmasm.internal.linux.PerfJitDump; 38 | 39 | 40 | public class JitDumpTest{ 41 | 42 | private File getDumpFileInstance(){ 43 | return new File(String.format("/tmp/jit-%d.dump", ProcessHandle.current().pid())); 44 | } 45 | 46 | @Test 47 | @EnabledOnOs(OS.LINUX) 48 | public void testGetInstanceOnLinux(){ 49 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 50 | Assertions.assertEquals(PerfJitDump.class, jitdump.getClass()); 51 | } 52 | catch(Exception e){ 53 | Assertions.fail(e); 54 | } 55 | 56 | var dumpfile = getDumpFileInstance(); 57 | Assertions.assertTrue(dumpfile.exists()); 58 | 59 | // cleanup 60 | dumpfile.delete(); 61 | } 62 | 63 | @Test 64 | @EnabledOnOs(OS.LINUX) 65 | public void testMemoryPermissions(){ 66 | var dumpfile = getDumpFileInstance(); 67 | String line = null; 68 | 69 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 70 | try(var lines = Files.lines(Path.of("/proc/self/maps"))){ 71 | line = lines.filter(s -> s.endsWith(dumpfile.getPath())) 72 | .findFirst() 73 | .get(); 74 | } 75 | } 76 | catch(Exception e){ 77 | Assertions.fail(e); 78 | } 79 | 80 | Assertions.assertEquals("r-xp", line.split("\\s+")[1]); 81 | 82 | // cleanup 83 | dumpfile.delete(); 84 | } 85 | 86 | @Test 87 | @EnabledOnOs(OS.LINUX) 88 | public void testJitDumpHeader(){ 89 | var dumpfile = getDumpFileInstance(); 90 | 91 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 92 | // Create jitdump file. Do nothing at here. 93 | } 94 | catch(Exception e){ 95 | Assertions.fail(e); 96 | } 97 | 98 | try(var ch = FileChannel.open(dumpfile.toPath(), StandardOpenOption.READ)){ 99 | final int headerSize = 40; // sizeof(struct jitheader) 100 | final int closeHeaderSize = 16; // sizeof(jr_code_close) 101 | Assertions.assertEquals(headerSize + closeHeaderSize, ch.size()); 102 | 103 | var buf = ByteBuffer.allocate(headerSize) 104 | .order(ByteOrder.nativeOrder()); 105 | ch.read(buf); 106 | buf.flip(); 107 | 108 | // magic 109 | Assertions.assertEquals(0x4A695444, buf.getInt()); 110 | // version 111 | Assertions.assertEquals(1, buf.getInt()); 112 | // total_size 113 | Assertions.assertEquals(headerSize, buf.getInt()); 114 | // elf_mach (skip) 115 | buf.position(buf.position() + 4); 116 | // pad1 117 | Assertions.assertEquals(0, buf.getInt()); 118 | // pid 119 | Assertions.assertEquals((int)ProcessHandle.current().pid(), buf.getInt()); 120 | // timestamp (skip) 121 | buf.position(buf.position() + 8); 122 | // flag 123 | Assertions.assertEquals(0L, buf.getLong()); 124 | 125 | buf = ByteBuffer.allocate(closeHeaderSize) 126 | .order(ByteOrder.nativeOrder()); 127 | ch.read(buf); 128 | buf.flip(); 129 | 130 | // id 131 | Assertions.assertEquals(3, buf.getInt()); 132 | // total_size 133 | Assertions.assertEquals(closeHeaderSize, buf.getInt()); 134 | } 135 | catch(Exception e){ 136 | Assertions.fail(e); 137 | } 138 | 139 | // cleanup 140 | dumpfile.delete(); 141 | } 142 | 143 | @Test 144 | @EnabledOnOs(OS.LINUX) 145 | public void testJitDumpFunctionEntry(){ 146 | var dumpfile = getDumpFileInstance(); 147 | var info = new CodeSegment.MethodInfo("func", 0x1234, 0); 148 | 149 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 150 | jitdump.writeFunction(info); 151 | } 152 | catch(Exception e){ 153 | Assertions.fail(e); 154 | } 155 | 156 | try(var ch = FileChannel.open(dumpfile.toPath(), StandardOpenOption.READ)){ 157 | final int fileHeaderSize = 40; // sizeof(struct jitheader) 158 | final int functionEntrySize = 56 + info.name().length() + 1 + info.size(); // sizeof(jr_code_load) == 56, null char of method name should be included. 159 | 160 | var buf = ByteBuffer.allocate(functionEntrySize) 161 | .order(ByteOrder.nativeOrder()); 162 | 163 | // Skip file header 164 | ch.position(fileHeaderSize); 165 | 166 | // Read function entry 167 | ch.read(buf); 168 | buf.flip(); 169 | 170 | // id 171 | Assertions.assertEquals(0, buf.getInt()); 172 | // total_size 173 | Assertions.assertEquals(functionEntrySize, buf.getInt()); 174 | // timestamp (skip) 175 | buf.position(buf.position() + 8); 176 | // pid 177 | Assertions.assertEquals((int)ProcessHandle.current().pid(), buf.getInt()); 178 | // tid (skip) 179 | buf.position(buf.position() + 4); 180 | // vma 181 | Assertions.assertEquals(0x1234L, buf.getLong()); 182 | // code_addr 183 | Assertions.assertEquals(0x1234L, buf.getLong()); 184 | // code_size 185 | Assertions.assertEquals(0L, buf.getLong()); 186 | // code_index 187 | Assertions.assertEquals(0L, buf.getLong()); 188 | // function name 189 | byte[] nameInBytes = new byte[info.name().length()]; 190 | buf.get(nameInBytes); 191 | Assertions.assertEquals("func", new String(nameInBytes)); 192 | Assertions.assertEquals((byte)0, buf.get()); 193 | } 194 | catch(Exception e){ 195 | Assertions.fail(e); 196 | } 197 | 198 | // cleanup 199 | dumpfile.delete(); 200 | } 201 | 202 | @Test 203 | @EnabledOnOs(OS.WINDOWS) 204 | public void testGetInstanceOnWindows(){ 205 | Assertions.assertThrows(UnsupportedPlatformException.class, () -> JitDump.getInstance(Path.of("C:\\tmp"))); 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/linux/CodeSegmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2023, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.linux; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.io.IOException; 27 | import java.lang.ref.Cleaner; 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | import java.util.NoSuchElementException; 31 | 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.PlatformException; 34 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 35 | 36 | 37 | @EnabledOnOs(OS.LINUX) 38 | public class CodeSegmentTest{ 39 | 40 | private String[] findMemorySegmentFromMaps(long startAddr, long endAddr) throws IOException{ 41 | String startAddrInStr = Long.toHexString(startAddr); 42 | String endAddrInStr = Long.toHexString(endAddr); 43 | try(var stream = Files.lines(Path.of("/proc/self/maps"))){ 44 | return stream.map(l -> l.split(" ")) 45 | .filter(l -> l[0].startsWith(startAddrInStr) || l[0].endsWith(endAddrInStr)) 46 | .findAny() 47 | .get(); 48 | } 49 | } 50 | 51 | @Test 52 | public void testAllocateCodeSegmentWithDefaultSize(){ 53 | try(var seg = new CodeSegment()){ 54 | var addr = seg.getAddr(); 55 | long startAddr = addr.address(); 56 | long endAddr = startAddr + 4096L; 57 | 58 | var entries = findMemorySegmentFromMaps(startAddr, endAddr); 59 | long actualStartAddr = Long.parseLong(entries[0].split("-")[0], 16); 60 | long actualEndAddr = Long.parseLong(entries[0].split("-")[1], 16); 61 | char execBit = entries[1].charAt(2); 62 | 63 | Assertions.assertTrue((actualEndAddr - actualStartAddr) >= 4096L); 64 | Assertions.assertEquals('x', execBit); 65 | } 66 | catch(Throwable t){ 67 | Assertions.fail(t); 68 | } 69 | } 70 | 71 | @Test 72 | public void testAllocateCodeSegmentWithGivenSize(){ 73 | final long size = 8192L; 74 | 75 | try(var seg = new CodeSegment(size)){ 76 | var addr = seg.getAddr(); 77 | long startAddr = addr.address(); 78 | long endAddr = startAddr + size; 79 | 80 | var entries = findMemorySegmentFromMaps(startAddr, endAddr); 81 | long actualStartAddr = Long.parseLong(entries[0].split("-")[0], 16); 82 | long actualEndAddr = Long.parseLong(entries[0].split("-")[1], 16); 83 | char execBit = entries[1].charAt(2); 84 | 85 | Assertions.assertTrue((actualEndAddr - actualStartAddr) >= size); 86 | Assertions.assertEquals('x', execBit); 87 | } 88 | catch(Throwable t){ 89 | Assertions.fail(t); 90 | } 91 | } 92 | 93 | @Test 94 | public void testCloseWithCleaner() throws PlatformException, UnsupportedPlatformException, InterruptedException{ 95 | Object obj = new Object(); 96 | var seg = new CodeSegment(); 97 | var rawAddr = seg.getAddr().address(); 98 | var action = new CodeSegment.CleanerAction(seg); 99 | Cleaner.create() 100 | .register(obj, action); 101 | 102 | // Release obj 103 | obj = null; 104 | System.gc(); 105 | 106 | // Attempt to give opportunity to work Clerner 107 | Thread.yield(); 108 | Thread.sleep(3000); 109 | 110 | // Check memory mapping whether region for CodeSegment is released. 111 | Assertions.assertThrows(NoSuchElementException.class, () -> { 112 | try(var stream = Files.lines(Path.of("/proc/self/maps"))){ 113 | stream.filter(l -> l.startsWith(Long.toHexString(rawAddr))) 114 | .findAny() 115 | .get(); 116 | } 117 | }); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /tools/disas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | ffmasm-disassembler 27 | 0.1.1 28 | jar 29 | 30 | ffmasm-disassembler 31 | 32 | 33 | scm:git:git://github.com/YaSuenag/ffmasm.git 34 | scm:git:ssh://github.com:YaSuenag/ffmasm.git 35 | https://github.com/YaSuenag/ffmasm 36 | 37 | 38 | 39 | UTF-8 40 | 22 41 | 22 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-compiler-plugin 49 | 3.10.1 50 | 51 | 52 | -Xlint:all 53 | 54 | true 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-javadoc-plugin 60 | 3.4.1 61 | 62 | 63 | 64 | 65 | 66 | 67 | github 68 | GitHub Packages 69 | https://maven.pkg.github.com/YaSuenag/ffmasm 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /tools/disas/src/main/java/com/yasuenag/ffmasmtools/disas/Disassembler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasmtools.disas; 20 | 21 | import java.lang.foreign.Arena; 22 | import java.lang.foreign.FunctionDescriptor; 23 | import java.lang.foreign.Linker; 24 | import java.lang.foreign.MemorySegment; 25 | import java.lang.foreign.SymbolLookup; 26 | import java.lang.foreign.ValueLayout; 27 | import java.lang.invoke.MethodHandle; 28 | import java.nio.file.Path; 29 | import java.util.regex.Pattern; 30 | 31 | 32 | public class Disassembler{ 33 | 34 | private static final MethodHandle decode_instructions_virtual; 35 | private static final MemorySegment disasOptions; 36 | 37 | private static Path getHSDISPath(){ 38 | var prop = System.getProperty("hsdis"); 39 | if(prop != null){ 40 | return Path.of(prop); 41 | } 42 | 43 | String libName = "hsdis-" + System.getProperty("os.arch"); 44 | var osName = System.getProperty("os.name"); 45 | if(osName.equals("Linux")){ 46 | libName += ".so"; 47 | } 48 | else if(osName.startsWith("Windows")){ 49 | libName += ".dll"; 50 | } 51 | else{ 52 | throw new RuntimeException(osName); 53 | } 54 | 55 | var javaHome = Path.of(System.getProperty("java.home")); 56 | var vmMatcher = Pattern.compile("^.+ ([^ ]+) VM$").matcher(System.getProperty("java.vm.name")); 57 | vmMatcher.find(); 58 | var vmType = vmMatcher.group(1).toLowerCase(); 59 | // Search order of hsdis: 60 | // 1. /lib//libhsdis-.so 61 | // 2. /lib//hsdis-.so 62 | // 3. /lib/hsdis-.so 63 | // 4. hsdis-.so (using LD_LIBRARY_PATH) 64 | // See src/hotspot/share/compiler/disassembler.cpp in OpenJDK for details. 65 | Path p = javaHome.resolve("lib", vmType, "lib" + libName); 66 | if(p.toFile().exists()){ 67 | return p; 68 | } 69 | else{ 70 | p = javaHome.resolve("lib", vmType, libName); 71 | if(p.toFile().exists()){ 72 | return p; 73 | } 74 | else{ 75 | p = javaHome.resolve("lib", libName); 76 | if(p.toFile().exists()){ 77 | return p; 78 | } 79 | } 80 | } 81 | return Path.of(libName); 82 | } 83 | 84 | static{ 85 | var hsdisPath = getHSDISPath(); 86 | var sym = SymbolLookup.libraryLookup(hsdisPath, Arena.ofAuto()); 87 | var disas = sym.find("decode_instructions_virtual").get(); 88 | var desc = FunctionDescriptor.of(ValueLayout.ADDRESS, // return value 89 | ValueLayout.ADDRESS, // start_va 90 | ValueLayout.ADDRESS, // end_va 91 | ValueLayout.ADDRESS, // buffer 92 | ValueLayout.JAVA_LONG, // length 93 | ValueLayout.ADDRESS, // event_callback 94 | ValueLayout.ADDRESS, // event_stream 95 | ValueLayout.ADDRESS, // printf_callback 96 | ValueLayout.ADDRESS, // printf_stream 97 | ValueLayout.ADDRESS, // options 98 | ValueLayout.JAVA_INT // newline 99 | ); 100 | decode_instructions_virtual = Linker.nativeLinker() 101 | .downcallHandle(disas, desc); 102 | disasOptions = Arena.ofAuto() 103 | .allocateFrom(""); 104 | } 105 | 106 | public static void dumpToStdout(MemorySegment code){ 107 | try{ 108 | decode_instructions_virtual.invoke( 109 | code, // start_va 110 | code.asSlice(code.byteSize()), // end_va 111 | code, // buffer 112 | code.byteSize(), // length 113 | MemorySegment.NULL, // event_callback 114 | MemorySegment.NULL, // event_stream 115 | MemorySegment.NULL, // printf_callback 116 | MemorySegment.NULL, // printf_stream 117 | disasOptions, // options 118 | 1 // newline 119 | ); 120 | } 121 | catch(Throwable t){ 122 | throw new RuntimeException(t); 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /tools/disas/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | module com.yasuenag.ffmasmtools.disas { 20 | exports com.yasuenag.ffmasmtools.disas; 21 | } 22 | --------------------------------------------------------------------------------