diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/api/VMInterface.java b/ssvm-core/src/main/java/dev/xdark/ssvm/api/VMInterface.java index 90e6ef3..45ed291 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/api/VMInterface.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/api/VMInterface.java @@ -21,7 +21,6 @@ */ public final class VMInterface { - private static final MethodInvoker FALLBACK_INVOKER = new InterpretedInvoker(); private static final int MAX_INSNS = 1024; private final InstructionProcessor[] processors = new InstructionProcessor[MAX_INSNS]; private final Map invokerMap = new HashMap<>(); diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/execution/InterpretedInvoker.java b/ssvm-core/src/main/java/dev/xdark/ssvm/execution/InterpretedInvoker.java index 9e5ded5..e9dab6c 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/execution/InterpretedInvoker.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/execution/InterpretedInvoker.java @@ -11,6 +11,7 @@ * @author xDark */ public final class InterpretedInvoker implements MethodInvoker { + public static final InterpretedInvoker INSTANCE = new InterpretedInvoker(); @Override public Result intercept(ExecutionContext ctx) { diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/execution/SimpleExecutionEngine.java b/ssvm-core/src/main/java/dev/xdark/ssvm/execution/SimpleExecutionEngine.java index ecc8329..0cec7f0 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/execution/SimpleExecutionEngine.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/execution/SimpleExecutionEngine.java @@ -17,7 +17,6 @@ */ public class SimpleExecutionEngine implements ExecutionEngine { - private static final MethodInvoker FALLBACK = new InterpretedInvoker(); private final VirtualMachine vm; public SimpleExecutionEngine(VirtualMachine vm) { @@ -52,7 +51,7 @@ public ExecutionContext execute(ExecutionRequest req try { MethodInvoker invoker = vmi.getInvoker(jm); if (invoker == null) { - invoker = FALLBACK; + invoker = InterpretedInvoker.INSTANCE; } Result result = invoker.intercept(ctx); if (result == Result.ABORT) { diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/BasicZipFile.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/BasicZipFile.java index 37697ee..4f627b1 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/BasicZipFile.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/BasicZipFile.java @@ -19,7 +19,7 @@ */ public abstract class BasicZipFile implements ZipFile { - private final int rawHandle; + private final long rawHandle; private final Map contents = new HashMap<>(); private final Map handles = new HashMap<>(); private Map names; @@ -27,7 +27,7 @@ public abstract class BasicZipFile implements ZipFile { /** * @param rawHandle Raw zip handle. */ - protected BasicZipFile(int rawHandle) { + protected BasicZipFile(long rawHandle) { this.rawHandle = rawHandle; } @@ -98,19 +98,17 @@ public synchronized long makeHandle(ZipEntry entry) { handle.set(value); } while (handles.containsKey(handle)); handles.put(handle.copy(), entry); - return (long) value << 32L | rawHandle & 0xffffffffL; + return (rawHandle & 0xFFFFFFFF00000000L) | value; } @Override public synchronized ZipEntry getEntry(long handle) { - handle >>= 32; - return handles.get(Handle.threadLocal((int) handle)); + return handles.get(Handle.threadLocal(handle)); } @Override public synchronized boolean freeHandle(long handle) { - handle >>= 32; - return handles.remove(Handle.threadLocal((int) handle)) != null; + return handles.remove(Handle.threadLocal(handle)) != null; } @Override diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/FileManager.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/FileManager.java index 390f8cd..a719845 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/FileManager.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/FileManager.java @@ -217,6 +217,14 @@ public interface FileManager { */ long openZipFile(String path, int mode) throws IOException; + /** + * Transfers a handle from {@link #open(String, int)} to being a treated as if it were opened with {@link #openZipFile(String, int)}. + * @param handle Handle to migrate. + * @param mode The mode in which the file is to be opened. + * @throws IOException If any I/O error occurs. + */ + void transferInputToZip(long handle, int mode) throws IOException; + /** * @param handle Zip file handle. * @return zip file by it's handle or {@code null}, diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/HostFileManager.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/HostFileManager.java index 5b388e0..627df90 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/HostFileManager.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/HostFileManager.java @@ -22,6 +22,7 @@ */ public class HostFileManager implements FileManager { + protected final Map inputPaths = new HashMap<>(); protected final Map inputs = new HashMap<>(); protected final Map outputs = new HashMap<>(); protected final Map zipFiles = new HashMap<>(); @@ -91,6 +92,7 @@ public synchronized long getRealHandle(long handle) { @Override public synchronized boolean close(long handle) throws IOException { Handle h = Handle.threadLocal(handle); + inputPaths.remove(h); InputStream in = inputs.remove(h); if (in != null) { in.close(); @@ -155,6 +157,7 @@ public synchronized long open(String path, int mode) throws IOException { InputStream in = new BufferedInputStream(new FileInputStream(path)); in.mark(Integer.MAX_VALUE); Handle h = Handle.of(fd); + inputPaths.put(h, path); inputs.put(h, in); return fd; } @@ -259,20 +262,30 @@ public synchronized long openZipFile(String path, int mode) throws IOException { return fd; } + @Override + public void transferInputToZip(long handle, int mode) throws IOException { + Handle h = Handle.of(handle); + if (inputs.remove(h) == null) throw new IOException("Cannot transfer, handle was not open prior"); + String path = inputPaths.get(h); + if (path == null) throw new IOException("Cannot transfer, handle was not associated with a file path prior"); + ZipFile zf = new SimpleZipFile((int) handle, new java.util.zip.ZipFile(new File(path), mode)); + zipFiles.put(h, zf); + } + @Override public synchronized ZipFile getZipFile(long handle) { - return zipFiles.get(Handle.threadLocal((int) handle)); + return zipFiles.get(Handle.threadLocal(handle)); } @Override public synchronized ZipEntry getZipEntry(long handle) { - ZipFile zf = zipFiles.get(Handle.threadLocal((int) handle)); + ZipFile zf = zipFiles.get(Handle.threadLocal(handle)); return zf == null ? null : zf.getEntry(handle); } @Override public synchronized boolean freeZipEntry(long handle) { - ZipFile zf = zipFiles.get(Handle.threadLocal((int) handle)); + ZipFile zf = zipFiles.get(Handle.threadLocal(handle)); return zf != null && zf.freeHandle(handle); } diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleFileManager.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleFileManager.java index 20e12fd..27ca741 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleFileManager.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleFileManager.java @@ -115,6 +115,9 @@ public long openZipFile(String path, int mode) throws IOException { return 0L; } + @Override + public void transferInputToZip(long handle, int mode) throws IOException {} + @Override public ZipFile getZipFile(long handle) { return null; diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleZipFile.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleZipFile.java index 158c4e0..4b0e591 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleZipFile.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/SimpleZipFile.java @@ -21,7 +21,7 @@ public class SimpleZipFile extends BasicZipFile { * @param rawHandle Raw zip handle. * @param handle Zip file. */ - public SimpleZipFile(int rawHandle, ZipFile handle) { + public SimpleZipFile(long rawHandle, ZipFile handle) { super(rawHandle); this.handle = handle; } diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/FileSystemNativeDispatcherNatives.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/FileSystemNativeDispatcherNatives.java index 22aeb3f..338b8a0 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/FileSystemNativeDispatcherNatives.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/FileSystemNativeDispatcherNatives.java @@ -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. @@ -26,40 +33,126 @@ 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()); + vmi.setInvoker(windowsDispatcher, "CreateFile0", "(JIIJII)J", ctx -> { + // TODO: How to get string from NativeBuffer at address 'lpFileName'? + // LPCWSTR lpFileName = jlong_to_ptr(lpFileName); + return Result.ABORT; + }); + /* + private static native long CreateFile0(long lpFileName, + int dwDesiredAccess, + int dwShareMode, + long lpSecurityAttributes, + int dwCreationDisposition, + int dwFlagsAndAttributes) + */ + + 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("", "(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; + }); } } diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/GenericFileSystemNatives.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/GenericFileSystemNatives.java index 92cd2f8..60d4d85 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/GenericFileSystemNatives.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/GenericFileSystemNatives.java @@ -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; @@ -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: @@ -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 -> { @@ -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;"); @@ -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); @@ -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 { @@ -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) { @@ -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) { @@ -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); @@ -249,6 +222,64 @@ 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; + long 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) { @@ -260,7 +291,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); diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/JarFileNatives.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/JarFileNatives.java index ac637e7..0131010 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/JarFileNatives.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/JarFileNatives.java @@ -1,17 +1,30 @@ package dev.xdark.ssvm.filesystem.natives; import dev.xdark.ssvm.VirtualMachine; +import dev.xdark.ssvm.api.MethodInvoker; import dev.xdark.ssvm.api.VMInterface; +import dev.xdark.ssvm.execution.Locals; import dev.xdark.ssvm.execution.Result; import dev.xdark.ssvm.filesystem.FileManager; import dev.xdark.ssvm.filesystem.ZipFile; +import dev.xdark.ssvm.memory.management.MemoryManager; import dev.xdark.ssvm.mirror.type.InstanceClass; +import dev.xdark.ssvm.mirror.type.JavaClass; import dev.xdark.ssvm.operation.VMOperations; import dev.xdark.ssvm.symbol.Symbols; +import dev.xdark.ssvm.thread.ThreadManager; +import dev.xdark.ssvm.value.ArrayValue; +import dev.xdark.ssvm.value.InstanceValue; +import dev.xdark.ssvm.value.NullValue; import dev.xdark.ssvm.value.ObjectValue; import lombok.experimental.UtilityClass; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Locale; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** @@ -31,20 +44,102 @@ public void init(VirtualMachine vm, FileManager fileManager) { Symbols symbols = vm.getSymbols(); InstanceClass zf = symbols.java_util_zip_ZipFile(); InstanceClass jf = symbols.java_util_jar_JarFile(); - vmi.setInvoker(jf, "getMetaInfEntryNames", "()[Ljava/lang/String;", ctx -> { + if (vm.getJvmVersion() <= 8) { + vmi.setInvoker(jf, "getMetaInfEntryNames", "()[Ljava/lang/String;", ctx -> { + VMOperations ops = vm.getOperations(); + ObjectValue _this = ctx.getLocals().loadReference(0); + + // Directly has file handle as a field in JDK 8 + long handle = ops.getLong(_this, zf, "jzfile"); + + ZipFile zip = fileManager.getZipFile(handle); + if (zip == null) { + ops.throwException(symbols.java_lang_IllegalStateException(), "zip closed"); + } + ObjectValue[] paths = zip.stream() + .map(ZipEntry::getName) + .filter(name -> name.toUpperCase(Locale.ENGLISH).startsWith("META-INF/")) + .map(ops::newUtf8) + .toArray(ObjectValue[]::new); + ctx.setResult(ops.toVMReferences(paths)); + + return Result.ABORT; + }); + } else { VMOperations ops = vm.getOperations(); - long handle = ops.getLong(ctx.getLocals().loadReference(0), zf, "jzfile"); - ZipFile zip = fileManager.getZipFile(handle); - if (zip == null) { - ops.throwException(symbols.java_lang_IllegalStateException(), "zip closed"); - } - ObjectValue[] paths = zip.stream() - .map(ZipEntry::getName) - .filter(name -> name.toUpperCase(Locale.ENGLISH).startsWith("META-INF/")) - .map(ops::newUtf8) - .toArray(ObjectValue[]::new); - ctx.setResult(ops.toVMReferences(paths)); - return Result.ABORT; - }); + MemoryManager mem = vm.getMemoryManager(); + ThreadManager threadManager = vm.getThreadManager(); + + // TODO: Noop for this method is a hack and should be fixed later (Affects JDK 9+) + vmi.setInvoker(jf, "checkForSpecialAttributes", "()V", MethodInvoker.noop()); + vmi.setInvoker(jf, "getManifestFromReference", "()Ljava/util/jar/Manifest;", ctx -> { + ObjectValue _this = ctx.getLocals().loadReference(0); + long handle = ZipFileNatives.getJdk9ZipFileHandle(ctx, _this); + ZipFile zipFile = fileManager.getZipFile(handle); + if (zipFile == null) + ops.throwException(symbols.java_lang_IllegalStateException(), "zip closed"); + + // Get manifest entry + ZipEntry entry = zipFile.getEntry(JarFile.MANIFEST_NAME); + if (entry == null) { + ctx.setResult(ctx.getMemoryManager().nullValue()); + return Result.ABORT; + } + + try { + byte[] manifestBytes = zipFile.readEntry(entry); + ArrayValue vmManifestBytes = ops.toVMBytes(manifestBytes); + + InstanceClass baisClass = (InstanceClass) vm.findBootstrapClass("java/io/ByteArrayInputStream"); + InstanceClass manifestClass = (InstanceClass) vm.findBootstrapClass("java/util/jar/Manifest"); + + // new ByteArrayInputStream(manifestBytes) + InstanceValue bais = mem.newInstance(baisClass); + Locals baisLocals = threadManager.currentThreadStorage().newLocals(2); + baisLocals.setReference(0, bais); + baisLocals.setReference(1, vmManifestBytes); + ops.invokeVoid(bais.getJavaClass().getMethod("", "([B)V"), baisLocals); + + // new Manifest(stream); + InstanceValue manifest = mem.newInstance(manifestClass); + Locals manifestLocals = threadManager.currentThreadStorage().newLocals(2); + manifestLocals.setReference(0, manifest); + manifestLocals.setReference(1, bais); + ops.invokeVoid(manifest.getJavaClass().getMethod("", "(Ljava/io/InputStream;)V"), manifestLocals); + + ctx.setResult(manifest); + return Result.ABORT; + } catch (IOException ex) { + ops.throwException(symbols.java_io_IOException(), ex.getMessage()); + throw new IllegalStateException(ex); + } + }); + vmi.setInvoker(jf, "getMetaInfEntryNames", "()[Ljava/lang/String;", ctx -> { + ObjectValue _this = ctx.getLocals().loadReference(0); + + // We can skip parsing the zip if the 'metanames' is empty + ObjectValue res = ops.getReference(_this, zf, "res", "Ljava/util/zip/ZipFile$CleanableResource;"); + ObjectValue zsrc = ops.getReference(res, "zsrc", "Ljava/util/zip/ZipFile$Source;"); + ObjectValue metanames = ops.getReference(zsrc, "metanames", "[I"); + if (metanames.isNull()) { + ObjectValue[] paths = new ObjectValue[0]; + ctx.setResult(ops.toVMReferences(paths)); + return Result.ABORT; + } + + // Pull meta-inf names from zip-file + long handle = ZipFileNatives.getJdk9ZipFileHandle(ctx, _this); + ZipFile zipFile = fileManager.getZipFile(handle); + if (zipFile == null) + ops.throwException(symbols.java_lang_IllegalStateException(), "zip closed"); + ObjectValue[] paths = zipFile.stream() + .map(ZipEntry::getName) + .filter(name -> name.startsWith("META-INF/")) + .map(ops::newUtf8) + .toArray(ObjectValue[]::new); + ctx.setResult(ops.toVMReferences(paths)); + return Result.ABORT; + }); + } } } diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/ZipFileNatives.java b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/ZipFileNatives.java index 80484b4..2a4eb79 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/ZipFileNatives.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/filesystem/natives/ZipFileNatives.java @@ -3,15 +3,16 @@ import dev.xdark.ssvm.VirtualMachine; import dev.xdark.ssvm.api.MethodInvoker; import dev.xdark.ssvm.api.VMInterface; -import dev.xdark.ssvm.execution.Locals; -import dev.xdark.ssvm.execution.PanicException; -import dev.xdark.ssvm.execution.Result; +import dev.xdark.ssvm.execution.*; import dev.xdark.ssvm.filesystem.FileManager; import dev.xdark.ssvm.filesystem.ZipFile; +import dev.xdark.ssvm.memory.management.MemoryManager; import dev.xdark.ssvm.mirror.type.InstanceClass; import dev.xdark.ssvm.operation.VMOperations; import dev.xdark.ssvm.symbol.Symbols; +import dev.xdark.ssvm.thread.ThreadManager; import dev.xdark.ssvm.value.ArrayValue; +import dev.xdark.ssvm.value.InstanceValue; import dev.xdark.ssvm.value.ObjectValue; import lombok.experimental.UtilityClass; @@ -36,26 +37,7 @@ public void init(VirtualMachine vm, FileManager fileManager) { Symbols symbols = vm.getSymbols(); InstanceClass zf = symbols.java_util_zip_ZipFile(); vmi.setInvoker(zf, "initIDs", "()V", MethodInvoker.noop()); - if (vmi.setInvoker(zf, "open", "(Ljava/lang/String;IJZ)J", ctx -> { - // Old-style zip file implementation. - Locals locals = ctx.getLocals(); - VMOperations ops = vm.getOperations(); - ObjectValue path = locals.loadReference(0); - ops.checkNotNull(path); - String zipPath = ops.readUtf8(path); - int mode = locals.loadInt(1); - // last file modification & usemmap are ignored. - try { - long handle = fileManager.openZipFile(zipPath, mode); - if (handle == 0L) { - ops.throwException(symbols.java_io_IOException(), zipPath); - } - ctx.setResult(handle); - } catch (IOException ex) { - ops.throwException(symbols.java_io_IOException(), ex.getMessage()); - } - return Result.ABORT; - })) { + if (hookZip(vm, fileManager)) { vmi.setInvoker(zf, "getTotal", "(J)I", ctx -> { ZipFile zip = fileManager.getZipFile(ctx.getLocals().loadLong(0)); if (zip == null) { @@ -261,6 +243,100 @@ public void init(VirtualMachine vm, FileManager fileManager) { } return Result.ABORT; }); + + // TODO: We can remove this entire invoker if we properly implement the inflater natives + // but until then this bypasses the need to do that. + if (vm.getJvmVersion() > 8) { + InstanceClass baisClass = (InstanceClass) vm.findBootstrapClass("java/io/ByteArrayInputStream"); + vmi.setInvoker(zf, "getInputStream", "(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;", ctx -> { + VMOperations ops = vm.getOperations(); + MemoryManager mem = vm.getMemoryManager(); + ThreadManager threadManager = vm.getThreadManager(); + + // Get the current zip file + Locals locals = ctx.getLocals(); + ObjectValue _this = locals.loadReference(0); + long handle = getJdk9ZipFileHandle(ctx, _this); + ZipFile zipFile = fileManager.getZipFile(handle); + if (zipFile == null) + ops.throwException(symbols.java_lang_IllegalStateException(), "zip closed"); + + try { + // Get the entry bytes + String entryName = ops.toString(ops.getReference((ObjectValue) locals.loadReference(1), "name", "Ljava/lang/String;")); + ZipEntry entry = zipFile.getEntry(entryName); + byte[] manifestBytes = zipFile.readEntry(entry); + ArrayValue vmManifestBytes = ops.toVMBytes(manifestBytes); + + // Wrap into 'new ByteArrayInputStream(bytes)' and pass that as the return value + InstanceValue bais = mem.newInstance(baisClass); + Locals baisLocals = threadManager.currentThreadStorage().newLocals(2); + baisLocals.setReference(0, bais); + baisLocals.setReference(1, vmManifestBytes); + ops.invokeVoid(bais.getJavaClass().getMethod("", "([B)V"), baisLocals); + ctx.setResult(bais); + } catch (IOException ex) { + ops.throwException(symbols.java_io_IOException(), ex.getMessage()); + } + + return Result.ABORT; + }); + } } } + + private static boolean hookZip(VirtualMachine vm, FileManager fileManager) { + boolean hooked = false; + VMOperations ops = vm.getOperations(); + VMInterface vmi = vm.getInterface(); + Symbols symbols = vm.getSymbols(); + InstanceClass zf = symbols.java_util_zip_ZipFile(); + hooked |= vmi.setInvoker(zf, "open", "(Ljava/lang/String;IJZ)J", ctx -> { + // Old-style zip file implementation. + // This method only exists in JDK 8 and below. Later versions migrated to using RandomAccessFile. + Locals locals = ctx.getLocals(); + ObjectValue path = locals.loadReference(0); + ops.checkNotNull(path); + String zipPath = ops.readUtf8(path); + int mode = locals.loadInt(1); + // last file modification & usemmap are ignored. + try { + long handle = fileManager.openZipFile(zipPath, mode); + if (handle == 0L) { + ops.throwException(symbols.java_io_IOException(), zipPath); + } + ctx.setResult(handle); + } catch (IOException ex) { + ops.throwException(symbols.java_io_IOException(), ex.getMessage()); + } + return Result.ABORT; + }); + hooked |= vmi.setInvoker(zf, "", "(Ljava/io/File;ILjava/nio/charset/Charset;)V", ctx -> { + // Interpret the method + InterpretedInvoker.INSTANCE.intercept(ctx); + + // We should have opened a file handle. + // We want to move it from a standard input to a zip file in the file manager. + ObjectValue _this = ctx.getLocals().loadReference(0); + long handle = getJdk9ZipFileHandle(ctx, _this); + try { + fileManager.transferInputToZip(handle, java.util.zip.ZipFile.OPEN_READ); + } catch (IOException ex) { + ops.throwException(symbols.java_io_IOException(), ex.getMessage()); + } + + return Result.ABORT; + }); + return hooked; + } + + public static long getJdk9ZipFileHandle(ExecutionContext ctx, ObjectValue zip) { + VMOperations ops = ctx.getVM().getOperations(); + InstanceClass zf = ctx.getSymbols().java_util_zip_ZipFile(); + ObjectValue res = ops.getReference(zip, zf, "res", "Ljava/util/zip/ZipFile$CleanableResource;"); + ObjectValue zsrc = ops.getReference(res, "zsrc", "Ljava/util/zip/ZipFile$Source;"); + ObjectValue zfile = ops.getReference(zsrc, "zfile", "Ljava/io/RandomAccessFile;"); + ObjectValue fd = ops.getReference(zfile, "fd", "Ljava/io/FileDescriptor;"); + return ops.getLong(fd, "handle"); + } } diff --git a/ssvm-core/src/main/java/dev/xdark/ssvm/io/Handle.java b/ssvm-core/src/main/java/dev/xdark/ssvm/io/Handle.java index cda2277..343051b 100644 --- a/ssvm-core/src/main/java/dev/xdark/ssvm/io/Handle.java +++ b/ssvm-core/src/main/java/dev/xdark/ssvm/io/Handle.java @@ -51,6 +51,11 @@ public int hashCode() { return Long.hashCode(value); } + @Override + public String toString() { + return String.valueOf(value); + } + /** * Returns thread-local handle. * diff --git a/ssvm-core/src/testFixtures/java/dev/xdark/ssvm/dummy/FileToString.java b/ssvm-core/src/testFixtures/java/dev/xdark/ssvm/dummy/FileToString.java new file mode 100644 index 0000000..e4d3320 --- /dev/null +++ b/ssvm-core/src/testFixtures/java/dev/xdark/ssvm/dummy/FileToString.java @@ -0,0 +1,56 @@ +package dev.xdark.ssvm.dummy; + +import dev.xdark.ssvm.util.IOUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Dummy classes to test various IO operations + */ +public class FileToString { + public static String readReadmePath() throws IOException { + return read(Paths.get("README.md")); + } + + public static String readReadmeFile() throws IOException { + return read(new File("README.md")); + } + + public static String readReadmeResource() throws IOException { + return read(FileToString.class.getResourceAsStream("/README.md")); + } + + public static String readPath(String path) throws IOException { + return read(new File(path)); + } + + public static String read(Path file) throws IOException { + return read(Files.readAllBytes(file)); + } + + public static String read(File file) throws IOException { + return read(new FileInputStream(file)); + } + + private static String read(InputStream stream) throws IOException { + return read(IOUtil.readAll(stream)); + } + + private static String read(byte[] bytes) { + int end = bytes.length; + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] == 0) { + end = i; + break; + } + } + return new String(bytes, 0, end, StandardCharsets.UTF_8); + } +} diff --git a/ssvm-io/src/test/java/dev/xdark/ssvm/io/FileReadTest.java b/ssvm-io/src/test/java/dev/xdark/ssvm/io/FileReadTest.java new file mode 100644 index 0000000..af8fa56 --- /dev/null +++ b/ssvm-io/src/test/java/dev/xdark/ssvm/io/FileReadTest.java @@ -0,0 +1,52 @@ +package dev.xdark.ssvm.io; + +import dev.xdark.ssvm.VirtualMachine; +import dev.xdark.ssvm.classloading.SupplyingClassLoaderInstaller; +import dev.xdark.ssvm.dummy.FileToString; +import dev.xdark.ssvm.filesystem.FileManager; +import dev.xdark.ssvm.filesystem.HostFileManager; +import dev.xdark.ssvm.invoke.Argument; +import dev.xdark.ssvm.invoke.InvocationUtil; +import dev.xdark.ssvm.mirror.type.InstanceClass; +import dev.xdark.ssvm.value.InstanceValue; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static dev.xdark.ssvm.classloading.SupplyingClassLoaderInstaller.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for file IO within a VM. + */ +public class FileReadTest { + @Test + public void doTest() throws IOException { + String readmeBaseline = FileToString.readReadmePath(); + + VirtualMachine vm = new VirtualMachine() { + @Override + protected FileManager createFileManager() { + return new HostFileManager(); + } + }; + vm.bootstrap(); + InvocationUtil util = InvocationUtil.create(vm); + + // Install loader into VM that can pull classes from the current classpath + Helper helper = SupplyingClassLoaderInstaller.install(vm, SupplyingClassLoaderInstaller.supplyFromRuntime()); + + // The IO class should be loadable, and we should be able to call its methods + InstanceClass dummyClassInstance = Assertions.assertDoesNotThrow(() -> helper.loadClass(FileToString.class.getName())); + InstanceValue dummyInstance = vm.getMemoryManager().newInstance(dummyClassInstance); + util.invokeVoid(dummyClassInstance.getMethod("", "()V"), Argument.reference(dummyInstance)); + + // String readReadmeFile = util.invokeStringReference(dummyClassInstance.getMethod("readReadmeFile", "()Ljava/lang/String;")); + // assertEquals(readmeBaseline, readReadmeFile); + + String readReadmePath = util.invokeStringReference(dummyClassInstance.getMethod("readReadmePath", "()Ljava/lang/String;")); + assertEquals(readmeBaseline, readReadmePath); + } +}