Introduction of the Java bindings (#364)

* Add the structure for the Java Bindings

Set up the structure hosting the Java bindings using JNA,
and provides a binding for the function ks_version.

* Ignore the compiled extension of macOS libraries.

* Add infrastructure to open and close Keystone handles.

Keystone enumerations have been added, as well as the required
wrappers for JNA to convert the enumerations to and from the
native library.

Create exceptions for handling issues related to Keystone.

Implement a cleaner that automatically collect any opened
Keystone handles when a Keystone instance is garbage collected.

* The close method of Keystone is now thread-safe.

* Rename the exception KeystoneOpenFailedException into OpenFailedKeystoneException.

* Add the assemble function into the wrapper of Keystone.

* Enhance the exceptions thrown by Keystone using ks_strerror.

The exception classes have been refactored to shorten hardcoded
error messages into the wrapper and instead, rely on the error
messages generated by the native library, thanks to the function
ks_strerror.

* Add the ability to check with a given architecture is supported.

Enhance the wrapper to map the function function ks_arch_supported
in order to determine whether a given architecture is supported.

* Add the features the change the assembly syntax and resolve symbols.

Enhance the wrapper in order to support the changes of options.

* Document the class KeystoneEncoded.

* Add additional unit tests for a better coverage.

* Reorder the native functions import and document the native functions prototype.

* Optimize the code.

* Add the target folder to the gitignore.

* Add missing Javadoc annotations for the exceptions.

* Add a gitkeep for the folder hosting the Keystone library for macOS.

* Update the Maven configuration for version and dependencies.

Use the version convention defined in the README, add a couple
of testing dependencies and plugins to package the bindings in
a fat JAR and enable Maven to use jUnit 5.

* Use JNA Direct Mapping for better performances on native function calls.

* Add the README.

* Add myself to the credits :)

* Use the syntax highlighting of Java in the README.

* Fix a typo.

* Optimize imports.

* Add predefined folders hosting the library Keystone on Windows.

* Add the Java bindings to the README of the bindings summary.
This commit is contained in:
Jämes Ménétrey
2018-07-18 14:05:01 +08:00
committed by Nguyen Anh Quynh
parent 067d2bdfa3
commit 097dca275a
33 changed files with 2307 additions and 0 deletions
+1
View File
@@ -8,6 +8,7 @@
*.so.*
*.exe
*.dll
*.dylib
*.class
*.jar
*.pyc
+1
View File
@@ -49,3 +49,4 @@ Ruben Boonen: PowerShell binding.
Marco Fornaro: C# binding.
David Zimmer: VB6 binding.
Michael Mohr: Debian packaging.
Jämes Ménétrey (ZenLulz): Java binding.
+1
View File
@@ -13,6 +13,7 @@ Except Python, all other bindings are contributed by community.
- C# binding: by Marco Fornaro
- VB6 binding: by David Zimmer
- Masm binding: by mrfearless
- Java binding: by Jämes Ménétrey (ZenLulz)
Other bindings maintained externally by community:
+3
View File
@@ -0,0 +1,3 @@
.idea
*.iml
target/
+5
View File
@@ -0,0 +1,5 @@
# Changelog of the Java bindings for Keystone
**Version 0.9.1-0: July 16th, 2018**:
- Initial public release.
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2018 Jämes Ménétrey (aka ZenLulz) <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+100
View File
@@ -0,0 +1,100 @@
# Java bindings for Keystone
Java bindings for the [Keystone](http://www.keystone-engine.org/) engine. Require JDK 10+.
## Sample
```java
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneMode;
import keystone.exceptions.AssembleFailedKeystoneException;
public class App {
public static void main(String[] args) {
try (Keystone keystone = new Keystone(KeystoneArchitecture.X86, KeystoneMode.Mode32)) {
try {
var result = keystone.assemble("INC ecx; DEC edx");
System.out.println("Number of statements encoded: " + result.getNumberOfStatements());
System.out.println("Base address: " + String.format("0x%08X", result.getAddress()));
System.out.print("Encoded bytes: ");
for (byte b: result.getMachineCode()) {
System.out.print(String.format("%X ", b));
}
} catch (AssembleFailedKeystoneException e) {
System.out.println(e.getMessage());
}
}
}
}
```
Output:
> Number of statements encoded: 2
Base address: 0x00000000
Encoded bytes: 41 4A
Other samples are provided by the unit tests included with the library, which cover most of the code.
## Getting Started
1. Clone the repository locally.
2. Download or compile Keystone library and store it in the folder `src/main/resources/{os-prefix}/{keystone-lib}`, according [the specifications of JNA](https://java-native-access.github.io/jna/4.5.0/javadoc/index.html?com/sun/jna/NativeLibrary.html), or [the related unit test](https://github.com/java-native-access/jna/blob/7122be473e5f6179deb1c2b1c2fdeb77d8486fed/test/com/sun/jna/PlatformTest.java#L29).
3. Compile the Java bindings and issue the JAR using Maven `mvn package`. The unit tests are automatically while running the goal.
4. In your project, include `target/binding-java-{version}.jar` and [JNA](https://github.com/java-native-access/jna) using your favourite dependency manager, or include `target/binding-java-{version}-jar-with-dependencies.jar` that is packaged with JNA.
### Exception handling
Each operation that returns the native enumeration `ks_err` is wrapped in an instance of a subclass of `KeystoneException`, thrown when an error occurs. The Javadoc indicates the exceptions thrown by the Java bindings for Keystone.
### Garbage collection
Keystone library requires to open and close an handle, that must be collected once an instance of the class `Keystone` is disposed. For that purpose, the class implements the interface `AutoCloseable`. Nonetheless, the Java bindings also implement a cleaning mechanism that automatically collects the handle when the instance of the class is *phantom reachable*, meaning no memory leak can happen, even if the instance is not closed properly.
### Native function calls
The Java bindings for Keystone rely on [JNA Direct Mapping](https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md) to improve the performances of native calls, approaching that of custom JNI.
## Found an issue or bug ?
Feel free to open a GitHub issue on [the official repository of Keystone](https://github.com/keystone-engine/keystone/issues) and ping the contributors.
## Contributors
Author: Jämes Ménétrey ([@ZenLulz](https://github.com/ZenLulz/))
Maintainers:
- Need some people here :) Feel free to contribute !
### Want to contribute ?
Hey you! Your help is more than welcome! Things to keep in mind when working on the Java bindings for Keystone:
- Think about the backward compatibility; while code refactoring is a good practice, changing entirely the API *may* result in struggles.
- Elaborate the unit tests that prove your code is working. Test all the paths of the newly-added functions/classes (IntelliJ can show some metrics using Coverage). Keep the code coverage high!
- Please; write the required Javadoc, so every developer has the chance to understand your code.
- Update the changelog with a summary of your changes.
#### Version notation
The version of the Java bindings for Keystone is indicated in the file Maven configuration file (`pom.xml`). The major, minor and incremental versions (w.x.y) match the version of the library Keystone that the bindings is developed with. The build number (the -z in w.x.y-z) is incremented for each newer version of the Java bindings. Please, don't forget to increment this version when you submit a pull request.
On the last commit for a pull request, please create a tag called `java-bindings-w.x.y-z`.
#### Pull request submission
Ping the contributors of the Java bindings when submitting a pull request, so your changes can be peer reviewed.
## License
The Java bindings for Keystone is open-sourced software licensed under the MIT license.
The license of the library Keystone may be different and is available [at the root of the repository of Keystone](https://github.com/keystone-engine/keystone).
+112
View File
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2018 Jämes Ménétrey <[email protected]>
~
~ This file is part of the Keystone Java bindings which is released under MIT.
~ See file LICENSE in the Java bindings folder for full license details.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>keystone</groupId>
<artifactId>java-bindings</artifactId>
<version>0.9.1-0</version>
<name>Keystone Java bindings</name>
<description>Keystone assembler framework: Core (Arm, Arm64, Hexagon, Mips, PowerPC, Sparc, SystemZ and X86) orm.</description>
<url>https://github.com/keystone-engine/keystone/tree/master/bindings/java</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>10</java.version>
<jna.version>4.5.1</jna.version>
<junit.jupiter.version>5.2.0</junit.jupiter.version>
<junit.platform.version>1.2.0</junit.platform.version>
</properties>
<developers>
<developer>
<email>[email protected]</email>
<name>Jämes Ménétrey</name>
<url>https://github.com/ZenLulz</url>
<roles>
<role>Developer</role>
</roles>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>${jna.version}</version>
</dependency>
<!-- Dependencies for testing. -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Configure Maven to compile using Java 10. -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- Package the project into a JAR with dependencies. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Unit tests are runnable using maven. -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,249 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import keystone.exceptions.AssembleFailedKeystoneException;
import keystone.exceptions.OpenFailedKeystoneException;
import keystone.exceptions.SetOptionFailedKeystoneException;
import keystone.natives.CleanerContainer;
import keystone.natives.DirectMappingKeystoneNative;
import keystone.natives.KeystoneCleanerContainer;
import keystone.utilities.Version;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The Keystone engine.
*/
public class Keystone implements AutoCloseable {
/**
* The pointer to the Keystone native resource.
*/
private final Pointer ksEngine;
/**
* The cleaner container that frees up the native resource if this object is not properly closed and is
* candidate for garbage collection.
*/
private final CleanerContainer ksEngineCleaner;
/**
* Indicates whether the current instance of Keystone has been closed.
*/
private final AtomicBoolean hasBeenClosed;
/**
* The memory retention of the symbol resolver callback, in order to prevent the garbage collector
* to free up the callback, that would result in a crash, as the native library still has a reference to it.
*/
private SymbolResolverCallback symbolResolverCallback;
/**
* Initializes a new instance of the class {@link Keystone}.
* <p>
* Some architectures are not supported. Use the static method {@link #isArchitectureSupported} to determine
* whether the engine support the architecture.
*
* @param architecture The architecture of the code generated by Keystone.
* @param mode The mode type.
* @throws OpenFailedKeystoneException if the Keystone library cannot be opened properly.
*/
public Keystone(KeystoneArchitecture architecture, KeystoneMode mode) {
ksEngine = initializeKeystoneEngine(architecture, mode);
ksEngineCleaner = initializeKeystoneCleanerContainer();
hasBeenClosed = new AtomicBoolean(false);
}
/**
* Determines whether the given architecture is supported by Keystone.
*
* @param architecture The architecture type to check.
* @return The return value is {@code true} if the architecture is supported, otherwise {@code false}.
*/
public static boolean isArchitectureSupported(KeystoneArchitecture architecture) {
return DirectMappingKeystoneNative.ks_arch_supported(architecture);
}
/**
* Opens an handle of Keystone.
*
* @param architecture The architecture of the code generated by Keystone.
* @param mode The mode type.
* @return The return value is a pointer to the handle of Keystone.
* @throws OpenFailedKeystoneException if the Keystone library cannot be opened properly.
*/
private Pointer initializeKeystoneEngine(KeystoneArchitecture architecture, KeystoneMode mode) {
var pointerToEngine = new PointerByReference();
var openResult = DirectMappingKeystoneNative.ks_open(architecture, mode, pointerToEngine);
if (openResult != KeystoneError.Ok) {
throw new OpenFailedKeystoneException(openResult);
}
return pointerToEngine.getValue();
}
/**
* Initializes the cleaner object, that is going to close the native handle of Keystone if
* the instance is garbage collected.
*
* @return The return value is a cleaner container.
*/
private CleanerContainer initializeKeystoneCleanerContainer() {
return new KeystoneCleanerContainer(ksEngine);
}
/**
* Assembles a string that contains assembly code.
*
* @param assembly The assembly instructions. Use ; or \n to separate statements.
* @return The return value is the machine code of the assembly instructions.
* @throws AssembleFailedKeystoneException if the assembly code cannot be assembled properly.
*/
public KeystoneEncoded assemble(String assembly) {
return assemble(assembly, 0);
}
/**
* Assembles a string that contains assembly code, located at a given address location.
*
* @param assembly The assembly instructions. Use ; or \n to separate statements.
* @param address The address of the first assembly instruction.
* @return The return value is the machine code of the assembly instructions.
* @throws AssembleFailedKeystoneException if the assembly code cannot be assembled properly.
*/
public KeystoneEncoded assemble(String assembly, int address) {
var pointerToMachineCodeBuffer = new PointerByReference();
var pointerToMachineCodeSize = new IntByReference();
var pointerToNumberOfStatements = new IntByReference();
var result = DirectMappingKeystoneNative.ks_asm(ksEngine, assembly, address, pointerToMachineCodeBuffer,
pointerToMachineCodeSize, pointerToNumberOfStatements);
if (result != 0) {
var errorCode = DirectMappingKeystoneNative.ks_errno(ksEngine);
throw new AssembleFailedKeystoneException(errorCode, assembly);
}
var machineCodeBuffer = pointerToMachineCodeBuffer.getValue();
var machineCode = machineCodeBuffer.getByteArray(0, pointerToMachineCodeSize.getValue());
DirectMappingKeystoneNative.ks_free(machineCodeBuffer);
return new KeystoneEncoded(machineCode, address, pointerToNumberOfStatements.getValue());
}
/**
* Assembles a string that contains assembly code.
*
* @param assembly A collection of assembly instructions.
* @return The return value is the machine code of the assembly instructions.
* @throws AssembleFailedKeystoneException if the assembly code cannot be assembled properly.
*/
public KeystoneEncoded assemble(Iterable<String> assembly) {
return assemble(assembly, 0);
}
/**
* Assembles a string that contains assembly code, located at a given address location.
*
* @param assembly A collection of assembly instructions.
* @param address The address of the first assembly instruction.
* @return The return value is the machine code of the assembly instructions.
* @throws AssembleFailedKeystoneException if the assembly code cannot be assembled properly.
*/
public KeystoneEncoded assemble(Iterable<String> assembly, int address) {
return assemble(String.join(";", assembly), address);
}
/**
* Gets the major and minor version numbers.
*
* @return The returned value is an instance of the class {@link Version}, containing the major and minor version numbers.
*/
public Version version() {
var major = new IntByReference();
var minor = new IntByReference();
DirectMappingKeystoneNative.ks_version(major, minor);
return new Version(major.getValue(), minor.getValue());
}
/**
* Sets the syntax of the assembly code used in this instance of Keystone.
*
* @param syntax The syntax of the assembly code.
* @throws SetOptionFailedKeystoneException if the syntax is not supported.
* @throws SetOptionFailedKeystoneException if the pair of type and value is not valid.
*/
public void setAssemblySyntax(KeystoneOptionValue.KeystoneOptionSyntax syntax) {
setOption(KeystoneOptionType.Syntax, syntax.value());
}
/**
* Sets an option for Keystone engine at runtime using a not-strongly typed option value.
* <p>
* It is suggested to prefer the methods {@link #setAssemblySyntax(KeystoneOptionValue.KeystoneOptionSyntax)},
* {@link #setSymbolResolver(SymbolResolverCallback)} or {@link #unsetSymbolResolver()}.
*
* @param type The type of the option.
* @param value The value of the option.
* @throws SetOptionFailedKeystoneException if the pair of type and value is not valid.
*/
public void setOption(KeystoneOptionType type, int value) {
var result = DirectMappingKeystoneNative.ks_option(ksEngine, type, value);
if (result != KeystoneError.Ok) {
throw new SetOptionFailedKeystoneException(result, type, value);
}
}
/**
* Sets a symbol resolver callback, to resolve unrecognized symbols encountered in the assembly code.
* <p>
* If the method is called many times, only the last callback will be triggered.
*
* @param callback The symbol resolver callback.
* @throws SetOptionFailedKeystoneException if the pair of type and value is not valid.
*/
public void setSymbolResolver(SymbolResolverCallback callback) {
symbolResolverCallback = callback;
DirectMappingKeystoneNative.ks_option(ksEngine, KeystoneOptionType.SymbolResolver, callback);
}
/**
* Unsets the current symbol resolver. The symbol resolver instance can be freely collected afterwards.
*
* @throws SetOptionFailedKeystoneException if the pair of type and value is not valid.
*/
public void unsetSymbolResolver() {
DirectMappingKeystoneNative.ks_option(ksEngine, KeystoneOptionType.SymbolResolver, 0);
symbolResolverCallback = null;
}
/**
* Closes this resource, relinquishing any underlying resources.
* This method is invoked automatically on objects managed by the
* {@code try}-with-resources statement.
* <p>
* The call to this method is thread-safe.
*/
@Override
public void close() {
var hasBeenAlreadyClosed = hasBeenClosed.getAndSet(true);
if (!hasBeenAlreadyClosed) {
ksEngineCleaner.close();
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import keystone.jna.JnaEnum;
import java.util.HashMap;
import java.util.Map;
/**
* The supported architectures of Keystone.
*/
public enum KeystoneArchitecture implements JnaEnum {
/**
* ARM architecture (including Thumb, Thumb-2).
*/
Arm(1),
/**
* ARM-64, also called AArch64.
*/
Arm64(2),
/**
* Mips architecture.
*/
Mips(3),
/**
* X86 architecture (including x86 & x86-64).
*/
X86(4),
/**
* PowerPC architecture (currently unsupported).
*/
Ppc(5),
/**
* Sparc architecture.
*/
Sparc(6),
/**
* SystemZ architecture (S390X).
*/
SystemZ(7),
/**
* Hexagon architecture
*/
Hexagon(8),
/**
* Ethereum Virtual Machine architecture.
*/
Evm(9),
Max(10);
/**
* Mapping table to determine an enumeration value based on an integer with a complexity of θ(1).
*/
private static Map<Integer, KeystoneArchitecture> intToEnumMapping = new HashMap<>();
static {
// Initializes the mapping table.
for (KeystoneArchitecture architecture : KeystoneArchitecture.values()) {
intToEnumMapping.put(architecture.value(), architecture);
}
}
/**
* Holds the integer value of the enumeration, that corresponds to the value used in the enumeration in C.
*/
private final int value;
/**
* Constructor of the enumeration.
*
* @param value The integer value, that corresponds to the value used in the enumeration in C.
*/
KeystoneArchitecture(int value) {
this.value = value;
}
/**
* Converts an integer value into its corresponding enumeration value.
* <p>
* The complexity of the conversion is θ(1).
*
* @param value The integer value.
* @return The return value is a value of the enumeration.
*/
public static KeystoneArchitecture fromValue(Integer value) {
return intToEnumMapping.get(value);
}
/**
* Retrieves the value of the enumeration, that corresponds to the value used in the enumeration in C.
*
* @return The return value is an integer value.
*/
public int value() {
return value;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
/**
* Wrap the result of an assemble using Keystone.
*/
public class KeystoneEncoded {
/**
* The machine code generated by Keystone.
*/
private final byte[] machineCode;
/**
* The address of the first assembly instruction.
*/
private final int address;
/**
* The number of statements successfully processed.
*/
private final int numberOfStatements;
/**
* Creates a new instance of the class {@link KeystoneEncoded}.
*
* @param machineCode The machine code generated by Keystone.
* @param address The address of the first assembly instruction.
* @param numberOfStatements The number of statements successfully processed.
*/
public KeystoneEncoded(byte[] machineCode, int address, int numberOfStatements) {
this.machineCode = machineCode;
this.address = address;
this.numberOfStatements = numberOfStatements;
}
/**
* Gets the machine code generated by Keystone.
*/
public byte[] getMachineCode() {
return machineCode;
}
/**
* Gets the address of the first assembly instruction.
*/
public int getAddress() {
return address;
}
/**
* Gets the number of statements successfully processed.
*/
public int getNumberOfStatements() {
return numberOfStatements;
}
}
@@ -0,0 +1,298 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import keystone.jna.JnaEnum;
import java.util.HashMap;
import java.util.Map;
/**
* All type of errors encountered by Keystone API.
*/
public enum KeystoneError implements JnaEnum {
/**
* No error: everything was fine.
*/
Ok(0),
/**
* Out-Of-Memory error: ks_open(), ks_emulate().
*/
Nomem(1),
/**
* Unsupported architecture: ks_open().
*/
Arch(2),
/**
* Invalid handle.
*/
Handle(3),
/**
* Invalid/unsupported mode: ks_open().
*/
Mode(4),
/**
* Unsupported version (bindings).
*/
Version(5),
/**
* Unsupported option.
*/
OptInvalid(6),
// generic input assembly errors - parser specific
/**
* Unknown token in expression.
*/
AsmExprToken(Base.asm),
/**
* Literal value out of range for directive.
*/
AsmDirectiveValueRange(Base.asm + 1),
/**
* Expected identifier in directive.
*/
AsmDirectiveId(Base.asm + 2),
/**
* Unexpected token in directive.
*/
AsmDirectiveToken(Base.asm + 3),
/**
* Expected string in directive.
*/
AsmDirectiveStr(Base.asm + 4),
/**
* Expected comma in directive.
*/
AsmDirectiveComma(Base.asm + 5),
/**
* Expected relocation name in directive.
*/
AsmDirectiveRelocName(Base.asm + 6),
/**
* Unexpected token in .reloc directive.
*/
AsmDirectiveRelocToken(Base.asm + 7),
/**
* Invalid floating point in directive.
*/
AsmDirectiveFpoint(Base.asm + 8),
/**
* Unknown directive.
*/
AsmDirectiveUnknown(Base.asm + 9),
/**
* Invalid equal directive.
*/
AsmDirectiveEqu(Base.asm + 10),
/**
* (Generic) invalid directive.
*/
AsmDirectiveInvalid(Base.asm + 11),
/**
* Invalid variant.
*/
AsmVariantInvalid(Base.asm + 12),
/**
* Brackets expression not supported on this target.
*/
AsmExprBracket(Base.asm + 13),
/**
* Unexpected symbol modifier following '@'.
*/
AsmSymbolModifier(Base.asm + 14),
/**
* Invalid symbol redefinition.
*/
AsmSymbolRedefined(Base.asm + 15),
/**
* Cannot find a symbol.
*/
AsmSymbolMissing(Base.asm + 16),
/**
* Expected ')' in parentheses expression.
*/
AsmRparen(Base.asm + 17),
/**
* Unexpected token at start of statement.
*/
AsmStatToken(Base.asm + 18),
/**
* Unsupported token yet.
*/
AsmUnsupported(Base.asm + 19),
/**
* Unexpected token in macro instantiation.
*/
AsmMacroToken(Base.asm + 20),
/**
* Unbalanced parentheses in macro argument.
*/
AsmMacroParen(Base.asm + 21),
/**
* Expected '=' after formal parameter identifier.
*/
AsmMacroEqu(Base.asm + 22),
/**
* Too many positional arguments.
*/
AsmMacroArgs(Base.asm + 23),
/**
* Macros cannot be nested more than 20 levels deep.
*/
AsmMacroLevelsExceed(Base.asm + 24),
/**
* Invalid macro string.
*/
AsmMacroStr(Base.asm + 25),
/**
* Invalid macro (generic error).
*/
AsmMacroInvalid(Base.asm + 26),
/**
* Unexpected backslash at end of escaped string.
*/
AsmEscBackslash(Base.asm + 27),
/**
* Invalid octal escape sequence (out of range).
*/
AsmEscOctal(Base.asm + 28),
/**
* Invalid escape sequence (unrecognized character).
*/
AsmEscSequence(Base.asm + 29),
/**
* Broken escape string.
*/
AsmEscStr(Base.asm + 30),
/**
* Invalid token.
*/
AsmTokenInvalid(Base.asm + 31),
/**
* This instruction is unsupported in this mode.
*/
AsmInsnUnsupported(Base.asm + 32),
/**
* Invalid fixup.
*/
AsmFixupInvalid(Base.asm + 33),
/**
* Invalid label.
*/
AsmLabelInvalid(Base.asm + 34),
/**
* Invalid fragment.
*/
AsmFragmentInvalid(Base.asm + 35),
// generic input assembly errors - architecture specific
AsmInvalidOperand(Base.asmArch),
AsmMissingFeature(Base.asmArch + 1),
AsmMnemonicFail(Base.asmArch + 2);
/**
* Mapping table to determine an enumeration value based on an integer with a complexity of θ(1).
*/
private static Map<Integer, KeystoneError> intToEnumMapping = new HashMap<>();
static {
// Initializes the mapping table.
for (KeystoneError error : KeystoneError.values()) {
intToEnumMapping.put(error.value(), error);
}
}
/**
* Holds the integer value of the enumeration, that corresponds to the value used in the enumeration in C.
*/
private final int value;
/**
* Constructor of the enumeration.
*
* @param value The integer value, that corresponds to the value used in the enumeration in C.
*/
KeystoneError(int value) {
this.value = value;
}
/**
* Converts an integer value into its corresponding enumeration value.
* <p>
* The complexity of the conversion is θ(1).
*
* @param value The integer value.
* @return The return value is a value of the enumeration.
*/
public static KeystoneError fromValue(Integer value) {
return intToEnumMapping.get(value);
}
/**
* Retrieves the value of the enumeration, that corresponds to the value used in the enumeration in C.
*
* @return The return value is an integer value.
*/
public int value() {
return value;
}
/**
* Internal interface referencing the base error codes.
*/
interface Base {
int asm = 128;
int asmArch = 512;
}
}
@@ -0,0 +1,170 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import keystone.jna.JnaEnum;
import java.util.HashMap;
import java.util.Map;
/**
* The supported mode type by Keystone.
*/
public enum KeystoneMode implements JnaEnum {
/**
* Little-endian mode (default mode).
*/
LittleEndian(0),
/**
* Big-endian mode.
*/
BigEndian(1 << 30),
// arm / arm64
/**
* ARM mode.
*/
Arm(1),
/**
* THUMB mode (including Thumb-2).
*/
ArmThumb(1 << 4),
/**
* ARMv8 A32 encodings for ARM.
*/
ArmV8(1 << 6),
// mips
/**
* MicroMips mode.
*/
MipsMicro(1 << 4),
/**
* Mips III ISA.
*/
Mips3(1 << 5),
/**
* Mips32r6 ISA.
*/
Mips32r6(1 << 6),
/**
* Mips32 ISA.
*/
Mips32(1 << 2),
/**
* Mips64 ISA.
*/
Mips64(1 << 3),
// x86 / x64
/**
* 16-bit mode.
*/
Mode16(1 << 1),
/**
* 32-bit mode.
*/
Mode32(1 << 2),
/**
* 64-bit mode.
*/
Mode64(1 << 3),
// ppc
/**
* 32-bit mode.
*/
Ppc32(1 << 2),
/**
* 64-bit mode.
*/
Ppc64(1 << 3),
/**
* Quad Processing eXtensions mode.
*/
PpcQpx(1 << 4),
// sparc
/**
* 32-bit mode.
*/
Sparc32(1 << 2),
/**
* 64-bit mode.
*/
Sparc64(1 << 3),
/**
* SparcV9 mode.
*/
SparcV9(1 << 4);
/**
* Mapping table to determine an enumeration value based on an integer with a complexity of θ(1).
*/
private static Map<Integer, KeystoneMode> intToEnumMapping = new HashMap<>();
static {
// Initializes the mapping table.
for (KeystoneMode mode : KeystoneMode.values()) {
intToEnumMapping.put(mode.value(), mode);
}
}
/**
* Holds the integer value of the enumeration, that corresponds to the value used in the enumeration in C.
*/
private final int value;
/**
* Constructor of the enumeration.
*
* @param value The integer value, that corresponds to the value used in the enumeration in C.
*/
KeystoneMode(int value) {
this.value = value;
}
/**
* Converts an integer value into its corresponding enumeration value.
* <p>
* The complexity of the conversion is θ(1).
*
* @param value The integer value.
* @return The return value is a value of the enumeration.
*/
public static KeystoneMode fromValue(Integer value) {
return intToEnumMapping.get(value);
}
/**
* Retrieves the value of the enumeration, that corresponds to the value used in the enumeration in C.
*
* @return The return value is an integer value.
*/
public int value() {
return value;
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import keystone.jna.JnaEnum;
import java.util.HashMap;
import java.util.Map;
public enum KeystoneOptionType implements JnaEnum {
/**
* Choose syntax for input assembly.
*/
Syntax(1),
/**
* Set symbol resolver callback.
*/
SymbolResolver(2);
/**
* Mapping table to determine an enumeration value based on an integer with a complexity of θ(1).
*/
private static Map<Integer, KeystoneOptionType> intToEnumMapping = new HashMap<>();
static {
// Initializes the mapping table.
for (KeystoneOptionType type : KeystoneOptionType.values()) {
intToEnumMapping.put(type.value(), type);
}
}
/**
* Holds the integer value of the enumeration, that corresponds to the value used in the enumeration in C.
*/
private final int value;
/**
* Constructor of the enumeration.
*
* @param value The integer value, that corresponds to the value used in the enumeration in C.
*/
KeystoneOptionType(int value) {
this.value = value;
}
/**
* Converts an integer value into its corresponding enumeration value.
* <p>
* The complexity of the conversion is θ(1).
*
* @param value The integer value.
* @return The return value is a value of the enumeration.
*/
public static KeystoneOptionType fromValue(Integer value) {
return intToEnumMapping.get(value);
}
/**
* Retrieves the value of the enumeration, that corresponds to the value used in the enumeration in C.
*
* @return The return value is an integer value.
*/
public int value() {
return value;
}
}
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import keystone.jna.JnaEnum;
import java.util.HashMap;
import java.util.Map;
/**
* Runtime option values (associated with {@link KeystoneOptionType}).
*/
public class KeystoneOptionValue {
/**
* Runtime option values associated with the assembly syntax.
*
* @see KeystoneOptionType#Syntax
*/
public enum KeystoneOptionSyntax implements JnaEnum {
/**
* X86 Intel syntax - default on X86 (KS_OPT_SYNTAX).
*/
Intel(1),
/**
* X86 ATT asm syntax (KS_OPT_SYNTAX).
*/
Att(1 << 1),
/**
* X86 Nasm syntax (KS_OPT_SYNTAX).
*/
Nasm(1 << 2),
/**
* X86 Masm syntax (KS_OPT_SYNTAX) - unsupported yet.
*/
Masm(1 << 3),
/**
* X86 GNU GAS syntax (KS_OPT_SYNTAX).
*/
Gas(1 << 4),
/**
* All immediates are in hex format (i.e 12 is 0x12).
*/
Radix16(1 << 5);
/**
* Mapping table to determine an enumeration value based on an integer with a complexity of θ(1).
*/
private static Map<Integer, KeystoneOptionSyntax> intToEnumMapping = new HashMap<>();
static {
// Initializes the mapping table.
for (KeystoneOptionSyntax syntax : KeystoneOptionSyntax.values()) {
intToEnumMapping.put(syntax.value(), syntax);
}
}
/**
* Holds the integer value of the enumeration, that corresponds to the value used in the enumeration in C.
*/
private final int value;
/**
* Constructor of the enumeration.
*
* @param value The integer value, that corresponds to the value used in the enumeration in C.
*/
KeystoneOptionSyntax(int value) {
this.value = value;
}
/**
* Converts an integer value into its corresponding enumeration value.
* <p>
* The complexity of the conversion is θ(1).
*
* @param value The integer value.
* @return The return value is a value of the enumeration.
*/
public static KeystoneOptionSyntax fromValue(Integer value) {
return intToEnumMapping.get(value);
}
/**
* Retrieves the value of an element in the enumeration.
*
* @return The return value is an integer number, that represents the value in the native library.
*/
@Override
public int value() {
return value;
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import com.sun.jna.Callback;
import com.sun.jna.ptr.LongByReference;
/**
* An interface that represents a callback to provide a value for a missing symbol.
*/
public interface SymbolResolverCallback extends Callback {
/**
* A callback triggered when a unrecognized symbol is found.
*
* @param symbol The symbol to resolve.
* @param value The value to modify if the symbol is resolved.
* @return The return value must be {@code true} if the symbol can be resolved; otherwise {@code false}.
*/
boolean onResolve(String symbol, LongByReference value);
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.exceptions;
import keystone.KeystoneError;
/**
* An exception that represents a failure while assembling code.
*/
public class AssembleFailedKeystoneException extends KeystoneException {
/**
* The assembly code that generates the error.
*/
private final String assembly;
public AssembleFailedKeystoneException(KeystoneError keystoneError, String assembly) {
super(keystoneError, "Error while assembling `" + assembly + "`");
this.assembly = assembly;
}
/**
* Gets the assembly code that generates the error.
*/
public String getAssembly() {
return assembly;
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.exceptions;
import keystone.KeystoneError;
import keystone.natives.DirectMappingKeystoneNative;
/**
* The base class for all the exceptions thrown by the library Keystone.
*/
public abstract class KeystoneException extends RuntimeException {
/**
* The error thrown by Keystone.
*/
private final KeystoneError keystoneError;
/**
* Creates a new instance of {@link KeystoneException}.
*
* @param keystoneError The error code of Keystone.
* @param message A human-readable message of the error.
*/
KeystoneException(KeystoneError keystoneError, String message) {
super(message + " : " + DirectMappingKeystoneNative.ks_strerror(keystoneError));
this.keystoneError = keystoneError;
}
/**
* Gets the error code of Keystone.
*/
public KeystoneError getKeystoneError() {
return keystoneError;
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.exceptions;
import keystone.KeystoneError;
/**
* An exception that represents a failure to open the library Keystone.
*/
public class OpenFailedKeystoneException extends KeystoneException {
public OpenFailedKeystoneException(KeystoneError keystoneError) {
super(keystoneError, "Keystone library could not be opened");
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.exceptions;
import keystone.KeystoneError;
import keystone.KeystoneOptionType;
/**
* An exception that represents an error while setting an option in the Keystone library.
*/
public class SetOptionFailedKeystoneException extends KeystoneException {
/**
* The type of the option that generated the exception.
*/
private final KeystoneOptionType optionType;
/**
* The value of the option that generated the exception.
*/
private final int optionValue;
public SetOptionFailedKeystoneException(KeystoneError keystoneError, KeystoneOptionType type, int value) {
super(keystoneError, createErrorMessage(type, value));
this.optionType = type;
this.optionValue = value;
}
private static String createErrorMessage(KeystoneOptionType type, int value) {
return "Error while setting the option `" + type +
"` with the value `" + value + "`";
}
/**
* Gets the type of the option that generated the exception.
*/
public KeystoneOptionType getOptionType() {
return optionType;
}
/**
* Gets the value of the option that generated the exception.
*/
public int getOptionValue() {
return optionValue;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.jna;
import com.sun.jna.DefaultTypeMapper;
import com.sun.jna.FromNativeContext;
import com.sun.jna.ToNativeContext;
import com.sun.jna.TypeConverter;
import java.util.function.Function;
/**
* Extends the default type mapper of JNA in order to provide custom bindings for enumerations.
*/
class EnumTypeMapper extends DefaultTypeMapper {
/**
* Add a {@link TypeConverter} to provide bidirectional mapping between
* a native and Java type.
*
* @param enumerationType The type of the enumeration to bind.
* @param fromNativeInteger A function that converts the native enumeration into the Java enumeration.
* @param <T> The type of the Java enumeration.
*/
<T extends JnaEnum> void addTypeConverter(Class<T> enumerationType, Function<Integer, T> fromNativeInteger) {
addTypeConverter(enumerationType, new TypeConverter() {
/**
* Converts the native enumeration into the Java enumeration.
*
* @param nativeValue The native element.
* @param context The context for converting a native value into a Java type.
* @return The return value is a Java enumeration.
*/
@Override
public Object fromNative(Object nativeValue, FromNativeContext context) {
return fromNativeInteger.apply((int) nativeValue);
}
/**
* Returns the native type of the conversion. The enumeration are of type {@link Integer}.
*/
@Override
public Class<?> nativeType() {
return Integer.class;
}
/**
* Converts a Java enumeration into a native enumeration.
*
* @param value The Java element.
* @param context The context of converting a Java type into a native value.
* @return The return value is a native value from an enumeration.
*/
@Override
public Object toNative(Object value, ToNativeContext context) {
return ((JnaEnum) value).value();
}
});
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.jna;
/**
* A contract that represents an JNA enumeration.
*/
public interface JnaEnum {
/**
* Retrieves the value of an element in the enumeration.
*
* @return The return value is an integer number, that represents the value in the native library.
*/
int value();
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.jna;
import keystone.*;
/**
* Extends the numeration type mapper in order to register the enumeration used by Keystone.
*/
public class KeystoneTypeMapper extends EnumTypeMapper {
public KeystoneTypeMapper() {
addTypeConverter(KeystoneError.class, KeystoneError::fromValue);
addTypeConverter(KeystoneArchitecture.class, KeystoneArchitecture::fromValue);
addTypeConverter(KeystoneMode.class, KeystoneMode::fromValue);
addTypeConverter(KeystoneOptionType.class, KeystoneOptionType::fromValue);
addTypeConverter(KeystoneOptionValue.KeystoneOptionSyntax.class, KeystoneOptionValue.KeystoneOptionSyntax::fromValue);
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.natives;
import java.lang.ref.Cleaner;
/**
* An abstract class that automatically runs the action passed by argument in the constructor once
* phantom reachable, thanks to {@link Cleaner}.
*
* @see Cleaner
* @see <a href="https://www.linkedin.com/pulse/java-9-cleaner-illustration-good-encapsulation-pawel-wlodarski/">Java 9 Cleaner as an illustration of good encapsulation</a>
*/
public abstract class CleanerContainer implements AutoCloseable {
/**
* Creates a single instance of the cleaner for all the native resources.
*/
private static final Cleaner cleaner = Cleaner.create();
/**
* The instance of {@link java.lang.ref.Cleaner.Cleanable} that refers to the native resource.
*/
private final Cleaner.Cleanable cleanable;
/**
* Registers a cleaning action to run when the object becomes phantom reachable.
*
* @param action a {@code Runnable} to invoke when the object becomes phantom reachable.
*/
CleanerContainer(Runnable action) {
cleanable = cleaner.register(this, action);
}
/**
* Closes this resource, relinquishing any underlying resources.
* This method is invoked automatically on objects managed by the
* {@code try}-with-resources statement.
*/
@Override
public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,179 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.natives;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import keystone.*;
import keystone.jna.KeystoneTypeMapper;
import java.util.HashMap;
import java.util.Map;
/**
* The class providing the native functions of Keystone using the Direct Mapping of JNA for best performance.
* <p>
* The original function prototypes are declared in the header file <i>keystone.h</i>.
*
* @see <a href="https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md">JNA: Direct Mapping</a>
*/
public final class DirectMappingKeystoneNative {
static {
Map<String, Object> options = new HashMap<>();
options.put(Library.OPTION_TYPE_MAPPER, new KeystoneTypeMapper());
// Direct Mapping using JNA
Native.register(NativeLibrary.getInstance("keystone", options));
}
/**
* Private constructor to prevent any instantiation of the class.
*/
private DirectMappingKeystoneNative() {
}
/**
* Determine if the given architecture is supported by this library.
* <p>
* Native function prototype: <i>bool ks_arch_supported(ks_arch arch);</i>
*
* @param architecture architecture type
* @return {@code true} if this library supports the given arch.
*/
public static native boolean ks_arch_supported(KeystoneArchitecture architecture);
/**
* Assemble a string given its the buffer, size, start address and number
* of instructions to be decoded.
* This API dynamically allocate memory to contain assembled instruction.
* Resulted array of bytes containing the machine code is put into @machineCode.
* <p>
* On failure, call {@link DirectMappingKeystoneNative#ks_errno} for error code.
* <p>
* NOTE 1: this API will automatically determine memory needed to contain
* output bytes in *encoding.
* <p>
* NOTE 2: caller must free the allocated memory itself to avoid memory leaking.
* <p>
* Native function prototype: <i>int ks_asm(ks_engine *ks,
* const char *string,
* uint64_t address,
* unsigned char **encoding, size_t *encoding_size,
* size_t *stat_count);</i>
*
* @param engine handle returned by ks_open()
* @param assembly NULL-terminated assembly string. Use ; or \n to separate statements.
* @param address address of the first assembly instruction, or 0 to ignore.
* @param machineCodeBuffer array of bytes containing encoding of input assembly string.
* NOTE: *encoding will be allocated by this function, and should be freed
* with ks_free() function.
* @param machineCodeSize size of machineCode
* @param numberOfStatements number of statements successfully processed
* @return 0 on success, or -1 on failure.
*/
public static native int ks_asm(Pointer engine, String assembly, int address, PointerByReference machineCodeBuffer,
IntByReference machineCodeSize, IntByReference numberOfStatements);
/**
* Close KS instance: MUST do to release the handle when it is not used anymore.
* NOTE: this must be called only when there is no longer usage of Keystone.
* The reason is this API releases some cached memory, thus access to any
* Keystone API after ks_close() might crash your application.
* After this, @engine is invalid, and no longer usable.
* <p>
* Native function prototype: <i>ks_err ks_close(ks_engine *ks);</i>
*
* @param engine pointer to a handle returned by ks_open().
* @return KS_ERR_OK on success, or other value on failure (refer to ks_err enum for detailed error).
*/
public static native KeystoneError ks_close(Pointer engine);
/**
* Report the last error number when some API function fail.
* Like glibc's errno, ks_errno might not retain its old error once accessed.
* <p>
* Native function prototype: <i>ks_err ks_errno(ks_engine *ks)</i>
*
* @param engine handle returned by ks_open()
* @return error code of ks_err enum type {@link KeystoneError}
*/
public static native KeystoneError ks_errno(Pointer engine);
/**
* Free memory allocated by ks_asm().
* <p>
* Native function prototype: <i>void ks_free(unsigned char *p)</i>
*
* @param machineCodeBuffer memory allocated in @encoding argument of ks_asm()
*/
public static native void ks_free(Pointer machineCodeBuffer);
/**
* Create new instance of Keystone engine.
* <p>
* Native function prototype: <i>ks_err ks_open(ks_arch arch, int mode, ks_engine **ks);</i>
*
* @param architecture architecture type (KS_ARCH_*).
* @param mode hardware mode. This is combined of KS_MODE_*.
* @param engine pointer to ks_engine, which will be updated at return time.
* @return KS_ERR_OK on success, or other value on failure (refer to ks_err enum for detailed error).
*/
public static native KeystoneError ks_open(KeystoneArchitecture architecture, KeystoneMode mode, PointerByReference engine);
/**
* Set option for Keystone engine at runtime.
* <p>
* Native function prototype: <i>err ks_option(ks_engine *ks, ks_opt_type type, size_t value);</i>
*
* @param engine handle returned by ks_open()
* @param type type of option to be set. See {@link KeystoneOptionType}
* @param value option value corresponding with @type
* @return {@link KeystoneError#Ok} on success, or other value on failure.
* Refer to {@link KeystoneError} enum for detailed error.
*/
public static native KeystoneError ks_option(Pointer engine, KeystoneOptionType type, int value);
/**
* Set option for Keystone engine at runtime
* <p>
* Native function prototype: <i>err ks_option(ks_engine *ks, ks_opt_type type, size_t value);</i>
*
* @param engine handle returned by ks_open()
* @param type ype of option to be set. See {@link KeystoneOptionType}
* @param callback callback to resolve a unrecognized symbol.
* @return {@link KeystoneError#Ok} on success, or other value on failure.
* Refer to {@link KeystoneError} enum for detailed error.
*/
public static native KeystoneError ks_option(Pointer engine, KeystoneOptionType type, SymbolResolverCallback callback);
/**
* Return a string describing given error code.
* <p>
* Native function prototype: <i>const char *ks_strerror(ks_err code);</i>
*
* @param errorCode error code.
* @return returns a pointer to a string that describes the error code passed in the argument @errorCode.
*/
public static native String ks_strerror(KeystoneError errorCode);
/**
* Returns combined API version & major and minor version numbers.
* <p>
* Native function prototype: <i>unsigned int ks_version(unsigned int *major, unsigned int *minor);</i>
*
* @param major The major number of API version.
* @param minor The minor number of API version.
* @return An hexadecimal number as (major << 8 | minor), which encodes both major & minor versions.
*/
public static native int ks_version(IntByReference major, IntByReference minor);
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.natives;
import com.sun.jna.Pointer;
/**
* The cleaner that automatically close the {@code Pointer} of Keystone once the managed object is phantom reachable.
*/
public class KeystoneCleanerContainer extends CleanerContainer {
/**
* Registers an object and a cleaning action to run when the object becomes phantom reachable.
*/
public KeystoneCleanerContainer(Pointer pointerToEngine) {
super(new KeystoneState(pointerToEngine));
}
/**
* The internal state that contains the logic to free the native resource.
*/
static class KeystoneState implements Runnable {
/**
* A pointer to the native resource.
*/
private final Pointer pointerToEngine;
/**
* Create a new instance of {@link KeystoneState}.
*
* @param pointerToEngine The pointer to the Keystone native resource.
*/
KeystoneState(Pointer pointerToEngine) {
this.pointerToEngine = pointerToEngine;
}
/**
* Calls the logic to collect the native resource.
*/
@Override
public void run() {
DirectMappingKeystoneNative.ks_close(pointerToEngine);
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.utilities;
/**
* Represents the version number of Keystone. This class cannot be inherited.
*/
public final class Version implements Comparable<Version> {
private final int major;
private final int minor;
/**
* Initializes a new instance of the {@link Version} class using the specified major and minor values.
*
* @param major The major version number.
* @param minor The minor version number.
*/
public Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
/**
* Gets the value of the major component of the version number for the current {@link Version} object.
*
* @return The major version number.
*/
public int major() {
return major;
}
/**
* Gets the value of the minor component of the version number for the current {@link Version} object.
*
* @return The minor version number.
*/
public int minor() {
return minor;
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
* <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==
* -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
* implies that <tt>x.compareTo(y)</tt> must throw an exception iff
* <tt>y.compareTo(x)</tt> throws an exception.)
*
* <p>The implementor must also ensure that the relation is transitive:
* <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
* <tt>x.compareTo(z)&gt;0</tt>.
*
* <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
* implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
* all <tt>z</tt>.
*
* <p>It is strongly recommended, but <i>not</i> strictly required that
* <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
* class that implements the <tt>Comparable</tt> interface and violates
* this condition should clearly indicate this fact. The recommended
* language is "Note: this class has a natural ordering that is
* inconsistent with equals."
*
* <p>In the foregoing description, the notation
* <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
* <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
* <tt>0</tt>, or <tt>1</tt> according to whether the value of
* <i>expression</i> is negative, zero or positive.
*
* @param o the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
* @throws NullPointerException if the specified object is null
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
@Override
public int compareTo(Version o) {
if (o == null) return 1;
if (major != o.major) {
if (major > o.major) {
return 1;
}
return -1;
}
return Integer.compare(minor, o.minor);
}
}
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone;
import com.sun.jna.ptr.LongByReference;
import keystone.exceptions.AssembleFailedKeystoneException;
import keystone.exceptions.OpenFailedKeystoneException;
import keystone.exceptions.SetOptionFailedKeystoneException;
import keystone.utilities.Version;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.LinkedList;
import static org.junit.jupiter.api.Assertions.*;
class KeystoneTest {
private Keystone keystone;
@BeforeEach
void setUp() {
keystone = new Keystone(KeystoneArchitecture.X86, KeystoneMode.Mode64);
}
@AfterEach
void tearDown() {
}
@Test
void ctor_ifInvalidArguments_shouldThrowAnException() {
try {
new Keystone(KeystoneArchitecture.Ppc, KeystoneMode.SparcV9);
fail("An exception must be thrown upon invalid arguments are used.");
} catch (OpenFailedKeystoneException e) {
assertEquals(KeystoneError.Mode, e.getKeystoneError());
}
}
@Test
void assemble_shouldAssembleIncDec() {
// Arrange
var assembly = "INC EAX;DEC EAX";
var expectedMachineCode = new byte[]{(byte) 0xFF, (byte) 0xC0, (byte) 0xFF, (byte) 0xC8};
var expectedNumberOfStatements = 2;
// Act
var encoded = keystone.assemble(assembly);
// Assert
assertArrayEquals(expectedMachineCode, encoded.getMachineCode());
assertEquals(expectedNumberOfStatements, encoded.getNumberOfStatements());
}
@Test
void assemble_withAddress_shouldAssembleDoubleNop() {
// Arrange
var assembly = "NOP;NOP";
var expectedMachineCode = new byte[]{(byte) 0x90, (byte) 0x90};
var expectedNumberOfStatements = 2;
var expectedAddress = 0x200;
// Act
var encoded = keystone.assemble(assembly, expectedAddress);
// Assert
assertArrayEquals(expectedMachineCode, encoded.getMachineCode());
assertEquals(expectedNumberOfStatements, encoded.getNumberOfStatements());
assertEquals(expectedAddress, encoded.getAddress());
}
@Test
void assemble_ifAssemblyCodeInvalid_shouldThrowAnException() {
// Arrange
var assembly = "UNK";
// Act and Assert
try {
keystone.assemble(assembly, 0);
fail("The assembly instruction is invalid. It should not pass the unit test.");
} catch (AssembleFailedKeystoneException e) {
assertEquals(KeystoneError.AsmMnemonicFail, e.getKeystoneError());
assertEquals(assembly, e.getAssembly());
}
}
@Test
void assemble_withCollectionAndAddress_shouldAssembleIncDec() {
// Arrange
var assembly = new LinkedList<String>();
var expectedMachineCode = new byte[]{(byte) 0xFF, (byte) 0xC0, (byte) 0xFF, (byte) 0xC8};
var expectedNumberOfStatements = 2;
// Act
assembly.add("INC EAX");
assembly.add("DEC EAX");
var encoded = keystone.assemble(assembly);
// Assert
assertArrayEquals(expectedMachineCode, encoded.getMachineCode());
assertEquals(expectedNumberOfStatements, encoded.getNumberOfStatements());
}
@Test
void assemble_withSymbolWithoutResolver_shouldFail() {
// Arrange
var assembly = "MOV EAX, TEST";
// Act and Assert
try {
keystone.assemble(assembly);
fail("The assembly instruction is composed of an undefined symbol. It should not pass the unit test");
} catch (AssembleFailedKeystoneException e) {
assertEquals(KeystoneError.AsmSymbolMissing, e.getKeystoneError());
}
}
@Test
void setAssemblySyntax_withAttSyntax_shouldBeEqualToX86Syntax() {
// Act
var x86Result = keystone.assemble("INC ECX; DEC EDX");
keystone.setAssemblySyntax(KeystoneOptionValue.KeystoneOptionSyntax.Att);
var attResult = keystone.assemble("INC %ecx; DEC %edx");
// Assert
assertArrayEquals(x86Result.getMachineCode(), attResult.getMachineCode());
}
@Test
void setOption_ifInvalidArguments_shouldTrowAnException() {
// Arrange
var expectedType = KeystoneOptionType.Syntax;
var invalidValue = -1;
// Act and Assert
try {
keystone.setOption(expectedType, invalidValue);
} catch (SetOptionFailedKeystoneException e) {
assertEquals(KeystoneError.OptInvalid, e.getKeystoneError());
assertEquals(expectedType, e.getOptionType());
assertEquals(invalidValue, e.getOptionValue());
}
}
@Test
void setSymbolResolver_assembleCustomSymbol_shouldProduceValidAssemblyCode() {
// Arrange
var expectedSymbol = "TEST";
var expectedValue = (byte) 0x66;
var movOpcode = (byte) 0xB8;
var assembly = "MOV EAX, " + expectedSymbol;
var symbolResolver = new SymbolResolverCallback() {
@Override
public boolean onResolve(String symbol, LongByReference value) {
assertEquals(expectedSymbol, symbol);
value.setValue(expectedValue);
return true;
}
};
// Act
keystone.setSymbolResolver(symbolResolver);
var assemblyCode = keystone.assemble(assembly);
// Assert
assertEquals(1, assemblyCode.getNumberOfStatements());
assertEquals(movOpcode, assemblyCode.getMachineCode()[0]);
assertEquals(expectedValue, assemblyCode.getMachineCode()[1]);
}
@Test
void unsetSymbolResolver_assembleCustomSymbol_shouldfailBecauseTheCallbackHasBeenUnset() {
// Arrange
var expectedSymbol = "TEST";
var expectedValue = (byte) 0x66;
var assembly = "MOV EAX, " + expectedSymbol;
var symbolResolver = new SymbolResolverCallback() {
@Override
public boolean onResolve(String symbol, LongByReference value) {
assertEquals(expectedSymbol, symbol);
value.setValue(expectedValue);
return true;
}
};
// Act and Assert
keystone.setSymbolResolver(symbolResolver);
keystone.unsetSymbolResolver();
try {
keystone.assemble(assembly);
fail("The assembly instruction is composed of an undefined symbol and no resolver should be available.");
} catch (AssembleFailedKeystoneException e) {
assertEquals(KeystoneError.AsmSymbolMissing, e.getKeystoneError());
}
}
@Test
void isArchitectureSupported_shouldSupportX86Everywhere() {
assertTrue(Keystone.isArchitectureSupported(KeystoneArchitecture.X86));
}
@Test
void version_shouldBeDifferentFromZero() {
assertEquals(1, keystone.version().compareTo(new Version(0, 0)));
}
@Test
void close_shouldNotThrowAnyException() {
keystone.close();
}
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.natives;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CleanerContainerTest {
@Test
void dtor_WhenGarbageCollected_ShouldTriggerThePhantomReachableAction() throws InterruptedException {
// Arrange
var atomicBoolean = new AtomicBoolean(false);
new MockCleanerContainer(atomicBoolean);
// Act
System.gc();
Thread.sleep(1);
// Assert
assertTrue(atomicBoolean.get());
}
}
class MockCleanerContainer extends CleanerContainer {
MockCleanerContainer(AtomicBoolean atomicBoolean) {
super(new MockState(atomicBoolean));
}
static class MockState implements Runnable {
private final AtomicBoolean atomicBoolean;
MockState(AtomicBoolean atomicBoolean) {
this.atomicBoolean = atomicBoolean;
}
@Override
public void run() {
atomicBoolean.set(true);
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2018 Jämes Ménétrey <[email protected]>
*
* This file is part of the Keystone Java bindings which is released under MIT.
* See file LICENSE in the Java bindings folder for full license details.
*/
package keystone.utilities;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class VersionTest {
private final int major = 3;
private final int minor = 1;
@Test
void major_shouldReturnTheValueSpecifiedInTheConstructor() {
// Arrange
var v = new Version(major, minor);
// Act
var m = v.major();
// Assert
assertEquals(major, m);
}
@Test
void minor_shouldReturnTheValueSpecifiedInTheConstructor() {
// Arrange
var v = new Version(major, minor);
// Act
var m = v.minor();
// Assert
assertEquals(minor, m);
}
@Test
void compareTo_ifMajorIsNotEqual_shouldReturnDifferent() {
// Arrange
var v1 = new Version(major, minor);
var v2 = new Version(major + 1, minor);
// Act
var lower = v1.compareTo(v2);
var higher = v2.compareTo(v1);
// Assert
assertEquals(-1, lower);
assertEquals(1, higher);
}
@Test
void compareTo_ifMinorIsNotEqual_shouldReturnDifferent() {
// Arrange
var v1 = new Version(major, minor + 1);
var v2 = new Version(major, minor);
// Act
var lower = v2.compareTo(v1);
var higher = v1.compareTo(v2);
// Assert
assertEquals(-1, lower);
assertEquals(1, higher);
}
}