diff --git a/bindings/cs/README.md b/bindings/cs/README.md
new file mode 100644
index 0000000..59f1b0a
--- /dev/null
+++ b/bindings/cs/README.md
@@ -0,0 +1,3 @@
+# libhat-sharp
+
+These bindings are not up-to-date, but should be compatible with v0.9.0
diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java
index bfedafb..ebf8ce2 100644
--- a/bindings/java/src/main/java/me/zero/libhat/Hat.java
+++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java
@@ -2,6 +2,7 @@
import com.sun.jna.Native;
import com.sun.jna.Pointer;
+import com.sun.jna.ptr.PointerByReference;
import me.zero.libhat.jna.Libhat;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,19 +19,12 @@ public final class Hat {
private Hat() {}
- public enum Status {
- SUCCESS,
- ERR_UNKNOWN,
- SIG_INVALID,
- SIG_EMPTY,
- SIG_NO_BYTE
- }
-
/**
* Creates a new {@link Signature} from the specified string representation of a byte pattern. For example:
*
* 48 8B 8D ? ? ? ? 48
* E8 ? ? ? ? 88 86
+ * FF 15 ? ? ? ? 48 8? ?D
*
* The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be
* called when the object is done being used, either explicitly or through a try-with-resources block.
@@ -42,17 +36,17 @@ public enum Status {
*/
public static @NotNull Signature parseSignature(@NotNull final String signature) {
Objects.requireNonNull(signature);
- final Pointer[] handle = new Pointer[1];
- final int status = Libhat.INSTANCE.libhat_parse_signature(signature, handle);
+ final PointerByReference out = new PointerByReference();
+ final int status = Libhat.INSTANCE.libhat_parse_signature(signature, out);
if (status != 0) {
- throw new RuntimeException("libhat internal error " + Status.values()[status]);
+ throw new LibhatException(status);
}
- return new Signature(handle[0]);
+ return new Signature(out.getValue());
}
/**
- * Creates a new {@link Signature} that describes a pattern matching the specified {@code bytes} where the
- * corresponding {@code mask} value is non-zero. In other words, a mask value of {@code 0} indicates a wildcard.
+ * Creates a new {@link Signature} that describes a pattern matching the bits of {@code bytes} where the
+ * corresponding {@code mask} bit is 1. In other words, where {@code (buffer & mask) == (bytes & mask)}.
* The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be
* called when the object is done being used, either explicitly or through a try-with-resources block.
*
@@ -69,12 +63,12 @@ public enum Status {
if (bytes.length != mask.length) {
throw new IllegalArgumentException("Mismatch between bytes.length and mask.length");
}
- final Pointer[] handle = new Pointer[1];
- final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, bytes.length, handle);
+ final PointerByReference out = new PointerByReference();
+ final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, new Libhat.size_t(bytes.length), out);
if (status != 0) {
- throw new RuntimeException("libhat internal error " + Status.values()[status]);
+ throw new LibhatException(status);
}
- return new Signature(handle[0]);
+ return new Signature(out.getValue());
}
/**
@@ -82,17 +76,20 @@ public enum Status {
* range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is
* found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying
* memory address of the returned result will not be aligned on any particular boundary. The specified
- * {@link ByteBuffer} must be a direct buffer.
+ * {@link ByteBuffer} must be a direct buffer. Additional hints may be specified to optimize the scan based on
+ * known properties of the buffer contents.
*
* @param signature The pattern to match
* @param buffer The buffer to search
+ * @param hints The hints to use
* @return The absolute position into {@code buffer} where a match was found,
* or {@link OptionalInt#empty()} if there was no match
* @throws IllegalArgumentException if the buffer is not direct or the signature has already been closed
* @throws NullPointerException if any arguments are {@code null}
*/
- public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer) {
- return findPattern(signature, buffer, ScanAlignment.X1);
+ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer,
+ @NotNull final ScanHint... hints) {
+ return findPattern(signature, buffer, ScanAlignment.X1, hints);
}
/**
@@ -100,18 +97,20 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu
* range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is
* found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying
* memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. The specified
- * {@link ByteBuffer} must be a direct buffer.
+ * {@link ByteBuffer} must be a direct buffer. Additional hints may be specified to optimize the scan based on known
+ * properties of the buffer contents.
*
* @param signature The pattern to match
* @param buffer The buffer to search
* @param alignment The result address alignment
+ * @param hints The hints to use
* @return The absolute position into {@code buffer} where a match was found,
* or {@link OptionalInt#empty()} if there was no match
* @throws IllegalArgumentException if the buffer is not direct
* @throws NullPointerException if any arguments are {@code null}
*/
public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer,
- @NotNull final ScanAlignment alignment) {
+ @NotNull final ScanAlignment alignment, @NotNull final ScanHint... hints) {
Objects.requireNonNull(signature);
Objects.requireNonNull(buffer);
Objects.requireNonNull(alignment);
@@ -126,8 +125,9 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu
final Pointer result = Libhat.INSTANCE.libhat_find_pattern(
Objects.requireNonNull(signature.handle),
new Pointer(start),
- count,
- alignment.alignment()
+ new Libhat.size_t(count),
+ alignment.alignment(),
+ ScanHint.toFlags(hints)
);
if (result == Pointer.NULL) {
@@ -139,33 +139,38 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu
/**
* Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the
* specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned.
- * The underlying memory address of the returned result will not be aligned on any particular boundary.
+ * The underlying memory address of the returned result will not be aligned on any particular boundary. Additional
+ * hints may be specified to optimize the scan based on known properties of the buffer contents.
*
* @param signature The pattern to match
* @param module The target module
* @param section The section to search in the module
+ * @param hints The hints to use
* @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found.
* @throws NullPointerException if any arguments are {@code null}
*/
public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module,
- @NotNull final String section) {
- return findPattern(signature, module, section, ScanAlignment.X1);
+ @NotNull final String section, @NotNull final ScanHint... hints) {
+ return findPattern(signature, module, section, ScanAlignment.X1, hints);
}
/**
* Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the
* specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned.
* The underlying memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}.
+ * Additional hints may be specified to optimize the scan based on known properties of the buffer contents.
*
* @param signature The pattern to match
* @param module The target module
* @param section The section to search in the module
* @param alignment The result address alignment
+ * @param hints The hints to use
* @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found.
* @throws NullPointerException if any arguments are {@code null}
*/
public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module,
- @NotNull final String section, @NotNull final ScanAlignment alignment) {
+ @NotNull final String section, @NotNull final ScanAlignment alignment,
+ @NotNull final ScanHint... hints) {
Objects.requireNonNull(signature);
Objects.requireNonNull(module);
Objects.requireNonNull(section);
@@ -173,46 +178,117 @@ public static Optional findPattern(@NotNull final Signature signature,
final Pointer result = Libhat.INSTANCE.libhat_find_pattern_mod(
Objects.requireNonNull(signature.handle),
- module.handle,
+ Objects.requireNonNull(module.handle),
section,
- alignment.alignment()
+ alignment.alignment(),
+ ScanHint.toFlags(hints)
);
return Optional.ofNullable(result);
}
/**
- * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer)}
+ * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanHint...)}.
+ * Additional hints may be specified to optimize the scan based on known properties of the buffer contents.
*
* @param signature A byte pattern string
- * @param buffer The buffer to search
+ * @param buffer The buffer to search
+ * @param hints The hints to use
* @return The search result
* @throws NullPointerException if any arguments are {@code null}
*/
- public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer) {
+ public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer,
+ @NotNull final ScanHint... hints) {
try (final Signature sig = parseSignature(signature)) {
- return findPattern(sig, buffer);
+ return findPattern(sig, buffer, hints);
}
}
/**
- * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment)}
+ * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment, ScanHint...)}.
+ * Additional hints may be specified to optimize the scan based on known properties of the buffer contents.
*
* @param signature A byte pattern string
- * @param buffer The buffer to search
+ * @param buffer The buffer to search
* @param alignment The memory address alignment of the result
+ * @param hints The hints to use
* @return The search result
* @throws NullPointerException if any arguments are {@code null}
*/
public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer,
- @NotNull final ScanAlignment alignment) {
+ @NotNull final ScanAlignment alignment, @NotNull final ScanHint... hints) {
try (final Signature sig = parseSignature(signature)) {
- return findPattern(sig, buffer, alignment);
+ return findPattern(sig, buffer, alignment, hints);
+ }
+ }
+
+ /**
+ * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()}
+ * and up to but excluding {@link ByteBuffer#limit()}, is readable. The specified {@link ByteBuffer} must be a direct
+ * buffer.
+ *
+ * @param buffer A direct buffer to an arbitrary memory region
+ * @return Whether the memory is readable
+ * @throws IllegalArgumentException if the buffer is not direct
+ * @throws NullPointerException if any arguments are {@code null}
+ */
+ public static boolean isReadable(@NotNull final ByteBuffer buffer) {
+ Objects.requireNonNull(buffer);
+ if (!buffer.isDirect()) {
+ throw new IllegalArgumentException("Provided buffer must be direct");
+ }
+
+ final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position();
+ final int count = buffer.remaining();
+ return Libhat.INSTANCE.libhat_is_readable(new Pointer(start), new Libhat.size_t(count));
+ }
+
+ /**
+ * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()}
+ * and up to but excluding {@link ByteBuffer#limit()}, is writable. The specified {@link ByteBuffer} must be a direct
+ * buffer.
+ *
+ * @param buffer A direct buffer to an arbitrary memory region
+ * @return Whether the memory is writable
+ * @throws IllegalArgumentException if the buffer is not direct
+ * @throws NullPointerException if any arguments are {@code null}
+ */
+ public static boolean isWritable(@NotNull final ByteBuffer buffer) {
+ Objects.requireNonNull(buffer);
+ if (!buffer.isDirect()) {
+ throw new IllegalArgumentException("Provided buffer must be direct");
+ }
+
+ final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position();
+ final int count = buffer.remaining();
+ return Libhat.INSTANCE.libhat_is_writable(new Pointer(start), new Libhat.size_t(count));
+ }
+
+ /**
+ * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()}
+ * and up to but excluding {@link ByteBuffer#limit()}, is executable. The specified {@link ByteBuffer} must be a direct
+ * buffer.
+ *
+ * @param buffer A direct buffer to an arbitrary memory region
+ * @return Whether the memory is executable
+ * @throws IllegalArgumentException if the buffer is not direct
+ * @throws NullPointerException if any arguments are {@code null}
+ */
+ public static boolean isExecutable(@NotNull final ByteBuffer buffer) {
+ Objects.requireNonNull(buffer);
+ if (!buffer.isDirect()) {
+ throw new IllegalArgumentException("Provided buffer must be direct");
}
+
+ final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position();
+ final int count = buffer.remaining();
+ return Libhat.INSTANCE.libhat_is_executable(new Pointer(start), new Libhat.size_t(count));
}
/**
- * Returns the module for the executable used to create this process
+ * Returns the module for the executable used to create this process. The returned {@link ProcessModule} is backed
+ * by a native heap allocation, and {@link ProcessModule#close()} must be called when the object is done being used,
+ * either explicitly or through a try-with-resources block.
*
* @return The module
* @throws IllegalStateException If the process module could not be retrieved
@@ -225,7 +301,9 @@ public static OptionalInt findPattern(@NotNull final String signature, @NotNull
/**
* Returns an {@link Optional} containing a handle to the module with the specified name, if such a module exists,
* otherwise the returned optional is empty. If the provided name is {@code null}, the module for the executable
- * used to create the current process is returned.
+ * used to create the current process is returned. The returned {@link ProcessModule} is backed by a native heap
+ * allocation, and {@link ProcessModule#close()} must be called when the object is done being used, either
+ * explicitly or through a try-with-resources block.
*
* @param module The module name, may be {@code null}
* @return The module
diff --git a/bindings/java/src/main/java/me/zero/libhat/LibhatException.java b/bindings/java/src/main/java/me/zero/libhat/LibhatException.java
new file mode 100644
index 0000000..9f28283
--- /dev/null
+++ b/bindings/java/src/main/java/me/zero/libhat/LibhatException.java
@@ -0,0 +1,23 @@
+package me.zero.libhat;
+
+import me.zero.libhat.jna.Libhat;
+
+/**
+ * @author Brady
+ */
+public class LibhatException extends RuntimeException {
+
+ private final int status;
+
+ LibhatException(final int status) {
+ super(Libhat.INSTANCE.libhat_status_to_string(status));
+ this.status = status;
+ }
+
+ /**
+ * @return The actual status code representing the error that occurred
+ */
+ public final int status() {
+ return this.status;
+ }
+}
diff --git a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java
index ae83071..aec34d0 100644
--- a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java
+++ b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java
@@ -1,17 +1,116 @@
package me.zero.libhat;
import com.sun.jna.Pointer;
+import me.zero.libhat.jna.Libhat;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
/**
* @author Brady
*/
-public final class ProcessModule {
+public final class ProcessModule implements AutoCloseable {
- @NotNull
+ @Nullable
Pointer handle;
ProcessModule(@NotNull final Pointer handle) {
- this.handle = handle;
+ this.handle = Objects.requireNonNull(handle);
+ }
+
+ @Override
+ public void close() {
+ if (this.handle != Pointer.NULL) {
+ Libhat.INSTANCE.libhat_free(this.handle);
+ this.handle = Pointer.NULL;
+ }
+ }
+
+ /**
+ * @return The base address of this module, as a {@code long}.
+ */
+ public long getBaseAddress() {
+ return Libhat.INSTANCE.libhat_module_address(this.checkHandle()).longValue();
+ }
+
+ /**
+ * Returns the complete memory region for this module. This may include portions which are uncommitted.
+ * To verify whether the region is safe to read, use {@link Hat#isReadable(ByteBuffer)}.
+ *
+ * @return The module data
+ * @throws IllegalStateException If the module data was empty
+ */
+ public @NotNull ByteBuffer getModuleData() {
+ final ByteBuffer data = Libhat.INSTANCE.libhat_module_get_data(this.checkHandle()).toBuffer();
+ if (data == null) {
+ throw new IllegalStateException("Module data was unexpectedly empty");
+ }
+ return data;
+ }
+
+ /**
+ * Returns the executable memory region containing machine code for the module. The standard section which
+ * contains executable code for the current platform will be returned first. If it cannot be identified
+ * by name, the first executable region defined by the module will be returned instead.
+ *
+ * @return The module's executable data, or {@link Optional#empty()} if it cannot be found.
+ */
+ public @NotNull Optional getExecutableData() {
+ return Optional.ofNullable(Libhat.INSTANCE.libhat_module_get_executable_data(this.checkHandle()).toBuffer());
+ }
+
+ /**
+ * Returns the memory region for a named section. On Linux-based platforms, section names are lazily loaded
+ * from the file that initialized the module, and internally cached for subsequent calls. On systems using the
+ * Mach-O format, "SEGNAME,SECNAME" is supported for disambiguation. i.e. "__TEXT,__const" vs "__DATA,__const"
+ *
+ * @param name The section name
+ * @return The data for the section, or {@link Optional#empty()} if it cannot be found.
+ * @throws NullPointerException if any arguments are {@code null}
+ */
+ public @NotNull Optional getSectionData(@NotNull final String name) {
+ Objects.requireNonNull(name);
+ return Optional.ofNullable(Libhat.INSTANCE.libhat_module_get_section_data(this.checkHandle(), name).toBuffer());
+ }
+
+ /**
+ * Invokes the callback for each named linker section defined by this module as long as it returns true. The
+ * returned byte range is not guaranteed to have page aligned begin and end addresses. The returned protections
+ * are yielded from the section headers, and may not reflect the current virtual protections for the relevant
+ * memory pages. On Linux-based platforms, section names are lazily loaded from the file that initialized the
+ * module, and internally cached for subsequent calls.
+ *
+ * @param callback The callback to accept each section
+ * @throws NullPointerException if any arguments are {@code null}
+ */
+ public void forEachSection(@NotNull final Predicate<@NotNull Section> callback) {
+ Objects.requireNonNull(callback);
+ Libhat.INSTANCE.libhat_module_for_each_section(this.checkHandle(), (name, data, prot, ud)
+ -> callback.test(new Section(name, data.toBuffer(), Protection.fromFlags(prot))), null);
+ }
+
+ /**
+ * Invokes the callback for each memory segment defined by this module as long as it returns true. Depending on
+ * the platform, a segment may be represented by multiple linker sections. The returned byte range is not
+ * guaranteed to have page aligned begin and end addresses. The returned protections are yielded from the
+ * segment headers, and may not reflect the current virtual protections for the relevant memory pages.
+ *
+ * @param callback The callback to accept each segment
+ * @throws NullPointerException if any arguments are {@code null}
+ */
+ public void forEachSegment(@NotNull final Predicate<@NotNull Segment> callback) {
+ Objects.requireNonNull(callback);
+ Libhat.INSTANCE.libhat_module_for_each_segment(this.checkHandle(), (data, prot, ud)
+ -> callback.test(new Segment(data.toBuffer(), Protection.fromFlags(prot))), null);
+ }
+
+ @NotNull
+ private Pointer checkHandle() {
+ return Objects.requireNonNull(this.handle,
+ "Attempted operation on a ProcessModule that has already been freed");
}
}
diff --git a/bindings/java/src/main/java/me/zero/libhat/Protection.java b/bindings/java/src/main/java/me/zero/libhat/Protection.java
new file mode 100644
index 0000000..d1148a7
--- /dev/null
+++ b/bindings/java/src/main/java/me/zero/libhat/Protection.java
@@ -0,0 +1,30 @@
+package me.zero.libhat;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.EnumSet;
+
+/**
+ * @author Brady
+ */
+public enum Protection {
+ READ,
+ WRITE,
+ EXECUTE;
+
+ private final int bit;
+
+ Protection() {
+ this.bit = (1 << this.ordinal());
+ }
+
+ static @NotNull EnumSet fromFlags(final int flags) {
+ final EnumSet set = EnumSet.noneOf(Protection.class);
+ for (final Protection flag : Protection.values()) {
+ if ((flags & flag.bit) != 0) {
+ set.add(flag);
+ }
+ }
+ return set;
+ }
+}
diff --git a/bindings/java/src/main/java/me/zero/libhat/ScanHint.java b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java
new file mode 100644
index 0000000..bff6b6b
--- /dev/null
+++ b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java
@@ -0,0 +1,37 @@
+package me.zero.libhat;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Brady
+ */
+public enum ScanHint {
+ /**
+ * The data being scanned is x86_64 machine code
+ */
+ X86_64,
+
+ /**
+ * Only utilize byte pair based scanning if the signature starts with a byte pair
+ */
+ PAIR0,
+
+ /**
+ * The data being scanned is AArch64 machine code
+ */
+ AARCH64;
+
+ private final int bit;
+
+ ScanHint() {
+ this.bit = (1 << this.ordinal());
+ }
+
+ static int toFlags(@NotNull ScanHint... hints) {
+ int mask = 0;
+ for (final ScanHint hint : hints) {
+ mask |= hint.bit;
+ }
+ return mask;
+ }
+}
diff --git a/bindings/java/src/main/java/me/zero/libhat/Section.java b/bindings/java/src/main/java/me/zero/libhat/Section.java
new file mode 100644
index 0000000..33febcb
--- /dev/null
+++ b/bindings/java/src/main/java/me/zero/libhat/Section.java
@@ -0,0 +1,42 @@
+package me.zero.libhat;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author Brady
+ */
+public final class Section {
+
+ @NotNull
+ private final String name;
+
+ @Nullable
+ private final ByteBuffer data;
+
+ @NotNull
+ private final EnumSet protection;
+
+ Section(@NotNull final String name, @Nullable final ByteBuffer data, @NotNull final EnumSet protection) {
+ this.name = Objects.requireNonNull(name);
+ this.data = data;
+ this.protection = Objects.requireNonNull(protection);
+ }
+
+ public @NotNull String getName() {
+ return this.name;
+ }
+
+ public @NotNull Optional getData() {
+ return Optional.ofNullable(this.data);
+ }
+
+ public @NotNull EnumSet getProtection() {
+ return this.protection;
+ }
+}
diff --git a/bindings/java/src/main/java/me/zero/libhat/Segment.java b/bindings/java/src/main/java/me/zero/libhat/Segment.java
new file mode 100644
index 0000000..0e7c7e6
--- /dev/null
+++ b/bindings/java/src/main/java/me/zero/libhat/Segment.java
@@ -0,0 +1,34 @@
+package me.zero.libhat;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author Brady
+ */
+public final class Segment {
+
+ @Nullable
+ private final ByteBuffer data;
+
+ @NotNull
+ private final EnumSet protection;
+
+ Segment(@Nullable final ByteBuffer data, @NotNull final EnumSet protection) {
+ this.data = data;
+ this.protection = Objects.requireNonNull(protection);
+ }
+
+ public @NotNull Optional getData() {
+ return Optional.ofNullable(data);
+ }
+
+ public @NotNull EnumSet getProtection() {
+ return protection;
+ }
+}
diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java
index 3040398..a4c68af 100644
--- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java
+++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java
@@ -1,6 +1,10 @@
package me.zero.libhat.jna;
import com.sun.jna.*;
+import com.sun.jna.ptr.PointerByReference;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.ByteBuffer;
/**
* @author Brady
@@ -9,51 +13,192 @@ public interface Libhat extends Library {
Libhat INSTANCE = Native.load("libhat_c", Libhat.class);
+ // ------------------------------------------------------------------------
+ // C types
+ // ------------------------------------------------------------------------
+
+ class size_t extends IntegerType {
+ private static final long serialVersionUID = 1L;
+
+ public size_t() {
+ this(0);
+ }
+
+ public size_t(long value) {
+ super(Native.SIZE_T_SIZE, value, true);
+ }
+ }
+
+ class uintptr_t extends IntegerType {
+ private static final long serialVersionUID = 1L;
+
+ public uintptr_t() {
+ this(0);
+ }
+
+ public uintptr_t(long value) {
+ super(Native.POINTER_SIZE, value, true);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Structures
+ // ------------------------------------------------------------------------
+
+ @Structure.FieldOrder({ "data", "size" })
+ class Span extends Structure {
+ public Pointer data;
+ public size_t size;
+
+ public Span() {}
+
+ public Span(final Pointer p) {
+ super(p);
+ read();
+ }
+
+ @Nullable
+ public final ByteBuffer toBuffer() {
+ if (this.data == null || this.size.longValue() == 0) {
+ return null;
+ }
+ return this.data.getByteBuffer(0, this.size.longValue());
+ }
+
+ public static class ByValue extends Span implements Structure.ByValue {}
+ }
+
+ // ------------------------------------------------------------------------
+ // Callback types
+ // ------------------------------------------------------------------------
+
+ interface ForEachSectionCallback extends Callback {
+ boolean invoke(String name, Span.ByValue data, int protection, Pointer userData);
+ }
+
+ interface ForEachSegmentCallback extends Callback {
+ boolean invoke(Span.ByValue data, int protection, Pointer userData);
+ }
+
+ // ------------------------------------------------------------------------
+ // API
+ // ------------------------------------------------------------------------
+
+ /*
+ * const char* libhat_status_to_string(libhat_status status);
+ */
+ String libhat_status_to_string(int status);
+
/*
- * libhat_status_t libhat_parse_signature(
- * const char* signatureStr,
- * signature_t** signatureOut
+ * libhat_status libhat_parse_signature(
+ * const char* signatureStr,
+ * const libhat_signature** signatureOut
* );
*/
- int libhat_parse_signature(String signatureStr, Pointer[] signatureOut);
+ int libhat_parse_signature(String signatureStr, PointerByReference signatureOut);
/*
- * libhat_status_t libhat_create_signature(
- * const char* bytes,
- * const char* mask,
- * size_t size,
- * signature_t** signatureOut
+ * libhat_status libhat_create_signature(
+ * const char* bytes,
+ * const char* mask,
+ * size_t size,
+ * const libhat_signature** signatureOut
* );
*/
- int libhat_create_signature(byte[] bytes, byte[] mask, int size, Pointer[] signatureOut);
+ int libhat_create_signature(byte[] bytes, byte[] mask, size_t size, PointerByReference signatureOut);
/*
* const void* libhat_find_pattern(
- * const signature_t* signature,
- * const void* buffer,
- * size_t size,
- * scan_alignment align
+ * const libhat_signature* signature,
+ * const void* buffer,
+ * size_t size,
+ * libhat_alignment align,
+ * libhat_hint hints
* );
*/
- Pointer libhat_find_pattern(Pointer signature, Pointer buffer, long size, int align);
+ Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int align, int hints);
/*
* const void* libhat_find_pattern_mod(
- * const signature_t* signature,
- * const void* module,
- * const char* section,
- * scan_alignment align
+ * const libhat_signature* signature,
+ * const libhat_module* module,
+ * const char* section,
+ * libhat_alignment align,
+ * libhat_hint hints
+ * );
+ */
+ Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align, int hints);
+
+ /*
+ * uintptr_t libhat_module_address(const libhat_module* module);
+ */
+ uintptr_t libhat_module_address(Pointer module);
+
+ /*
+ * libhat_span libhat_module_get_data(const libhat_module* module);
+ */
+ Span.ByValue libhat_module_get_data(Pointer module);
+
+ /*
+ * libhat_span libhat_module_get_executable_data(const libhat_module* module);
+ */
+ Span.ByValue libhat_module_get_executable_data(Pointer module);
+
+ /*
+ * libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name);
+ */
+ Span.ByValue libhat_module_get_section_data(Pointer module, String name);
+
+ /*
+ * void libhat_module_for_each_section(
+ * const libhat_module* module,
+ * libhat_for_each_section_cb callback,
+ * void* user_data
* );
*/
- Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align);
+ void libhat_module_for_each_section(Pointer module, ForEachSectionCallback callback, Pointer userData);
+
+ /*
+ * void libhat_module_for_each_segment(
+ * const libhat_module* module,
+ * libhat_for_each_segment_cb callback,
+ * void* user_data
+ * );
+ */
+ void libhat_module_for_each_segment(Pointer module, ForEachSegmentCallback callback, Pointer userData);
+
+ /*
+ * bool libhat_is_readable(const void* data, size_t size);
+ */
+ boolean libhat_is_readable(Pointer data, size_t size);
+
+ /*
+ * bool libhat_is_writable(const void* data, size_t size);
+ */
+ boolean libhat_is_writable(Pointer data, size_t size);
+
+ /*
+ * bool libhat_is_executable(const void* data, size_t size);
+ */
+ boolean libhat_is_executable(Pointer data, size_t size);
+
+ /*
+ * const libhat_module* libhat_get_process_module();
+ */
+ Pointer libhat_get_process_module();
/*
- * const void* libhat_get_module(const char* name);
+ * const libhat_module* libhat_get_module(const char* name);
*/
Pointer libhat_get_module(String name);
/*
- * void libhat_free(void* mem);
+ * const libhat_module* libhat_module_at(const void* address);
+ */
+ Pointer libhat_module_at(Pointer address);
+
+ /*
+ * void libhat_free(const void* object);
*/
- void libhat_free(Pointer mem);
+ void libhat_free(Pointer object);
}
diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h
index bcaa44c..4210ae4 100644
--- a/include/libhat/c/libhat.h
+++ b/include/libhat/c/libhat.h
@@ -20,13 +20,15 @@
#define LIBHAT_API
#endif
+#include
#include
+#include
#ifdef __cplusplus
extern "C" {
#endif
-typedef enum libhat_status_t {
+typedef enum libhat_status {
libhat_success, // The operation was successful
libhat_err_unknown,
libhat_err_sig_missing_masked_byte,
@@ -34,48 +36,102 @@ typedef enum libhat_status_t {
libhat_err_sig_empty_signature,
libhat_err_sig_expected_wildcard,
libhat_err_sig_invalid_token_length,
-} libhat_status_t;
-
-typedef enum scan_alignment {
- scan_alignment_x1 = 1,
- scan_alignment_x4 = 4,
- scan_alignment_x16 = 16,
-} scan_alignment_t;
-
-typedef struct signature {
- void* data;
- size_t count;
-} signature_t;
-
-LIBHAT_API libhat_status_t libhat_parse_signature(
- const char* signatureStr,
- signature_t** signatureOut
+} libhat_status;
+
+typedef enum libhat_alignment {
+ libhat_alignment_x1 = 1,
+ libhat_alignment_x4 = 4,
+ libhat_alignment_x16 = 16,
+} libhat_alignment;
+
+typedef enum libhat_hint {
+ libhat_hint_none = 0,
+ libhat_hint_x86_64 = 1 << 0,
+ libhat_hint_pair0 = 1 << 1,
+ libhat_hint_aarch64 = 1 << 2,
+} libhat_hint;
+
+typedef enum libhat_protection {
+ libhat_protection_none = 0b000,
+ libhat_protection_read = 0b001,
+ libhat_protection_write = 0b010,
+ libhat_protection_execute = 0b100,
+} libhat_protection;
+
+typedef struct libhat_signature libhat_signature;
+typedef struct libhat_module libhat_module;
+
+typedef struct libhat_span {
+ const void* data;
+ size_t size;
+} libhat_span;
+
+typedef bool(*libhat_for_each_section_cb)(const char* name, libhat_span data, libhat_protection prot, void* user_data);
+typedef bool(*libhat_for_each_segment_cb)(libhat_span data, libhat_protection prot, void* user_data);
+
+LIBHAT_API const char* libhat_status_to_string(libhat_status status);
+
+LIBHAT_API libhat_status libhat_parse_signature(
+ const char* signatureStr,
+ const libhat_signature** signatureOut
);
-LIBHAT_API libhat_status_t libhat_create_signature(
- const char* bytes,
- const char* mask,
- size_t size,
- signature_t** signatureOut
+LIBHAT_API libhat_status libhat_create_signature(
+ const char* bytes,
+ const char* mask,
+ size_t size,
+ const libhat_signature** signatureOut
);
LIBHAT_API const void* libhat_find_pattern(
- const signature_t* signature,
- const void* buffer,
- size_t size,
- scan_alignment_t align
+ const libhat_signature* signature,
+ const void* buffer,
+ size_t size,
+ libhat_alignment align = libhat_alignment_x1,
+ libhat_hint hints = libhat_hint_none
);
LIBHAT_API const void* libhat_find_pattern_mod(
- const signature_t* signature,
- const void* module,
- const char* section,
- scan_alignment_t align
+ const libhat_signature* signature,
+ const libhat_module* module,
+ const char* section,
+ libhat_alignment align = libhat_alignment_x1,
+ libhat_hint hints = libhat_hint_none
);
-LIBHAT_API const void* libhat_get_module(const char* name);
+LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module);
+
+LIBHAT_API libhat_span libhat_module_get_data(const libhat_module* module);
+
+LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* module);
+
+LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name);
+
+LIBHAT_API void libhat_module_for_each_section(
+ const libhat_module* module,
+ libhat_for_each_section_cb callback,
+ void* user_data
+);
+
+LIBHAT_API void libhat_module_for_each_segment(
+ const libhat_module* module,
+ libhat_for_each_segment_cb callback,
+ void* user_data
+);
+
+LIBHAT_API bool libhat_is_readable(const void* data, size_t size);
+
+LIBHAT_API bool libhat_is_writable(const void* data, size_t size);
+
+LIBHAT_API bool libhat_is_executable(const void* data, size_t size);
+
+LIBHAT_API const libhat_module* libhat_get_process_module();
+
+LIBHAT_API const libhat_module* libhat_get_module(const char* name);
+
+LIBHAT_API const libhat_module* libhat_module_at(const void* address);
-LIBHAT_API void libhat_free(void* mem);
+LIBHAT_API void libhat_free(const void* object);
#ifdef __cplusplus
}
diff --git a/include/libhat/process.hpp b/include/libhat/process.hpp
index 476c088..03ffddc 100644
--- a/include/libhat/process.hpp
+++ b/include/libhat/process.hpp
@@ -63,7 +63,7 @@ LIBHAT_EXPORT namespace hat::process {
friend hat::process::module get_process_module();
friend std::optional get_module(std::string_view);
- friend std::optional module_at(void* address);
+ friend std::optional module_at(const void* address);
std::shared_ptr impl{};
};
@@ -86,5 +86,5 @@ LIBHAT_EXPORT namespace hat::process {
/// Returns the module containing the specified address. If the given address is not located within a
/// loaded module, std::nullopt is returned instead.
- [[nodiscard]] std::optional module_at(void* address);
+ [[nodiscard]] std::optional module_at(const void* address);
}
diff --git a/include/libhat/result.hpp b/include/libhat/result.hpp
index cc0812d..33d60f1 100644
--- a/include/libhat/result.hpp
+++ b/include/libhat/result.hpp
@@ -98,7 +98,7 @@ LIBHAT_EXPORT namespace hat {
#endif
}
- LIBHAT_CONSTEXPR_RESULT T& value() {
+ LIBHAT_CONSTEXPR_RESULT T& value() & {
#ifdef LIBHAT_RESULT_EXPECTED
return impl.value();
#else
@@ -106,7 +106,15 @@ LIBHAT_EXPORT namespace hat {
#endif
}
- LIBHAT_CONSTEXPR_RESULT const T& value() const {
+ LIBHAT_CONSTEXPR_RESULT T&& value() && {
+#ifdef LIBHAT_RESULT_EXPECTED
+ return std::move(impl).value();
+#else
+ return std::get<0>(std::move(impl));
+#endif
+ }
+
+ LIBHAT_CONSTEXPR_RESULT const T& value() const& {
#ifdef LIBHAT_RESULT_EXPECTED
return impl.value();
#else
@@ -114,7 +122,15 @@ LIBHAT_EXPORT namespace hat {
#endif
}
- LIBHAT_CONSTEXPR_RESULT E& error() {
+ LIBHAT_CONSTEXPR_RESULT const T&& value() const&& {
+#ifdef LIBHAT_RESULT_EXPECTED
+ return std::move(impl).value();
+#else
+ return std::get<0>(std::move(impl));
+#endif
+ }
+
+ LIBHAT_CONSTEXPR_RESULT E& error() & {
#ifdef LIBHAT_RESULT_EXPECTED
return impl.error();
#else
@@ -122,11 +138,27 @@ LIBHAT_EXPORT namespace hat {
#endif
}
- LIBHAT_CONSTEXPR_RESULT const E& error() const {
+ LIBHAT_CONSTEXPR_RESULT E&& error() && {
+#ifdef LIBHAT_RESULT_EXPECTED
+ return std::move(impl).error();
+#else
+ return std::get<1>(std::move(impl));
+#endif
+ }
+
+ LIBHAT_CONSTEXPR_RESULT const E& error() const& {
#ifdef LIBHAT_RESULT_EXPECTED
return impl.error();
#else
return std::get<1>(impl);
+#endif
+ }
+
+ LIBHAT_CONSTEXPR_RESULT const E&& error() const&& {
+#ifdef LIBHAT_RESULT_EXPECTED
+ return std::move(impl).error();
+#else
+ return std::get<1>(std::move(impl));
#endif
}
};
diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp
index 3e20022..cc418c3 100644
--- a/src/c/libhat.cpp
+++ b/src/c/libhat.cpp
@@ -3,31 +3,62 @@
#include
#include
-static signature_t* allocate_signature(const hat::signature_view signature) {
- const auto bytes = std::as_bytes(signature);
- auto* mem = malloc(sizeof(signature_t) + bytes.size());
- auto* sig = static_cast(mem);
- sig->data = static_cast(mem) + sizeof(signature_t);
- sig->count = signature.size();
- std::memcpy(sig->data, bytes.data(), bytes.size());
- return sig;
+namespace {
+
+ struct libhat_ffi_object {
+ virtual ~libhat_ffi_object() = default;
+ };
+
+ template
+ struct libhat_ffi_wrapper : libhat_ffi_object, T {
+
+ template
+ explicit libhat_ffi_wrapper(Args&&... args) : T(std::forward(args)...) {}
+ };
}
-static hat::scan_alignment to_cpp_align(const scan_alignment align) {
+struct libhat_signature final : libhat_ffi_wrapper {
+ using libhat_ffi_wrapper::libhat_ffi_wrapper;
+};
+
+struct libhat_module final : libhat_ffi_wrapper {
+ using libhat_ffi_wrapper::libhat_ffi_wrapper;
+};
+
+static hat::scan_alignment to_cpp_align(const libhat_alignment align) {
switch (align) {
- case scan_alignment_x1:
+ case libhat_alignment_x1:
return hat::scan_alignment::X1;
- case scan_alignment_x4:
+ case libhat_alignment_x4:
return hat::scan_alignment::X4;
- case scan_alignment_x16:
+ case libhat_alignment_x16:
return hat::scan_alignment::X16;
}
exit(EXIT_FAILURE);
}
+static hat::scan_hint to_cpp_hints(const libhat_hint hints) {
+ return static_cast(hints);
+}
+
extern "C" {
-LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, signature_t** signatureOut) {
+LIBHAT_API const char* libhat_status_to_string(const libhat_status status) {
+#define STATUS_CASE(x) case x: return #x
+ switch (status) {
+ STATUS_CASE(libhat_success);
+ STATUS_CASE(libhat_err_unknown);
+ STATUS_CASE(libhat_err_sig_missing_masked_byte);
+ STATUS_CASE(libhat_err_sig_element_parse_error);
+ STATUS_CASE(libhat_err_sig_empty_signature);
+ STATUS_CASE(libhat_err_sig_expected_wildcard);
+ STATUS_CASE(libhat_err_sig_invalid_token_length);
+ }
+#undef STATUS_CASE
+ return "invalid value for libhat_status";
+}
+
+LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, const libhat_signature** signatureOut) {
auto result = hat::parse_signature(signatureStr);
if (!result.has_value()) {
*signatureOut = nullptr;
@@ -41,78 +72,124 @@ LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, sign
}
return libhat_err_unknown;
}
- *signatureOut = allocate_signature(result.value());
+ *signatureOut = new libhat_signature{std::move(result).value()};
return libhat_success;
}
-LIBHAT_API libhat_status_t libhat_create_signature(
- const char* bytes,
- const char* mask,
- const size_t size,
- signature_t** signatureOut
+LIBHAT_API libhat_status libhat_create_signature(
+ const char* bytes,
+ const char* mask,
+ const size_t size,
+ const libhat_signature** signatureOut
) {
hat::signature signature{};
+ bool containsByte = false;
signature.reserve(size);
for (size_t i{}; i < size; i++) {
- if (static_cast(mask[i])) {
- signature.emplace_back(static_cast(bytes[i]));
- } else {
- signature.emplace_back(std::nullopt);
- }
+ containsByte |= signature.emplace_back(
+ static_cast(bytes[i]),
+ static_cast(mask[i])
+ ).all();
+ }
+ if (!containsByte) {
+ return libhat_err_sig_missing_masked_byte;
}
- *signatureOut = allocate_signature(signature);
+ *signatureOut = new libhat_signature{std::move(signature)};
return libhat_success;
}
LIBHAT_API const void* libhat_find_pattern(
- const signature_t* signature,
- const void* buffer,
- const size_t size,
- const scan_alignment align
+ const libhat_signature* signature,
+ const void* buffer,
+ const size_t size,
+ const libhat_alignment align,
+ const libhat_hint hints
) {
- const hat::signature_view view{
- static_cast(signature->data),
- signature->count
- };
-
const auto begin = static_cast(buffer);
const auto end = static_cast(buffer) + size;
- const auto result = hat::find_pattern(begin, end, view, to_cpp_align(align));
+ const auto result = hat::find_pattern(begin, end, *signature, to_cpp_align(align), to_cpp_hints(hints));
return result.has_result() ? result.get() : nullptr;
}
LIBHAT_API const void* libhat_find_pattern_mod(
- const signature_t* signature,
- const void* module,
- const char* section,
- const scan_alignment align
+ const libhat_signature* signature,
+ const libhat_module* module,
+ const char* section,
+ const libhat_alignment align,
+ const libhat_hint hints
) {
- const hat::signature_view view{
- static_cast(signature->data),
- signature->count
- };
+ const auto result = hat::find_pattern(*signature, section, *module, to_cpp_align(align), to_cpp_hints(hints));
+ return result.has_result() ? result.get() : nullptr;
+}
+
+LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module) {
+ return module->address();
+}
- const auto mod = hat::process::module_at(const_cast(module));
- if (!mod.has_value()) {
- return nullptr;
+LIBHAT_API libhat_span libhat_module_get_data(const libhat_module* module) {
+ const auto data = module->get_module_data();
+ return {data.data(), data.size()};
+}
+
+LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* module) {
+ const auto data = module->get_executable_data();
+ return {data.data(), data.size()};
+}
+
+LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name) {
+ const auto data = module->get_section_data(name);
+ return {data.data(), data.size()};
+}
+
+LIBHAT_API void libhat_module_for_each_section(const libhat_module* module, const libhat_for_each_section_cb callback, void* user_data) {
+ std::string buffer;
+ module->for_each_section([=, &buffer](auto name, auto data, auto prot) {
+ buffer.assign(name);
+ return callback(buffer.c_str(), {data.data(), data.size()}, static_cast(prot), user_data);
+ });
+}
+
+LIBHAT_API void libhat_module_for_each_segment(const libhat_module* module, const libhat_for_each_segment_cb callback, void* user_data) {
+ module->for_each_segment([=](auto data, auto prot) {
+ return callback({data.data(), data.size()}, static_cast(prot), user_data);
+ });
+}
+
+LIBHAT_API bool libhat_is_readable(const void* data, size_t size) {
+ return hat::process::is_readable({static_cast(data), size});
+}
+
+LIBHAT_API bool libhat_is_writable(const void* data, size_t size) {
+ return hat::process::is_writable({static_cast(data), size});
+}
+
+LIBHAT_API bool libhat_is_executable(const void* data, size_t size) {
+ return hat::process::is_executable({static_cast(data), size});
+}
+
+LIBHAT_API const libhat_module* libhat_get_process_module() {
+ return new libhat_module{hat::process::get_process_module()};
+}
+
+LIBHAT_API const libhat_module* libhat_get_module(const char* name) {
+ if (!name) {
+ return libhat_get_process_module();
}
- const auto result = hat::find_pattern(view, section, mod.value(), to_cpp_align(align));
- return result.has_result() ? result.get() : nullptr;
+ if (auto mod = hat::process::get_module(name); mod.has_value()) {
+ return new libhat_module{std::move(mod).value()};
+ }
+ return nullptr;
}
-LIBHAT_API const void* libhat_get_module(const char* name) {
- if (name) {
- if (const auto mod = hat::process::get_module(name); mod.has_value()) {
- return reinterpret_cast(mod.value().address());
- } else {
- return nullptr;
- }
+LIBHAT_API const libhat_module* libhat_module_at(const void* address) {
+ if (auto mod = hat::process::module_at(address); mod.has_value()) {
+ return new libhat_module{std::move(mod).value()};
}
- return reinterpret_cast(hat::process::get_process_module().address());
+ return nullptr;
}
-LIBHAT_API void libhat_free(void* mem) {
- free(mem);
+LIBHAT_API void libhat_free(const void* object) {
+ delete static_cast(object);
}
} // extern "C"
diff --git a/src/os/linux/Process.cpp b/src/os/linux/Process.cpp
index 59fcf8e..75a23a0 100644
--- a/src/os/linux/Process.cpp
+++ b/src/os/linux/Process.cpp
@@ -262,7 +262,7 @@ namespace hat::process {
return module;
}
- std::optional module_at(void* address) {
+ std::optional module_at(const void* address) {
Dl_info dlinfo{};
if (!dladdr(address, &dlinfo)) {
return {};
diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp
index 9bbb526..7c3b785 100644
--- a/src/os/mac/Process.cpp
+++ b/src/os/mac/Process.cpp
@@ -310,7 +310,7 @@ namespace hat::process {
return {};
}
- std::optional module_at(void* address) {
+ std::optional module_at(const void* address) {
Dl_info dlinfo{};
if (!dladdr(address, &dlinfo)) {
return {};
diff --git a/src/os/win32/Process.cpp b/src/os/win32/Process.cpp
index c287a11..74a6230 100644
--- a/src/os/win32/Process.cpp
+++ b/src/os/win32/Process.cpp
@@ -108,7 +108,7 @@ namespace hat::process {
return {};
}
- std::optional module_at(void* address) {
+ std::optional module_at(const void* address) {
HMODULE out{};
const auto status = GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,