mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
369 lines
17 KiB
Java
369 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2022, 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 java.lang.foreign;
|
|
|
|
import jdk.internal.access.JavaLangAccess;
|
|
import jdk.internal.access.SharedSecrets;
|
|
import jdk.internal.foreign.MemorySessionImpl;
|
|
import jdk.internal.foreign.Utils;
|
|
import jdk.internal.javac.Restricted;
|
|
import jdk.internal.loader.BuiltinClassLoader;
|
|
import jdk.internal.loader.NativeLibraries;
|
|
import jdk.internal.loader.NativeLibrary;
|
|
import jdk.internal.loader.RawNativeLibraries;
|
|
import jdk.internal.reflect.CallerSensitive;
|
|
import jdk.internal.reflect.Reflection;
|
|
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.nio.file.FileSystems;
|
|
import java.nio.file.Path;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.function.BiFunction;
|
|
|
|
/**
|
|
* A <em>symbol lookup</em> retrieves the address of a symbol in one or more libraries.
|
|
* A symbol is a named entity, such as a function or a global variable.
|
|
* <p>
|
|
* A symbol lookup is created with respect to a particular library (or libraries).
|
|
* Subsequently, the {@link SymbolLookup#find(String)} method takes the name of a symbol
|
|
* and returns the address of the symbol in that library.
|
|
* <p>
|
|
* The address of a symbol is modeled as a zero-length
|
|
* {@linkplain MemorySegment memory segment}. The segment can be used in different ways:
|
|
* <ul>
|
|
* <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>
|
|
* <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>
|
|
* <li>It can be {@linkplain MemorySegment#set(AddressLayout, long, MemorySegment) stored}
|
|
* inside another memory segment.</li>
|
|
* <li>It can be used to access the region of memory backing a global variable
|
|
* (this requires {@linkplain MemorySegment#reinterpret(long) resizing}
|
|
* the segment first).</li>
|
|
* </ul>
|
|
*
|
|
* <h2 id="obtaining">Obtaining a symbol lookup</h2>
|
|
*
|
|
* The factory methods {@link #libraryLookup(String, Arena)} and
|
|
* {@link #libraryLookup(Path, Arena)} create a symbol lookup for a library known to
|
|
* the operating system. The library is specified by either its name or a path.
|
|
* The library is loaded if not already loaded. The symbol lookup, which is known as a
|
|
* <em>library lookup</em>, and its lifetime is controlled by an {@linkplain Arena arena}.
|
|
* For instance, if the provided arena is a confined arena, the library associated with
|
|
* the symbol lookup is unloaded when the confined arena is {@linkplain Arena#close() closed}:
|
|
*
|
|
* {@snippet lang = java:
|
|
* try (Arena arena = Arena.ofConfined()) {
|
|
* SymbolLookup libGL = SymbolLookup.libraryLookup("libGL.so", arena); // libGL.so loaded here
|
|
* MemorySegment glGetString = libGL.findOrThrow("glGetString");
|
|
* ...
|
|
* } // libGL.so unloaded here
|
|
*}
|
|
* <p>
|
|
* If a library was previously loaded through JNI, i.e., by {@link System#load(String)}
|
|
* or {@link System#loadLibrary(String)}, then the library was also associated with
|
|
* a particular class loader. The factory method {@link #loaderLookup()} creates
|
|
* a symbol lookup for all the libraries associated with the caller's class loader:
|
|
*
|
|
* {@snippet lang=java :
|
|
* System.loadLibrary("GL"); // libGL.so loaded here
|
|
* ...
|
|
* SymbolLookup libGL = SymbolLookup.loaderLookup();
|
|
* MemorySegment glGetString = libGL.findOrThrow("glGetString");
|
|
* }
|
|
*
|
|
* This symbol lookup, which is known as a <em>loader lookup</em>, is dynamic with
|
|
* respect to the libraries associated with the class loader. If other libraries are
|
|
* subsequently loaded through JNI and associated with the class loader, then the loader
|
|
* lookup will expose their symbols automatically.
|
|
* <p>
|
|
* Note that a loader lookup only exposes symbols in libraries that were previously
|
|
* loaded through JNI, i.e., by {@link System#load(String)} or {@link System#loadLibrary(String)}.
|
|
* A loader lookup does not expose symbols in libraries that were loaded in the course
|
|
* of creating a library lookup:
|
|
*
|
|
* {@snippet lang = java:
|
|
* libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true
|
|
* loaderLookup().find("glGetString").isPresent(); // false
|
|
*}
|
|
*
|
|
* Note also that a library lookup for library {@code L} exposes symbols in {@code L}
|
|
* even if {@code L} was previously loaded through JNI (the association with
|
|
* a class loader is immaterial to the library lookup):
|
|
*
|
|
* {@snippet lang = java:
|
|
* System.loadLibrary("GL"); // libGL.so loaded here
|
|
* libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true
|
|
*}
|
|
*
|
|
* <p>
|
|
* Finally, each {@link Linker} provides a symbol lookup for libraries that are commonly
|
|
* used on the OS and processor combination supported by that {@link Linker}. This
|
|
* symbol lookup, which is known as a <em>default lookup</em>, helps clients to quickly
|
|
* find addresses of well-known symbols. For example, a {@link Linker} for Linux/x64
|
|
* might choose to expose symbols in {@code libc} through the default lookup:
|
|
*
|
|
* {@snippet lang = java:
|
|
* Linker nativeLinker = Linker.nativeLinker();
|
|
* SymbolLookup stdlib = nativeLinker.defaultLookup();
|
|
* MemorySegment malloc = stdlib.findOrThrow("malloc");
|
|
*}
|
|
*
|
|
* @since 22
|
|
*/
|
|
@FunctionalInterface
|
|
public interface SymbolLookup {
|
|
|
|
/**
|
|
* Returns the address of the symbol with the given name.
|
|
*
|
|
* @param name the symbol name
|
|
* @return a zero-length memory segment whose address indicates the address of
|
|
* the symbol, if found
|
|
* @see #findOrThrow(String)
|
|
*/
|
|
Optional<MemorySegment> find(String name);
|
|
|
|
/**
|
|
* Returns the address of the symbol with the given name or throws an exception.
|
|
*<p>
|
|
* This is equivalent to the following code, but is more efficient:
|
|
* to:
|
|
* {@snippet lang= java :
|
|
* String name = ...
|
|
* MemorySegment address = lookup.find(name)
|
|
* .orElseThrow(() -> new NoSuchElementException("Symbol not found: " + name));
|
|
* }
|
|
*
|
|
* @param name the symbol name
|
|
* @return a zero-length memory segment whose address indicates the address of
|
|
* the symbol
|
|
* @throws NoSuchElementException if no symbol address can be found for the
|
|
* given name
|
|
* @see #find(String)
|
|
*
|
|
* @since 23
|
|
*/
|
|
default MemorySegment findOrThrow(String name) {
|
|
Objects.requireNonNull(name);
|
|
Optional<MemorySegment> address = find(name);
|
|
// Avoid lambda capturing
|
|
if (address.isPresent()) {
|
|
return address.get();
|
|
}
|
|
throw new NoSuchElementException("Symbol not found: " + name);
|
|
}
|
|
|
|
/**
|
|
* {@return a composed symbol lookup that returns the result of finding the symbol
|
|
* with this lookup if found, otherwise returns the result of finding
|
|
* the symbol with the other lookup}
|
|
*
|
|
* @apiNote This method could be used to chain multiple symbol lookups together,
|
|
* e.g. so that symbols could be retrieved, in order, from multiple
|
|
* libraries:
|
|
* {@snippet lang = java:
|
|
* var lookup = SymbolLookup.libraryLookup("foo", arena)
|
|
* .or(SymbolLookup.libraryLookup("bar", arena))
|
|
* .or(SymbolLookup.loaderLookup());
|
|
*}
|
|
* The above code creates a symbol lookup that first searches for symbols in
|
|
* the "foo" library. If no symbol is found in "foo" then "bar" is searched.
|
|
* Finally, if a symbol is neither found in "foo" nor in "bar", the
|
|
* {@linkplain SymbolLookup#loaderLookup() loader lookup} is used.
|
|
*
|
|
* @param other the symbol lookup that should be used to look for symbols not found
|
|
* in this lookup
|
|
*/
|
|
default SymbolLookup or(SymbolLookup other) {
|
|
Objects.requireNonNull(other);
|
|
return name -> find(name).or(() -> other.find(name));
|
|
}
|
|
|
|
/**
|
|
* Returns a symbol lookup for symbols in the libraries associated with the caller's
|
|
* class loader.
|
|
* <p>
|
|
* A library is associated with a class loader {@code CL} when the library is loaded
|
|
* via an invocation of {@link System#load(String)} or
|
|
* {@link System#loadLibrary(String)} from code in a class defined by {@code CL}.
|
|
* If that code makes further invocations of {@link System#load(String)} or
|
|
* {@link System#loadLibrary(String)} then more libraries are loaded and associated
|
|
* with {@code CL}. The symbol lookup returned by this method is always current: it
|
|
* reflects all the libraries associated with the relevant class loader, even if they
|
|
* were loaded after this method returned.
|
|
* <p>
|
|
* Libraries associated with a class loader are unloaded when the class loader becomes
|
|
* {@linkplain java.lang.ref##reachability unreachable}. The
|
|
* symbol lookup returned by this method is associated with an automatic
|
|
* {@linkplain MemorySegment.Scope scope} which keeps the caller's class loader
|
|
* reachable. Therefore, libraries associated with the caller's class loader are
|
|
* kept loaded (and their symbols available) as long as a loader lookup for that
|
|
* class loader, or any of the segments obtained by it, is reachable.
|
|
* <p>
|
|
* In cases where this method is called from a context where there is no caller
|
|
* frame on the stack (e.g. when called directly from a JNI attached thread), the
|
|
* caller's class loader defaults to the
|
|
* {@linkplain ClassLoader#getSystemClassLoader system class loader}.
|
|
*
|
|
* @return a symbol lookup for symbols in the libraries associated with
|
|
* the caller's class loader
|
|
* @see System#load(String)
|
|
* @see System#loadLibrary(String)
|
|
*/
|
|
@CallerSensitive
|
|
@SuppressWarnings("restricted")
|
|
static SymbolLookup loaderLookup() {
|
|
Class<?> caller = Reflection.getCallerClass();
|
|
// If there's no caller class, fallback to system loader
|
|
ClassLoader loader = caller != null ?
|
|
caller.getClassLoader() :
|
|
ClassLoader.getSystemClassLoader();
|
|
Arena loaderArena;// builtin loaders never go away
|
|
if ((loader == null || loader instanceof BuiltinClassLoader)) {
|
|
loaderArena = Arena.global();
|
|
} else {
|
|
MemorySessionImpl session = MemorySessionImpl.createHeap(loader);
|
|
loaderArena = session.asArena();
|
|
}
|
|
return name -> {
|
|
Objects.requireNonNull(name);
|
|
if (Utils.containsNullChars(name)) return Optional.empty();
|
|
JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess();
|
|
// note: ClassLoader::findNative supports a null loader
|
|
NativeLibraries nativeLibraries = javaLangAccess.nativeLibrariesFor(loader);
|
|
long addr = nativeLibraries.find(name);
|
|
return addr == 0L ?
|
|
Optional.empty() :
|
|
Optional.of(MemorySegment.ofAddress(addr)
|
|
.reinterpret(loaderArena, null)); // restricted
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Loads a library with the given name (if not already loaded) and creates a symbol
|
|
* lookup for symbols in that library. The lifetime of the returned library lookup
|
|
* is controlled by the provided arena. For instance, if the provided arena is a
|
|
* confined arena, the library associated with the returned lookup will be unloaded
|
|
* when the provided confined arena is {@linkplain Arena#close() closed}.
|
|
*
|
|
* @implNote The process of resolving a library name is OS-specific. For instance,
|
|
* in a POSIX-compliant OS, the library name is resolved according to the
|
|
* specification of the {@code dlopen} function for that OS. In Windows,
|
|
* the library name is resolved according to the specification of the
|
|
* {@code LoadLibrary} function.
|
|
*
|
|
* @param name the name of the library in which symbols should be looked up
|
|
* @param arena the arena associated with symbols obtained from the returned lookup
|
|
* @return a new symbol lookup suitable to find symbols in a library with the
|
|
* given name
|
|
* @throws IllegalStateException if {@code arena.scope().isAlive() == false}
|
|
* @throws WrongThreadException if {@code arena} is a confined arena, and this method
|
|
* is called from a thread {@code T}, other than the arena's owner thread
|
|
* @throws IllegalArgumentException if {@code name} does not identify a valid library
|
|
* @throws IllegalCallerException if the caller is in a module that does not have
|
|
* native access enabled
|
|
*/
|
|
@CallerSensitive
|
|
@Restricted
|
|
static SymbolLookup libraryLookup(String name, Arena arena) {
|
|
Reflection.ensureNativeAccess(Reflection.getCallerClass(),
|
|
SymbolLookup.class, "libraryLookup", false);
|
|
if (Utils.containsNullChars(name)) {
|
|
throw new IllegalArgumentException("Cannot open library: " + name);
|
|
}
|
|
return libraryLookup(name, RawNativeLibraries::load, arena);
|
|
}
|
|
|
|
/**
|
|
* Loads a library from the given path (if not already loaded) and creates a symbol
|
|
* lookup for symbols in that library. The lifetime of the returned library lookup
|
|
* is controlled by the provided arena. For instance, if the provided arena is a
|
|
* confined arena, the library associated with the returned lookup will be unloaded
|
|
* when the provided confined arena is {@linkplain Arena#close() closed}.
|
|
*
|
|
* @implNote On Linux, the functionalities provided by this factory method and the
|
|
* returned symbol lookup are implemented using the {@code dlopen},
|
|
* {@code dlsym} and {@code dlclose} functions.
|
|
*
|
|
* @param path the path of the library in which symbols should be looked up
|
|
* @param arena the arena associated with symbols obtained from the returned lookup
|
|
* @return a new symbol lookup suitable to find symbols in a library with the given
|
|
* path
|
|
* @throws IllegalStateException if {@code arena.scope().isAlive() == false}
|
|
* @throws WrongThreadException if {@code arena} is a confined arena, and this method
|
|
* is called from a thread {@code T}, other than the arena's owner thread
|
|
* @throws IllegalArgumentException if {@code path} does not point to a valid library
|
|
* in the default file system
|
|
* @throws IllegalCallerException if the caller is in a module that does not have
|
|
* native access enabled
|
|
*/
|
|
@CallerSensitive
|
|
@Restricted
|
|
static SymbolLookup libraryLookup(Path path, Arena arena) {
|
|
Reflection.ensureNativeAccess(Reflection.getCallerClass(),
|
|
SymbolLookup.class, "libraryLookup", false);
|
|
if (path.getFileSystem() != FileSystems.getDefault()) {
|
|
throw new IllegalArgumentException("Path not in default file system: " + path);
|
|
}
|
|
return libraryLookup(path, RawNativeLibraries::load, arena);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
private static <Z>
|
|
SymbolLookup libraryLookup(Z libDesc,
|
|
BiFunction<RawNativeLibraries, Z, NativeLibrary> loadLibraryFunc,
|
|
Arena libArena) {
|
|
|
|
Objects.requireNonNull(libDesc);
|
|
Objects.requireNonNull(libArena);
|
|
// attempt to load native library from path or name
|
|
RawNativeLibraries nativeLibraries = RawNativeLibraries.newInstance(MethodHandles.lookup());
|
|
NativeLibrary library = loadLibraryFunc.apply(nativeLibraries, libDesc);
|
|
if (library == null) {
|
|
throw new IllegalArgumentException("Cannot open library: " + libDesc);
|
|
}
|
|
// register hook to unload library when 'libScope' becomes not alive
|
|
MemorySessionImpl.toMemorySession(libArena).addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() {
|
|
@Override
|
|
public void cleanup() {
|
|
nativeLibraries.unload(library);
|
|
}
|
|
});
|
|
return name -> {
|
|
Objects.requireNonNull(name);
|
|
if (Utils.containsNullChars(name)) return Optional.empty();
|
|
long addr = library.find(name);
|
|
return addr == 0L ?
|
|
Optional.empty() :
|
|
Optional.of(MemorySegment.ofAddress(addr)
|
|
.reinterpret(libArena, null)); // restricted
|
|
};
|
|
}
|
|
}
|