com.github.bloodshura.ignitium.ntv.process.impl.UnixProcess Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ignitium-native Show documentation
Show all versions of ignitium-native Show documentation
An API for working with native system APIs, with a higher-level interface.
The newest version!
package com.github.bloodshura.ignitium.ntv.process.impl;
import com.github.bloodshura.ignitium.collection.list.XList;
import com.github.bloodshura.ignitium.collection.list.impl.XArrayList;
import com.github.bloodshura.ignitium.collection.view.XBasicView;
import com.github.bloodshura.ignitium.collection.view.XView;
import com.github.bloodshura.ignitium.io.File;
import com.github.bloodshura.ignitium.math.BaseConverter;
import com.github.bloodshura.ignitium.ntv.ErrorCodeNativeException;
import com.github.bloodshura.ignitium.ntv.NativeException;
import com.github.bloodshura.ignitium.ntv.lib.Unix;
import com.github.bloodshura.ignitium.ntv.lib.Unix.iovec;
import com.github.bloodshura.ignitium.ntv.process.Module;
import com.github.bloodshura.ignitium.ntv.process.NativeProcess;
import com.github.bloodshura.ignitium.ntv.util.Buffer;
import com.github.bloodshura.ignitium.ntv.util.RangeUtils;
import com.github.bloodshura.ignitium.sys.XSystem;
import com.github.bloodshura.ignitium.sys.terminal.TerminalProcess;
import com.github.bloodshura.ignitium.tokenizer.SimpleTokenizer;
import com.github.bloodshura.ignitium.worker.StringWorker;
import com.sun.jna.LastErrorException;
import com.sun.jna.Pointer;
import javax.annotation.Nonnull;
import java.io.IOException;
public class UnixProcess extends NativeProcess {
private final iovec local;
private final iovec remote;
public UnixProcess(int processId) {
super(processId);
this.local = new iovec();
this.remote = new iovec();
// Disable JNA's auto-read and auto-write features.
// Cause: https://github.com/java-native-access/jna/issues/778
local.setAutoSynch(false);
remote.setAutoSynch(false);
}
@Nonnull
@Override
public XView getModules() throws NativeException {
File file = new File("/proc/" + getId() + "/maps");
try {
XList modules = new XArrayList<>();
for (String line : file.readLines()) {
SimpleTokenizer tokenizer = new SimpleTokenizer();
XView tokens = tokenizer.tokenize(line);
if (tokens.size() >= 6) {
String region = tokens.get(0);
int regionSeparator = region.indexOf('-');
String path = StringWorker.join(' ', tokens.toArray(5));
int pathSeparator = path.lastIndexOf('/');
if (!path.isEmpty() && regionSeparator != -1) {
String startStr = region.substring(0, regionSeparator);
String endStr = region.substring(regionSeparator + 1);
if (BaseConverter.isEncodable(startStr, BaseConverter.HEXADECIMAL) && BaseConverter.isEncodable(endStr, BaseConverter.HEXADECIMAL)) {
long start = BaseConverter.encodeToLong(startStr, BaseConverter.HEXADECIMAL);
long end = BaseConverter.encodeToInt(endStr, BaseConverter.HEXADECIMAL);
String name = pathSeparator != -1 ? path.substring(pathSeparator + 1) : path;
modules.add(new Module(this, name, start, (int) (end - start)));
}
}
}
}
return new XBasicView<>(modules);
} catch (IOException exception) {
throw new NativeException("Could not read modules of process " + getId(), exception);
}
}
@Override
public void inject(@Nonnull File library) throws NativeException {
if (!XSystem.getTerminal().exists("gdb")) {
throw new NativeException("GDB (GNU Debugger) command not found");
}
if (getModules().any(module -> module.getName().equals(library.getFullName()))) {
throw new NativeException("There's already a loaded library named \"" + library.getFullName() + '"');
}
XList gdbCommands = new XArrayList<>();
gdbCommands.add("attach " + getId());
gdbCommands.add("set $dlopen = (void*(*)(char*, int)) dlopen");
gdbCommands.add("call $dlopen(\"" + library + "\", 1)");
gdbCommands.add("detach");
String gdbCommand = gdbCommands.map(command -> "--eval-command \"" + command + "\"").toString(' ');
String shellCommand = "gdb --batch --quiet " + gdbCommand;
try (TerminalProcess process = XSystem.getTerminal().runInShell(shellCommand)) {
// TODO
} catch (IOException exception) {
throw new NativeException("Failed to run GDB injection command", exception);
}
}
@Override
public boolean isReadable(long address, int size) throws NativeException {
if (!RangeUtils.validateRange(address, size)) {
return false;
}
Pointer pointer = new Pointer(address);
Buffer buffer = Buffer.common(size);
populate(pointer, buffer, size);
try {
return Unix.process_vm_readv(getId(), local, 1, remote, 1, 0) == size;
} catch (LastErrorException exception) {
return false;
}
}
@Override
public boolean isWritable(long address, int size) throws NativeException {
if (!RangeUtils.validateRange(address, size)) {
return false;
}
throw new UnsupportedOperationException("UnixProcess::isWritable not implemented");
}
@Override
public void read(long address, @Nonnull Buffer buffer, int size) throws NativeException {
RangeUtils.checkRange(address, size, (int) buffer.size());
Pointer pointer = new Pointer(address);
populate(pointer, buffer, size);
try {
long read = Unix.process_vm_readv(getId(), local, 1, remote, 1, 0);
if (read == 0x10000 /* -1 as ssize_t, according to Linux's man */) {
throw new ErrorCodeNativeException("Could not read " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL));
}
if (read != size) {
throw new NativeException("Expected to read " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL) + ", but " + read + " bytes were read instead");
}
} catch (LastErrorException exception) {
throw new NativeException("Could not read " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL) + ": " + exception.getMessage());
}
}
@Override
public void write(long address, @Nonnull Buffer buffer) throws NativeException {
RangeUtils.checkRange(address, (int) buffer.size());
Pointer pointer = new Pointer(address);
int size = (int) buffer.size();
populate(pointer, buffer, size);
try {
long written = Unix.process_vm_writev(getId(), local, 1, remote, 1, 0);
if (written == 0x10000 /* -1 as ssize_t, according to Linux's man */) {
throw new ErrorCodeNativeException("Could not write " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL));
}
if (written != size) {
throw new NativeException("Expected to write " + size + " bytes at address 0x" + BaseConverter.encode(address, BaseConverter.HEXADECIMAL) + ", but " + written + " bytes were written instead");
}
} catch (LastErrorException exception) {
throw new NativeException(exception.getMessage());
}
}
private void populate(@Nonnull Pointer pointer, @Nonnull Buffer buffer, int size) {
// Calling 'writeField' instead of simply assigning the values to their respective fields,
// because we disabled auto-synching of the iovec instances on constructor.
// 'writeField' not only writes to the fields, but also to the underlying native memory.
local.writeField("iov_base", buffer);
local.writeField("iov_len", size);
remote.writeField("iov_base", pointer);
remote.writeField("iov_len", size);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy