|
| 1 | +/* |
| 2 | + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | + * |
| 5 | + * This code is free software; you can redistribute it and/or modify it |
| 6 | + * under the terms of the GNU General Public License version 2 only, as |
| 7 | + * published by the Free Software Foundation. Oracle designates this |
| 8 | + * particular file as subject to the "Classpath" exception as provided |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. |
| 10 | + * |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | + * accompanied this code). |
| 16 | + * |
| 17 | + * You should have received a copy of the GNU General Public License version |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | + * |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| 22 | + * or visit www.oracle.com if you need additional information or have any |
| 23 | + * questions. |
| 24 | + */ |
| 25 | +package com.oracle.svm.core.windows; |
| 26 | + |
| 27 | +import static com.oracle.svm.core.Isolates.IMAGE_HEAP_BEGIN; |
| 28 | +import static com.oracle.svm.core.Isolates.IMAGE_HEAP_RELOCATABLE_BEGIN; |
| 29 | +import static com.oracle.svm.core.Isolates.IMAGE_HEAP_RELOCATABLE_END; |
| 30 | +import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; |
| 31 | + |
| 32 | +import org.graalvm.nativeimage.ImageSingletons; |
| 33 | +import org.graalvm.nativeimage.StackValue; |
| 34 | +import org.graalvm.nativeimage.c.function.CFunctionPointer; |
| 35 | +import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; |
| 36 | +import org.graalvm.nativeimage.c.type.CCharPointer; |
| 37 | +import org.graalvm.nativeimage.c.type.WordPointer; |
| 38 | +import org.graalvm.nativeimage.hosted.Feature; |
| 39 | +import org.graalvm.word.LocationIdentity; |
| 40 | +import org.graalvm.word.Pointer; |
| 41 | +import org.graalvm.word.PointerBase; |
| 42 | +import org.graalvm.word.UnsignedWord; |
| 43 | +import org.graalvm.word.WordFactory; |
| 44 | + |
| 45 | +import com.oracle.svm.core.annotate.AutomaticFeature; |
| 46 | +import com.oracle.svm.core.annotate.Uninterruptible; |
| 47 | +import com.oracle.svm.core.c.CGlobalData; |
| 48 | +import com.oracle.svm.core.c.CGlobalDataFactory; |
| 49 | +import com.oracle.svm.core.c.function.CEntryPointActions; |
| 50 | +import com.oracle.svm.core.c.function.CEntryPointErrors; |
| 51 | +import com.oracle.svm.core.os.AbstractCopyingImageHeapProvider; |
| 52 | +import com.oracle.svm.core.os.ImageHeapProvider; |
| 53 | +import com.oracle.svm.core.os.VirtualMemoryProvider; |
| 54 | +import com.oracle.svm.core.os.VirtualMemoryProvider.Access; |
| 55 | +import com.oracle.svm.core.windows.headers.FileAPI; |
| 56 | +import com.oracle.svm.core.windows.headers.LibC; |
| 57 | +import com.oracle.svm.core.windows.headers.LibC.WCharPointer; |
| 58 | +import com.oracle.svm.core.windows.headers.LibLoaderAPI; |
| 59 | +import com.oracle.svm.core.windows.headers.MemoryAPI; |
| 60 | +import com.oracle.svm.core.windows.headers.WinBase; |
| 61 | +import com.oracle.svm.core.windows.headers.WinBase.HANDLE; |
| 62 | +import com.oracle.svm.core.windows.headers.WinBase.HMODULE; |
| 63 | + |
| 64 | +@AutomaticFeature |
| 65 | +class WindowsImageHeapProviderFeature implements Feature { |
| 66 | + @Override |
| 67 | + public void duringSetup(DuringSetupAccess access) { |
| 68 | + if (!ImageSingletons.contains(ImageHeapProvider.class)) { |
| 69 | + ImageSingletons.add(ImageHeapProvider.class, new WindowsImageHeapProvider()); |
| 70 | + } |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +/** |
| 75 | + * An image heap provider for Windows that creates image heaps that are copy-on-write clones of the |
| 76 | + * loaded image heap. |
| 77 | + */ |
| 78 | +public class WindowsImageHeapProvider extends AbstractCopyingImageHeapProvider { |
| 79 | + @Override |
| 80 | + @Uninterruptible(reason = "Called during isolate initialization.") |
| 81 | + protected int commitAndCopyMemory(Pointer loadedImageHeap, UnsignedWord imageHeapSize, Pointer newImageHeap) { |
| 82 | + HANDLE imageHeapFileMapping = getImageHeapFileMapping(); |
| 83 | + if (imageHeapFileMapping.isNull()) { |
| 84 | + /* Fall back to copying from memory. */ |
| 85 | + return super.commitAndCopyMemory(loadedImageHeap, imageHeapSize, newImageHeap); |
| 86 | + } |
| 87 | + |
| 88 | + /* Map a copy-on-write view of the image heap. */ |
| 89 | + if (VirtualMemoryProvider.get().mapFile(newImageHeap, imageHeapSize, imageHeapFileMapping, getImageHeapFileOffset(), |
| 90 | + Access.READ | Access.WRITE).isNull()) { |
| 91 | + /* Fall back to copying from memory. */ |
| 92 | + return super.commitAndCopyMemory(loadedImageHeap, imageHeapSize, newImageHeap); |
| 93 | + } |
| 94 | + |
| 95 | + /* Copy relocatable pages. */ |
| 96 | + return copyMemory(IMAGE_HEAP_RELOCATABLE_BEGIN.get(), IMAGE_HEAP_RELOCATABLE_END.get().subtract(IMAGE_HEAP_RELOCATABLE_BEGIN.get()), |
| 97 | + newImageHeap.add(IMAGE_HEAP_RELOCATABLE_BEGIN.get().subtract(IMAGE_HEAP_BEGIN.get()))); |
| 98 | + } |
| 99 | + |
| 100 | + @Override |
| 101 | + @Uninterruptible(reason = "Called during isolate initialization.") |
| 102 | + protected int copyMemory(Pointer loadedImageHeap, UnsignedWord imageHeapSize, Pointer newImageHeap) { |
| 103 | + LibC.memcpy(newImageHeap, loadedImageHeap, imageHeapSize); |
| 104 | + return CEntryPointErrors.NO_ERROR; |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * The pseudovariable __ImageBase provided by the MSVC linker represents the DOS header of the |
| 109 | + * module, which is what a Win32 module begins with. In other words, it is the base address of |
| 110 | + * the module and is the same as its HMODULE. |
| 111 | + */ |
| 112 | + private static final CGlobalData<PointerBase> IMAGE_BASE = CGlobalDataFactory.forSymbol("__ImageBase"); |
| 113 | + |
| 114 | + /** The cached handle of the image heap file mapping that closes when the process exits. */ |
| 115 | + private static final CGlobalData<WordPointer> IMAGE_HEAP_FILE_MAPPING = CGlobalDataFactory.createWord(WindowsUtils.UNINITIALIZED_HANDLE); |
| 116 | + |
| 117 | + @Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true) |
| 118 | + private static HANDLE getImageHeapFileMapping() { |
| 119 | + HANDLE value = IMAGE_HEAP_FILE_MAPPING.get().read(); |
| 120 | + if (value.equal(WindowsUtils.UNINITIALIZED_HANDLE)) { |
| 121 | + HANDLE fileMapping = createImageHeapFileMapping(); |
| 122 | + HANDLE existingMapping = (HANDLE) ((Pointer) IMAGE_HEAP_FILE_MAPPING.get()).compareAndSwapWord(0, |
| 123 | + WindowsUtils.UNINITIALIZED_HANDLE, fileMapping, LocationIdentity.ANY_LOCATION); |
| 124 | + |
| 125 | + if (existingMapping.equal(WindowsUtils.UNINITIALIZED_HANDLE)) { |
| 126 | + value = fileMapping; |
| 127 | + } else { |
| 128 | + /* Another thread has already created the mapping, so use that. */ |
| 129 | + value = existingMapping; |
| 130 | + WinBase.CloseHandle(fileMapping); |
| 131 | + } |
| 132 | + } |
| 133 | + return value; |
| 134 | + } |
| 135 | + |
| 136 | + /** Returns a handle to the image heap file mapping or the null pointer in case of an error. */ |
| 137 | + @Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true) |
| 138 | + private static HANDLE createImageHeapFileMapping() { |
| 139 | + /* Get the path of the file that contains the image heap. */ |
| 140 | + WCharPointer filePath = StackValue.get(WinBase.MAX_PATH, WCharPointer.class); |
| 141 | + int length = LibLoaderAPI.GetModuleFileNameW((HMODULE) IMAGE_BASE.get(), filePath, WinBase.MAX_PATH); |
| 142 | + if (length == 0 || length == WinBase.MAX_PATH) { |
| 143 | + return WordFactory.nullPointer(); |
| 144 | + } |
| 145 | + |
| 146 | + /* Open the file for mapping. */ |
| 147 | + HANDLE fileHandle = FileAPI.CreateFileW(filePath, FileAPI.GENERIC_READ(), FileAPI.FILE_SHARE_READ() | FileAPI.FILE_SHARE_DELETE(), |
| 148 | + WordFactory.nullPointer(), FileAPI.OPEN_EXISTING(), 0, WordFactory.nullPointer()); |
| 149 | + if (fileHandle.equal(WinBase.INVALID_HANDLE_VALUE())) { |
| 150 | + return WordFactory.nullPointer(); |
| 151 | + } |
| 152 | + |
| 153 | + /* Create the mapping and close the file. */ |
| 154 | + HANDLE fileMapping = MemoryAPI.CreateFileMappingW(fileHandle, WordFactory.nullPointer(), MemoryAPI.PAGE_READONLY(), |
| 155 | + 0, 0, WordFactory.nullPointer()); |
| 156 | + WinBase.CloseHandle(fileHandle); |
| 157 | + return fileMapping; |
| 158 | + } |
| 159 | + |
| 160 | + private static final CGlobalData<WordPointer> IMAGE_HEAP_FILE_OFFSET = CGlobalDataFactory.createWord(); |
| 161 | + |
| 162 | + @Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true) |
| 163 | + private static UnsignedWord getImageHeapFileOffset() { |
| 164 | + /* |
| 165 | + * The file offset of a relative virtual address (RVA) in a PE image can be determined by |
| 166 | + * inspecting the PE image headers. |
| 167 | + * |
| 168 | + * We do this using two helper functions from ntdll.dll: RtlImageNtHeader and |
| 169 | + * RtlAddressInSectionTable. Unfortunately, they are not publicly documented, but I think it |
| 170 | + * is unlikely that this will pose a problem (and it would definitely not go unnoticed). |
| 171 | + * |
| 172 | + * Alternatively, we could use equivalent functions from dbghelp.dll (ImageNtHeader and |
| 173 | + * ImageRvaToVa), but this would introduce an additional DLL dependency (which we would like |
| 174 | + * to avoid). Or we could even do it manually. The tricky part would be finding the header |
| 175 | + * of the section containing the RVA. |
| 176 | + */ |
| 177 | + UnsignedWord value = IMAGE_HEAP_FILE_OFFSET.get().read(); |
| 178 | + if (value.equal(0)) { |
| 179 | + /* Get the NT header of the module that contains the image heap. */ |
| 180 | + PointerBase ntHeader = invokeRtlImageNtHeader(IMAGE_BASE.get()); |
| 181 | + /* Compute the RVA of the image heap. */ |
| 182 | + int rva = (int) (IMAGE_HEAP_BEGIN.get().rawValue() - IMAGE_BASE.get().rawValue()); |
| 183 | + /* Get the file offset of the image heap. */ |
| 184 | + value = invokeRtlAddressInSectionTable(ntHeader, rva); |
| 185 | + IMAGE_HEAP_FILE_OFFSET.get().write(value); |
| 186 | + } |
| 187 | + return value; |
| 188 | + } |
| 189 | + |
| 190 | + private static final CGlobalData<CCharPointer> NTDLL_DLL = CGlobalDataFactory.createCString("ntdll.dll"); |
| 191 | + private static final CGlobalData<CCharPointer> RTL_IMAGE_NT_HEADER = CGlobalDataFactory.createCString("RtlImageNtHeader"); |
| 192 | + private static final CGlobalData<CCharPointer> RTL_ADDRESS_IN_SECTION_TABLE = CGlobalDataFactory.createCString("RtlAddressInSectionTable"); |
| 193 | + |
| 194 | + private static final int ERROR_BAD_EXE_FORMAT = 0xC1; |
| 195 | + |
| 196 | + /** Locates the IMAGE_NT_HEADERS structure in a PE image and returns a pointer to the data. */ |
| 197 | + @Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true) |
| 198 | + private static PointerBase invokeRtlImageNtHeader(PointerBase imageBase) { |
| 199 | + RtlImageNtHeader rtlImageNtHeader = WindowsUtils.getFunctionPointer(NTDLL_DLL.get(), RTL_IMAGE_NT_HEADER.get(), true); |
| 200 | + PointerBase ntHeader = rtlImageNtHeader.invoke(imageBase); |
| 201 | + if (ntHeader.isNull()) { |
| 202 | + CEntryPointActions.failFatally(ERROR_BAD_EXE_FORMAT, RTL_IMAGE_NT_HEADER.get()); |
| 203 | + } |
| 204 | + return ntHeader; |
| 205 | + } |
| 206 | + |
| 207 | + private interface RtlImageNtHeader extends CFunctionPointer { |
| 208 | + @InvokeCFunctionPointer(transition = NO_TRANSITION) |
| 209 | + PointerBase invoke(PointerBase imageBase); |
| 210 | + } |
| 211 | + |
| 212 | + /** |
| 213 | + * Locates a relative virtual address (RVA) within the image header of a file that is mapped as |
| 214 | + * a file and returns the offset of the corresponding byte in the file. |
| 215 | + */ |
| 216 | + @Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true) |
| 217 | + private static UnsignedWord invokeRtlAddressInSectionTable(PointerBase ntHeader, int rva) { |
| 218 | + RtlAddressInSectionTable rtlAddressInSectionTable = WindowsUtils.getFunctionPointer(NTDLL_DLL.get(), RTL_ADDRESS_IN_SECTION_TABLE.get(), true); |
| 219 | + UnsignedWord offset = (UnsignedWord) rtlAddressInSectionTable.invoke(ntHeader, WordFactory.nullPointer(), rva); |
| 220 | + if (offset.equal(0)) { |
| 221 | + CEntryPointActions.failFatally(ERROR_BAD_EXE_FORMAT, RTL_ADDRESS_IN_SECTION_TABLE.get()); |
| 222 | + } |
| 223 | + return offset; |
| 224 | + } |
| 225 | + |
| 226 | + private interface RtlAddressInSectionTable extends CFunctionPointer { |
| 227 | + @InvokeCFunctionPointer(transition = NO_TRANSITION) |
| 228 | + PointerBase invoke(PointerBase ntHeader, PointerBase base, int rva); |
| 229 | + } |
| 230 | +} |
0 commit comments