Skip to content

Commit

Permalink
GP-3490: Fixing GhidraDev classpath issues
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanmkurtz committed Jul 26, 2024
1 parent 8cde73e commit 88bec10
Show file tree
Hide file tree
Showing 16 changed files with 285 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -16,7 +16,6 @@
package ghidra.app.plugin.runtimeinfo;

import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -249,11 +248,9 @@ private void addExtensionsClasspath() {
*/
private Map<Integer, String> getClasspathMap(String propertyName) {
Map<Integer, String> map = new HashMap<>();
StringTokenizer st =
new StringTokenizer(System.getProperty(propertyName, ""), File.pathSeparator);
int i = 0;
while (st.hasMoreTokens()) {
map.put(i++, st.nextToken());
for (String entry : GhidraClassLoader.getClasspath(propertyName)) {
map.put(i++, entry);
}
return map;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -30,7 +30,6 @@

import javax.swing.event.ChangeListener;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Expand Down Expand Up @@ -491,24 +490,11 @@ private static List<String> gatherSearchPaths() {
// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -169,28 +169,15 @@ protected Map<String, GModule> findGhidraModules() throws IOException {
});
}

// 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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -18,8 +18,7 @@
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;

Expand Down Expand Up @@ -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
*/
Expand Down
46 changes: 26 additions & 20 deletions Ghidra/Framework/Utility/src/main/java/ghidra/GhidraLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -150,11 +150,15 @@ private static List<String> buildClasspath(GhidraApplicationLayout layout) throw
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.
Expand All @@ -165,22 +169,16 @@ private static List<String> buildClasspath(GhidraApplicationLayout layout) throw
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);
}
Expand All @@ -194,8 +192,16 @@ private static List<String> buildClasspath(GhidraApplicationLayout layout) throw
// 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);
}

/**
Expand Down Expand Up @@ -265,9 +271,9 @@ private static void setExtensionJarPaths(Map<String, GModule> modules,
}

/**
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -20,6 +20,7 @@
import java.util.*;

import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;

/**
* The application properties. Application properties may either be stored on disk, or created
Expand Down Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion Ghidra/application.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
application.name=Ghidra
application.version=11.2
application.release.name=DEV
application.layout.version=2
application.layout.version=3
application.gradle.min=8.5
application.gradle.max=
application.java.min=21
Expand Down
Loading

0 comments on commit 88bec10

Please sign in to comment.