/* * Copyright (c) 2007, 2012, 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. * * 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.sun.max.vm.jni; import static com.sun.max.vm.tele.Inspectable.isVmInspected; import com.sun.max.annotate.*; import com.sun.max.memory.*; import com.sun.max.unsafe.*; import com.sun.max.util.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; import com.sun.max.vm.type.*; /** * Interface to the platform dependent functions for dealing with dynamically loaded libraries and * resolving symbols in the main program. * * The naming convention used matches that of the POSIX C dynamic linking functions (usually * defined in dlfcn.h). * * This class plays a key role in the VM bootstrap by providing a simple path to resolve critical * native symbols, e.g., for debugging and monitor support. Everything actually hinges on three native functions, * "dlsym", "dlerror" and "dlopen", which are passed to the {@link #initialize} method. Every other native * symbol can be found from there. * * If the VM is being inspected we maintain data structures that support access to native functions. */ public final class DynamicLinker { private static final String DLOPEN = "dlopen"; private static final String DLSYM = "dlsym"; private static final String JNI_ONLOAD = "JNI_OnLoad"; public static boolean TraceDL; static { VMOptions.addFieldOption("-XX:", "TraceDL", "Trace Dynamic Linker calls."); } private DynamicLinker() { } private static Word mainHandle = Word.zero(); private static final NativeFunction dlopen = resolveNativeFunction("dlopen", Address.class); private static final NativeFunction dlsym = resolveNativeFunction("dlsym", Word.class, Address.class); private static final NativeFunction dlerror = resolveNativeFunction("dlerror"); @HOSTED_ONLY private static NativeFunction resolveNativeFunction(String name, Class... parameterTypes) { ClassMethodActor cma = (ClassMethodActor) ClassRegistry.findMethod(DynamicLinker.class, name, parameterTypes); return cma.nativeFunction; } /** * Initialize the system, "opening" the main program dynamic library. */ public static void initialize(Word dlopenAddress, Word dlsymAddress, Word dlerrorAddress) { dlopen.setAddress(dlopenAddress.asAddress()); dlsym.setAddress(dlsymAddress.asAddress()); dlerror.setAddress(dlerrorAddress.asAddress()); mainHandle = dlopen(Address.zero()); LibInfo.add(Pointer.zero(), mainHandle); } @C_FUNCTION private static native Word dlopen(Address absolutePath); @C_FUNCTION private static native Word dlerror(); @C_FUNCTION private static native Word dlsym(Word handle, Address name); @C_FUNCTION private static native Word dlclose(Word handle); public static native int invokeJNIOnLoad(Address onload); private static Word doLoad(String absolutePath) { final Word handle; if (absolutePath == null) { // This should not happen as mainHandle loaded in initialize (mjj) handle = mainHandle; } else { Pointer cString = CString.utf8FromJava(absolutePath); handle = dlopen(cString); if (handle.isZero()) { Memory.deallocate(cString); try { final Pointer errorMessage = dlerror().asPointer(); if (errorMessage.isZero()) { throw new UnsatisfiedLinkError(absolutePath); } throw new UnsatisfiedLinkError(absolutePath + ": " + CString.utf8ToJava(errorMessage)); } catch (Utf8Exception utf8Exception) { throw new UnsatisfiedLinkError(); } } LibInfo.add(cString, handle); if (TraceDL) { trace(DLOPEN, absolutePath, handle); } } return handle; } /** * Loads the dynamic native library contained in a given file. * * @param absolutePath an absolute pathname denoting the library file to load * @return the opaque handle for the loaded library or {@link Address#zero()} if the library could not be loaded * @throws UnsatisfiedLinkError if there was an error locating or loading the library */ public static Word load(String absolutePath) throws UnsatisfiedLinkError { return doLoad(absolutePath); } /** * Variant of {@link #load(String)} used to load JVMTI agent libraries, before we have a heap. */ public static Word load(Pointer pathAsCString) { Word handle = dlopen(pathAsCString); if (handle.isNotZero()) { LibInfo.add(pathAsCString, handle); } return handle; } /** * Looks up a symbol from a given dynamically loaded native library. * * @param handle a handle to a native library dynamically loaded by {@link #load} * @param symbol the symbol to lookup * @return the address of the symbol or null if it is not found in the library */ public static Word lookupSymbol(Word handle, String symbol) { Word h = handle; if (h.isZero()) { h = mainHandle; } return dlsym(symbol, h); } private static final int STACK_BUFFER_SIZE = 2048; /** * Looks up a symbol in a given library. * * @param symbol one or two symbols to be used for the lookup (see * {@link Mangle#mangleMethod(TypeDescriptor, String, SignatureDescriptor, boolean)}) * @param h a handle to a native library dynamically loaded by {@link #load} * @return the address or null if it is not found in the library */ private static Word dlsym(String symbol, Word h) { if (TraceDL) { trace(DLSYM, symbol, h); } Pointer symbolAsCString = Intrinsics.alloca(STACK_BUFFER_SIZE, false); Word addr; int delim = symbol.indexOf(Mangle.LONG_NAME_DELIMITER); if (delim == -1) { final int i = CString.writePartialUtf8(symbol, 0, symbol.length(), symbolAsCString, STACK_BUFFER_SIZE); FatalError.check(i == symbol.length() + 1, "Symbol name is too long for buffer"); addr = dlsym(h, symbolAsCString); } else { int shortNameLength = delim; int i = CString.writePartialUtf8(symbol, 0, shortNameLength, symbolAsCString, STACK_BUFFER_SIZE); FatalError.check(i == shortNameLength + 1, "Symbol name is too long for buffer"); addr = dlsym(h, symbolAsCString); if (addr.isZero()) { int longNameSuffixLength = symbol.length() - (delim + 1); i = CString.writePartialUtf8(symbol, delim + 1, longNameSuffixLength, symbolAsCString.plus(shortNameLength), STACK_BUFFER_SIZE - shortNameLength); FatalError.check(i == longNameSuffixLength + 1, "Symbol name is too long for buffer"); addr = dlsym(h, symbolAsCString); } } if (addr.isNotZero() && criticalLinked && isVmInspected()) { // every user loaded library checks for JNI_OnLoad first and it is not // usually defined in the library but resolves elsewhere so is useless as a sentinel. if (!symbol.equals(JNI_ONLOAD)) { LibInfo.setSentinel(h, symbolAsCString, addr.asAddress()); } } return addr; } private static void trace(String callType, String string, Word handle) { boolean lockDisabledSafepoints = Log.lock(); Log.print("[Thread \""); Log.print(VmThread.current().getName()); Log.print("\" "); Log.print(callType); Log.print(" "); Log.print(string); if (callType.equals(DLSYM)) { Log.print(" in "); printLibrary(handle); } Log.println("]"); Log.unlock(lockDisabledSafepoints); } private static void printLibrary(Word handle) { for (int i = 0; i < libInfoIndex; i++) { LibInfo libInfo = libInfoArray[i]; if (libInfo.handle.equals(handle)) { if (libInfo.pathAsCString.isZero()) { Log.print("maxvm"); } else { Log.printCString(libInfo.pathAsCString); } return; } } Log.print("unknown library"); } public static void close(Word handle) { // TODO: should we check for unsuccessful close? dlclose(handle); } @ALIAS(declaringClass = ClassLoader.class) private static native long findNative(ClassLoader loader, String name); /** * Looks up the symbol for a native method. * * @param classMethodActor the actor for a native method * @param symbol the symbol of the native method's implementation * @return the address of {@code symbol} * @throws UnsatisfiedLinkError if the symbol cannot be found in any of the dynamic libraries bound to the VM */ public static Word lookup(MethodActor classMethodActor, String symbol) throws UnsatisfiedLinkError { Word symbolAddress = Word.zero(); if (MaxineVM.isHosted()) { symbolAddress = MethodID.fromMethodActor(classMethodActor); } else { // First look in the native libraries loaded by the class loader of the class in which this native method was declared ClassLoader classLoader = classMethodActor.holder().classLoader; symbolAddress = Address.fromLong(findNative(classLoader, symbol)); // Now look in the system library path if (symbolAddress.isZero() && classLoader != null) { symbolAddress = Address.fromLong(findNative(null, symbol)); } } if (symbolAddress.isZero()) { throw new UnsatisfiedLinkError(symbol); } return symbolAddress; } /* * Inspector support for finding native functions. dlfcn isn't very helpful. * We make an assumption that functions in the library * are loaded contiguously. We can't find the base address * only the value of a given symbol. Also dlsym has a complex * lookup mechanism that, for example, will return the * same value for a duplicate symbol when looked up using * different library handles. So we can't use a sentinel symbol * like "_init", which is found in all libraries. So we set a random * sentinel based on the first symbol looked up. The Inspector * can then 'relocate" all the other symbols using the ELF * file, assuming a contiguous load. * * Library name is recorded always for tracing. The sentinel symbol is only * recorded if the VM is being inspected. */ static { new CriticalNativeMethod(Memory.class, "memory_allocate"); } /** * This is only important for Inspector support as it prevents runaway recursion * in library sentinel function handling. */ private static boolean criticalLinked; /** * Critical native methods linked, so safe to call {@link Memory#allocate(Size)}. */ public static void markCriticalLinked() { criticalLinked = true; // This will force the sentinel to be set for mainHandle lookupSymbol(mainHandle, "log_lock"); } public static boolean isCriticalLinked() { return criticalLinked; } public static class LibInfo { @INSPECTED Pointer pathAsCString; @INSPECTED Word handle; @INSPECTED Pointer sentinelAsCString; @INSPECTED Address sentinelAddress; static void add(Pointer pathAsCString, Word handle) { if (libInfoIndex < 16) { // TODO increase space LibInfo libInfo = libInfoArray[libInfoIndex]; libInfo.pathAsCString = pathAsCString; libInfo.handle = handle; libInfoIndex++; } } static void setSentinel(Word handle, Pointer sentinel, Address sentinelAddress) { for (int i = 0; i < libInfoIndex; i++) { LibInfo libInfo = libInfoArray[i]; if (libInfo.handle.equals(handle)) { if (libInfo.sentinelAsCString.isZero()) { Size length = CString.length(sentinel).plus(1); Pointer sentinelCopy = Memory.mustAllocate(length); Memory.copyBytes(sentinel, sentinelCopy, length); libInfo.sentinelAsCString = sentinelCopy; libInfo.sentinelAddress = sentinelAddress; } } } } } @INSPECTED private static int libInfoIndex; @INSPECTED private static LibInfo[] libInfoArray; static { libInfoArray = new LibInfo[16]; for (int i = 0; i < libInfoArray.length; i++) { libInfoArray[i] = new LibInfo(); } } }