use of com.oracle.svm.jni.nativeapi.JNIJavaVM in project graal by oracle.
the class NativeImageAgent method onLoadCallback.
@Override
protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, String options) {
String traceOutputFile = null;
String configOutputDir = null;
ConfigurationSet mergeConfigs = new ConfigurationSet();
ConfigurationSet omittedConfigs = new ConfigurationSet();
boolean builtinCallerFilter = true;
boolean builtinHeuristicFilter = true;
List<String> callerFilterFiles = new ArrayList<>();
List<String> accessFilterFiles = new ArrayList<>();
boolean experimentalClassLoaderSupport = true;
boolean experimentalClassDefineSupport = false;
boolean experimentalOmitClasspathConfig = false;
boolean build = false;
boolean configurationWithOrigins = false;
// in seconds
int configWritePeriod = -1;
// in seconds
int configWritePeriodInitialDelay = 1;
boolean trackReflectionMetadata = true;
String[] tokens = !options.isEmpty() ? options.split(",") : new String[0];
for (String token : tokens) {
if (token.startsWith("trace-output=")) {
if (traceOutputFile != null) {
return usage(1, "cannot specify trace-output= more than once.");
}
traceOutputFile = getTokenValue(token);
} else if (token.startsWith("config-output-dir=") || token.startsWith("config-merge-dir=")) {
if (configOutputDir != null) {
return usage(1, "cannot specify more than one of config-output-dir= or config-merge-dir=.");
}
configOutputDir = transformPath(getTokenValue(token));
if (token.startsWith("config-merge-dir=")) {
mergeConfigs.addDirectory(Paths.get(configOutputDir));
}
} else if (token.startsWith("config-to-omit=")) {
String omittedConfigDir = getTokenValue(token);
omittedConfigDir = transformPath(omittedConfigDir);
omittedConfigs.addDirectory(Paths.get(omittedConfigDir));
} else if (token.equals("experimental-omit-config-from-classpath")) {
experimentalOmitClasspathConfig = true;
} else if (token.startsWith("experimental-omit-config-from-classpath=")) {
experimentalOmitClasspathConfig = Boolean.parseBoolean(getTokenValue(token));
} else if (token.startsWith("restrict-all-dir") || token.equals("restrict") || token.startsWith("restrict=")) {
warn("restrict mode is no longer supported, ignoring option: " + token);
} else if (token.equals("no-builtin-caller-filter")) {
builtinCallerFilter = false;
} else if (token.startsWith("builtin-caller-filter=")) {
builtinCallerFilter = Boolean.parseBoolean(getTokenValue(token));
} else if (token.equals("no-builtin-heuristic-filter")) {
builtinHeuristicFilter = false;
} else if (token.startsWith("builtin-heuristic-filter=")) {
builtinHeuristicFilter = Boolean.parseBoolean(getTokenValue(token));
} else if (token.equals("no-filter")) {
// legacy
builtinCallerFilter = false;
builtinHeuristicFilter = false;
} else if (token.startsWith("no-filter=")) {
// legacy
builtinCallerFilter = !Boolean.parseBoolean(getTokenValue(token));
builtinHeuristicFilter = builtinCallerFilter;
} else if (token.startsWith("caller-filter-file=")) {
callerFilterFiles.add(getTokenValue(token));
} else if (token.startsWith("access-filter-file=")) {
accessFilterFiles.add(getTokenValue(token));
} else if (token.equals("experimental-class-loader-support")) {
experimentalClassLoaderSupport = true;
} else if (token.startsWith("experimental-class-loader-support=")) {
experimentalClassLoaderSupport = Boolean.parseBoolean(getTokenValue(token));
} else if (token.equals("experimental-class-define-support")) {
experimentalClassDefineSupport = true;
} else if (token.startsWith("experimental-class-define-support=")) {
experimentalClassDefineSupport = Boolean.parseBoolean(getTokenValue(token));
} else if (token.startsWith("config-write-period-secs=")) {
configWritePeriod = parseIntegerOrNegative(getTokenValue(token));
if (configWritePeriod <= 0) {
return usage(1, "config-write-period-secs must be an integer greater than 0");
}
} else if (token.startsWith("config-write-initial-delay-secs=")) {
configWritePeriodInitialDelay = parseIntegerOrNegative(getTokenValue(token));
if (configWritePeriodInitialDelay < 0) {
return usage(1, "config-write-initial-delay-secs must be an integer greater or equal to 0");
}
} else if (token.equals("build")) {
build = true;
} else if (token.startsWith("build=")) {
build = Boolean.parseBoolean(getTokenValue(token));
} else if (token.equals("experimental-configuration-with-origins")) {
configurationWithOrigins = true;
} else if (token.equals("track-reflection-metadata")) {
trackReflectionMetadata = true;
} else if (token.startsWith("track-reflection-metadata=")) {
trackReflectionMetadata = Boolean.parseBoolean(getTokenValue(token));
} else {
return usage(1, "unknown option: '" + token + "'.");
}
}
if (traceOutputFile == null && configOutputDir == null && !build) {
configOutputDir = transformPath(AGENT_NAME + "_config-pid{pid}-{datetime}/");
inform("no output/build options provided, tracking dynamic accesses and writing configuration to directory: " + configOutputDir);
}
if (configurationWithOrigins && !mergeConfigs.isEmpty()) {
configurationWithOrigins = false;
inform("using configuration with origins with configuration merging is currently unsupported. Disabling configuration with origins mode.");
}
if (configurationWithOrigins) {
warn("using experimental configuration with origins mode. Note that native-image cannot process these files, and this flag may change or be removed without a warning!");
}
RuleNode callerFilter = null;
if (!builtinCallerFilter) {
callerFilter = RuleNode.createRoot();
callerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include);
}
if (!callerFilterFiles.isEmpty()) {
if (callerFilter == null) {
callerFilter = AccessAdvisor.copyBuiltinCallerFilterTree();
}
if (!parseFilterFiles(callerFilter, callerFilterFiles)) {
return 1;
}
}
RuleNode accessFilter = null;
if (!accessFilterFiles.isEmpty()) {
accessFilter = AccessAdvisor.copyBuiltinAccessFilterTree();
if (!parseFilterFiles(accessFilter, accessFilterFiles)) {
return 1;
}
}
final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(configurationWithOrigins);
final Supplier<InterceptedState> interceptedStateSupplier = configurationWithOrigins ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier();
if (configOutputDir != null) {
if (traceOutputFile != null) {
return usage(1, "can only once specify exactly one of trace-output=, config-output-dir= or config-merge-dir=.");
}
try {
configOutputDirPath = Files.createDirectories(Path.of(configOutputDir));
configOutputLockFilePath = configOutputDirPath.resolve(ConfigurationFile.LOCK_FILE_NAME);
try {
Files.writeString(configOutputLockFilePath, Long.toString(ProcessProperties.getProcessID()), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
} catch (FileAlreadyExistsException e) {
String process;
try {
process = Files.readString(configOutputLockFilePath).stripTrailing();
} catch (Exception ignored) {
process = "(unknown)";
}
return error(2, "Output directory '" + configOutputDirPath + "' is locked by process " + process + ", " + "which means another agent instance is already writing to this directory. " + "Only one agent instance can safely write to a specific target directory at the same time. " + "Unless file '" + ConfigurationFile.LOCK_FILE_NAME + "' is a leftover from an earlier process that terminated abruptly, it is unsafe to delete it. " + "For running multiple processes with agents at the same time to create a single configuration, read Agent.md " + "or https://www.graalvm.org/reference-manual/native-image/Agent/ on how to use the native-image-configure tool.");
}
if (experimentalOmitClasspathConfig) {
ignoreConfigFromClasspath(jvmti, omittedConfigs);
}
AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
TraceProcessor omittedConfigProcessor = null;
Predicate<String> shouldExcludeClassesWithHash = null;
if (!omittedConfigs.isEmpty()) {
Function<IOException, Exception> ignore = e -> {
warn("Failed to load omitted config: " + e);
return null;
};
omittedConfigProcessor = new TraceProcessor(advisor, omittedConfigs.loadJniConfig(ignore), omittedConfigs.loadReflectConfig(ignore), omittedConfigs.loadProxyConfig(ignore), omittedConfigs.loadResourceConfig(ignore), omittedConfigs.loadSerializationConfig(ignore), omittedConfigs.loadPredefinedClassesConfig(null, null, ignore), null);
shouldExcludeClassesWithHash = omittedConfigProcessor.getPredefinedClassesConfiguration()::containsClassWithHash;
}
if (configurationWithOrigins) {
ConfigurationWithOriginsResultWriter writer = new ConfigurationWithOriginsResultWriter(advisor, recordKeeper);
tracer = writer;
tracingResultWriter = writer;
} else {
Path[] predefinedClassDestDirs = { Files.createDirectories(configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR)) };
Function<IOException, Exception> handler = e -> {
if (e instanceof NoSuchFileException) {
warn("file " + ((NoSuchFileException) e).getFile() + " for merging could not be found, skipping");
return null;
} else if (e instanceof FileNotFoundException) {
warn("could not open configuration file: " + e);
return null;
}
// rethrow
return e;
};
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler), mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler), mergeConfigs.loadPredefinedClassesConfig(predefinedClassDestDirs, shouldExcludeClassesWithHash, handler), omittedConfigProcessor);
ConfigurationResultWriter writer = new ConfigurationResultWriter(processor);
tracer = writer;
tracingResultWriter = writer;
}
expectedConfigModifiedBefore = getMostRecentlyModified(configOutputDirPath, getMostRecentlyModified(configOutputLockFilePath, null));
} catch (Throwable t) {
return error(2, t.toString());
}
} else if (traceOutputFile != null) {
try {
Path path = Paths.get(transformPath(traceOutputFile));
TraceFileWriter writer = new TraceFileWriter(path);
tracer = writer;
tracingResultWriter = writer;
} catch (Throwable t) {
return error(2, t.toString());
}
}
if (build) {
int status = buildImage(jvmti);
if (status == 0) {
System.exit(status);
}
return status;
}
try {
BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport, trackReflectionMetadata);
} catch (Throwable t) {
return error(3, t.toString());
}
try {
JniCallInterceptor.onLoad(tracer, this, interceptedStateSupplier);
} catch (Throwable t) {
return error(4, t.toString());
}
setupExecutorServiceForPeriodicConfigurationCapture(configWritePeriod, configWritePeriodInitialDelay);
return 0;
}
use of com.oracle.svm.jni.nativeapi.JNIJavaVM in project graal by oracle.
the class JNIJavaVMList method removeJavaVM.
/**
* Remove an entry.
*/
public static boolean removeJavaVM(JNIJavaVM javavm) {
WordPointer p = HEAD.get().readWord(0);
while (p.isNonNull()) {
Word capacity = p.read(0);
for (Word i = unsigned(1); i.belowOrEqual(capacity); i = i.add(1)) {
JNIJavaVM entry = p.read(i);
if (entry.equal(javavm)) {
p.write(i, nullPointer());
return true;
}
}
// next
p = p.read(capacity.add(1));
}
return false;
}
use of com.oracle.svm.jni.nativeapi.JNIJavaVM in project graal by oracle.
the class JNIJavaVMList method addJavaVM.
/**
* Insert a new entry at an arbitrary location.
*/
public static void addJavaVM(JNIJavaVM newEntry) {
final UnsignedWord wordSize = SizeOf.unsigned(WordPointer.class);
Pointer nextPointer = HEAD.get();
UnsignedWord capacity = zero();
for (; ; ) {
Pointer p = nextPointer.readWord(0);
if (p.isNull()) {
// No empty slots, create new array
UnsignedWord newCapacity = capacity.notEqual(0) ? capacity.multiply(2) : INITIAL_CAPACITY;
Pointer newArray = UnmanagedMemory.calloc(newCapacity.add(2).multiply(wordSize));
newArray.writeWord(0, newCapacity);
newArray.writeWord(wordSize, newEntry);
p = nextPointer.compareAndSwapWord(0, nullPointer(), newArray, ANY_LOCATION);
if (p.equal(nullPointer())) {
return;
}
// Another thread already created and linked a new array, continue in that array
UnmanagedMemory.free(newArray);
}
capacity = p.readWord(0);
p = p.add(wordSize);
UnsignedWord end = p.add(capacity.multiply(wordSize));
while (p.belowThan(end)) {
JNIJavaVM entry = p.readWord(0);
if (entry.isNull() && p.logicCompareAndSwapWord(0, nullPointer(), newEntry, ANY_LOCATION)) {
return;
}
p = p.add(wordSize);
}
nextPointer = p;
}
}
use of com.oracle.svm.jni.nativeapi.JNIJavaVM in project graal by oracle.
the class JNIJavaVMList method gather.
/**
* Gather non-null entries in a buffer and provide the total number of non-null entries.
*/
@Uninterruptible(reason = "Called from uninterruptible code.")
public static void gather(JNIJavaVMPointer buffer, int bufferLength, CIntPointer totalCountPointer) {
int totalCount = 0;
WordPointer p = HEAD.get().readWord(0);
while (p.isNonNull()) {
Word capacity = p.read(0);
for (Word i = unsigned(1); i.belowOrEqual(capacity); i = i.add(1)) {
JNIJavaVM entry = p.read(i);
if (entry.isNonNull()) {
if (totalCount < bufferLength) {
buffer.write(totalCount, entry);
}
totalCount++;
}
}
// next
p = p.read(capacity.add(1));
}
totalCountPointer.write(totalCount);
}
use of com.oracle.svm.jni.nativeapi.JNIJavaVM in project graal by oracle.
the class JNIFunctionTables method getGlobalJavaVM.
public JNIJavaVM getGlobalJavaVM() {
JNIJavaVM javaVM = globalJavaVM;
if (javaVM.isNull()) {
/*
* The function pointer table filled during image generation must be in the read-only
* part of the image heap, because code relocations are needed for it. To work around
* this limitation, we copy the read-only table filled during image generation to a
* writable table of the same size.
*/
for (int i = 0; i < invokeInterfaceDataPrototype.length; i++) {
invokeInterfaceDataMutable[i] = invokeInterfaceDataPrototype[i];
}
javaVM = (JNIJavaVM) dataAddress(javaVMData);
JNIInvokeInterface invokes = (JNIInvokeInterface) dataAddress(invokeInterfaceDataMutable);
invokes.setIsolate(CurrentIsolate.getIsolate());
javaVM.setFunctions(invokes);
globalJavaVM = javaVM;
}
return javaVM;
}
Aggregations