|
24 | 24 | */ |
25 | 25 | package com.oracle.svm.core.pltgot; |
26 | 26 |
|
| 27 | +import org.graalvm.nativeimage.c.type.WordPointer; |
27 | 28 | import org.graalvm.word.Pointer; |
28 | 29 | import org.graalvm.word.UnsignedWord; |
29 | 30 | import org.graalvm.word.impl.Word; |
30 | 31 |
|
31 | 32 | import com.oracle.svm.core.SubstrateTarget; |
| 33 | +import com.oracle.svm.core.locks.PlatformLockingSupport; |
| 34 | +import com.oracle.svm.core.locks.PlatformLockingSupport.PlatformMutex; |
| 35 | +import com.oracle.svm.core.os.VirtualMemoryProvider; |
| 36 | +import com.oracle.svm.guest.staging.c.CGlobalData; |
| 37 | +import com.oracle.svm.guest.staging.c.CGlobalDataFactory; |
| 38 | +import com.oracle.svm.guest.staging.c.function.CEntryPointErrors; |
32 | 39 | import com.oracle.svm.shared.Uninterruptible; |
| 40 | +import com.oracle.svm.shared.util.VMError; |
33 | 41 |
|
34 | 42 | public class GOTAccess { |
| 43 | + private static final CGlobalData<Pointer> GOT_WRITABLE = CGlobalDataFactory.createWord(Word.<Word> signed(1)); |
| 44 | + private static final CGlobalData<WordPointer> ACTIVE_WRITERS = CGlobalDataFactory.createWord(); |
| 45 | + /** |
| 46 | + * Changing page protection calls the OS, so use a platform mutex instead of NativeSpinLockUtils. |
| 47 | + */ |
| 48 | + private static final CGlobalData<PlatformMutex> GOT_PROTECTION_LOCK = CGlobalDataFactory.createBytes(() -> PlatformLockingSupport.singleton().mutexSize()); |
35 | 49 |
|
36 | 50 | @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) |
37 | 51 | public static int getGOTEntryOffsetFromHeapRegister(int gotEntry) { |
38 | 52 | return -(gotEntry + 1) * SubstrateTarget.getWordSize(); |
39 | 53 | } |
40 | 54 |
|
41 | | - @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) |
| 55 | + @Uninterruptible(reason = "Writing to GOT must be uninterruptible because safepoints re-protect the GOT mapping when no writer leases are active.") |
42 | 56 | public static void writeToGOTEntry(int gotEntry, UnsignedWord address) { |
| 57 | + acquireWriteLease(); |
| 58 | + try { |
| 59 | + writeToGOTEntryWithLease(gotEntry, address); |
| 60 | + } finally { |
| 61 | + releaseWriteLease(); |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + @Uninterruptible(reason = "Writing to GOT must be uninterruptible because safepoints re-protect the GOT mapping when no writer leases are active.") |
| 66 | + public static void writeToGOTEntries(int[] gotEntries, int[] offsets, Pointer baseAddress) { |
| 67 | + assert gotEntries.length == offsets.length : "GOT entries and offsets must have the same length."; |
| 68 | + acquireWriteLease(); |
| 69 | + try { |
| 70 | + for (int i = 0; i < gotEntries.length; i++) { |
| 71 | + writeToGOTEntryWithLease(gotEntries[i], baseAddress.add(offsets[i])); |
| 72 | + } |
| 73 | + } finally { |
| 74 | + releaseWriteLease(); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + @Uninterruptible(reason = "Uses the no-transition GOT mutex, the lease and store must not be interrupted by a safepoint.", callerMustBe = true) |
| 79 | + private static void acquireWriteLease() { |
| 80 | + lockMutex(); |
| 81 | + try { |
| 82 | + setGOTWritable(true); |
| 83 | + UnsignedWord writers = ACTIVE_WRITERS.get().read(0); |
| 84 | + ACTIVE_WRITERS.get().write(0, writers.add(1)); |
| 85 | + } finally { |
| 86 | + unlockMutex(); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + @Uninterruptible(reason = "Uses the no-transition GOT mutex, the lease and store must not be interrupted by a safepoint.", callerMustBe = true) |
| 91 | + private static void releaseWriteLease() { |
| 92 | + lockMutex(); |
| 93 | + try { |
| 94 | + UnsignedWord writers = ACTIVE_WRITERS.get().read(0); |
| 95 | + VMError.guarantee(writers.aboveThan(0), "GOT writer count underflow."); |
| 96 | + ACTIVE_WRITERS.get().write(0, writers.subtract(1)); |
| 97 | + } finally { |
| 98 | + unlockMutex(); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + @Uninterruptible(reason = "Releasing the GOT mutex without a thread-state transition requires the caller to be uninterruptible.", callerMustBe = true) |
| 103 | + private static void unlockMutex() { |
| 104 | + PlatformLockingSupport.singleton().unlockMutex(GOT_PROTECTION_LOCK.get()); |
| 105 | + } |
| 106 | + |
| 107 | + @Uninterruptible(reason = "Acquiring the GOT mutex without a thread-state transition requires the caller to be uninterruptible.", callerMustBe = true) |
| 108 | + private static void lockMutex() { |
| 109 | + PlatformLockingSupport.singleton().lockMutexNoTransition(GOT_PROTECTION_LOCK.get()); |
| 110 | + } |
| 111 | + |
| 112 | + @Uninterruptible(reason = "Locking the GOT mutex requires the whole critical section to be uninterruptible.") |
| 113 | + static int initializeInFirstIsolate() { |
| 114 | + int ret = PlatformLockingSupport.singleton().initializeMutex(GOT_PROTECTION_LOCK.get()); |
| 115 | + if (ret != 0) { |
| 116 | + return CEntryPointErrors.THREADING_INITIALIZATION_FAILED; |
| 117 | + } |
| 118 | + lockMutex(); |
| 119 | + try { |
| 120 | + setGOTWritable(false); |
| 121 | + } finally { |
| 122 | + unlockMutex(); |
| 123 | + } |
| 124 | + return CEntryPointErrors.NO_ERROR; |
| 125 | + } |
| 126 | + |
| 127 | + @Uninterruptible(reason = "Locking the GOT mutex requires the whole critical section to be uninterruptible.") |
| 128 | + public static void reprotectGOTAtSafepoint() { |
| 129 | + if (!GOTHeapSupport.hasGOTEntries()) { |
| 130 | + return; |
| 131 | + } |
| 132 | + lockMutex(); |
| 133 | + try { |
| 134 | + if (ACTIVE_WRITERS.get().read(0).rawValue() == 0) { |
| 135 | + setGOTWritable(false); |
| 136 | + } |
| 137 | + } finally { |
| 138 | + unlockMutex(); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + @Uninterruptible(reason = "The caller holds a GOT writer lease.", callerMustBe = true) |
| 143 | + private static void writeToGOTEntryWithLease(int gotEntry, UnsignedWord address) { |
43 | 144 | Pointer gotStartAddress = GOTHeapSupport.GOT_START_ADDRESS.get().read(); |
44 | 145 | Pointer gotEndAddress = gotStartAddress.add(GOTHeapSupport.getPageAlignedGOTSize()); |
45 | 146 | gotEndAddress.writeWord(getGOTEntryOffsetFromHeapRegister(gotEntry), address); |
46 | 147 | } |
47 | 148 |
|
| 149 | + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) |
| 150 | + private static boolean isGOTWritable() { |
| 151 | + return GOT_WRITABLE.get().readWord(0).rawValue() != 0; |
| 152 | + } |
| 153 | + |
| 154 | + @Uninterruptible(reason = "The caller holds the no-transition GOT mutex.", callerMustBe = true) |
| 155 | + private static void setGOTWritable(boolean writable) { |
| 156 | + if (isGOTWritable() != writable) { |
| 157 | + Pointer gotMappingStartAddress = GOTHeapSupport.GOT_START_ADDRESS.get().read(); |
| 158 | + int protection = VirtualMemoryProvider.Access.READ | (writable ? VirtualMemoryProvider.Access.WRITE : 0); |
| 159 | + int ret = VirtualMemoryProvider.get().protect(gotMappingStartAddress, GOTHeapSupport.getPageAlignedGOTSize(), protection); |
| 160 | + VMError.guarantee(ret == 0, "Failed to change GOT protection."); |
| 161 | + GOT_WRITABLE.get().writeWord(0, Word.signed(writable ? 1 : 0)); |
| 162 | + } |
| 163 | + } |
| 164 | + |
48 | 165 | @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) |
49 | 166 | public static Word readFromGOTEntry(int gotEntry) { |
50 | 167 | Pointer gotStartAddress = GOTHeapSupport.GOT_START_ADDRESS.get().read(); |
51 | 168 | Pointer gotEndAddress = gotStartAddress.add(GOTHeapSupport.getPageAlignedGOTSize()); |
52 | 169 | return gotEndAddress.readWord(getGOTEntryOffsetFromHeapRegister(gotEntry)); |
53 | 170 | } |
| 171 | + |
54 | 172 | } |
0 commit comments