1 /* 2 * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.lang.foreign; 27 28 import jdk.internal.access.JavaLangAccess; 29 import jdk.internal.access.SharedSecrets; 30 import jdk.internal.foreign.MemorySessionImpl; 31 import jdk.internal.foreign.Utils; 32 import jdk.internal.javac.PreviewFeature; 33 import jdk.internal.loader.BuiltinClassLoader; 34 import jdk.internal.loader.NativeLibrary; 35 import jdk.internal.loader.RawNativeLibraries; 36 import jdk.internal.reflect.CallerSensitive; 37 import jdk.internal.reflect.Reflection; 38 39 import java.lang.invoke.MethodHandles; 40 import java.nio.file.Path; 41 import java.util.Objects; 42 import java.util.Optional; 43 import java.util.function.BiFunction; 44 45 /** 46 * A <em>symbol lookup</em> retrieves the address of a symbol in one or more libraries. 47 * A symbol is a named entity, such as a function or a global variable. 48 * <p> 49 * A symbol lookup is created with respect to a particular library (or libraries). Subsequently, the {@link SymbolLookup#find(String)} 50 * method takes the name of a symbol and returns the address of the symbol in that library. 51 * <p> 52 * The address of a symbol is modelled as a zero-length {@linkplain MemorySegment memory segment}. The segment can be used in different ways: 53 * <ul> 54 * <li>It can be passed to a {@link Linker} to create a downcall method handle, which can then be used to call the foreign function at the segment's address.</li> 55 * <li>It can be passed to an existing {@linkplain Linker#downcallHandle(FunctionDescriptor, Linker.Option...) downcall method handle}, as an argument to the underlying foreign function.</li> 56 * <li>It can be {@linkplain MemorySegment#set(AddressLayout, long, MemorySegment) stored} inside another memory segment.</li> 57 * <li>It can be used to access the region of memory backing a global variable (this requires 58 * {@linkplain MemorySegment#reinterpret(long) resizing} the segment first).</li> 59 * </ul> 60 * 61 * <h2 id="obtaining">Obtaining a symbol lookup</h2> 62 * 63 * The factory methods {@link #libraryLookup(String, Arena)} and {@link #libraryLookup(Path, Arena)} 64 * create a symbol lookup for a library known to the operating system. The library is specified by either its name or a path. 65 * The library is loaded if not already loaded. The symbol lookup, which is known as a <em>library lookup</em>, and its 66 * lifetime is controlled by an {@linkplain Arena arena}. For instance, if the provided arena is a 67 * confined arena, the library associated with the symbol lookup is unloaded when the confined arena 68 * is {@linkplain Arena#close() closed}: 69 * 70 * {@snippet lang = java: 71 * try (Arena arena = Arena.ofConfined()) { 72 * SymbolLookup libGL = SymbolLookup.libraryLookup("libGL.so", arena); // libGL.so loaded here 73 * MemorySegment glGetString = libGL.find("glGetString").orElseThrow(); 74 * ... 75 * } // libGL.so unloaded here 76 *} 77 * <p> 78 * If a library was previously loaded through JNI, i.e., by {@link System#load(String)} 79 * or {@link System#loadLibrary(String)}, then the library was also associated with a particular class loader. The factory 80 * method {@link #loaderLookup()} creates a symbol lookup for all the libraries associated with the caller's class loader: 81 * 82 * {@snippet lang=java : 83 * System.loadLibrary("GL"); // libGL.so loaded here 84 * ... 85 * SymbolLookup libGL = SymbolLookup.loaderLookup(); 86 * MemorySegment glGetString = libGL.find("glGetString").orElseThrow(); 87 * } 88 * 89 * This symbol lookup, which is known as a <em>loader lookup</em>, is dynamic with respect to the libraries associated 90 * with the class loader. If other libraries are subsequently loaded through JNI and associated with the class loader, 91 * then the loader lookup will expose their symbols automatically. 92 * <p> 93 * Note that a loader lookup only exposes symbols in libraries that were previously loaded through JNI, i.e., 94 * by {@link System#load(String)} or {@link System#loadLibrary(String)}. A loader lookup does not expose symbols in libraries 95 * that were loaded in the course of creating a library lookup: 96 * 97 * {@snippet lang = java: 98 * libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true 99 * loaderLookup().find("glGetString").isPresent(); // false 100 *} 101 * 102 * Note also that a library lookup for library {@code L} exposes symbols in {@code L} even if {@code L} was previously loaded 103 * through JNI (the association with a class loader is immaterial to the library lookup): 104 * 105 * {@snippet lang = java: 106 * System.loadLibrary("GL"); // libGL.so loaded here 107 * libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true 108 *} 109 * 110 * <p> 111 * Finally, each {@link Linker} provides a symbol lookup for libraries that are commonly used on the OS and processor 112 * combination supported by that {@link Linker}. This symbol lookup, which is known as a <em>default lookup</em>, 113 * helps clients to quickly find addresses of well-known symbols. For example, a {@link Linker} for Linux/x64 might choose to 114 * expose symbols in {@code libc} through the default lookup: 115 * 116 * {@snippet lang = java: 117 * Linker nativeLinker = Linker.nativeLinker(); 118 * SymbolLookup stdlib = nativeLinker.defaultLookup(); 119 * MemorySegment malloc = stdlib.find("malloc").orElseThrow(); 120 *} 121 * 122 * @since 19 123 */ 124 @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) 125 @FunctionalInterface 126 public interface SymbolLookup { 127 128 /** 129 * Returns the address of the symbol with the given name. 130 * @param name the symbol name. 131 * @return a zero-length memory segment whose address indicates the address of the symbol, if found. 132 */ 133 Optional<MemorySegment> find(String name); 134 135 /** 136 * {@return a composed symbol lookup that returns result of finding the symbol with this lookup if found, 137 * otherwise returns the result of finding the symbol with the other lookup} 138 * 139 * @apiNote This method could be used to chain multiple symbol lookups together, e.g. so that symbols could 140 * be retrieved, in order, from multiple libraries: 141 * {@snippet lang = java: 142 * var lookup = SymbolLookup.libraryLookup("foo", arena) 143 * .or(SymbolLookup.libraryLookup("bar", arena)) 144 * .or(SymbolLookup.loaderLookup()); 145 *} 146 * The above code creates a symbol lookup that first searches for symbols in the "foo" library. If no symbol is found 147 * in "foo" then "bar" is searched. Finally, if a symbol is not found in neither "foo" nor "bar", the {@linkplain 148 * SymbolLookup#loaderLookup() loader lookup} is used. 149 * 150 * @param other the symbol lookup that should be used to look for symbols not found in this lookup. 151 */ 152 default SymbolLookup or(SymbolLookup other) { 153 Objects.requireNonNull(other); 154 return name -> find(name).or(() -> other.find(name)); 155 } 156 157 /** 158 * Returns a symbol lookup for symbols in the libraries associated with the caller's class loader. 159 * <p> 160 * A library is associated with a class loader {@code CL} when the library is loaded via an invocation of 161 * {@link System#load(String)} or {@link System#loadLibrary(String)} from code in a class defined by {@code CL}. 162 * If that code makes further invocations of {@link System#load(String)} or {@link System#loadLibrary(String)}, 163 * then more libraries are loaded and associated with {@code CL}. The symbol lookup returned by this method is always 164 * current: it reflects all the libraries associated with the relevant class loader, even if they were loaded after 165 * this method returned. 166 * <p> 167 * Libraries associated with a class loader are unloaded when the class loader becomes 168 * <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>. The symbol lookup 169 * returned by this method is associated with a fresh {@linkplain MemorySegment.Scope scope} which keeps the caller's 170 * class loader reachable. Therefore, libraries associated with the caller's class loader are kept loaded 171 * (and their symbols available) as long as a loader lookup for that class loader, or any of the segments 172 * obtained by it, is reachable. 173 * <p> 174 * In cases where this method is called from a context where there is no caller frame on the stack 175 * (e.g. when called directly from a JNI attached thread), the caller's class loader defaults to the 176 * {@linkplain ClassLoader#getSystemClassLoader system class loader}. 177 * 178 * @return a symbol lookup for symbols in the libraries associated with the caller's class loader. 179 * @see System#load(String) 180 * @see System#loadLibrary(String) 181 */ 182 @CallerSensitive 183 static SymbolLookup loaderLookup() { 184 Class<?> caller = Reflection.getCallerClass(); 185 // If there's no caller class, fallback to system loader 186 ClassLoader loader = caller != null ? 187 caller.getClassLoader() : 188 ClassLoader.getSystemClassLoader(); 189 Arena loaderArena;// builtin loaders never go away 190 if ((loader == null || loader instanceof BuiltinClassLoader)) { 191 loaderArena = Arena.global(); 192 } else { 193 MemorySessionImpl session = MemorySessionImpl.heapSession(loader); 194 loaderArena = session.asArena(); 195 } 196 return name -> { 197 Objects.requireNonNull(name); 198 if (Utils.containsNullChars(name)) return Optional.empty(); 199 JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess(); 200 // note: ClassLoader::findNative supports a null loader 201 long addr = javaLangAccess.findNative(loader, name); 202 return addr == 0L ? 203 Optional.empty() : 204 Optional.of(MemorySegment.ofAddress(addr) 205 .reinterpret(loaderArena, null)); 206 }; 207 } 208 209 /** 210 * Loads a library with the given name (if not already loaded) and creates a symbol lookup for symbols in that library. 211 * The lifetime of the returned library lookup is controlled by the provided arena. 212 * For instance, if the provided arena is a confined arena, the library 213 * associated with the returned lookup will be unloaded when the provided confined arena is 214 * {@linkplain Arena#close() closed}. 215 * <p> 216 * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>. 217 * Restricted methods are unsafe, and, if used incorrectly, their use might crash 218 * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on 219 * restricted methods, and use safe and supported functionalities, where possible. 220 * 221 * @implNote The process of resolving a library name is OS-specific. For instance, in a POSIX-compliant OS, 222 * the library name is resolved according to the specification of the {@code dlopen} function for that OS. 223 * In Windows, the library name is resolved according to the specification of the {@code LoadLibrary} function. 224 * 225 * @param name the name of the library in which symbols should be looked up. 226 * @param arena the arena associated with symbols obtained from the returned lookup. 227 * @return a new symbol lookup suitable to find symbols in a library with the given name. 228 * @throws IllegalStateException if {@code arena.scope().isAlive() == false} 229 * @throws WrongThreadException if {@code arena} is a confined arena, and this method is called from a 230 * thread {@code T}, other than the arena's owner thread. 231 * @throws IllegalArgumentException if {@code name} does not identify a valid library. 232 * @throws IllegalCallerException If the caller is in a module that does not have native access enabled. 233 */ 234 @CallerSensitive 235 static SymbolLookup libraryLookup(String name, Arena arena) { 236 Reflection.ensureNativeAccess(Reflection.getCallerClass(), SymbolLookup.class, "libraryLookup"); 237 if (Utils.containsNullChars(name)) { 238 throw new IllegalArgumentException("Cannot open library: " + name); 239 } 240 return libraryLookup(name, RawNativeLibraries::load, arena); 241 } 242 243 /** 244 * Loads a library from the given path (if not already loaded) and creates a symbol lookup for symbols 245 * in that library. The lifetime of the returned library lookup is controlled by the provided arena. 246 * For instance, if the provided arena is a confined arena, the library 247 * associated with the returned lookup will be unloaded when the provided confined arena is 248 * {@linkplain Arena#close() closed}. 249 * <p> 250 * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>. 251 * Restricted methods are unsafe, and, if used incorrectly, their use might crash 252 * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on 253 * restricted methods, and use safe and supported functionalities, where possible. 254 * 255 * @implNote On Linux, the functionalities provided by this factory method and the returned symbol lookup are 256 * implemented using the {@code dlopen}, {@code dlsym} and {@code dlclose} functions. 257 * @param path the path of the library in which symbols should be looked up. 258 * @param arena the arena associated with symbols obtained from the returned lookup. 259 * @return a new symbol lookup suitable to find symbols in a library with the given path. 260 * @throws IllegalStateException if {@code arena.scope().isAlive() == false} 261 * @throws WrongThreadException if {@code arena} is a confined arena, and this method is called from a 262 * thread {@code T}, other than the arena's owner thread. 263 * @throws IllegalArgumentException if {@code path} does not point to a valid library. 264 * @throws IllegalCallerException If the caller is in a module that does not have native access enabled. 265 */ 266 @CallerSensitive 267 static SymbolLookup libraryLookup(Path path, Arena arena) { 268 Reflection.ensureNativeAccess(Reflection.getCallerClass(), SymbolLookup.class, "libraryLookup"); 269 return libraryLookup(path, RawNativeLibraries::load, arena); 270 } 271 272 private static <Z> SymbolLookup libraryLookup(Z libDesc, BiFunction<RawNativeLibraries, Z, NativeLibrary> loadLibraryFunc, Arena libArena) { 273 Objects.requireNonNull(libDesc); 274 Objects.requireNonNull(libArena); 275 // attempt to load native library from path or name 276 RawNativeLibraries nativeLibraries = RawNativeLibraries.newInstance(MethodHandles.lookup()); 277 NativeLibrary library = loadLibraryFunc.apply(nativeLibraries, libDesc); 278 if (library == null) { 279 throw new IllegalArgumentException("Cannot open library: " + libDesc); 280 } 281 // register hook to unload library when 'libScope' becomes not alive 282 MemorySessionImpl.toMemorySession(libArena).addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() { 283 @Override 284 public void cleanup() { 285 nativeLibraries.unload(library); 286 } 287 }); 288 return name -> { 289 Objects.requireNonNull(name); 290 if (Utils.containsNullChars(name)) return Optional.empty(); 291 long addr = library.find(name); 292 return addr == 0L ? 293 Optional.empty() : 294 Optional.of(MemorySegment.ofAddress(addr) 295 .reinterpret(libArena, null)); 296 }; 297 } 298 }