From ff719b39ca538161384410182abda54d7e9428f1 Mon Sep 17 00:00:00 2001 From: Bernhard Urban-Forster Date: Thu, 24 Oct 2024 08:11:58 +0200 Subject: [PATCH] [GR-58575] SubstrateVM PLT/GOT Feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces an additional level of indirection for calls where a GOT (Global Offset Table) is an array of method pointers and PLT (Procedure Linkage Table) is a collection of small stubs. With this feature enabled, direct calls are emitted as indirect calls through the GOT. The virtual table is filled with PLT stubs instead. While inspired by ELF, no ELF mechanisms are used in the implementation. Example usecase: Hijack code execution on call boundaries to diverge execution from AOT code to an interpreter. Contributors: - Aleksandar Gradinac: Initial implementation on linux-amd64. - Marko Spasic: Miscellaneous improvements. - Bernhard Urban-Forster: Support for linux-aarch64, darwin-aarch64 and darwin-amd64. - Alfonso² Peterssen: Support for windows-amd64. Co-authored-by: Aleksandar Gradinac Co-authored-by: Marko Spasic Co-authored-by: Alfonso² Peterssen --- substratevm/mx.substratevm/suite.py | 8 +- .../aarch64/SubstrateAArch64Backend.java | 9 + .../graal/amd64/SubstrateAMD64Backend.java | 11 +- .../svm/core/graal/llvm/LLVMGenerator.java | 5 + .../posix/darwin/DarwinGOTHeapSupport.java | 115 ++++++++ .../posix/darwin/DarwinPLTGOTFeature.java | 47 ++++ .../core/posix/linux/LinuxGOTHeapSupport.java | 153 +++++++++++ .../core/posix/linux/LinuxPLTGOTFeature.java | 47 ++++ .../core/windows/WindowsGOTHeapSupport.java | 112 ++++++++ .../core/windows/WindowsPLTGOTFeature.java | 47 ++++ .../graal/code/SubstrateLIRGenerator.java | 2 + .../ExitMethodAddressResolutionNode.java | 61 +++++ .../com/oracle/svm/core/pltgot/GOTAccess.java | 55 ++++ .../svm/core/pltgot/GOTHeapSupport.java | 162 +++++++++++ .../pltgot/IdentityMethodAddressResolver.java | 54 ++++ .../MethodAddressResolutionDispatcher.java | 65 +++++ .../core/pltgot/MethodAddressResolver.java | 40 +++ .../svm/core/pltgot/PLTGOTConfiguration.java | 52 ++++ .../AArch64ExitMethodAddressResolutionOp.java | 52 ++++ ...ch64MethodAddressResolutionDispatcher.java | 60 +++++ .../AMD64ExitMethodAddressResolutionOp.java | 52 ++++ ...MD64MethodAddressResolutionDispatcher.java | 63 +++++ .../com/oracle/svm/graal/pltgot/GOTCall.java | 35 +++ .../pltgot/PLTGOTNonSnippetLowerings.java | 130 +++++++++ .../pltgot/SubstrateGOTCallTargetNode.java | 45 ++++ .../aarch64/PLTGOTAArch64Lowerings.java | 67 +++++ .../pltgot/amd64/PLTGOTAMD64Lowerings.java | 67 +++++ .../com/oracle/svm/hosted/FeatureImpl.java | 10 +- .../svm/hosted/NativeImageGenerator.java | 3 +- ...llectPLTGOTCallSitesResolutionSupport.java | 88 ++++++ .../svm/hosted/pltgot/GOTEntryAllocator.java | 79 ++++++ .../pltgot/HostedPLTGOTConfiguration.java | 98 +++++++ .../IdentityMethodAddressResolverFeature.java | 168 ++++++++++++ .../MethodAddressResolutionSupport.java | 60 +++++ ...MethodAddressResolutionSupportFactory.java | 29 ++ .../svm/hosted/pltgot/PLTGOTFeature.java | 255 ++++++++++++++++++ .../svm/hosted/pltgot/PLTGOTOptions.java | 41 +++ .../PLTGOTPointerRelocationProvider.java | 65 +++++ .../svm/hosted/pltgot/PLTSectionSupport.java | 110 ++++++++ .../svm/hosted/pltgot/PLTStubGenerator.java | 39 +++ .../AArch64HostedPLTGOTConfiguration.java | 67 +++++ .../aarch64/AArch64PLTStubGenerator.java | 182 +++++++++++++ .../amd64/AMD64HostedPLTGOTConfiguration.java | 66 +++++ .../pltgot/amd64/AMD64PLTStubGenerator.java | 167 ++++++++++++ 44 files changed, 3136 insertions(+), 7 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPLTGOTFeature.java create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPLTGOTFeature.java create mode 100644 substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java create mode 100644 substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPLTGOTFeature.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/ExitMethodAddressResolutionNode.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTAccess.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolutionDispatcher.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolver.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/PLTGOTConfiguration.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64ExitMethodAddressResolutionOp.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64ExitMethodAddressResolutionOp.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/GOTCall.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/PLTGOTNonSnippetLowerings.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/SubstrateGOTCallTargetNode.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/aarch64/PLTGOTAArch64Lowerings.java create mode 100644 substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/amd64/PLTGOTAMD64Lowerings.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/CollectPLTGOTCallSitesResolutionSupport.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/GOTEntryAllocator.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/HostedPLTGOTConfiguration.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/IdentityMethodAddressResolverFeature.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupport.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupportFactory.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTFeature.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTOptions.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTPointerRelocationProvider.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTSectionSupport.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTStubGenerator.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64HostedPLTGOTConfiguration.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64PLTStubGenerator.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64HostedPLTGOTConfiguration.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64PLTStubGenerator.java diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 677ef89796ce..415f8587a603 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -480,7 +480,7 @@ "subDir": "src", "sourceDirs": ["src"], "dependencies": [ - "com.oracle.svm.core.graal.amd64", + "com.oracle.svm.hosted", "com.oracle.svm.core.graal.aarch64", "com.oracle.svm.core.graal.riscv64", ], @@ -511,7 +511,7 @@ "subDir": "src", "sourceDirs": ["src"], "dependencies": [ - "com.oracle.svm.core.graal.amd64", + "com.oracle.svm.hosted", ], "requiresConcealed" : { "jdk.internal.vm.ci" : [ @@ -639,8 +639,8 @@ "sourceDirs": ["src"], "dependencies": [ "com.oracle.objectfile", - "com.oracle.svm.core", - "com.oracle.graal.reachability" + "com.oracle.graal.reachability", + "com.oracle.svm.core.graal.amd64", ], "requires" : [ "jdk.jfr", diff --git a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java index 12598c0855fb..b87390e83b09 100755 --- a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java +++ b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64Backend.java @@ -72,6 +72,7 @@ import com.oracle.svm.core.meta.SubstrateMethodPointerConstant; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.nodes.SafepointCheckNode; +import com.oracle.svm.core.pltgot.PLTGOTConfiguration; import com.oracle.svm.core.thread.VMThreads.StatusSupport; import com.oracle.svm.core.util.VMError; @@ -667,6 +668,14 @@ public Register getHeapBaseRegister() { protected int getVMPageSize() { return SubstrateOptions.getPageSize(); } + + @Override + public void emitExitMethodAddressResolution(Value ip) { + PLTGOTConfiguration configuration = PLTGOTConfiguration.singleton(); + RegisterValue exitThroughRegisterValue = configuration.getExitMethodAddressResolutionRegister(getRegisterConfig()).asValue(ip.getValueKind()); + emitMove(exitThroughRegisterValue, ip); + append(configuration.createExitMethodAddressResolutionOp(exitThroughRegisterValue)); + } } public class SubstrateAArch64NodeLIRBuilder extends AArch64NodeLIRBuilder implements SubstrateNodeLIRBuilder { diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java index 97ab24224391..1f3ffa102aff 100644 --- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java @@ -93,6 +93,7 @@ import com.oracle.svm.core.meta.SubstrateMethodPointerConstant; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.nodes.SafepointCheckNode; +import com.oracle.svm.core.pltgot.PLTGOTConfiguration; import com.oracle.svm.core.thread.VMThreads.StatusSupport; import com.oracle.svm.core.util.VMError; @@ -748,6 +749,14 @@ public void emitInstructionSynchronizationBarrier() { throw shouldNotReachHere("AMD64 does not need instruction synchronization"); } + @Override + public void emitExitMethodAddressResolution(Value ip) { + PLTGOTConfiguration configuration = PLTGOTConfiguration.singleton(); + RegisterValue exitThroughRegisterValue = configuration.getExitMethodAddressResolutionRegister(getRegisterConfig()).asValue(ip.getValueKind()); + emitMove(exitThroughRegisterValue, ip); + append(configuration.createExitMethodAddressResolutionOp(exitThroughRegisterValue)); + } + @Override public void emitFarReturn(AllocatableValue result, Value sp, Value ip, boolean fromMethodWithCalleeSavedRegisters) { append(new AMD64FarReturnOp(result, asAllocatable(sp), asAllocatable(ip), fromMethodWithCalleeSavedRegisters)); @@ -1813,7 +1822,7 @@ public void emitCode(CompilationResultBuilder crb, ResolvedJavaMethod installedC } } - private AMD64Assembler createAssemblerNoOptions() { + public AMD64Assembler createAssemblerNoOptions() { OptionValues o = new OptionValues(EconomicMap.create()); return createAssembler(o); } diff --git a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMGenerator.java b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMGenerator.java index 4761dbda4642..4e213d0a817a 100644 --- a/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMGenerator.java +++ b/substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMGenerator.java @@ -1147,6 +1147,11 @@ public void emitInstructionSynchronizationBarrier() { throw unimplemented("the LLVM backend doesn't support instruction synchronization"); // ExcludeFromJacocoGeneratedReport } + @Override + public void emitExitMethodAddressResolution(Value ip) { + throw unimplemented("the LLVM backend doesn't support PLT/GOT"); // ExcludeFromJacocoGeneratedReport + } + @Override public I append(I op) { throw unimplemented("the LLVM backend doesn't support LIR instructions"); // ExcludeFromJacocoGeneratedReport diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java new file mode 100644 index 000000000000..489fea0db173 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinGOTHeapSupport.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix.darwin; + +import static com.oracle.svm.core.posix.headers.Mman.MAP_ANON; +import static com.oracle.svm.core.posix.headers.Mman.MAP_FAILED; +import static com.oracle.svm.core.posix.headers.Mman.MAP_PRIVATE; +import static com.oracle.svm.core.posix.headers.Mman.PROT_READ; +import static com.oracle.svm.core.posix.headers.Mman.PROT_WRITE; +import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.mmap; + +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.function.CEntryPointErrors; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.os.VirtualMemoryProvider.Access; +import com.oracle.svm.core.pltgot.GOTHeapSupport; +import com.oracle.svm.core.posix.headers.darwin.DarwinVirtualMemory; + +public class DarwinGOTHeapSupport extends GOTHeapSupport { + + private static final CGlobalData DARWIN_GOT_START_ADDRESS = CGlobalDataFactory.createWord(); + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int initialize(WordPointer gotStartAddress) { + int flags = MAP_ANON() | MAP_PRIVATE(); + Pointer gotMemory = mmap(WordFactory.nullPointer(), getPageAlignedGotSize(), PROT_READ() | PROT_WRITE(), flags, -1, 0); + if (gotMemory.isNull() || gotMemory.equal(MAP_FAILED())) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_CREATE_FAILED; + } + + Pointer gotStartInMemory = gotMemory.add(getGotOffsetFromStartOfMapping()); + LibC.memcpy(gotStartInMemory, IMAGE_GOT_BEGIN.get(), getGotSectionSize()); + + gotStartAddress.write(gotMemory); + DARWIN_GOT_START_ADDRESS.get().write(gotMemory); + + return CEntryPointErrors.NO_ERROR; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int mapGot(Pointer start) { + WordPointer taskSelf = DarwinVirtualMemory.mach_task_self(); + + /* Unmap part of the heap address space that is designated for the GOT */ + int ret = DarwinVirtualMemory.vm_deallocate(DarwinVirtualMemory.mach_task_self(), start, getPageAlignedGotSize()); + if (ret != 0) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_MMAP_FAILED; + } + + WordPointer gotStart = StackValue.get(WordPointer.class); + gotStart.write(start); + + CIntPointer currentProt = StackValue.get(CIntPointer.class); + CIntPointer maxProt = StackValue.get(CIntPointer.class); + + int intFalse = 0; + + /* + * Map reserved address space for GOT to "global" GOT allocation, so that all isolates are + * backed by the same table. + */ + ret = DarwinVirtualMemory.vm_remap(taskSelf, gotStart, getPageAlignedGotSize(), WordFactory.nullPointer(), intFalse, + taskSelf, DARWIN_GOT_START_ADDRESS.get().read(), intFalse, currentProt, maxProt, DarwinVirtualMemory.VM_INHERIT_SHARE()); + if (ret != 0) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_WRONG_MMAP; + } + + /* + * The new mapping "inherits" cur_prot and max_prot from the original mapping, but another + * isolate could race trying to write the original GOT => the new mapping could inherit + * cur_prot=RW. Ensure that the new-mapping remains read-only, regardless of races. + */ + if (currentProt.read() != PROT_READ()) { + ret = VirtualMemoryProvider.get().protect(start, getPageAlignedGotSize(), Access.READ); + if (ret != 0) { + return ret; + } + } + + return CEntryPointErrors.NO_ERROR; + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPLTGOTFeature.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPLTGOTFeature.java new file mode 100644 index 000000000000..d15b4d87b74f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/darwin/DarwinPLTGOTFeature.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix.darwin; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; + +import com.oracle.svm.core.code.DynamicMethodAddressResolutionHeapSupport; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; + +@AutomaticallyRegisteredFeature +public class DarwinPLTGOTFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return Platform.includedIn(Platform.DARWIN.class) && PLTGOTOptions.EnablePLTGOT.getValue(); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(DynamicMethodAddressResolutionHeapSupport.class, new DarwinGOTHeapSupport()); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java new file mode 100644 index 000000000000..cddb80ab1096 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxGOTHeapSupport.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix.linux; + +import static com.oracle.svm.core.posix.headers.Mman.MAP_SHARED; +import static com.oracle.svm.core.posix.headers.Mman.PROT_READ; +import static com.oracle.svm.core.posix.headers.Mman.PROT_WRITE; +import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.mmap; +import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.shm_open; +import static com.oracle.svm.core.posix.headers.Mman.NoTransitions.shm_unlink; + +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.function.CEntryPointErrors; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.os.VirtualMemoryProvider.Access; +import com.oracle.svm.core.pltgot.GOTHeapSupport; +import com.oracle.svm.core.posix.headers.Errno; +import com.oracle.svm.core.posix.headers.Fcntl; +import com.oracle.svm.core.posix.headers.Unistd; +import com.oracle.svm.core.thread.VMThreads; + +public class LinuxGOTHeapSupport extends GOTHeapSupport { + + private static final String FILE_NAME_PREFIX = "/ni-got-"; + private static final int FILE_NAME_PREFIX_LEN = FILE_NAME_PREFIX.length(); + private static final CGlobalData memoryViewFd = CGlobalDataFactory.createWord((WordBase) WordFactory.signed(-1)); + private static final CGlobalData fileNamePrefix = CGlobalDataFactory.createCString(FILE_NAME_PREFIX); + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int initialize(WordPointer gotStartAddress) { + int pid = Unistd.NoTransitions.getpid(); + int pidLen = 0; + int temp = pid; + while (temp != 0) { + pidLen++; + temp /= 10; + } + + /* GOT shared memory file name format: fileNamePrefix-pid */ + CCharPointer nameStr = StackValue.get(64 * SizeOf.get(CCharPointer.class)); + LibC.memcpy(nameStr, fileNamePrefix.get(), WordFactory.unsigned(FILE_NAME_PREFIX_LEN)); + + int iter = FILE_NAME_PREFIX_LEN + pidLen; + nameStr.write(iter, (byte) '\0'); + temp = pid; + + while (temp != 0) { + iter--; + nameStr.write(iter, (byte) ('0' + (temp % 10))); + temp /= 10; + } + + int fd = -1; + for (int i = 0; i < 10; ++i) { + fd = shm_open(nameStr, Fcntl.O_CREAT() | Fcntl.O_EXCL() | Fcntl.O_RDWR(), 0); + if (fd == -1) { + int errno = LibC.errno(); + if (errno != Errno.EEXIST()) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_CREATE_FAILED; + } + VMThreads.singleton().nativeSleep(5); + } else { + shm_unlink(nameStr); + break; + } + } + + if (fd == -1) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_UNIQUE_FILE_CREATE_FAILED; + } + + UnsignedWord gotPageAlignedSize = getPageAlignedGotSize(); + + if (Unistd.NoTransitions.ftruncate(fd, WordFactory.signed(gotPageAlignedSize.rawValue())) != 0) { + Unistd.NoTransitions.close(fd); + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_RESIZE_FAILED; + } + + Pointer gotMemory = mmap(WordFactory.nullPointer(), gotPageAlignedSize, PROT_READ() | PROT_WRITE(), MAP_SHARED(), fd, 0); + if (gotMemory.isNull()) { + Unistd.NoTransitions.close(fd); + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_MAP_FAILED; + } + + Pointer gotStartInMemory = gotMemory.add(getGotOffsetFromStartOfMapping()); + LibC.memcpy(gotStartInMemory, IMAGE_GOT_BEGIN.get(), getGotSectionSize()); + + /* Keep the initial GOT mapping for writing. */ + + memoryViewFd.get().write(WordFactory.signed(fd)); + gotStartAddress.write(gotMemory); + + return CEntryPointErrors.NO_ERROR; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int mapGot(Pointer start) { + SignedWord memViewFd = memoryViewFd.get().read(); + if (memViewFd.lessThan(0)) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_INVALID; + } + + Pointer mappedAddress = VirtualMemoryProvider.get().mapFile( + start, + getPageAlignedGotSize(), + memViewFd, + WordFactory.zero(), + Access.READ); + + if (mappedAddress.isNull()) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_MMAP_FAILED; + } + + return CEntryPointErrors.NO_ERROR; + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPLTGOTFeature.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPLTGOTFeature.java new file mode 100644 index 000000000000..3accab55cfe2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxPLTGOTFeature.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.posix.linux; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; + +import com.oracle.svm.core.code.DynamicMethodAddressResolutionHeapSupport; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; + +@AutomaticallyRegisteredFeature +public class LinuxPLTGOTFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return Platform.includedIn(Platform.LINUX.class) && PLTGOTOptions.EnablePLTGOT.getValue(); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(DynamicMethodAddressResolutionHeapSupport.class, new LinuxGOTHeapSupport()); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java new file mode 100644 index 000000000000..85081e020f09 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsGOTHeapSupport.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.windows; + +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.function.CEntryPointErrors; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.os.VirtualMemoryProvider.Access; +import com.oracle.svm.core.pltgot.GOTHeapSupport; +import com.oracle.svm.core.util.UnsignedUtils; +import com.oracle.svm.core.windows.headers.MemoryAPI; +import com.oracle.svm.core.windows.headers.WinBase; +import com.oracle.svm.core.windows.headers.WinBase.HANDLE; + +public class WindowsGOTHeapSupport extends GOTHeapSupport { + + private static final CGlobalData GOT_MAPPING_HANDLE = CGlobalDataFactory.createWord(); + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected int initialize(WordPointer gotStartAddress) { + UnsignedWord alignedGotSize = getPageAlignedGotSize(); + + HANDLE gotMappingHandle = MemoryAPI.CreateFileMappingW( + WinBase.INVALID_HANDLE_VALUE(), // in-memory + WordFactory.nullPointer(), + MemoryAPI.PAGE_READWRITE(), + 0, + UnsignedUtils.safeToInt(alignedGotSize), + WordFactory.nullPointer() // anonymous + ); + + if (gotMappingHandle.isNull()) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_CREATE_FAILED; + } + + Pointer gotMappedAddress = MemoryAPI.MapViewOfFile( + gotMappingHandle, + MemoryAPI.FILE_MAP_READ() | MemoryAPI.FILE_MAP_WRITE(), + 0, + 0, + WordFactory.zero() // map it whole + ); + + if (gotMappedAddress.isNull()) { + WinBase.CloseHandle(gotMappingHandle); + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_MAP_FAILED; + } + + Pointer gotStartInMemory = gotMappedAddress.add(getGotOffsetFromStartOfMapping()); + LibC.memcpy(gotStartInMemory, IMAGE_GOT_BEGIN.get(), getGotSectionSize()); + + // Keep the initial GOT mapping for writing. + + GOT_MAPPING_HANDLE.get().write(gotMappingHandle); + gotStartAddress.write(gotMappedAddress); + + return CEntryPointErrors.NO_ERROR; + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int mapGot(Pointer address) { + HANDLE gotMappingHandle = GOT_MAPPING_HANDLE.get().read(); + if (gotMappingHandle.isNull()) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_FD_INVALID; + } + + Pointer mappedAddress = VirtualMemoryProvider.get().mapFile( + address, + getPageAlignedGotSize(), + gotMappingHandle, + WordFactory.zero(), + Access.READ); + + if (mappedAddress.isNull()) { + return CEntryPointErrors.DYNAMIC_METHOD_ADDRESS_RESOLUTION_GOT_MMAP_FAILED; + } + + return CEntryPointErrors.NO_ERROR; + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPLTGOTFeature.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPLTGOTFeature.java new file mode 100644 index 000000000000..ee5d4522f970 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPLTGOTFeature.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.windows; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; + +import com.oracle.svm.core.code.DynamicMethodAddressResolutionHeapSupport; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; + +@AutomaticallyRegisteredFeature +public class WindowsPLTGOTFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return Platform.includedIn(Platform.WINDOWS.class) && PLTGOTOptions.EnablePLTGOT.getValue(); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(DynamicMethodAddressResolutionHeapSupport.class, new WindowsGOTHeapSupport()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateLIRGenerator.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateLIRGenerator.java index 22109f78d166..a9b2ffc401a9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateLIRGenerator.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateLIRGenerator.java @@ -36,4 +36,6 @@ public interface SubstrateLIRGenerator { void emitVerificationMarker(Object marker); void emitInstructionSynchronizationBarrier(); + + void emitExitMethodAddressResolution(Value ip); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/ExitMethodAddressResolutionNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/ExitMethodAddressResolutionNode.java new file mode 100644 index 000000000000..6d76253e0888 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/ExitMethodAddressResolutionNode.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import org.graalvm.nativeimage.c.function.CodePointer; + +import com.oracle.svm.core.graal.code.SubstrateLIRGenerator; + +import jdk.graal.compiler.core.common.type.StampFactory; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.lir.gen.LIRGeneratorTool; +import jdk.graal.compiler.nodeinfo.NodeCycles; +import jdk.graal.compiler.nodeinfo.NodeInfo; +import jdk.graal.compiler.nodeinfo.NodeSize; +import jdk.graal.compiler.nodes.ControlSinkNode; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.spi.LIRLowerable; +import jdk.graal.compiler.nodes.spi.NodeLIRBuilderTool; + +@NodeInfo(cycles = NodeCycles.CYCLES_1, size = NodeSize.SIZE_1) +public final class ExitMethodAddressResolutionNode extends ControlSinkNode implements LIRLowerable { + public static final NodeClass TYPE = NodeClass.create(ExitMethodAddressResolutionNode.class); + + @Input protected ValueNode ip; + + public ExitMethodAddressResolutionNode(ValueNode ip) { + super(TYPE, StampFactory.forVoid()); + this.ip = ip; + } + + @Override + public void generate(NodeLIRBuilderTool builder) { + LIRGeneratorTool gen = builder.getLIRGeneratorTool(); + ((SubstrateLIRGenerator) gen).emitExitMethodAddressResolution(builder.operand(ip)); + } + + @NodeIntrinsic + public static native void exitMethodAddressResolution(CodePointer ip); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTAccess.java new file mode 100644 index 000000000000..6c8cffc4f24d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTAccess.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.config.ConfigurationValues; + +import jdk.graal.compiler.word.Word; + +public class GOTAccess { + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static int getGotEntryOffsetFromHeapRegister(int gotEntry) { + return -(gotEntry + 1) * ConfigurationValues.getTarget().wordSize; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void writeToGotEntry(int gotEntry, UnsignedWord address) { + Pointer gotStartAddress = GOTHeapSupport.GOT_START_ADDRESS.get().read(); + Pointer gotEndAddress = gotStartAddress.add(GOTHeapSupport.getPageAlignedGotSize()); + gotEndAddress.writeWord(getGotEntryOffsetFromHeapRegister(gotEntry), address); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static Word readFromGotEntry(int gotEntry) { + Pointer gotStartAddress = GOTHeapSupport.GOT_START_ADDRESS.get().read(); + Pointer gotEndAddress = gotStartAddress.add(GOTHeapSupport.getPageAlignedGotSize()); + return gotEndAddress.readWord(getGotEntryOffsetFromHeapRegister(gotEntry)); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java new file mode 100644 index 000000000000..eb26f7b4aff5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/GOTHeapSupport.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.function.CEntryPointErrors; +import com.oracle.svm.core.code.DynamicMethodAddressResolutionHeapSupport; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.os.VirtualMemoryProvider.Access; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.util.PointerUtils; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.nodes.PauseNode; + +public abstract class GOTHeapSupport extends DynamicMethodAddressResolutionHeapSupport { + + public static final String IMAGE_GOT_END_SYMBOL_NAME = "__svm_got_end"; + public static final CGlobalData IMAGE_GOT_END = CGlobalDataFactory.forSymbol(IMAGE_GOT_END_SYMBOL_NAME); + public static final String IMAGE_GOT_BEGIN_SYMBOL_NAME = "__svm_got_begin"; + public static final CGlobalData IMAGE_GOT_BEGIN = CGlobalDataFactory.forSymbol(IMAGE_GOT_BEGIN_SYMBOL_NAME); + + private static final SignedWord GOT_UNINITIALIZED = WordFactory.signed(-1); + private static final SignedWord GOT_INITIALIZATION_IN_PROGRESS = WordFactory.signed(-2); + private static final CGlobalData GOT_STATUS = CGlobalDataFactory.createWord(GOT_UNINITIALIZED); + static final CGlobalData GOT_START_ADDRESS = CGlobalDataFactory.createWord(); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected static UnsignedWord getGotSectionSize() { + return IMAGE_GOT_END.get().subtract(IMAGE_GOT_BEGIN.get()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected static UnsignedWord getPageAlignedGotSize() { + UnsignedWord gotSectionSize = getGotSectionSize(); + UnsignedWord pageSize = VirtualMemoryProvider.get().getGranularity(); + return PointerUtils.roundUp((PointerBase) gotSectionSize, pageSize); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected static UnsignedWord getGotOffsetFromStartOfMapping() { + return getPageAlignedGotSize().subtract(getGotSectionSize()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public UnsignedWord getRequiredPreHeapMemoryInBytes() { + return getPageAlignedGotSize(); + } + + @Fold + public static GOTHeapSupport get() { + return (GOTHeapSupport) ImageSingletons.lookup(DynamicMethodAddressResolutionHeapSupport.class); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void makeGOTWritable() { + changeGOTMappingProtection(true); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void makeGOTReadOnly() { + changeGOTMappingProtection(false); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected abstract int mapGot(Pointer address); + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int install(Pointer heapBase) { + return mapGot(getPreHeapMappingStartAddress(heapBase)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected Pointer getPreHeapMappingStartAddress() { + return getPreHeapMappingStartAddress(KnownIntrinsics.heapBase()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected Pointer getPreHeapMappingStartAddress(PointerBase heapBase) { + return ((Pointer) heapBase).subtract(getRequiredPreHeapMemoryInBytes()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void changeGOTMappingProtection(boolean writable) { + Pointer gotMappingStartAddress = GOT_START_ADDRESS.get().read(); + VMError.guarantee(gotMappingStartAddress.isNonNull()); + int access = Access.READ; + if (writable) { + access |= Access.WRITE; + } + int ret = VirtualMemoryProvider.get().protect(gotMappingStartAddress, getPageAlignedGotSize(), access); + VMError.guarantee(ret == 0, "Failed to change GOT protection."); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int initialize() { + boolean isFirstIsolate = GOT_STATUS.get().logicCompareAndSwapWord(0, GOT_UNINITIALIZED, GOT_INITIALIZATION_IN_PROGRESS, LocationIdentity.ANY_LOCATION); + if (!isFirstIsolate) { + while (true) { + SignedWord status = GOT_STATUS.get().readWordVolatile(0, LocationIdentity.ANY_LOCATION); + if (status.notEqual(GOT_INITIALIZATION_IN_PROGRESS)) { + long rawStatus = status.rawValue(); + assert rawStatus == (int) rawStatus; + return (int) rawStatus; + } + /* Being nice to the CPU while busy waiting */ + PauseNode.pause(); + } + } + + // Only the first isolate can reach here. + int ret = initialize(GOT_START_ADDRESS.get()); + if (ret == CEntryPointErrors.NO_ERROR) { + makeGOTReadOnly(); + } + GOT_STATUS.get().writeWordVolatile(0, WordFactory.signed(ret)); + return ret; + } + + /** + * Initialize the GOT and write its address to {@code gotStartAddress}. Return + * {@link CEntryPointErrors#NO_ERROR} on success, an error code otherwise. + */ + protected abstract int initialize(WordPointer gotStartAddress); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java new file mode 100644 index 000000000000..996d6cf8d4a5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/IdentityMethodAddressResolver.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.config.ConfigurationValues; + +public class IdentityMethodAddressResolver implements MethodAddressResolver { + + private static final CGlobalData methodTable = CGlobalDataFactory.forSymbol("__svm_methodtable_begin"); + + @Override + @Uninterruptible(reason = "Called from the PLT stub where stack walks are not safe.") + public long resolveMethodWithGotEntry(long gotEntry) { + /* Fetch the absolute address of the method that corresponds to the target GOT entry. */ + UnsignedWord methodTableOffset = WordFactory.unsigned(gotEntry).multiply(ConfigurationValues.getTarget().wordSize); + UnsignedWord address = methodTable.get().readWord(methodTableOffset); + /* + * Write the resolved address to the GOT entry so that it can be directly used for future + * calls instead of going through this resolver. + */ + GOTAccess.writeToGotEntry((int) gotEntry, address); + + return address.rawValue(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolutionDispatcher.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolutionDispatcher.java new file mode 100644 index 000000000000..6d95f0e43975 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolutionDispatcher.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.thread.JavaSpinLockUtils; + +import jdk.internal.misc.Unsafe; + +public class MethodAddressResolutionDispatcher { + private static final MethodAddressResolutionDispatcher dispatcher = new MethodAddressResolutionDispatcher(); + private static final long LOCK_OFFSET = Unsafe.getUnsafe().objectFieldOffset(MethodAddressResolutionDispatcher.class, "lock"); + + @SuppressWarnings("unused")// + private volatile int lock; + private int activeResolverInstances = 0; + + @Uninterruptible(reason = "PLT/GOT method address resolution doesn't support interruptible code paths.") + protected static long resolveMethodAddress(long gotEntry) { + try { + JavaSpinLockUtils.lockNoTransition(dispatcher, LOCK_OFFSET); + if (dispatcher.activeResolverInstances == 0) { + GOTHeapSupport.get().makeGOTWritable(); + } + dispatcher.activeResolverInstances++; + } finally { + JavaSpinLockUtils.unlock(dispatcher, LOCK_OFFSET); + } + + long resolvedMethodAddress = PLTGOTConfiguration.singleton().getMethodAddressResolver().resolveMethodWithGotEntry(gotEntry); + + try { + JavaSpinLockUtils.lockNoTransition(dispatcher, LOCK_OFFSET); + if (dispatcher.activeResolverInstances == 1) { + GOTHeapSupport.get().makeGOTReadOnly(); + } + dispatcher.activeResolverInstances--; + } finally { + JavaSpinLockUtils.unlock(dispatcher, LOCK_OFFSET); + } + return resolvedMethodAddress; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolver.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolver.java new file mode 100644 index 000000000000..ee45a0204c51 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/MethodAddressResolver.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import com.oracle.svm.core.Uninterruptible; + +public interface MethodAddressResolver { + + /** + * Resolves the absolute address of a method represented by the given GOT entry. + * + * Note that it is the responsibility of this method to write the resolved address to the GOT + * entry as otherwise it will be called for subsequent calls of the same method. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + long resolveMethodWithGotEntry(long gotEntry); + +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/PLTGOTConfiguration.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/PLTGOTConfiguration.java new file mode 100644 index 000000000000..f880c0b20f3c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/PLTGOTConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot; + +import org.graalvm.nativeimage.ImageSingletons; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.lir.LIRInstruction; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.RegisterValue; + +public abstract class PLTGOTConfiguration { + protected MethodAddressResolver methodAddressResolver; + + @Fold + public static PLTGOTConfiguration singleton() { + return ImageSingletons.lookup(PLTGOTConfiguration.class); + } + + @Fold + public MethodAddressResolver getMethodAddressResolver() { + return methodAddressResolver; + } + + public abstract Register getExitMethodAddressResolutionRegister(RegisterConfig registerConfig); + + public abstract LIRInstruction createExitMethodAddressResolutionOp(RegisterValue exitThroughRegisterValue); + +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64ExitMethodAddressResolutionOp.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64ExitMethodAddressResolutionOp.java new file mode 100644 index 000000000000..4f824a1a146f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64ExitMethodAddressResolutionOp.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot.aarch64; + +import static jdk.vm.ci.code.ValueUtil.asRegister; + +import jdk.graal.compiler.asm.aarch64.AArch64MacroAssembler; +import jdk.graal.compiler.lir.LIRInstructionClass; +import jdk.graal.compiler.lir.Opcode; +import jdk.graal.compiler.lir.aarch64.AArch64BlockEndOp; +import jdk.graal.compiler.lir.asm.CompilationResultBuilder; +import jdk.vm.ci.meta.Value; + +@Opcode("EXIT_METHOD_ADDRESS_RESOLUTION") +public class AArch64ExitMethodAddressResolutionOp extends AArch64BlockEndOp { + public static final LIRInstructionClass TYPE = LIRInstructionClass.create(AArch64ExitMethodAddressResolutionOp.class); + private @Use Value ip; + + public AArch64ExitMethodAddressResolutionOp(Value ip) { + super(TYPE); + this.ip = ip; + } + + @Override + protected void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) { + crb.frameContext.leave(crb); + masm.jmp(asRegister(ip)); + crb.frameContext.returned(crb); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java new file mode 100644 index 000000000000..15dcfb3f23f6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/aarch64/AArch64MethodAddressResolutionDispatcher.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot.aarch64; + +import static com.oracle.svm.core.pltgot.ExitMethodAddressResolutionNode.exitMethodAddressResolution; + +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.deopt.DeoptimizationSlotPacking; +import com.oracle.svm.core.graal.code.StubCallingConvention; +import com.oracle.svm.core.pltgot.MethodAddressResolutionDispatcher; +import com.oracle.svm.core.snippets.KnownIntrinsics; + +import jdk.graal.compiler.nodes.UnreachableNode; + +public final class AArch64MethodAddressResolutionDispatcher extends MethodAddressResolutionDispatcher { + /** + * This method is never called directly, instead we jump to it through a scratch register from + * the PLT stub (see {@code AArch64PLTStubGenerator}). The PLT stub writes the GOT entry into + * the stack frame padding space after the return address that is conventionally used for the + * deopt frame data. We do this in order to avoid having to spill the argument passing registers + * to the stack when we call @{code resolveMethodAddress}. Note that we cannot use the full + * word, see {@link DeoptimizationSlotPacking} on the convention that should be used. + */ + @StubCallingConvention + @Uninterruptible(reason = "PLT/GOT method address resolution doesn't support interruptible code paths.") + @NeverInline("This method must never be inlined or called directly because we only jump to it from the PLT stub.") + public static void resolveMethodAddress() { + Pointer paddingSlot = KnownIntrinsics.readCallerStackPointer(); + long gotEntry = DeoptimizationSlotPacking.decodeGOTIndex(paddingSlot.readWord(0).rawValue()); + long resolvedMethodAddress = MethodAddressResolutionDispatcher.resolveMethodAddress(gotEntry); + exitMethodAddressResolution(WordFactory.pointer(resolvedMethodAddress)); + throw UnreachableNode.unreachable(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64ExitMethodAddressResolutionOp.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64ExitMethodAddressResolutionOp.java new file mode 100644 index 000000000000..8c10e02166d5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64ExitMethodAddressResolutionOp.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot.amd64; + +import static jdk.vm.ci.code.ValueUtil.asRegister; + +import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; +import jdk.graal.compiler.lir.LIRInstructionClass; +import jdk.graal.compiler.lir.Opcode; +import jdk.graal.compiler.lir.amd64.AMD64BlockEndOp; +import jdk.graal.compiler.lir.asm.CompilationResultBuilder; +import jdk.vm.ci.meta.Value; + +@Opcode("EXIT_METHOD_ADDRESS_RESOLUTION") +public class AMD64ExitMethodAddressResolutionOp extends AMD64BlockEndOp { + public static final LIRInstructionClass TYPE = LIRInstructionClass.create(AMD64ExitMethodAddressResolutionOp.class); + private @Use Value ip; + + public AMD64ExitMethodAddressResolutionOp(Value ip) { + super(TYPE); + this.ip = ip; + } + + @Override + public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { + crb.frameContext.leave(crb); + masm.jmp(asRegister(ip)); + crb.frameContext.returned(crb); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java new file mode 100644 index 000000000000..8f06638f0394 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/pltgot/amd64/AMD64MethodAddressResolutionDispatcher.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.pltgot.amd64; + +import static com.oracle.svm.core.pltgot.ExitMethodAddressResolutionNode.exitMethodAddressResolution; + +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.graal.code.ExplicitCallingConvention; +import com.oracle.svm.core.graal.code.StubCallingConvention; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.pltgot.MethodAddressResolutionDispatcher; + +import jdk.graal.compiler.nodes.UnreachableNode; + +public final class AMD64MethodAddressResolutionDispatcher extends MethodAddressResolutionDispatcher { + /** + * This method is never called directly, we jump to it from a PLT stub for a method with a given + * gotEntry. Because the {@link SubstrateCallingConventionKind#ForwardReturnValue} calling + * convention takes the value of its one and only parameter {@code gotEntry} from the return + * register, we load the got entry value of a method that we are resolving into the return value + * register in the PLT stub. + * + * In the lir op for {@code exitMethodAddressResolution} we jump through the return register to + * the {@code resolvedMethodAddress} and that's why the method {@code resolveMethodAddress} has + * to have a return kind {@code long} so that the calleeSaved register restore for this method + * doesn't override the return register. See + * {@link jdk.graal.compiler.lir.asm.FrameContext#leave}. + */ + @StubCallingConvention + @Uninterruptible(reason = "PLT/GOT method address resolution doesn't support interruptible code paths.") + @ExplicitCallingConvention(SubstrateCallingConventionKind.ForwardReturnValue) + @NeverInline("This method must never be inlined or called directly because we only jump to it from the PLT stub.") + public static long resolveMethodAddress(long gotEntry) { + long resolvedMethodAddress = MethodAddressResolutionDispatcher.resolveMethodAddress(gotEntry); + exitMethodAddressResolution(WordFactory.pointer(resolvedMethodAddress)); + throw UnreachableNode.unreachable(); + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/GOTCall.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/GOTCall.java new file mode 100644 index 000000000000..e397e8f53d42 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/GOTCall.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.pltgot; + +import jdk.vm.ci.code.DebugInfo; +import jdk.vm.ci.code.site.Infopoint; +import jdk.vm.ci.code.site.InfopointReason; + +public class GOTCall extends Infopoint { + public GOTCall(int pcOffset, DebugInfo debugInfo, InfopointReason reason) { + super(pcOffset, debugInfo, reason); + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/PLTGOTNonSnippetLowerings.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/PLTGOTNonSnippetLowerings.java new file mode 100644 index 000000000000..5216dc48dab4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/PLTGOTNonSnippetLowerings.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.pltgot; + +import java.util.Map; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.LocationIdentity; + +import com.oracle.svm.core.FrameAccess; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.graal.meta.KnownOffsets; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; +import com.oracle.svm.core.graal.snippets.NonSnippetLowerings; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.GOTAccess; +import com.oracle.svm.hosted.nodes.ReadReservedRegister; +import com.oracle.svm.hosted.pltgot.GOTEntryAllocator; +import com.oracle.svm.hosted.pltgot.HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.MethodAddressResolutionSupport; + +import jdk.graal.compiler.core.common.memory.BarrierType; +import jdk.graal.compiler.core.common.memory.MemoryOrderMode; +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.graph.NodeInputList; +import jdk.graal.compiler.nodes.CallTargetNode; +import jdk.graal.compiler.nodes.ConstantNode; +import jdk.graal.compiler.nodes.FixedNode; +import jdk.graal.compiler.nodes.IndirectCallTargetNode; +import jdk.graal.compiler.nodes.InvokeNode; +import jdk.graal.compiler.nodes.InvokeWithExceptionNode; +import jdk.graal.compiler.nodes.LoweredCallTargetNode; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.java.MethodCallTargetNode; +import jdk.graal.compiler.nodes.memory.ReadNode; +import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.meta.JavaType; + +@Platforms(Platform.HOSTED_ONLY.class) +public final class PLTGOTNonSnippetLowerings { + + public static void registerLowerings(RuntimeConfiguration runtimeConfig, Map, NodeLoweringProvider> lowerings) { + InvokeThroughGOTLowering invokeLowering = new InvokeThroughGOTLowering(runtimeConfig); + lowerings.put(InvokeNode.class, invokeLowering); + lowerings.put(InvokeWithExceptionNode.class, invokeLowering); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static final class InvokeThroughGOTLowering extends NonSnippetLowerings.InvokeLowering { + + private final MethodAddressResolutionSupport methodAddressResolutionSupport; + private final GOTEntryAllocator gotEntryAllocator; + + InvokeThroughGOTLowering(RuntimeConfiguration runtimeConfig) { + super(runtimeConfig, SubstrateOptions.VerifyTypes.getValue(), KnownOffsets.singleton()); + this.methodAddressResolutionSupport = HostedPLTGOTConfiguration.singleton().getMethodAddressResolutionSupport(); + this.gotEntryAllocator = HostedPLTGOTConfiguration.singleton().getGOTEntryAllocator(); + } + + @Override + protected LoweredCallTargetNode createDirectCall(StructuredGraph graph, MethodCallTargetNode callTarget, NodeInputList parameters, JavaType[] signature, + CallingConvention.Type callType, CallTargetNode.InvokeKind invokeKind, SharedMethod callee, FixedNode node) { + SharedMethod caller = (SharedMethod) graph.method(); + if (methodAddressResolutionSupport.shouldCallViaPLTGOT(caller, callee)) { + ValueNode heapBaseNode = graph.addOrUnique(ReadReservedRegister.createReadHeapBaseNode(graph)); + int targetGotEntry = gotEntryAllocator.getMethodGotEntry(callee); + ValueNode offsetNode = ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), GOTAccess.getGotEntryOffsetFromHeapRegister(targetGotEntry), graph); + OffsetAddressNode offsetAddressNode = graph.unique(new OffsetAddressNode(heapBaseNode, offsetNode)); + ReadNode methodAddress = graph + .add(new ReadNode(offsetAddressNode, LocationIdentity.ANY_LOCATION, FrameAccess.getWordStamp(), BarrierType.NONE, MemoryOrderMode.PLAIN)); + SubstrateGOTCallTargetNode loweredCallTarget = graph.add( + new SubstrateGOTCallTargetNode(methodAddress, parameters.toArray(ValueNode.EMPTY_ARRAY), callTarget.returnStamp(), signature, callee, callType, invokeKind)); + + graph.addBeforeFixed(node, methodAddress); + return loweredCallTarget; + } + + return super.createDirectCall(graph, callTarget, parameters, signature, callType, invokeKind, callee, node); + } + + @Override + protected IndirectCallTargetNode createIndirectCall(StructuredGraph graph, MethodCallTargetNode callTarget, NodeInputList parameters, SharedMethod callee, JavaType[] signature, + CallingConvention.Type callType, CallTargetNode.InvokeKind invokeKind, ValueNode entry) { + SharedMethod caller = (SharedMethod) graph.method(); + /* + * We don't change how virtual methods are called; instead we make sure that the + * relocation to an appropriate PLT stub will be emitted in a vtable slot for the + * callee. + * + * This will force all the implementations of a callee method to be resolved via PLT/GOT + * mechanism. In the future, when we introduce the concept of hot and cold calls we + * could use a dispatch stub instead to reduce the number of GOT entries. + */ + if (methodAddressResolutionSupport.shouldCallViaPLTGOT(caller, callee)) { + for (SharedMethod implementation : callee.getImplementations()) { + gotEntryAllocator.reserveMethodGotEntry(implementation); + } + } + return super.createIndirectCall(graph, callTarget, parameters, callee, signature, callType, invokeKind, entry); + } + } + +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/SubstrateGOTCallTargetNode.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/SubstrateGOTCallTargetNode.java new file mode 100644 index 000000000000..c90c6f187114 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/SubstrateGOTCallTargetNode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.pltgot; + +import com.oracle.svm.core.nodes.SubstrateIndirectCallTargetNode; + +import jdk.graal.compiler.core.common.type.StampPair; +import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.nodeinfo.NodeInfo; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +@NodeInfo +public final class SubstrateGOTCallTargetNode extends SubstrateIndirectCallTargetNode { + public static final NodeClass TYPE = NodeClass.create(SubstrateGOTCallTargetNode.class); + + public SubstrateGOTCallTargetNode(ValueNode computedAddress, ValueNode[] arguments, StampPair returnStamp, JavaType[] signature, ResolvedJavaMethod target, CallingConvention.Type callType, + InvokeKind invokeKind) { + super(TYPE, computedAddress, arguments, returnStamp, signature, target, callType, invokeKind, null); + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/aarch64/PLTGOTAArch64Lowerings.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/aarch64/PLTGOTAArch64Lowerings.java new file mode 100644 index 000000000000..41415f28353c --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/aarch64/PLTGOTAArch64Lowerings.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.pltgot.aarch64; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; +import com.oracle.svm.core.graal.snippets.aarch64.AArch64SnippetsFeature; +import com.oracle.svm.graal.pltgot.PLTGOTNonSnippetLowerings; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; + +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.util.Providers; + +@AutomaticallyRegisteredFeature +@Platforms(Platform.AARCH64.class) +public class PLTGOTAArch64Lowerings implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return PLTGOTOptions.EnablePLTGOT.getValue(); + } + + @Override + public List> getRequiredFeatures() { + return Collections.singletonList(AArch64SnippetsFeature.class); + } + + @Override + public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues options, Providers providers, Map, NodeLoweringProvider> lowerings, boolean hosted) { + if (hosted) { + PLTGOTNonSnippetLowerings.registerLowerings(runtimeConfig, lowerings); + } + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/amd64/PLTGOTAMD64Lowerings.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/amd64/PLTGOTAMD64Lowerings.java new file mode 100644 index 000000000000..d6811cdba0c3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/pltgot/amd64/PLTGOTAMD64Lowerings.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.graal.pltgot.amd64; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; +import com.oracle.svm.core.graal.snippets.amd64.AMD64SnippetsFeature; +import com.oracle.svm.graal.pltgot.PLTGOTNonSnippetLowerings; +import com.oracle.svm.hosted.pltgot.PLTGOTOptions; + +import jdk.graal.compiler.graph.Node; +import jdk.graal.compiler.options.OptionValues; +import jdk.graal.compiler.phases.util.Providers; + +@AutomaticallyRegisteredFeature +@Platforms(Platform.AMD64.class) +public class PLTGOTAMD64Lowerings implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return PLTGOTOptions.EnablePLTGOT.getValue(); + } + + @Override + public List> getRequiredFeatures() { + return Collections.singletonList(AMD64SnippetsFeature.class); + } + + @Override + public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues options, Providers providers, Map, NodeLoweringProvider> lowerings, boolean hosted) { + if (hosted) { + PLTGOTNonSnippetLowerings.registerLowerings(runtimeConfig, lowerings); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index 76534d482e6c..e8331c298511 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -69,6 +69,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.code.SubstrateBackend; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.meta.SharedField; @@ -766,15 +767,22 @@ public void registerLinkerInvocationTransformer(Function entryPoints, JavaMainSupport j createAbstractImage(k, hostedEntryPoints, heap, hMetaAccess, codeCache); - FeatureImpl.AfterAbstractImageCreationAccessImpl access = new FeatureImpl.AfterAbstractImageCreationAccessImpl(featureHandler, loader, debug, image); + FeatureImpl.AfterAbstractImageCreationAccessImpl access = new FeatureImpl.AfterAbstractImageCreationAccessImpl(featureHandler, loader, debug, image, + runtimeConfiguration.getBackendForNormalMethod()); featureHandler.forEachGraalFeature(feature -> feature.afterAbstractImageCreation(access)); image.build(imageName, debug); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/CollectPLTGOTCallSitesResolutionSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/CollectPLTGOTCallSitesResolutionSupport.java new file mode 100644 index 000000000000..340c6140f2e0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/CollectPLTGOTCallSitesResolutionSupport.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ConcurrentSkipListSet; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.MethodAddressResolver; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedUniverse; + +class CollectPLTGOTCallSitesResolutionSupport implements MethodAddressResolutionSupport { + private final MethodAddressResolutionSupport resolver; + private final ConcurrentMap> callerCalleesMap; + private final Set calleesWithUnknownCaller; + + CollectPLTGOTCallSitesResolutionSupport(MethodAddressResolutionSupport resolver) { + this.resolver = resolver; + this.callerCalleesMap = new ConcurrentSkipListMap<>(HostedUniverse.METHOD_COMPARATOR); + this.calleesWithUnknownCaller = new ConcurrentSkipListSet<>(HostedUniverse.METHOD_COMPARATOR); + } + + @Override + public boolean shouldCallViaPLTGOT(SharedMethod caller, SharedMethod callee) { + boolean shouldCall = resolver.shouldCallViaPLTGOT(caller, callee); + if (shouldCall) { + var callees = callerCalleesMap.computeIfAbsent((HostedMethod) caller, k -> ConcurrentHashMap.newKeySet()); + callees.add((HostedMethod) callee); + } + return shouldCall; + } + + @Override + public boolean shouldCallViaPLTGOT(SharedMethod callee) { + boolean shouldCall = resolver.shouldCallViaPLTGOT(callee); + if (shouldCall) { + calleesWithUnknownCaller.add((HostedMethod) callee); + } + return shouldCall; + } + + @Override + public void augmentImageObjectFile(ObjectFile imageObjectFile) { + resolver.augmentImageObjectFile(imageObjectFile); + } + + @Override + public MethodAddressResolver createMethodAddressResolver() { + return resolver.createMethodAddressResolver(); + } + + public Map> getCallerCalleesMap() { + return Collections.unmodifiableMap(callerCalleesMap); + } + + public Set getCalleesWithUnknownCaller() { + return Collections.unmodifiableSet(calleesWithUnknownCaller); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/GOTEntryAllocator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/GOTEntryAllocator.java new file mode 100644 index 000000000000..7c70ca2d51b1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/GOTEntryAllocator.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.meta.HostedMethod; + +public class GOTEntryAllocator { + public static final int GOT_NO_ENTRY = -2; + + private final ConcurrentHashMap gotMap = new ConcurrentHashMap<>(); + private SharedMethod[] got = null; + + private final AtomicInteger currentFreeEntry = new AtomicInteger(0); + + public int getMethodGotEntry(SharedMethod method) { + return gotMap.computeIfAbsent(method, m -> currentFreeEntry.getAndIncrement()); + } + + public void reserveMethodGotEntry(SharedMethod method) { + getMethodGotEntry(method); + } + + public int queryGotEntry(SharedMethod method) { + assert hasGOTLayout(); + return gotMap.getOrDefault(method, GOT_NO_ENTRY); + } + + public void reserveAndLayout(Set methods, MethodAddressResolutionSupport resolver) { + assert !hasGOTLayout(); + + methods.stream() + .filter(resolver::shouldCallViaPLTGOT) + .forEach(this::reserveMethodGotEntry); + + VMError.guarantee(got == null, "Can layout the GOT only once."); + got = new SharedMethod[gotMap.keySet().size()]; + for (Map.Entry entry : gotMap.entrySet()) { + got[entry.getValue()] = entry.getKey(); + } + } + + public boolean hasGOTLayout() { + return got != null; + } + + public SharedMethod[] getGOT() { + VMError.guarantee(got != null, "Must layout the GOT first before use."); + return got; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/HostedPLTGOTConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/HostedPLTGOTConfiguration.java new file mode 100644 index 000000000000..196c972a6b0e --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/HostedPLTGOTConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.lang.reflect.Method; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.pltgot.PLTGOTConfiguration; +import com.oracle.svm.hosted.meta.HostedMetaAccess; +import com.oracle.svm.hosted.meta.HostedMethod; + +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterConfig; + +public abstract class HostedPLTGOTConfiguration extends PLTGOTConfiguration { + public static final SectionName SVM_GOT_SECTION = new SectionName.ProgbitsSectionName("svm_got"); + + protected MethodAddressResolutionSupport methodAddressResolutionSupport; + private final GOTEntryAllocator gotEntryAllocator = new GOTEntryAllocator(); + + private PLTSectionSupport pltSectionSupport; + private HostedMetaAccess hostedMetaAccess; + + @SuppressWarnings("this-escape") + public HostedPLTGOTConfiguration() { + this.pltSectionSupport = new PLTSectionSupport(getArchSpecificPLTStubGenerator()); + } + + public static HostedPLTGOTConfiguration singleton() { + return (HostedPLTGOTConfiguration) ImageSingletons.lookup(PLTGOTConfiguration.class); + } + + public abstract Method getArchSpecificResolverAsMethod(); + + public abstract Register getGOTPassingRegister(RegisterConfig registerConfig); + + public abstract PLTStubGenerator getArchSpecificPLTStubGenerator(); + + public void setHostedMetaAccess(HostedMetaAccess metaAccess) { + assert hostedMetaAccess == null : "The field hostedMetaAccess can't be set twice."; + this.hostedMetaAccess = metaAccess; + } + + public void initializeMethodAddressResolutionSupport(MethodAddressResolutionSupportFactory methodAddressResolutionSupportFactory) { + assert methodAddressResolutionSupport == null : "The field methodAddressResolutionSupport can't be initialized twice."; + methodAddressResolutionSupport = methodAddressResolutionSupportFactory.create(); + if (PLTGOTOptions.PrintPLTGOTCallsInfo.getValue()) { + methodAddressResolutionSupport = new CollectPLTGOTCallSitesResolutionSupport(methodAddressResolutionSupport); + } + methodAddressResolver = getMethodAddressResolutionSupport().createMethodAddressResolver(); + } + + public MethodAddressResolutionSupport getMethodAddressResolutionSupport() { + assert methodAddressResolutionSupport != null : "Must call initializeMethodAddressResolutionSupport before calling getMethodAddressResolutionSupport"; + return methodAddressResolutionSupport; + } + + public PLTSectionSupport getPLTSectionSupport() { + return pltSectionSupport; + } + + public void markResolverMethodPatch() { + pltSectionSupport.markResolverMethodPatch(getArchSpecificResolverAsHostedMethod()); + } + + public HostedMethod getArchSpecificResolverAsHostedMethod() { + assert hostedMetaAccess != null : "Must set hostedMetaAccess before calling getArchSpecificResolverAsHostedMethod"; + return hostedMetaAccess.lookupJavaMethod(getArchSpecificResolverAsMethod()); + } + + public GOTEntryAllocator getGOTEntryAllocator() { + return gotEntryAllocator; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/IdentityMethodAddressResolverFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/IdentityMethodAddressResolverFeature.java new file mode 100644 index 000000000000..8ccdc3c026d6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/IdentityMethodAddressResolverFeature.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.util.Collections; +import java.util.List; + +import org.graalvm.nativeimage.AnnotationAccess; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.code.ExplicitCallingConvention; +import com.oracle.svm.core.graal.code.StubCallingConvention; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.IdentityMethodAddressResolver; +import com.oracle.svm.core.pltgot.MethodAddressResolver; +import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.image.RelocatableBuffer; + +/** + * An example dynamic method address resolver implementation. + * + * The code of all reachable methods is located in the text section of the generated image. This + * resolver introduces another section ('.svm_methodtable') that contains absolute addresses of + * methods whose address is dynamically resolved. The methodtable section is the same size as the + * GOT, and the index of any method is the same in both. This feature registers the + * {@link IdentityMethodAddressResolver} resolver that, when a method is called for the first time, + * lookups the absolute method address in the above section. This address is then written in the + * appropriate GOT entry and is used for subsequent calls of the same method. + * + */ + +public class IdentityMethodAddressResolverFeature implements InternalFeature { + + // Restrict segment names to 16 chars on Mach-O. + public static final SectionName SVM_METHODTABLE = new SectionName.ProgbitsSectionName("svm_methodtbl"); + + private RelocatableBuffer offsetsSectionBuffer; + + private ObjectFile.ProgbitsSectionImpl offsetsSectionBufferImpl; + + protected class IdentityMethodAddressResolverSupport implements MethodAddressResolutionSupport { + private static boolean isAllowed(SharedMethod method) { + if (AnnotationAccess.isAnnotationPresent(method, CEntryPoint.class)) { + return false; + } + if (AnnotationAccess.isAnnotationPresent(method, CFunction.class)) { + return false; + } + if (AnnotationAccess.isAnnotationPresent(method, StubCallingConvention.class)) { + return false; + } + if (AnnotationAccess.isAnnotationPresent(method, Uninterruptible.class)) { + return false; + } + if (AnnotationAccess.isAnnotationPresent(method, SubstrateForeignCallTarget.class)) { + return false; + } + if (AnnotationAccess.isAnnotationPresent(method.getDeclaringClass(), InternalVMMethod.class)) { + return false; + } + if (AnnotationAccess.isAnnotationPresent(method, ExplicitCallingConvention.class) && + AnnotationAccess.getAnnotation(method, ExplicitCallingConvention.class).value().equals(SubstrateCallingConventionKind.ForwardReturnValue)) { + /* + * Methods that use ForwardReturnValue calling convention can't be resolved with + * PLT/GOT on AMD64 because + * AMD64MethodAddressResolutionDispatcher.resolveMethodAddress uses the same calling + * convention, and we can't save the callers value of the `rax` register on AMD64 + * without spilling it. + */ + return false; + } + return true; + } + + @Override + @SuppressWarnings("unused") + public boolean shouldCallViaPLTGOT(SharedMethod caller, SharedMethod callee) { + return isAllowed(callee); + } + + @Override + public boolean shouldCallViaPLTGOT(SharedMethod callee) { + return isAllowed(callee); + } + + @Override + public void augmentImageObjectFile(ObjectFile imageObjectFile) { + GOTEntryAllocator gotEntryAllocator = HostedPLTGOTConfiguration.singleton().getGOTEntryAllocator(); + SharedMethod[] got = gotEntryAllocator.getGOT(); + long methodCount = got.length; + int wordSize = ConfigurationValues.getTarget().wordSize; + long gotSectionSize = methodCount * wordSize; + offsetsSectionBuffer = new RelocatableBuffer(gotSectionSize, imageObjectFile.getByteOrder()); + offsetsSectionBufferImpl = new BasicProgbitsSectionImpl(offsetsSectionBuffer.getBackingArray()); + String name = SVM_METHODTABLE.getFormatDependentName(imageObjectFile.getFormat()); + ObjectFile.Section offsetsSection = imageObjectFile.newProgbitsSection(name, imageObjectFile.getPageSize(), true, false, offsetsSectionBufferImpl); + + ObjectFile.RelocationKind relocationKind = ObjectFile.RelocationKind.getDirect(wordSize); + for (int gotEntryNo = 0; gotEntryNo < got.length; ++gotEntryNo) { + offsetsSectionBuffer.addRelocationWithoutAddend(gotEntryNo * wordSize, relocationKind, new MethodPointer(got[gotEntryNo], true)); + } + + imageObjectFile.createDefinedSymbol(offsetsSection.getName(), offsetsSection, 0, 0, false, false); + imageObjectFile.createDefinedSymbol("__svm_methodtable_begin", offsetsSection, 0, wordSize, false, SubstrateOptions.InternalSymbolsAreGlobal.getValue()); + imageObjectFile.createDefinedSymbol("__svm_methodtable_end", offsetsSection, gotSectionSize, wordSize, false, SubstrateOptions.InternalSymbolsAreGlobal.getValue()); + } + + @Override + public MethodAddressResolver createMethodAddressResolver() { + return new IdentityMethodAddressResolver(); + } + } + + @Override + public List> getRequiredFeatures() { + return Collections.singletonList(PLTGOTFeature.class); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + HostedPLTGOTConfiguration.singleton().initializeMethodAddressResolutionSupport(IdentityMethodAddressResolverSupport::new); + } + + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + prepareOffsetsSection((NativeImage) ((FeatureImpl.BeforeImageWriteAccessImpl) access).getImage()); + } + + private void prepareOffsetsSection(NativeImage image) { + image.markRelocationSitesFromBuffer(offsetsSectionBuffer, offsetsSectionBufferImpl); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupport.java new file mode 100644 index 000000000000..724206517249 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupport.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.MethodAddressResolver; + +/** + * Provides necessary services for dynamic method address resolution through the PLT/GOT. + */ +public interface MethodAddressResolutionSupport { + + /** + * Predicate that determines if an indirect GOT call should be emitted when the callee is called + * from the caller. + * + */ + boolean shouldCallViaPLTGOT(SharedMethod caller, SharedMethod callee); + + /** + * Predicate that determines if an indirect GOT call should be used when the caller of the + * method is not known. + */ + boolean shouldCallViaPLTGOT(SharedMethod callee); + + /** + * Allows the resolver to augment the object file produced by the image builder. This can be + * used, for example, to create a custom section in the resulting object file. + */ + void augmentImageObjectFile(ObjectFile imageObjectFile); + + /** + * Creates a resolver that will be used to resolve the addresses of methods called through the + * PLT/GOT at runtime. + */ + MethodAddressResolver createMethodAddressResolver(); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupportFactory.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupportFactory.java new file mode 100644 index 000000000000..af7289ca1bc3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/MethodAddressResolutionSupportFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +public interface MethodAddressResolutionSupportFactory { + MethodAddressResolutionSupport create(); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTFeature.java new file mode 100644 index 000000000000..3dc472c1856c --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTFeature.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Consumer; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; + +import com.oracle.graal.pointsto.reports.ReportUtils; +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.RuntimeCompilation; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.GOTAccess; +import com.oracle.svm.core.pltgot.GOTHeapSupport; +import com.oracle.svm.core.pltgot.PLTGOTConfiguration; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.image.MethodPointerRelocationProvider; +import com.oracle.svm.hosted.image.RelocatableBuffer; +import com.oracle.svm.hosted.pltgot.aarch64.AArch64HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.amd64.AMD64HostedPLTGOTConfiguration; + +import jdk.graal.compiler.util.json.JsonWriter; + +/** + * Introduces the PLT and GOT mechanism to native calls that allows resolving the target method's + * address at runtime. + * + * To create a custom method address resolver, a user needs to: + *
    + *
  1. Implement the {@link MethodAddressResolutionSupport} resolver support class.
  2. + *
  3. Create a feature that enables the {@link PLTGOTOptions#EnablePLTGOT} option and registers + * their resolver in the {@link ImageSingletons}.
  4. + *
+ * For an example resolver, see {@link com.oracle.svm.core.pltgot.IdentityMethodAddressResolver}. + * + * From an implementation point of view, this feature and the supporting classes implement + * the PLT (Procedure + * Linkage Table) and GOT (Global Offset Table) mechanism used by the Linux dynamic linker to + * enable lazy binding. + * + * Native Image constructs the GOT during the image build by assigning each eligible method an entry + * into the GOT. At runtime, the GOT is represented as an array of pointers mapped right before the + * image heap. Multiple isolates all see the same GOT. Direct calls of eligible methods are replaced + * with indirect calls that load the method's address from the GOT. + * + * For each eligible method, Native Image constructs a PLT stub. Initially, each GOT entry points to + * the PLT stub of the method assigned to that GOT entry. Vtable entries of eligible methods also + * point to the PLT stub. + * + * There are two kinds of calls on the native level: + *
    + *
  1. Direct calls
  2. + *
  3. Indirect calls
  4. + *
+ * Native image emits direct calls as IP relative calls. If a method is directly called and requires + * the PLT/GOT, these calls are instead emitted as indirect calls where the address of the method to + * call is located at HEAP_BASE_REGISTER - (METHOD_GOT_ENTRY_NO * WORD_SIZE). Indirect calls, used + * for virtual calls, are unchanged. For virtual calls, each vtable entry is rewritten to point to + * the PLT stub associated with the method we are calling, and it will never change during program + * execution. As a consequence, virtual calls for methods that are resolved with the PLT/GOT + * mechanism are doubly indirected. Also, we only rewrite the vtable entries for the methods that + * are called through the PLT/GOT mechanism. + * + * A couple of implementation notes: + *
    + *
  • The GOT is always mapped relative to the image heap of an isolate and is backed by the same + * memory across isolates. This means that modifications made to the GOT by one isolate are visible + * to all other isolates. This mapping is necessary to avoid relocations in the code (we don't want + * to patch the code at runtime).
  • + *
  • Virtual calls now have two levels of indirection instead of one. The vtables contain + * addresses of PLT stubs corresponding to the virtual methods. A PLT stub either resolves the + * address of the actual method and writes it to the GOT, or it reads the previously resolved method + * address from the GOT, before finally jumping into the target method. The vtables themselves are + * never modified so that the relocatable section of the image heap can remain read-only.
  • + * + * If the PLT/GOT mechanism is used for all eligible methods, the additional indirections that are + * now present in calls can result in a slowdown that ranges from just a few percent to up to 20% + * depending on the workload for the default configuration. + *
+ */ +public class PLTGOTFeature implements InternalFeature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return PLTGOTOptions.EnablePLTGOT.getValue(); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + VMError.guarantee(Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class) || Platform.includedIn(Platform.WINDOWS.class), + "PLT and GOT is currently only supported on Linux, Darwin and Windows."); + VMError.guarantee(Platform.includedIn(Platform.AARCH64.class) || Platform.includedIn(Platform.AMD64.class), "PLT and GOT is currently only supported on AArch64 and AMD64."); + VMError.guarantee(!RuntimeCompilation.isEnabled(), "PLT and GOT is currently not supported with runtime compilation."); + VMError.guarantee(SubstrateOptions.SpawnIsolates.getValue(), "PLT and GOT cannot work without isolates."); + VMError.guarantee("lir".equals(SubstrateOptions.CompilerBackend.getValue()), "PLT and GOT cannot work with a custom compiler backend."); + + ImageSingletons.add(PLTGOTConfiguration.class, createConfiguration()); + } + + private static PLTGOTConfiguration createConfiguration() { + if (Platform.includedIn(Platform.AMD64.class)) { + return new AMD64HostedPLTGOTConfiguration(); + } else if (Platform.includedIn(Platform.AARCH64.class)) { + return new AArch64HostedPLTGOTConfiguration(); + } else { + throw VMError.shouldNotReachHere("PLT and GOT is currently only supported on AArch64 and AMD64."); + } + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + Method resolver = HostedPLTGOTConfiguration.singleton().getArchSpecificResolverAsMethod(); + ((FeatureImpl.BeforeAnalysisAccessImpl) access).registerAsRoot(resolver, false, "PLT GOT support, registered in " + PLTGOTFeature.class); + } + + @Override + public void beforeCompilation(BeforeCompilationAccess access) { + HostedPLTGOTConfiguration.singleton().setHostedMetaAccess(((FeatureImpl.BeforeCompilationAccessImpl) access).getMetaAccess()); + } + + @Override + public void afterCompilation(AfterCompilationAccess access) { + MethodAddressResolutionSupport methodAddressResolutionSupport = HostedPLTGOTConfiguration.singleton().getMethodAddressResolutionSupport(); + GOTEntryAllocator gotEntryAllocator = HostedPLTGOTConfiguration.singleton().getGOTEntryAllocator(); + + gotEntryAllocator.reserveAndLayout(((FeatureImpl.AfterCompilationAccessImpl) access).getCompilations().keySet(), methodAddressResolutionSupport); + + Set gotTable = Set.of(gotEntryAllocator.getGOT()); + ImageSingletons.add(MethodPointerRelocationProvider.class, new PLTGOTPointerRelocationProvider(gotTable::contains)); + } + + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + HostedPLTGOTConfiguration.singleton().markResolverMethodPatch(); + if (PLTGOTOptions.PrintPLTGOTCallsInfo.getValue()) { + reportPLTGOTCallSites(); + } + } + + @Override + public void afterAbstractImageCreation(AfterAbstractImageCreationAccess access) { + FeatureImpl.AfterAbstractImageCreationAccessImpl accessImpl = (FeatureImpl.AfterAbstractImageCreationAccessImpl) access; + ObjectFile imageObjectFile = accessImpl.getImage().getObjectFile(); + SharedMethod[] got = HostedPLTGOTConfiguration.singleton().getGOTEntryAllocator().getGOT(); + /* We must create the PLT and the GOT section before we mark any relocations. */ + PLTSectionSupport pltSectionSupport = HostedPLTGOTConfiguration.singleton().getPLTSectionSupport(); + pltSectionSupport.createPLTSection(got, imageObjectFile, accessImpl.getSubstrateBackend()); + createGOTSection(got, imageObjectFile, pltSectionSupport); + HostedPLTGOTConfiguration.singleton().getMethodAddressResolutionSupport().augmentImageObjectFile(imageObjectFile); + } + + public static void createGOTSection(SharedMethod[] got, ObjectFile objectFile, PLTSectionSupport pltSectionSupport) { + int wordSize = ConfigurationValues.getTarget().wordSize; + int gotSectionSize = got.length * wordSize; + RelocatableBuffer gotBuffer = new RelocatableBuffer(gotSectionSize, objectFile.getByteOrder()); + ObjectFile.ProgbitsSectionImpl gotBufferImpl = new BasicProgbitsSectionImpl(gotBuffer.getBackingArray()); + String name = HostedPLTGOTConfiguration.SVM_GOT_SECTION.getFormatDependentName(objectFile.getFormat()); + ObjectFile.Section gotSection = objectFile.newProgbitsSection(name, objectFile.getPageSize(), true, false, gotBufferImpl); + + ObjectFile.RelocationKind relocationKind = ObjectFile.RelocationKind.getDirect(wordSize); + for (int gotEntryNo = 0; gotEntryNo < got.length; ++gotEntryNo) { + int methodGOTEntryOffsetInSection = gotSectionSize + GOTAccess.getGotEntryOffsetFromHeapRegister(gotEntryNo); + pltSectionSupport.markRelocationToPLTResolverJump(gotBufferImpl, methodGOTEntryOffsetInSection, relocationKind, got[gotEntryNo]); + } + + objectFile.createDefinedSymbol(gotSection.getName(), gotSection, 0, 0, false, false); + objectFile.createDefinedSymbol(GOTHeapSupport.IMAGE_GOT_BEGIN_SYMBOL_NAME, gotSection, 0, wordSize, false, + SubstrateOptions.InternalSymbolsAreGlobal.getValue()); + objectFile.createDefinedSymbol(GOTHeapSupport.IMAGE_GOT_END_SYMBOL_NAME, gotSection, gotSectionSize, wordSize, false, + SubstrateOptions.InternalSymbolsAreGlobal.getValue()); + + if (PLTGOTOptions.PrintGOT.getValue()) { + ReportUtils.report("GOT Section contents", SubstrateOptions.reportsPath(), "got", "txt", writer -> { + writer.println("GOT Entry No | GOT Entry Offset From Image Heap Register | Method Name"); + for (int i = 0; i < got.length; ++i) { + writer.printf("%5X %5X %s%n", i, -GOTAccess.getGotEntryOffsetFromHeapRegister(i), got[i].toString()); + } + }); + } + } + + private static void reportPLTGOTCallSites() { + CollectPLTGOTCallSitesResolutionSupport resolver = (CollectPLTGOTCallSitesResolutionSupport) HostedPLTGOTConfiguration.singleton().getMethodAddressResolutionSupport(); + Consumer reportWriter = (pw) -> { + final String methodFormat = "%H.%n(%p)"; + try (JsonWriter writer = new JsonWriter(pw)) { + writer.append('{').newline(); + var calleesWithUnknownCaller = resolver.getCalleesWithUnknownCaller().stream().map(m -> m.format(methodFormat)).toList(); + if (!calleesWithUnknownCaller.isEmpty()) { + writer.quote("UNKNOWN_CALLER").append(":[").indent().newline(); + appendCallees(writer, calleesWithUnknownCaller.iterator()); + writer.newline().unindent().append("]"); + } + for (var entry : resolver.getCallerCalleesMap().entrySet()) { + writer.append(',').newline(); + var caller = entry.getKey(); + var calleesIter = entry.getValue().stream().map(m -> m.format(methodFormat)) + .sorted().iterator(); + writer.quote(caller.format(methodFormat)).append(":[").indent().newline(); + appendCallees(writer, calleesIter); + writer.unindent().newline().append(']'); + } + writer.newline().append('}').newline(); + } catch (IOException e) { + VMError.shouldNotReachHere(e); + } + }; + ReportUtils.report("PLT/GOT call-sites info", + SubstrateOptions.reportsPath(), "plt_got_call-sites_info", "json", reportWriter); + } + + private static void appendCallees(JsonWriter writer, Iterator callees) throws IOException { + while (callees.hasNext()) { + var callee = callees.next(); + writer.quote(callee); + if (callees.hasNext()) { + writer.append(','); + writer.newline(); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTOptions.java new file mode 100644 index 000000000000..869874560091 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTOptions.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import com.oracle.svm.core.option.HostedOptionKey; + +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionType; + +public class PLTGOTOptions { + @Option(help = "Enables support for dynamic method address resolution. Should never be enabled directly.", type = OptionType.Expert)// + public static final HostedOptionKey EnablePLTGOT = new HostedOptionKey<>(false); + + @Option(help = "Prints the contents of the GOT.", type = OptionType.Debug)// + public static final HostedOptionKey PrintGOT = new HostedOptionKey<>(false); + + @Option(help = "Prints Infopoint call sites inside methods called through PLT/GOT.", type = OptionType.Debug)// + public static final HostedOptionKey PrintPLTGOTCallsInfo = new HostedOptionKey<>(false); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTPointerRelocationProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTPointerRelocationProvider.java new file mode 100644 index 000000000000..df3eb86848f5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTGOTPointerRelocationProvider.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.util.function.Predicate; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.hosted.image.MethodPointerRelocationProvider; +import com.oracle.svm.hosted.meta.HostedMethod; + +/** + * Emits method pointer relocations in the image object file that take PLT/GOT into account. + * + * For methods invoked through PLT/GOT, unless overridden explicitly, emits relocations that point + * to the generated PLT stub. + */ +public class PLTGOTPointerRelocationProvider extends MethodPointerRelocationProvider { + + private final Predicate shouldMarkRelocationToPLTStub; + private final PLTSectionSupport pltSectionSupport; + + public PLTGOTPointerRelocationProvider(Predicate shouldMarkRelocationToPLTStub) { + this.shouldMarkRelocationToPLTStub = shouldMarkRelocationToPLTStub; + this.pltSectionSupport = HostedPLTGOTConfiguration.singleton().getPLTSectionSupport(); + } + + private boolean hasPLTStub(HostedMethod target, boolean isStaticallyResolved) { + return !isStaticallyResolved && shouldMarkRelocationToPLTStub.test(target); + } + + @Override + public void markMethodPointerRelocation(ObjectFile.ProgbitsSectionImpl section, int offset, ObjectFile.RelocationKind relocationKind, HostedMethod target, long addend, + MethodPointer methodPointer, boolean isInjectedNotCompiled) { + boolean isStaticallyResolved = methodPointer.isAbsolute(); + if (hasPLTStub(target, isStaticallyResolved)) { + pltSectionSupport.markRelocationToPLTStub(section, offset, relocationKind, target, addend); + } else { + super.markMethodPointerRelocation(section, offset, relocationKind, target, addend, methodPointer, isInjectedNotCompiled); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTSectionSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTSectionSupport.java new file mode 100644 index 000000000000..1f61742d6e83 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTSectionSupport.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import java.util.HashMap; +import java.util.Map; + +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.ObjectFile.ProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile.RelocationKind; +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.image.RelocatableBuffer; +import com.oracle.svm.hosted.meta.HostedMethod; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public class PLTSectionSupport { + + public static final SectionName SVM_PLT_SECTION_NAME = new SectionName.ProgbitsSectionName("svm_plt"); + + public static String pltSymbolNameForMethod(ResolvedJavaMethod method) { + return "svm_plt_" + NativeImage.localSymbolNameForMethod(method); + } + + private final Map methodPLTStubStart = new HashMap<>(); + private final Map methodPLTStubResolverOffset = new HashMap<>(); + + private ProgbitsSectionImpl pltBufferImpl; + private PLTStubGenerator stubGenerator; + + public PLTSectionSupport(PLTStubGenerator stubGenerator) { + this.stubGenerator = stubGenerator; + } + + void createPLTSection(SharedMethod[] got, ObjectFile objectFile, SubstrateBackend substrateBackend) { + byte[] pltCode = stubGenerator.generatePLT(got, substrateBackend); + int pltSectionSize = pltCode.length; + + RelocatableBuffer pltBuffer = new RelocatableBuffer(pltSectionSize, objectFile.getByteOrder()); + pltBufferImpl = new BasicProgbitsSectionImpl(pltBuffer.getBackingArray()); + String name = SVM_PLT_SECTION_NAME.getFormatDependentName(objectFile.getFormat()); + ObjectFile.Section pltSection = objectFile.newProgbitsSection(name, objectFile.getPageSize(), false, true, pltBufferImpl); + + pltBuffer.getByteBuffer().put(pltCode, 0, pltSectionSize); + + objectFile.createDefinedSymbol(pltSection.getName(), pltSection, 0, 0, true, false); + + for (SharedMethod method : got) { + HostedMethod m = (HostedMethod) method; + int offset = getMethodPLTStubStart(m); + objectFile.createDefinedSymbol(pltSymbolNameForMethod(m), pltSection, offset, ConfigurationValues.getTarget().wordSize, true, + SubstrateOptions.InternalSymbolsAreGlobal.getValue()); + } + + } + + void markRelocationToPLTStub(ProgbitsSectionImpl section, int offset, RelocationKind relocationKind, SharedMethod target, long addend) { + section.markRelocationSite(offset, relocationKind, pltSymbolNameForMethod(target), addend); + } + + void markRelocationToPLTResolverJump(ProgbitsSectionImpl section, int offset, RelocationKind relocationKind, SharedMethod target) { + assert methodPLTStubResolverOffset.get(target) != null : "Trying to mark a relocation to the `resolver-jump` part of the plt stub for a target that doesn't have a plt stub: " + target; + section.markRelocationSite(offset, relocationKind, pltSymbolNameForMethod(target), methodPLTStubResolverOffset.get(target)); + } + + void markResolverMethodPatch(HostedMethod resolverMethod) { + stubGenerator.markResolverMethodPatch(pltBufferImpl, resolverMethod); + } + + public void recordMethodPLTStubStart(SharedMethod method, int offset) { + methodPLTStubStart.put(method, offset); + } + + public void recordMethodPLTStubResolverOffset(SharedMethod method, int resolverOffset) { + methodPLTStubResolverOffset.put(method, resolverOffset); + } + + private int getMethodPLTStubStart(SharedMethod method) { + return methodPLTStubStart.get(method); + } + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTStubGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTStubGenerator.java new file mode 100644 index 000000000000..694872d0ecce --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/PLTStubGenerator.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.meta.SharedMethod; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public interface PLTStubGenerator { + + byte[] generatePLT(SharedMethod[] got, SubstrateBackend substrateBackend); + + void markResolverMethodPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod resolverMethod); + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64HostedPLTGOTConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64HostedPLTGOTConfiguration.java new file mode 100644 index 000000000000..c8765d716650 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64HostedPLTGOTConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot.aarch64; + +import java.lang.reflect.Method; + +import com.oracle.svm.core.pltgot.aarch64.AArch64ExitMethodAddressResolutionOp; +import com.oracle.svm.core.pltgot.aarch64.AArch64MethodAddressResolutionDispatcher; +import com.oracle.svm.hosted.pltgot.HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.PLTStubGenerator; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.lir.LIRInstruction; +import jdk.vm.ci.aarch64.AArch64; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.RegisterValue; + +public final class AArch64HostedPLTGOTConfiguration extends HostedPLTGOTConfiguration { + + @Override + public Method getArchSpecificResolverAsMethod() { + return ReflectionUtil.lookupMethod(AArch64MethodAddressResolutionDispatcher.class, "resolveMethodAddress"); + } + + @Override + public PLTStubGenerator getArchSpecificPLTStubGenerator() { + return new AArch64PLTStubGenerator(); + } + + @Override + public Register getExitMethodAddressResolutionRegister(RegisterConfig registerConfig) { + return AArch64.rscratch2; + } + + @Override + public LIRInstruction createExitMethodAddressResolutionOp(RegisterValue exitThroughRegisterValue) { + return new AArch64ExitMethodAddressResolutionOp(exitThroughRegisterValue); + } + + @Override + public Register getGOTPassingRegister(RegisterConfig registerConfig) { + throw new UnsupportedOperationException("AArch64 passes got entries via (unused) deopt frame handle slot."); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64PLTStubGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64PLTStubGenerator.java new file mode 100644 index 000000000000..fff68c2ea1f4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/aarch64/AArch64PLTStubGenerator.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot.aarch64; + +import static com.oracle.objectfile.ObjectFile.RelocationKind.AARCH64_R_AARCH64_ADD_ABS_LO12_NC; +import static com.oracle.objectfile.ObjectFile.RelocationKind.AARCH64_R_AARCH64_ADR_PREL_PG_HI21; +import static jdk.graal.compiler.asm.aarch64.AArch64Address.createImmediateAddress; +import static jdk.graal.compiler.asm.aarch64.AArch64Address.AddressingMode.IMMEDIATE_SIGNED_UNSCALED; +import static jdk.vm.ci.aarch64.AArch64.sp; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.ReservedRegisters; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.deopt.DeoptimizationSlotPacking; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.GOTAccess; +import com.oracle.svm.core.pltgot.aarch64.AArch64MethodAddressResolutionDispatcher; +import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.pltgot.HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.PLTSectionSupport; +import com.oracle.svm.hosted.pltgot.PLTStubGenerator; +import com.oracle.svm.hosted.pltgot.amd64.AMD64PLTStubGenerator; + +import jdk.graal.compiler.asm.Assembler; +import jdk.graal.compiler.asm.Label; +import jdk.graal.compiler.asm.aarch64.AArch64Address; +import jdk.graal.compiler.asm.aarch64.AArch64MacroAssembler; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Generates the contents of the PLT section for the PLT/GOT mechanism. + * + * Please first see {@link AMD64PLTStubGenerator} for more details on how PLT/GOT mechanism works in + * general. + * + * AArch64 and AMD64 .svm_plt sections are different because on AArch64 we can't use the + * {@code ForwardReturnValue} calling convention for the + * {@link AArch64MethodAddressResolutionDispatcher#resolveMethodAddress()} without spilling the + * register x0, because x0 is both the first parameter register and the return value register. + * That's why we pass the method's GOT entry index via the (unused) deopt frame handle slot on the + * stack. The .svm_plt stub at the beginning of the section stores the got entry just after the + * return address, which is the deopt frame handle slot, and then we jump to the + * {@link AArch64MethodAddressResolutionDispatcher#resolveMethodAddress()}. As there are other users + * of the deopt slot, an encoding is used (see{@link DeoptimizationSlotPacking}). + * + * The AArch64 variant has a common svm_plt stub to avoid repeating the instruction that stores the + * got entry at the deopt frame handle slot and the instructions that load the address of the + * resolver method and jump to it. + * + * An example of an `.svm_plt` section that contains 2 methods that are called via PLT/GOT on + * darwin-aarch64: + * + *
+ * 0x7f0000 <.svm_plt>:
+ * 0x7f0000:  ldur x8, [sp]
+ * 0x7f0004:  and x8, x8, #0xff00000000000000
+ * 0x7f0008:  orr x9, x9, x8
+ * 0x7f000c:  adrp x8, 0x5df000
+ * 0x7f0010:  add x8, x8, #0xdd0
+ * 0x7f0014:  stur x9, [sp]        @ store gotEntry at the deopt frame handle slot
+ * 0x7f0018:  br x8                @ Jumps to 
+ * 0x7f001c :
+ * 0x7f001c:  ldr x9, [x27,#-8]    @ <--- We jump here from the virtual method call site
+ * 0x7f0020:  br x9                @ Jumps to the resolved method, or to the line below if the method wasn't resolved.
+ * 0x7f0024:  mov w9, wzr          @ <---- We jump here from the direct method call site. This line loads the gotEntry id
+ * 0x7f0028:  b 0x7f0000 <.svm_plt>
+ * 0x7f002c :
+ * 0x7f002c:  ldr x9, [x27,#-16]
+ * 0x7f0030:  br x9
+ * 0x7f0034:  orr w9, wzr, #0x1
+ * 0x7f0038:  b 0x7f0000 <.svm_plt>
+ * 0x7f003c :
+ * 0x7f003c:  ldr x9, [x27,#-24]
+ * 0x7f0040:  br x9
+ * 0x7f0044:  orr w9, wzr, #0x2
+ * 0x7f0048:  b 0x7f0000 <.svm_plt>
+ * 
+ */ +public class AArch64PLTStubGenerator implements PLTStubGenerator { + private int resolverAddressLoadOffset = -1; + + @Override + public byte[] generatePLT(SharedMethod[] got, SubstrateBackend substrateBackend) { + AArch64MacroAssembler masm = new AArch64MacroAssembler(ConfigurationValues.getTarget()); + Label pltStart = new Label(); + masm.bind(pltStart); + try (AArch64MacroAssembler.ScratchRegister scratchRegister1 = masm.getScratchRegister(); AArch64MacroAssembler.ScratchRegister scratchRegister2 = masm.getScratchRegister()) { + Register resolverJmpRegister = scratchRegister1.getRegister(); + Register gotEntryPassingRegister = scratchRegister2.getRegister(); + + generateResolverCallStub(masm, gotEntryPassingRegister, resolverJmpRegister); + + PLTSectionSupport support = HostedPLTGOTConfiguration.singleton().getPLTSectionSupport(); + for (int gotEntryNo = 0; gotEntryNo < got.length; ++gotEntryNo) { + HostedMethod method = (HostedMethod) got[gotEntryNo]; + int pltStubStart = masm.position(); + + /* Start of PLT stub for this GOT entry. */ + support.recordMethodPLTStubStart(method, pltStubStart); + + int gotEntryOffset = GOTAccess.getGotEntryOffsetFromHeapRegister(gotEntryNo); + Register heapReg = ReservedRegisters.singleton().getHeapBaseRegister(); + AArch64Address addr = masm.makeAddress(64, heapReg, gotEntryOffset, gotEntryPassingRegister); + + masm.maybeEmitIndirectTargetMarker(); + masm.ldr(64, gotEntryPassingRegister, addr); + masm.jmp(gotEntryPassingRegister); + + /* + * This is used as initial entry in the GOT, so that on first access this entry is + * going to be resolved. + */ + support.recordMethodPLTStubResolverOffset(method, masm.position() - pltStubStart); + masm.maybeEmitIndirectTargetMarker(); + masm.mov(gotEntryPassingRegister, gotEntryNo); + masm.jmp(pltStart); + } + } + return masm.close(true); + } + + @Override + public void markResolverMethodPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod resolverMethod) { + pltBuffer.markRelocationSite(resolverAddressLoadOffset, AARCH64_R_AARCH64_ADR_PREL_PG_HI21, NativeImage.localSymbolNameForMethod(resolverMethod), 0); + pltBuffer.markRelocationSite(resolverAddressLoadOffset + 4, AARCH64_R_AARCH64_ADD_ABS_LO12_NC, NativeImage.localSymbolNameForMethod(resolverMethod), 0); + } + + public void generateResolverCallStub(AArch64MacroAssembler masm, Register gotEntryPassingRegister, Register jmpTarget) { + masm.setCodePatchingAnnotationConsumer(this::recordResolverCallForPatching); + + /* + * GR-54839: Upper byte of deoptSlot may be used by leaveInterpreterStub, therefore an + * encoding is used for this word. + */ + Register scratch = jmpTarget; + masm.ldr(64, scratch, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, sp, 0)); + masm.and(64, scratch, scratch, DeoptimizationSlotPacking.MASK_VARIABLE_FRAMESIZE); + masm.orr(64, gotEntryPassingRegister, gotEntryPassingRegister, scratch); + + /* + * use indirect jump to avoid problems around branch islands (displacement larger than + * +/-128MB) + */ + masm.adrpAdd(jmpTarget); + masm.str(64, gotEntryPassingRegister, createImmediateAddress(64, IMMEDIATE_SIGNED_UNSCALED, sp, 0)); + masm.jmp(jmpTarget); + } + + private void recordResolverCallForPatching(Assembler.CodeAnnotation a) { + if (resolverAddressLoadOffset != -1) { + return; + } + assert a instanceof AArch64MacroAssembler.AdrpAddMacroInstruction; + AArch64MacroAssembler.AdrpAddMacroInstruction annotation = (AArch64MacroAssembler.AdrpAddMacroInstruction) a; + resolverAddressLoadOffset = annotation.instructionPosition; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64HostedPLTGOTConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64HostedPLTGOTConfiguration.java new file mode 100644 index 000000000000..766195155610 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64HostedPLTGOTConfiguration.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot.amd64; + +import java.lang.reflect.Method; + +import com.oracle.svm.core.pltgot.amd64.AMD64ExitMethodAddressResolutionOp; +import com.oracle.svm.core.pltgot.amd64.AMD64MethodAddressResolutionDispatcher; +import com.oracle.svm.hosted.pltgot.HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.PLTStubGenerator; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.lir.LIRInstruction; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.RegisterValue; + +public final class AMD64HostedPLTGOTConfiguration extends HostedPLTGOTConfiguration { + + @Override + public Method getArchSpecificResolverAsMethod() { + return ReflectionUtil.lookupMethod(AMD64MethodAddressResolutionDispatcher.class, "resolveMethodAddress", long.class); + } + + @Override + public PLTStubGenerator getArchSpecificPLTStubGenerator() { + return new AMD64PLTStubGenerator(); + } + + @Override + public Register getExitMethodAddressResolutionRegister(RegisterConfig registerConfig) { + return getGOTPassingRegister(registerConfig); + } + + @Override + public LIRInstruction createExitMethodAddressResolutionOp(RegisterValue exitThroughRegisterValue) { + return new AMD64ExitMethodAddressResolutionOp(exitThroughRegisterValue); + } + + @Override + public Register getGOTPassingRegister(RegisterConfig registerConfig) { + return registerConfig.getReturnRegister(getArchSpecificResolverAsHostedMethod().getSignature().getReturnKind()); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64PLTStubGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64PLTStubGenerator.java new file mode 100644 index 000000000000..355f145d631b --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/pltgot/amd64/AMD64PLTStubGenerator.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.pltgot.amd64; + +import java.util.ArrayList; +import java.util.List; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.ReservedRegisters; +import com.oracle.svm.core.graal.amd64.SubstrateAMD64Backend; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.pltgot.GOTAccess; +import com.oracle.svm.core.pltgot.amd64.AMD64MethodAddressResolutionDispatcher; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.pltgot.HostedPLTGOTConfiguration; +import com.oracle.svm.hosted.pltgot.PLTSectionSupport; +import com.oracle.svm.hosted.pltgot.PLTStubGenerator; + +import jdk.graal.compiler.asm.Assembler; +import jdk.graal.compiler.asm.amd64.AMD64Address; +import jdk.graal.compiler.asm.amd64.AMD64BaseAssembler; +import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Generates the contents of the PLT section for the PLT/GOT mechanism. + * + * Every method that can be dynamically resolved through the PLT/GOT mechanism has its unique PLT + * stub. We handle method resolution for virtual calls and direct calls differently. + * + * + * + * When resolving a virtual method the corresponding PLT stub loads the GOT entry associated with + * the called method via r14 - (GOT_ENTRY + 1) * 8 into the return value register (ie. rax). If the + * method is already resolved then the second instruction in the PLT stub will jump directly to the + * resolved method. Otherwise, because the GOT entries are initialized to point to the third + * instruction in the corresponding PLT stub, the second instruction will jump to the instruction + * below that loads the gotEntry id into the return value register and then the fourth instruction + * jumps to the {@link AMD64MethodAddressResolutionDispatcher#resolveMethodAddress(long)} that + * resolves the method with a given got entry. + * + * When resolving direct calls we jump directly to the second part of the PLT stub (third + * instruction) from the callsite because we rewrite the direct calls to the first two instructions + * of the corresponding PLT stub. That way, for direct calls, we only jump to the PLT stub if the + * method wasn't already resolved. + * + * The {@link AMD64MethodAddressResolutionDispatcher#resolveMethodAddress(long)} uses the + * {@link SubstrateCallingConventionKind#ForwardReturnValue} which expects its one and only argument + * to be in the return value register. This calling convention for the method resolver enables us to + * avoid having to spill the callers first argument to the stack. Also, we can reuse the same + * register as scratch for the second instruction in the PLT stub because the return value register + * is caller saved. + * + * An example of the `.svm_plt` section that contains 2 methods that are called via PLT/GOT (on + * Linux AMD64): + * + *
+ * {@code
+ *0000000000355000 :
+ *   355000:  mov      rax,QWORD PTR [r14-0x8] # <- virtual call jumps here.
+ *   355004:  jmp      rax                     # <- if the method is resolved jump to it, else jump to the instruction below.
+ *   355006:  mov      rax,0x0                 # <- direct call jumps here only once if the method hasn't already been resolved
+ *   35500b:  jmp      19e700 
+ *
+ * 0000000000355010 :
+ *   355010:  mov      rax,QWORD PTR [r14-0x10] # load the address from the GOT table. (`35501b` if the method wasn't resolved already, else method's address)
+ *   355014:  jmp      rax
+ *   355016:  mov      rax,0x1
+ *   35501b:  jmp      19e700 
+ * }
+ * 
+ */ +public class AMD64PLTStubGenerator implements PLTStubGenerator { + private List resolverPatchOffsets = new ArrayList<>(); + private int resolverKindAddend; + private ObjectFile.RelocationKind resolverPatchRelocationKind = null; + + @Override + public byte[] generatePLT(SharedMethod[] got, SubstrateBackend substrateBackend) { + HostedPLTGOTConfiguration configuration = HostedPLTGOTConfiguration.singleton(); + VMError.guarantee(configuration.getArchSpecificResolverAsHostedMethod().getCallingConventionKind().equals(SubstrateCallingConventionKind.ForwardReturnValue), + "AMD64PLTStubGenerator assumes that %s is using %s ", + configuration.getArchSpecificResolverAsHostedMethod().format("%H.%n(%p)"), SubstrateCallingConventionKind.ForwardReturnValue.name()); + + SubstrateAMD64Backend amd64Backend = (SubstrateAMD64Backend) substrateBackend; + RegisterConfig registerConfig = amd64Backend.getCodeCache().getRegisterConfig(); + Register register = configuration.getGOTPassingRegister(registerConfig); + + AMD64MacroAssembler asm = (AMD64MacroAssembler) amd64Backend.createAssemblerNoOptions(); + PLTSectionSupport support = HostedPLTGOTConfiguration.singleton().getPLTSectionSupport(); + + asm.setCodePatchingAnnotationConsumer(this::recordResolverCallForPatching); + for (int gotEntryNo = 0; gotEntryNo < got.length; ++gotEntryNo) { + HostedMethod method = (HostedMethod) got[gotEntryNo]; + + /* + * This is the method's call target. The GOT will be read to determine the method's + * actual address. + */ + int pltStubStart = asm.position(); + support.recordMethodPLTStubStart(method, pltStubStart); + + int gotEntryOffset = GOTAccess.getGotEntryOffsetFromHeapRegister(gotEntryNo); + asm.maybeEmitIndirectTargetMarker(); + asm.movq(register, new AMD64Address(ReservedRegisters.singleton().getHeapBaseRegister(), gotEntryOffset)); + asm.jmp(register); + + /* + * This is the initial target of the jmp directly above. Calls the resolver stub with + * the key for this method. + */ + support.recordMethodPLTStubResolverOffset(method, asm.position() - pltStubStart); + asm.maybeEmitIndirectTargetMarker(); + asm.movl(register, gotEntryNo); + /* + * We patch this later to the + * AMD64MethodAddressResolutionDispatcher#resolveMethodAddress(long). + */ + asm.jmp(); + } + + return asm.close(true); + } + + @Override + public void markResolverMethodPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod resolverMethod) { + for (int resolverPatchOffset : resolverPatchOffsets) { + pltBuffer.markRelocationSite(resolverPatchOffset, resolverPatchRelocationKind, NativeImage.localSymbolNameForMethod(resolverMethod), resolverKindAddend); + } + } + + private void recordResolverCallForPatching(Assembler.CodeAnnotation a) { + assert a instanceof AMD64BaseAssembler.OperandDataAnnotation; + AMD64BaseAssembler.OperandDataAnnotation annotation = (AMD64BaseAssembler.OperandDataAnnotation) a; + resolverPatchOffsets.add(annotation.operandPosition); + resolverKindAddend = -annotation.operandSize; + resolverPatchRelocationKind = ObjectFile.RelocationKind.getPCRelative(annotation.operandSize); + } +}