GP-3490: Fixing GhidraDev classpath issues

This commit is contained in:
Ryan Kurtz
2024-07-26 13:12:29 -04:00
parent 8cde73e787
commit 88bec10e60
16 changed files with 285 additions and 141 deletions

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -30,7 +30,6 @@ import java.util.stream.Collectors;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -491,24 +490,11 @@ public class ClassSearcher {
// jar files will *not* be on the standard classpath, but instead will be on CP_EXT.
//
List<String> rawPaths = new ArrayList<>();
getPropertyPaths(GhidraClassLoader.CP, rawPaths);
getPropertyPaths(GhidraClassLoader.CP_EXT, rawPaths);
rawPaths.addAll(GhidraClassLoader.getClasspath(GhidraClassLoader.CP));
rawPaths.addAll(GhidraClassLoader.getClasspath(GhidraClassLoader.CP_EXT));
return canonicalizePaths(rawPaths);
}
private static void getPropertyPaths(String property, List<String> results) {
String paths = System.getProperty(property);
log.trace("Paths in {}: {}", property, paths);
if (StringUtils.isBlank(paths)) {
return;
}
StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
while (st.hasMoreTokens()) {
results.add(st.nextToken());
}
}
private static List<String> canonicalizePaths(Collection<String> paths) {
//@formatter:off

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -169,28 +169,15 @@ public class GhidraApplicationLayout extends ApplicationLayout {
});
}
// Examine the classpath to look for modules outside of the application root directories.
// These might exist if Ghidra was launched from an Eclipse project that resides
// external to the Ghidra installation.
for (String entry : System.getProperty("java.class.path", "").split(File.pathSeparator)) {
ResourceFile classpathEntry = new ResourceFile(entry);
// We only care about directories (skip jars)
if (!classpathEntry.isDirectory()) {
continue;
}
// Skip extensions in an application root directory... already found those.
if (FileUtilities.isPathContainedWithin(applicationRootDirs, classpathEntry)) {
continue;
}
// We are going to assume that the classpath entry is in a subdirectory of the module
// directory (i.e., bin/), so only check parent directory for the module.
ResourceFile classpathEntryParent = classpathEntry.getParentFile();
if (classpathEntryParent != null &&
ModuleUtilities.isModuleDirectory(classpathEntryParent)) {
moduleRootDirectories.add(classpathEntryParent);
// Add external modules defined via a system property. This will typically be used by
// user's developing 3rd party modules from something like Eclipse.
String externalModules = System.getProperty("ghidra.external.modules", "");
if (!externalModules.isBlank()) {
for (String path : externalModules.split(File.pathSeparator)) {
ResourceFile eclipseProjectDir = new ResourceFile(path);
if (ModuleUtilities.isModuleDirectory(eclipseProjectDir)) {
moduleRootDirectories.add(eclipseProjectDir);
}
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,8 +18,7 @@ package ghidra;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.net.*;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import ghidra.util.Msg;
@@ -51,6 +50,25 @@ public class GhidraClassLoader extends URLClassLoader {
*/
public static final String CP_EXT = "java.class.path.ext";
/**
* Gets a {@link List} containing the current classpath referenced by the given property name
*
* @param propertyName The property name of the classpath to get
* @return A {@link List} containing the current classpath referenced by the given property name
*/
public static List<String> getClasspath(String propertyName) {
List<String> result = new ArrayList<>();
// StringTokenizer is better than split() here because our result list will stay empty if
// the classpath is empty
StringTokenizer st =
new StringTokenizer(System.getProperty(propertyName, ""), File.pathSeparator);
while (st.hasMoreTokens()) {
result.add(st.nextToken());
}
return result;
}
/**
* Used to prevent duplicate URL's from being added to the classpath
*/

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -150,11 +150,15 @@ public class GhidraLauncher {
List<String> classpathList = new ArrayList<>();
Map<String, GModule> modules = getOrderedModules(layout);
// First add any "bin" paths the module might have. These could come from external modules
// being developed and passed in via system property if we are in release mode, or they
// could be generated for each Ghidra module by Eclipse if we are in development mode.
addModuleBinPaths(classpathList, modules);
if (SystemUtilities.isInDevelopmentMode()) {
// First add Eclipse's module "bin" paths. If we didn't find any, assume Ghidra was
// If we didn't find any "bin" paths and we are in development mode, assume Ghidra was
// compiled with Gradle, and add the module jars Gradle built.
addModuleBinPaths(classpathList, modules);
boolean gradleDevMode = classpathList.isEmpty();
if (gradleDevMode) {
// Add the module jars Gradle built.
@@ -165,22 +169,16 @@ public class GhidraLauncher {
else { /* Eclipse dev mode */
// Support loading pre-built, jar-based, non-repo extensions in Eclipse dev mode
addExtensionJarPaths(classpathList, modules, layout);
// Eclipse launches the Utility module, so it's already on the classpath. We don't
// want to add it a second time, so remove the one we discovered.
GModule utilityModule = modules.get("Utility");
if (utilityModule == null) {
throw new IOException("Failed to find the 'Utility' module!");
}
classpathList.removeIf(
e -> e.startsWith(utilityModule.getModuleRoot().getAbsolutePath()));
}
// In development mode, jars do not live in module directories. Instead, each jar lives
// in an external, non-repo location, which is listed in build/libraryDependencies.txt.
// In development mode, 3rd party library jars do not live in module directories.
// Instead, each jar lives in an external, non-repo location, which is listed in
// build/libraryDependencies.txt.
addExternalJarPaths(classpathList, layout.getApplicationRootDirs());
}
else {
// Release mode is simple. We expect all of Ghidra's modules to be in pre-build jars.
addPatchPaths(classpathList, layout.getPatchDir());
addModuleJarPaths(classpathList, modules);
}
@@ -194,8 +192,16 @@ public class GhidraLauncher {
// the standard classpath.)
setExtensionJarPaths(modules, layout, classpathList);
classpathList = orderClasspath(classpathList, modules);
return classpathList;
// Ghidra launches from the Utility module, so it's already on the classpath. We don't
// want to add it a second time, so remove the one we discovered.
GModule utilityModule = modules.get("Utility");
if (utilityModule == null) {
throw new IOException("Failed to find the 'Utility' module!");
}
classpathList.removeIf(
e -> e.startsWith(utilityModule.getModuleRoot().getAbsolutePath()));
return orderClasspath(classpathList, modules);
}
/**
@@ -265,9 +271,9 @@ public class GhidraLauncher {
}
/**
* Add extension module lib jars to the given path list. (This only needed in dev mode to find
* any pre-built extensions that have been installed, since we already find extension module
* jars in production mode.)
* Add extension module lib jars to the given path list. (This is only needed in dev mode to
* find any pre-built extensions that have been installed, since we already find extension
* module jars in production mode.)
*
* @param pathList The list of paths to add to.
* @param modules The modules to get the jars of.

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,6 +20,7 @@ import java.text.SimpleDateFormat;
import java.util.*;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
/**
* The application properties. Application properties may either be stored on disk, or created
@@ -52,8 +53,11 @@ public class ApplicationProperties extends Properties {
* Current application versions are:
* <ul>
* <li>1: Layout used by Ghidra &lt; 11.1</li>
* <li>2: Introduced with Ghidra 11.1. Default user settings/cache/temp directories changed,
* and XDG environment variables are supported.
* <li>2: Introduced with Ghidra 11.1. Default user settings/cache/temp directories changed,
* and XDG environment variables are supported.</li>
* <li>3: Introduced with Ghidra 11.2. Ghidra no longer finds external modules by examining
* the initial classpath. Instead, the "ghidra.external.modules" system property is used
* (see {@link GhidraApplicationLayout}).</li>
* </ul>
*/
public static final String APPLICATION_LAYOUT_VERSION_PROPERTY = "application.layout.version";