Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
import dev.xdark.ssvm.execution.Locals;
import dev.xdark.ssvm.execution.Result;
import dev.xdark.ssvm.filesystem.FileManager;
import dev.xdark.ssvm.mirror.member.JavaField;
import dev.xdark.ssvm.mirror.member.JavaMethod;
import dev.xdark.ssvm.mirror.member.area.ClassArea;
import dev.xdark.ssvm.mirror.type.InstanceClass;
import dev.xdark.ssvm.operation.VMOperations;
import dev.xdark.ssvm.value.ArrayValue;
import dev.xdark.ssvm.value.InstanceValue;
import dev.xdark.ssvm.value.ObjectValue;
import lombok.experimental.UtilityClass;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.BasicFileAttributes;

/**
* Initializes sun/nio/fs/WindowsNativeDispatcher.
Expand All @@ -26,40 +33,113 @@ public class FileSystemNativeDispatcherNatives {
* @param fileManager File manager.
*/
public void init(VirtualMachine vm, FileManager fileManager) {
VMInterface vmi = vm.getInterface();
InstanceClass windowsDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/WindowsNativeDispatcher");
if (windowsDispatcher == null) {
InstanceClass unixDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/UnixNativeDispatcher");
vmi.setInvoker(unixDispatcher, "getcwd", "()[B", ctx -> {
byte[] cwd = fileManager.getCurrentWorkingDirectory().getBytes(StandardCharsets.UTF_8);
ctx.setResult(vm.getOperations().toVMBytes(cwd));
return Result.ABORT;
});
vmi.setInvoker(unixDispatcher, "init", "()V", MethodInvoker.noop());
vmi.setInvoker(unixDispatcher, "init", "()I", ctx -> {
ctx.setResult(0);
return Result.ABORT;
});
InstanceClass linuxDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/LinuxNativeDispatcher");
if (linuxDispatcher != null) {
vmi.setInvoker(linuxDispatcher, "init", "()V", MethodInvoker.noop());
} else {
InstanceClass bsdDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/BsdNativeDispatcher");
vmi.setInvoker(bsdDispatcher, "initIDs", "()V", MethodInvoker.noop());
InstanceClass macDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nui/fs/MacOSXNativeDispatcher");
if (macDispatcher != null) {
vmi.setInvoker(macDispatcher, "normalizepath", "([CI)[C", ctx -> {
Locals locals = ctx.getLocals();
VMOperations ops = vm.getOperations();
ArrayValue path = ops.checkNotNull(locals.loadReference(0));
// int form = locals.load(1).asInt();
ctx.setResult(path);
return Result.ABORT;
});
if (windowsDispatcher != null) {
initWindows(vm, fileManager, windowsDispatcher);
return;
}

InstanceClass unixDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/UnixNativeDispatcher");
if (unixDispatcher != null) {
initUnix(vm, fileManager, unixDispatcher);
return;
}

InstanceClass linuxDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/LinuxNativeDispatcher");
if (linuxDispatcher != null) {
initLinux(vm, fileManager, linuxDispatcher);
return;
}

InstanceClass bsdDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/BsdNativeDispatcher");
if (bsdDispatcher != null) {
initBsd(vm, fileManager, bsdDispatcher);
return;
}

InstanceClass macDispatcher = (InstanceClass) vm.findBootstrapClass("sun/nui/fs/MacOSXNativeDispatcher");
if (macDispatcher != null) {
initMac(vm, fileManager, macDispatcher);
}
}

private static void initMac(VirtualMachine vm, FileManager fileManager, InstanceClass macDispatcher) {
VMInterface vmi = vm.getInterface();
vmi.setInvoker(macDispatcher, "normalizepath", "([CI)[C", ctx -> {
Locals locals = ctx.getLocals();
VMOperations ops = vm.getOperations();
ArrayValue path = ops.checkNotNull(locals.loadReference(0));
// int form = locals.load(1).asInt();
ctx.setResult(path);
return Result.ABORT;
});
}

private static void initBsd(VirtualMachine vm, FileManager fileManager, InstanceClass bsdDispatcher) {
VMInterface vmi = vm.getInterface();
vmi.setInvoker(bsdDispatcher, "initIDs", "()V", MethodInvoker.noop());
}

private static void initLinux(VirtualMachine vm, FileManager fileManager, InstanceClass linuxDispatcher) {
VMInterface vmi = vm.getInterface();
vmi.setInvoker(linuxDispatcher, "init", "()V", MethodInvoker.noop());
}

private static void initUnix(VirtualMachine vm, FileManager fileManager, InstanceClass unixDispatcher) {
VMInterface vmi = vm.getInterface();
vmi.setInvoker(unixDispatcher, "getcwd", "()[B", ctx -> {
byte[] cwd = fileManager.getCurrentWorkingDirectory().getBytes(StandardCharsets.UTF_8);
ctx.setResult(vm.getOperations().toVMBytes(cwd));
return Result.ABORT;
});
vmi.setInvoker(unixDispatcher, "init", "()V", MethodInvoker.noop());
vmi.setInvoker(unixDispatcher, "init", "()I", ctx -> {
ctx.setResult(0);
return Result.ABORT;
});
}

private static void initWindows(VirtualMachine vm, FileManager fileManager, InstanceClass windowsDispatcher) {
VMOperations ops = vm.getOperations();
VMInterface vmi = vm.getInterface();
vmi.setInvoker(windowsDispatcher, "initIDs", "()V", MethodInvoker.noop());

InstanceClass winPath = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/WindowsPath");
JavaField winPathString = winPath.getField("path", "Ljava/lang/String;");

InstanceClass winAttrs = (InstanceClass) vm.findBootstrapClass("sun/nio/fs/WindowsFileAttributes");
JavaMethod winAttrsCtor = winAttrs.getMethod("<init>", "(IJJJJIIII)V");
vmi.setInvoker(winAttrs, "get", "(Lsun/nio/fs/WindowsPath;Z)Lsun/nio/fs/WindowsFileAttributes;", ctx -> {
ObjectValue pathParam = ctx.getLocals().loadReference(0);
String pathLiteral = ops.toString(vm.getMemoryManager().readReference(pathParam, winPathString.getOffset()));
try {
InstanceValue attrsInstance = vm.getMemoryManager().newInstance(winAttrs);
BasicFileAttributes attrs = fileManager.getAttributes(pathLiteral, BasicFileAttributes.class);
if (attrs != null) {
Locals newLocals = vm.getThreadStorage().newLocals(winAttrsCtor);
long creationTime = attrs.creationTime().toMillis();
long lastAccessTime = attrs.lastAccessTime().toMillis();
long lastWriteTime = attrs.lastModifiedTime().toMillis();
long size = attrs.size();
newLocals.setReference(0, attrsInstance);
newLocals.setInt(1, 32); // fileAttrs
newLocals.setLong(2, creationTime);
newLocals.setLong(4, lastAccessTime);
newLocals.setLong(6, lastWriteTime);
newLocals.setLong(8, size);
newLocals.setInt(10, 0); // reparseTag
newLocals.setInt(11, 0); // volSerialNumber
newLocals.setInt(12, 0); // fileIndexHigh
newLocals.setInt(13, 0); // fileIndexLow
ops.invokeReference(winAttrsCtor, newLocals);
ctx.setResult(attrsInstance);
} else {
ops.throwException(vm.getSymbols().java_io_FileNotFoundException(), pathLiteral);
}
} catch (IOException ex) {
ops.throwException(vm.getSymbols().java_io_IOException(), ex.getMessage());
}
} else {
vmi.setInvoker(windowsDispatcher, "initIDs", "()V", MethodInvoker.noop());
}
return Result.ABORT;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import dev.xdark.ssvm.VirtualMachine;
import dev.xdark.ssvm.api.MethodInvoker;
import dev.xdark.ssvm.api.VMInterface;
import dev.xdark.ssvm.execution.ExecutionContext;
import dev.xdark.ssvm.execution.Locals;
import dev.xdark.ssvm.execution.Result;
import dev.xdark.ssvm.filesystem.FileManager;
import dev.xdark.ssvm.memory.allocation.MemoryData;
import dev.xdark.ssvm.memory.management.MemoryManager;
import dev.xdark.ssvm.mirror.member.JavaField;
import dev.xdark.ssvm.mirror.member.JavaMethod;
import dev.xdark.ssvm.mirror.type.InstanceClass;
import dev.xdark.ssvm.operation.VMOperations;
Expand All @@ -20,6 +22,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.zip.ZipFile;

/**
* Initializes multiple classes:
Expand All @@ -41,6 +45,7 @@ public class GenericFileSystemNatives {
*/
public void init(VirtualMachine vm, FileManager fileManager) {
VMInterface vmi = vm.getInterface();
VMOperations ops = vm.getOperations();
InstanceClass fd = vm.getSymbols().java_io_FileDescriptor();

MethodInvoker set = ctx -> {
Expand All @@ -53,7 +58,6 @@ public void init(VirtualMachine vm, FileManager fileManager) {
}
if (lateinit) {
vmi.setInvoker(fd, "initIDs", "()V", ctx -> {
VMOperations ops = vm.getOperations();
InstanceValue in = (InstanceValue) ops.getReference(fd, "in", "Ljava/io/FileDescriptor;");
ops.putLong(in, fd, "handle", mapVMStream(vm, fileManager, 0));
InstanceValue out = (InstanceValue) ops.getReference(fd, "out", "Ljava/io/FileDescriptor;");
Expand Down Expand Up @@ -89,7 +93,6 @@ public void init(VirtualMachine vm, FileManager fileManager) {
if (out == null) {
return Result.ABORT;
}
VMOperations ops = vm.getOperations();
byte[] bytes = ops.toJavaBytes(locals.loadReference(1));
int off = locals.loadInt(2);
int len = locals.loadInt(3);
Expand Down Expand Up @@ -118,7 +121,6 @@ public void init(VirtualMachine vm, FileManager fileManager) {
vmi.setInvoker(fos, "open0", "(Ljava/lang/String;Z)V", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
VMOperations ops = vm.getOperations();
String path = ops.readUtf8(locals.loadReference(1));
boolean append = locals.loadInt(2) != 0;
try {
Expand All @@ -145,37 +147,10 @@ public void init(VirtualMachine vm, FileManager fileManager) {
});
InstanceClass fis = (InstanceClass) vm.findBootstrapClass("java/io/FileInputStream");
vmi.setInvoker(fis, "initIDs", "()V", MethodInvoker.noop());
vmi.setInvoker(fis, "readBytes", "([BII)I", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
long handle = getFileStreamHandle(vm, _this);
InputStream in = fileManager.getFdIn(handle);
if (in == null) {
ctx.setResult(-1);
} else {
try {
int off = locals.loadInt(2);
int len = locals.loadInt(3);
byte[] bytes = new byte[len];
int read = in.read(bytes);
if (read > 0) {
ArrayValue vmBuffer = locals.loadReference(1);
MemoryManager memoryManager = vm.getMemoryManager();
int start = memoryManager.arrayBaseOffset(byte.class) + off;
MemoryData data = vmBuffer.getMemory().getData();
data.write(start, bytes, 0, read);
}
ctx.setResult(read);
} catch (IOException ex) {
vm.getOperations().throwException(vm.getSymbols().java_io_IOException(), ex.getMessage());
}
}
return Result.ABORT;
});
vmi.setInvoker(fis, "readBytes", "([BII)I", ctx -> readBytes(ctx, vm, fileManager));
vmi.setInvoker(fis, "read0", "()I", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
VMOperations ops = vm.getOperations();
long handle = getFileStreamHandle(vm, _this);
InputStream in = fileManager.getFdIn(handle);
if (in == null) {
Expand All @@ -192,7 +167,6 @@ public void init(VirtualMachine vm, FileManager fileManager) {
vmi.setInvoker(fis, "skip0", "(J)J", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
VMOperations ops = vm.getOperations();
long handle = getFileStreamHandle(vm, _this);
InputStream in = fileManager.getFdIn(handle);
if (in == null) {
Expand All @@ -209,7 +183,6 @@ public void init(VirtualMachine vm, FileManager fileManager) {
vmi.setInvoker(fis, "open0", "(Ljava/lang/String;)V", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
VMOperations ops = vm.getOperations();
String path = ops.readUtf8(locals.loadReference(1));
try {
long handle = fileManager.open(path, FileManager.READ);
Expand Down Expand Up @@ -249,6 +222,77 @@ public void init(VirtualMachine vm, FileManager fileManager) {
}
return Result.ABORT;
});

InstanceClass raf = (InstanceClass) vm.findBootstrapClass("java/io/RandomAccessFile");
if (raf != null) {
JavaField pathField = raf.getField("path", "Ljava/lang/String;");
vmi.setInvoker(raf, "initIDs", "()V", MethodInvoker.noop());
vmi.setInvoker(raf, "length", "()J", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
String pathLiteral = ops.toString(vm.getMemoryManager().readReference(_this, pathField.getOffset()));
try {
long size = fileManager.getAttributes(pathLiteral, BasicFileAttributes.class).size();
ctx.setResult(size);
} catch (FileNotFoundException ex) {
ops.throwException(vm.getSymbols().java_io_FileNotFoundException(), ex.getMessage());
} catch (IOException ex) {
ops.throwException(vm.getSymbols().java_io_IOException(), ex.getMessage());
}
return Result.ABORT;
});
vmi.setInvoker(raf, "readBytes", "([BII)I", ctx -> readBytes(ctx, vm, fileManager));
vmi.setInvoker(raf, "seek0", "(J)V", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
long handle = getFileStreamHandle(vm, _this);
InputStream in = fileManager.getFdIn(handle);
if (in != null) {
try {
long pos = locals.loadLong(1);
in.reset();
int skipped = 0;
while (skipped < pos) {
skipped += in.skip(pos - skipped);
}
} catch (IOException ex) {
ops.throwException(vm.getSymbols().java_io_IOException(), ex.getMessage());
}
}
return Result.ABORT;
});
vmi.setInvoker(raf, "open0", "(Ljava/lang/String;I)V", ctx -> {
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
String path = ops.readUtf8(locals.loadReference(1));
int mode = locals.loadInt(2);
try {
int fmMode = FileManager.READ;
if (mode == 2) fmMode = FileManager.ACCESS_READ | FileManager.ACCESS_WRITE;

// TODO: Temporary hack
// - The context check here is so that we can determine if this is being done from a ZipFile constructor
// - We want to open this as a zip file in our file manager so other Jar/ZipFileNatives hooks function correctly
// - However, this is obviously a huge hack. I'm not sure how we should best approach this.
long handle;
if (ctx.getVM().getThreadManager().currentOsThread().getBacktrace().at(9).getMethod().toString().contains("java/util/zip/ZipFile.<init>")) {
// Zip file modes are different
fmMode = ZipFile.OPEN_READ;
handle = fileManager.openZipFile(path, fmMode);
} else {
handle = fileManager.open(path, fmMode);
}

ObjectValue _fd = ops.getReference(_this, fos, "fd", "Ljava/io/FileDescriptor;");
ops.putLong(_fd, fd, "handle", handle);
} catch (FileNotFoundException ex) {
ops.throwException(vm.getSymbols().java_io_FileNotFoundException(), ex.getMessage());
} catch (IOException ex) {
ops.throwException(vm.getSymbols().java_io_IOException(), ex.getMessage());
}
return Result.ABORT;
});
}
}

private static long mapVMStream(VirtualMachine vm, FileManager fileManager, int d) {
Expand All @@ -260,7 +304,37 @@ private static long mapVMStream(VirtualMachine vm, FileManager fileManager, int
return 0L;
}

private static Result readBytes(ExecutionContext<?> ctx, VirtualMachine vm, FileManager fileManager) {
// Both FileInputStream/RandomAccessFile declare this method in the exact same way and should be treated similarly
Locals locals = ctx.getLocals();
InstanceValue _this = locals.loadReference(0);
long handle = getFileStreamHandle(vm, _this);
InputStream in = fileManager.getFdIn(handle);
if (in == null) {
ctx.setResult(-1);
} else {
try {
int off = locals.loadInt(2);
int len = locals.loadInt(3);
byte[] bytes = new byte[len];
int read = in.read(bytes);
if (read > 0) {
ArrayValue vmBuffer = locals.loadReference(1);
MemoryManager memoryManager = vm.getMemoryManager();
int start = memoryManager.arrayBaseOffset(byte.class) + off;
MemoryData data = vmBuffer.getMemory().getData();
data.write(start, bytes, 0, read);
}
ctx.setResult(read);
} catch (IOException ex) {
vm.getOperations().throwException(vm.getSymbols().java_io_IOException(), ex.getMessage());
}
}
return Result.ABORT;
}

private static long getFileStreamHandle(VirtualMachine vm, InstanceValue fs) {
// Both FileInputStream/RandomAccessFile declare this method in the exact same way
JavaMethod getFD = vm.getRuntimeResolver().resolveVirtualMethod(fs, "getFD", "()Ljava/io/FileDescriptor;");
Locals locals = vm.getThreadStorage().newLocals(getFD);
locals.setReference(0, fs);
Expand Down
Loading