mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-4735 refactor pdb symbol server 'remote' to 'untrusted'
Change name of symbolserver 'remote' property to 'untrusted' to reflectits intended usage.Add column in config table to allow user to toggle trusted status onhttp:// symbol servers (the only type that currently supports thisconcept)
This commit is contained in:
parent
21d433b26c
commit
dcc56457ea
26 changed files with 1057 additions and 890 deletions
|
@ -19,13 +19,9 @@
|
|||
//The ~/symbols directory should already exist and be initialized as a symbol
|
||||
//storage location.
|
||||
//@category PDB
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.PdbAnalyzer;
|
||||
import ghidra.app.plugin.core.analysis.PdbUniversalAnalyzer;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
|
@ -38,16 +34,11 @@ public class PdbSymbolServerExamplePrescript extends GhidraScript {
|
|||
File symDir = new File(homeDir, "symbols");
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(symDir);
|
||||
HttpSymbolServer msSymbolServer =
|
||||
new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/"));
|
||||
HttpSymbolServer.createTrusted("https://msdl.microsoft.com/download/symbols/");
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore, List.of(msSymbolServer));
|
||||
|
||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
||||
|
||||
// You only need to enable the "allow remote" option on the specific
|
||||
// analyzer you are using
|
||||
PdbUniversalAnalyzer.setAllowRemoteOption(currentProgram, true);
|
||||
PdbAnalyzer.setAllowRemoteOption(currentProgram, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessMode;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -44,8 +43,8 @@ public class GetMSDownloadLinkScript extends GhidraScript {
|
|||
@Override
|
||||
protected void run() throws Exception {
|
||||
SymbolServerService symbolService =
|
||||
new SymbolServerService(new SameDirSymbolStore(null), List.of(
|
||||
new HttpSymbolServer(URI.create(MS_PUBLIC_SYMBOL_SERVER_URL))));
|
||||
new SymbolServerService(new SameDirSymbolStore(null),
|
||||
List.of(HttpSymbolServer.createTrusted(MS_PUBLIC_SYMBOL_SERVER_URL)));
|
||||
|
||||
File f = askFile("File To Scan", "Select");
|
||||
if (f == null) {
|
||||
|
@ -63,9 +62,7 @@ public class GetMSDownloadLinkScript extends GhidraScript {
|
|||
", sizeOfImage: " + Integer.toHexString(sizeOfImage));
|
||||
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromValues(f.getName().toLowerCase(),
|
||||
Integer.toHexString(timeDateStamp), sizeOfImage);
|
||||
List<SymbolFileLocation> findResults =
|
||||
symbolService.find(symbolFileInfo, FindOption.of(FindOption.ALLOW_REMOTE),
|
||||
monitor);
|
||||
List<SymbolFileLocation> findResults = symbolService.find(symbolFileInfo, monitor);
|
||||
if (findResults.isEmpty()) {
|
||||
println("Not found on " + MS_PUBLIC_SYMBOL_SERVER_URL);
|
||||
return;
|
||||
|
|
|
@ -42,7 +42,7 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
|||
|
||||
private static final String ERROR_TITLE = "Error in PDB Analyzer";
|
||||
|
||||
private boolean searchRemoteLocations = false;
|
||||
private boolean searchUntrustedLocations = false;
|
||||
|
||||
// only try once per transaction due to extensive error logging which may get duplicated
|
||||
private long lastTransactionId = -1;
|
||||
|
@ -85,7 +85,7 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
|||
return false;
|
||||
}
|
||||
|
||||
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
|
||||
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
||||
if (pdbFile == null) {
|
||||
// warnings have already been logged
|
||||
return false;
|
||||
|
@ -138,15 +138,15 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
|||
@Override
|
||||
public void registerOptions(Options options, Program program) {
|
||||
|
||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS,
|
||||
searchRemoteLocations, null,
|
||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS);
|
||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS,
|
||||
searchUntrustedLocations, null,
|
||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(Options options, Program program) {
|
||||
searchRemoteLocations = options.getBoolean(
|
||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
|
||||
searchUntrustedLocations = options.getBoolean(
|
||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, searchUntrustedLocations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,18 +165,18 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the "allow remote" option that will be used by the analyzer when it is next invoked
|
||||
* Sets the "allow untrusted" option that will be used by the analyzer when it is next invoked
|
||||
* on the specified program.
|
||||
* <p>
|
||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||
* will default to NOT searching remote symbol servers. A headless script could
|
||||
* use this method to allow the analyzer to search remote symbol servers.
|
||||
* will default to NOT searching untrusted symbol servers. A headless script could
|
||||
* use this method to allow the analyzer to search untrusted symbol servers.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param allowRemote boolean flag, true means analyzer can search remote symbol
|
||||
* @param allowUntrusted boolean flag, true means analyzer can search untrusted symbol
|
||||
* servers
|
||||
*/
|
||||
public static void setAllowRemoteOption(Program program, boolean allowRemote) {
|
||||
PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote);
|
||||
public static void setAllowUntrustedOption(Program program, boolean allowUntrusted) {
|
||||
PdbAnalyzerCommon.setAllowUntrustedOption(NAME, program, allowUntrusted);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,9 @@ import pdb.symbolserver.SymbolFileInfo;
|
|||
* Shared configuration values and pdb searching logic
|
||||
*/
|
||||
public class PdbAnalyzerCommon {
|
||||
static final String OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS =
|
||||
"If checked, allow searching remote symbol servers for PDB files.";
|
||||
static final String OPTION_NAME_SEARCH_REMOTE_LOCATIONS = "Search remote symbol servers";
|
||||
static final String OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS =
|
||||
"If checked, allow searching untrusted symbol servers for PDB files.";
|
||||
static final String OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS = "Search untrusted symbol servers";
|
||||
|
||||
static final String OPTION_DESCRIPTION_PDB_FILE = "Path to a manually chosen PDB file.";
|
||||
static final String OPTION_NAME_PDB_FILE = "PDB File";
|
||||
|
@ -101,12 +101,13 @@ public class PdbAnalyzerCommon {
|
|||
*
|
||||
* @param analyzerName name of analyzer
|
||||
* @param program {@link Program}
|
||||
* @param allowRemote boolean flag, true means the analyzer can search remote
|
||||
* @param allowUntrusted boolean flag, true means the analyzer can search remote
|
||||
* symbol servers
|
||||
*/
|
||||
static void setAllowRemoteOption(String analyzerName, Program program, boolean allowRemote) {
|
||||
static void setAllowUntrustedOption(String analyzerName, Program program, boolean allowUntrusted) {
|
||||
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
|
||||
options.setBoolean(analyzerName + "." + OPTION_NAME_SEARCH_REMOTE_LOCATIONS, allowRemote);
|
||||
options.setBoolean(analyzerName + "." + OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS,
|
||||
allowUntrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +142,7 @@ public class PdbAnalyzerCommon {
|
|||
: null;
|
||||
if (pdbFile == null) {
|
||||
Set<FindOption> findOpts = allowRemote
|
||||
? FindOption.of(FindOption.ALLOW_REMOTE)
|
||||
? FindOption.of(FindOption.ALLOW_UNTRUSTED)
|
||||
: FindOption.NO_OPTIONS;
|
||||
pdbFile = PdbPlugin.findPdb(program, findOpts, monitor);
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
|||
private File DEFAULT_FORCE_LOAD_FILE = new File(DEFAULT_SYMBOLS_DIR, "sample.pdb");
|
||||
private File forceLoadFile;
|
||||
|
||||
private boolean searchRemoteLocations = false;
|
||||
private boolean searchUntrustedLocations = false;
|
||||
|
||||
//==============================================================================================
|
||||
// Additional instance data
|
||||
|
@ -164,7 +164,7 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
|||
pdbFile = forceLoadFile;
|
||||
}
|
||||
else {
|
||||
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
|
||||
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
||||
}
|
||||
if (pdbFile == null) {
|
||||
// warnings have already been logged
|
||||
|
@ -262,9 +262,9 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
|||
options.registerOption(OPTION_NAME_FORCELOAD_FILE, OptionType.FILE_TYPE,
|
||||
DEFAULT_FORCE_LOAD_FILE, null, OPTION_DESCRIPTION_FORCELOAD_FILE);
|
||||
}
|
||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS,
|
||||
searchRemoteLocations, null,
|
||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS);
|
||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS,
|
||||
searchUntrustedLocations, null,
|
||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS);
|
||||
|
||||
pdbReaderOptions.registerOptions(options);
|
||||
pdbApplicatorOptions.registerAnalyzerOptions(options);
|
||||
|
@ -281,8 +281,8 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
|||
forceLoadFile = options.getFile(OPTION_NAME_FORCELOAD_FILE, forceLoadFile);
|
||||
}
|
||||
|
||||
searchRemoteLocations = options.getBoolean(
|
||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
|
||||
searchUntrustedLocations = options.getBoolean(
|
||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, searchUntrustedLocations);
|
||||
|
||||
pdbReaderOptions.loadOptions(options);
|
||||
pdbApplicatorOptions.loadAnalyzerOptions(options);
|
||||
|
@ -312,19 +312,19 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the "allow remote" option that will be used by the analyzer when it is next invoked
|
||||
* Sets the "allow untrusted" option that will be used by the analyzer when it is next invoked
|
||||
* on the specified program.
|
||||
* <p>
|
||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||
* will default to NOT searching remote symbol servers. A headless script could
|
||||
* use this method to allow the analyzer to search remote symbol servers.
|
||||
* will default to NOT searching untrusted symbol servers. A headless script could
|
||||
* use this method to allow the analyzer to search untrusted symbol servers.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param allowRemote boolean flag, true means analyzer can search remote symbol
|
||||
* @param allowUntrusted boolean flag, true means analyzer can search remote symbol
|
||||
* servers
|
||||
*/
|
||||
public static void setAllowRemoteOption(Program program, boolean allowRemote) {
|
||||
PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote);
|
||||
public static void setAllowUntrustedOption(Program program, boolean allowUntrusted) {
|
||||
PdbAnalyzerCommon.setAllowUntrustedOption(NAME, program, allowUntrusted);
|
||||
}
|
||||
|
||||
//==============================================================================================
|
||||
|
|
|
@ -136,11 +136,6 @@ public class ContainerFileSymbolServer implements SymbolServer {
|
|||
return fsFSRL.withPath(filename).toPrettyFullpathString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContainerFileSymbolServer: [ fsrl: %s ]".formatted(fsFSRL);
|
||||
|
|
|
@ -114,11 +114,6 @@ public class DisabledSymbolServer implements SymbolServer {
|
|||
return delegate.getFileLocation(filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return delegate.isLocal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DisabledSymbolServer: [ %s ]", delegate.toString());
|
||||
|
|
|
@ -15,18 +15,16 @@
|
|||
*/
|
||||
package pdb.symbolserver;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Options that control how Pdb files are searched for on a SymbolServer.
|
||||
*/
|
||||
public enum FindOption {
|
||||
/**
|
||||
* Allow connections to remote symbol servers
|
||||
* Allow connections to untrusted symbol servers
|
||||
*/
|
||||
ALLOW_REMOTE,
|
||||
ALLOW_UNTRUSTED,
|
||||
/**
|
||||
* Only return the first result
|
||||
*/
|
||||
|
|
|
@ -25,23 +25,31 @@ import java.net.http.HttpResponse;
|
|||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.net.HttpClients;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.CancelledListener;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.symbolserver.SymbolServer.MutableTrust;
|
||||
|
||||
/**
|
||||
* A {@link SymbolServer} that is accessed via HTTP.
|
||||
* <p>
|
||||
*
|
||||
*/
|
||||
public class HttpSymbolServer extends AbstractSymbolServer {
|
||||
public class HttpSymbolServer extends AbstractSymbolServer implements MutableTrust {
|
||||
private static final String GHIDRA_USER_AGENT = "Ghidra_HttpSymbolServer_client";
|
||||
private static final int HTTP_STATUS_OK = HttpURLConnection.HTTP_OK;
|
||||
private static final int HTTP_REQUEST_TIMEOUT_MS = 10 * 1000; // 10 seconds
|
||||
|
||||
/**
|
||||
* pattern to match an optional "!" in front of a typical url string
|
||||
*/
|
||||
private static final Pattern NAMEPAT = Pattern.compile("(\\!?)(http(s?)://.*)");
|
||||
|
||||
/**
|
||||
* Predicate that tests if the location string is an instance of a HttpSymbolServer location.
|
||||
*
|
||||
|
@ -49,10 +57,48 @@ public class HttpSymbolServer extends AbstractSymbolServer {
|
|||
* @return boolean true if the string should be handled by the HttpSymbolServer class
|
||||
*/
|
||||
public static boolean isHttpSymbolServerLocation(String locationString) {
|
||||
return locationString.startsWith("http://") || locationString.startsWith("https://");
|
||||
return NAMEPAT.matcher(locationString).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new HttpSymbolServer instance from a locationString.
|
||||
*
|
||||
* @param locationString string previously returned by {@link #getName()}
|
||||
* @param context {@link SymbolServerInstanceCreatorContext}
|
||||
* @return new instance
|
||||
*/
|
||||
public static SymbolServer createInstance(String locationString,
|
||||
SymbolServerInstanceCreatorContext context) {
|
||||
Matcher m = NAMEPAT.matcher(locationString);
|
||||
if (!m.matches()) {
|
||||
return null;
|
||||
}
|
||||
boolean isTrusted = "!".equals(m.group(1));
|
||||
String url = m.group(2);
|
||||
return new HttpSymbolServer(URI.create(url), isTrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a trusted http symbol server
|
||||
*
|
||||
* @param url string url
|
||||
* @return new {@link HttpSymbolServer} instance
|
||||
*/
|
||||
public static HttpSymbolServer createTrusted(String url) {
|
||||
return new HttpSymbolServer(URI.create(url), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an untrusted http symbol server
|
||||
* @param url string url
|
||||
* @return new {@link HttpSymbolServer} instance
|
||||
*/
|
||||
public static HttpSymbolServer createUntrusted(String url) {
|
||||
return new HttpSymbolServer(URI.create(url), false);
|
||||
}
|
||||
|
||||
private final URI serverURI;
|
||||
private boolean trusted;
|
||||
|
||||
/**
|
||||
* Creates a new instance of a HttpSymbolServer.
|
||||
|
@ -60,13 +106,29 @@ public class HttpSymbolServer extends AbstractSymbolServer {
|
|||
* @param serverURI URI / URL of the symbol server
|
||||
*/
|
||||
public HttpSymbolServer(URI serverURI) {
|
||||
this(serverURI, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of a HttpSymbolServer.
|
||||
*
|
||||
* @param serverURI URI / URL of the symbol server
|
||||
* @param isTrusted flag, if true the the http server can be trusted when querying and downloading
|
||||
*/
|
||||
public HttpSymbolServer(URI serverURI, boolean isTrusted) {
|
||||
String path = serverURI.getPath();
|
||||
this.serverURI =
|
||||
path.endsWith("/") ? serverURI : serverURI.resolve(serverURI.getPath() + "/");
|
||||
this.trusted = isTrusted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return (trusted ? "!" : "") + serverURI.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptiveName() {
|
||||
return serverURI.toString();
|
||||
}
|
||||
|
||||
|
@ -170,14 +232,19 @@ public class HttpSymbolServer extends AbstractSymbolServer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return false;
|
||||
public boolean isTrusted() {
|
||||
return trusted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrusted(boolean isTrusted) {
|
||||
this.trusted = isTrusted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("HttpSymbolServer: [ url: %s, storageLevel: %d]", serverURI.toString(),
|
||||
storageLevel);
|
||||
return String.format("HttpSymbolServer: [ url: %s, trusted: %b, storageLevel: %d]",
|
||||
serverURI.toString(), trusted, storageLevel);
|
||||
}
|
||||
|
||||
private String logPrefix() {
|
||||
|
|
|
@ -371,11 +371,6 @@ public class LocalSymbolStore extends AbstractSymbolServer implements SymbolStor
|
|||
return new SymbolServerInputStream(new FileInputStream(file), file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("LocalSymbolStore: [ rootDir: %s, storageLevel: %d]",
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.*;
|
|||
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.symbolserver.SymbolServer.StatusRequiresContext;
|
||||
|
||||
/**
|
||||
* A Pdb symbol server / symbol store, similar to the {@link LocalSymbolStore},
|
||||
|
@ -27,7 +28,7 @@ import ghidra.util.task.TaskMonitor;
|
|||
* <p>
|
||||
*
|
||||
*/
|
||||
public class SameDirSymbolStore implements SymbolStore {
|
||||
public class SameDirSymbolStore implements SymbolStore, StatusRequiresContext {
|
||||
|
||||
/**
|
||||
* Descriptive string
|
||||
|
@ -166,11 +167,6 @@ public class SameDirSymbolStore implements SymbolStore {
|
|||
return getFile(filename).getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SameDirSymbolStore: [ dir: %s ]", rootDir);
|
||||
|
|
|
@ -17,8 +17,7 @@ package pdb.symbolserver;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -29,6 +28,27 @@ import ghidra.util.task.TaskMonitor;
|
|||
*
|
||||
*/
|
||||
public interface SymbolServer {
|
||||
/**
|
||||
* Optional add-on interface for {@link SymbolServer}s that flag server types as requiring a
|
||||
* valid context object to be queried for {@link SymbolServer#isValid(TaskMonitor)}
|
||||
*/
|
||||
interface StatusRequiresContext {
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional add-on interface for {@link SymbolServer}s that allow their trusted-ness value to
|
||||
* be modified.
|
||||
*/
|
||||
public interface MutableTrust {
|
||||
|
||||
/**
|
||||
* Sets the trusted attribute of this symbol server.
|
||||
*
|
||||
* @param isTrusted boolean flag, if true this symbolserver will be marked as trusted
|
||||
*/
|
||||
void setTrusted(boolean isTrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the symbol server, suitable to use as the identity of this instance,
|
||||
|
@ -55,6 +75,16 @@ public interface SymbolServer {
|
|||
*/
|
||||
boolean isValid(TaskMonitor monitor);
|
||||
|
||||
/**
|
||||
* Returns true if this {@link SymbolServer} is 'trusted', meaning
|
||||
* it can be searched without security issues / warning the user.
|
||||
*
|
||||
* @return boolean true if this symbolserver is trusted, false if untrusted
|
||||
*/
|
||||
default boolean isTrusted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the raw filename exists in the symbol server.
|
||||
*
|
||||
|
@ -103,10 +133,13 @@ public interface SymbolServer {
|
|||
String getFileLocation(String filename);
|
||||
|
||||
/**
|
||||
* Returns true if this {@link SymbolServer} is 'local', meaning
|
||||
* it can be searched without security issues / warning the user.
|
||||
* Returns the number of configured symbol servers that are considered 'untrusted'.
|
||||
*
|
||||
* @return boolean true if this symbolserver is 'local', false if remote
|
||||
* @param symbolServers list of {@link SymbolServer}s
|
||||
* @return number of untrusted symbol servers
|
||||
*/
|
||||
boolean isLocal();
|
||||
static int getUntrustedCount(Collection<SymbolServer> symbolServers) {
|
||||
return (int) symbolServers.stream().filter(ss -> !ss.isTrusted()).count();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package pdb.symbolserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
@ -167,7 +166,7 @@ public class SymbolServerInstanceCreatorRegistry {
|
|||
registerSymbolServerInstanceCreator(0, DisabledSymbolServer::isDisabledSymbolServerLocation,
|
||||
DisabledSymbolServer::createInstance);
|
||||
registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation,
|
||||
(loc, context) -> new HttpSymbolServer(URI.create(loc)));
|
||||
HttpSymbolServer::createInstance);
|
||||
registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation,
|
||||
SameDirSymbolStore::createInstance);
|
||||
registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation,
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
*/
|
||||
package pdb.symbolserver;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -31,8 +30,7 @@ import pdb.PdbUtils;
|
|||
|
||||
/**
|
||||
* A (lowercase-'S') service that searches for and fetches symbol files
|
||||
* from a set of local and remote {@link SymbolServer symbolservers}. (not to be
|
||||
* confused with a Plugin service)
|
||||
* from a set of {@link SymbolServer symbolservers}. (not to be confused with a Plugin service)
|
||||
* <p>
|
||||
* Instances of this class are meant to be easily created when needed
|
||||
* and just as easily thrown away when not used or when the search
|
||||
|
@ -50,9 +48,8 @@ public class SymbolServerService {
|
|||
/**
|
||||
* Creates a new SymbolServerService instance.
|
||||
* <p>
|
||||
* @param symbolStore a {@link SymbolStore} - where all
|
||||
* remote files are placed when downloaded. Also treated as a SymbolServer
|
||||
* and searched first
|
||||
* @param symbolStore a {@link SymbolStore} - where all remote files are placed when
|
||||
* downloaded. Also treated as a SymbolServer and searched first
|
||||
* @param symbolServers a list of {@link SymbolServer symbol servers} - searched in order
|
||||
*/
|
||||
public SymbolServerService(SymbolStore symbolStore, List<SymbolServer> symbolServers) {
|
||||
|
@ -91,19 +88,6 @@ public class SymbolServerService {
|
|||
return new ArrayList<>(symbolServers.subList(1, symbolServers.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of configured symbol servers that are considered 'remote'.
|
||||
* @return number of remote symbol servers
|
||||
*/
|
||||
public int getRemoteSymbolServerCount() {
|
||||
int remoteSymbolServerCount = (int) getSymbolServers()
|
||||
.stream()
|
||||
.filter(ss -> !ss.isLocal())
|
||||
.count();
|
||||
|
||||
return remoteSymbolServerCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all {@link SymbolServer symbol servers} for a matching pdb symbol file.
|
||||
*
|
||||
|
@ -150,9 +134,9 @@ public class SymbolServerService {
|
|||
|
||||
for_each_symbol_server_loop: for (SymbolServer symbolServer : symbolServers) {
|
||||
monitor.checkCancelled();
|
||||
if (!symbolServer.isLocal() && !findOptions.contains(FindOption.ALLOW_REMOTE)) {
|
||||
if (!symbolServer.isTrusted() && !findOptions.contains(FindOption.ALLOW_UNTRUSTED)) {
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": skipping non-local symbol server " +
|
||||
logPrefix() + ": skipping untrusted symbol server " +
|
||||
symbolServer.getDescriptiveName());
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -15,11 +15,37 @@
|
|||
*/
|
||||
package pdb.symbolserver.ui;
|
||||
|
||||
import java.util.List;
|
||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.button.BrowseButton;
|
||||
import docking.widgets.button.GButton;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.label.GHtmlLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.table.GTable;
|
||||
import docking.widgets.textfield.HintTextField;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import ghidra.util.task.*;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
import resources.Icons;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Dialog that allows the user to configure the Pdb search locations and symbol directory
|
||||
|
@ -32,13 +58,40 @@ public class ConfigPdbDialog extends DialogComponentProvider {
|
|||
return choosePdbDialog.wasSuccess;
|
||||
}
|
||||
|
||||
private static final String MS_SYMBOLSERVER_ENVVAR = "_NT_SYMBOL_PATH";
|
||||
|
||||
private static final Dimension BUTTON_SIZE = new Dimension(32, 32);
|
||||
|
||||
private List<WellKnownSymbolServerLocation> knownSymbolServers =
|
||||
WellKnownSymbolServerLocation.loadAll();
|
||||
|
||||
private SymbolStore localSymbolStore;
|
||||
private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext =
|
||||
SymbolServerInstanceCreatorRegistry.getInstance().getContext();
|
||||
private SymbolServerTableModel tableModel;
|
||||
|
||||
private SymbolServerPanel symbolServerConfigPanel;
|
||||
private boolean wasSuccess;
|
||||
private boolean configChanged;
|
||||
|
||||
public ConfigPdbDialog() {
|
||||
super("Configure Symbol Server Search", true, false, true, false);
|
||||
super("Configure Symbol Server Search", true, false, true, true);
|
||||
|
||||
build();
|
||||
|
||||
tableModel.addTableModelListener(e -> updateButtonEnablement());
|
||||
setupInitialSymbolServer();
|
||||
}
|
||||
|
||||
private void setupInitialSymbolServer() {
|
||||
SymbolServerService temporarySymbolServerService =
|
||||
PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext);
|
||||
if (temporarySymbolServerService
|
||||
.getSymbolStore() instanceof LocalSymbolStore tempLocalSymbolStore) {
|
||||
setSymbolStorageLocation(tempLocalSymbolStore.getRootDir(), false);
|
||||
tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers());
|
||||
setConfigChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,27 +101,33 @@ public class ConfigPdbDialog extends DialogComponentProvider {
|
|||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
if (symbolServerConfigPanel.isConfigChanged()) {
|
||||
symbolServerConfigPanel.saveConfig();
|
||||
if (isConfigChanged()) {
|
||||
saveConfig();
|
||||
}
|
||||
wasSuccess = true;
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dialogShown() {
|
||||
TableColumnInitializer.initializeTableColumns(symbolServerConfigPanel.table, tableModel);
|
||||
symbolServerConfigPanel.refreshSymbolServerLocationStatus(true /* only query trusted */);
|
||||
}
|
||||
|
||||
private void build() {
|
||||
symbolServerConfigPanel = new SymbolServerPanel(this::onSymbolServerServiceChange,
|
||||
SymbolServerInstanceCreatorRegistry.getInstance().getContext());
|
||||
tableModel = new SymbolServerTableModel();
|
||||
|
||||
symbolServerConfigPanel = new SymbolServerPanel();
|
||||
|
||||
addButtons();
|
||||
addWorkPanel(symbolServerConfigPanel);
|
||||
setRememberSize(false);
|
||||
okButton.setEnabled(symbolServerConfigPanel.getSymbolServerService() != null);
|
||||
setMinimumSize(400, 250);
|
||||
okButton.setEnabled(hasSymbolServer());
|
||||
}
|
||||
|
||||
private void onSymbolServerServiceChange(SymbolServerService newService) {
|
||||
okButton.setEnabled(newService != null);
|
||||
rootPanel.revalidate();
|
||||
private void updateButtonEnablement() {
|
||||
okButton.setEnabled(hasSymbolServer());
|
||||
symbolServerConfigPanel.updatePanelButtonEnablement();
|
||||
}
|
||||
|
||||
private void addButtons() {
|
||||
|
@ -81,7 +140,16 @@ public class ConfigPdbDialog extends DialogComponentProvider {
|
|||
* Screen shot usage only
|
||||
*/
|
||||
public void pushAddLocationButton() {
|
||||
symbolServerConfigPanel.pushAddLocationButton();
|
||||
symbolServerConfigPanel.addLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen shot usage only
|
||||
*
|
||||
* @param list fake well known symbol servers
|
||||
*/
|
||||
public void setWellknownSymbolServers(List<WellKnownSymbolServerLocation> list) {
|
||||
knownSymbolServers = list;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,9 +158,520 @@ public class ConfigPdbDialog extends DialogComponentProvider {
|
|||
* @param fakeDirectoryText fake text to display in the storage directory text field
|
||||
* @param symbolServers list of symbol servers to force set
|
||||
*/
|
||||
public void setSymbolServerService(String fakeDirectoryText,
|
||||
List<SymbolServer> symbolServers) {
|
||||
symbolServerConfigPanel.setSymbolServers(symbolServers);
|
||||
symbolServerConfigPanel.setSymbolStorageDirectoryTextOnly(fakeDirectoryText);
|
||||
public void setSymbolServerService(String fakeDirectoryText, List<SymbolServer> symbolServers) {
|
||||
setSymbolServers(symbolServers);
|
||||
setSymbolStorageLocationPath(fakeDirectoryText);
|
||||
}
|
||||
|
||||
private void setSymbolStorageLocationPath(String path) {
|
||||
symbolServerConfigPanel.symbolStorageLocationTextField.setText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link SymbolServerService} instance representing the currently
|
||||
* displayed configuration, or null if the displayed configuration is not valid.
|
||||
*
|
||||
* @return new {@link SymbolServerService} or null
|
||||
*/
|
||||
SymbolServerService getSymbolServerService() {
|
||||
return (localSymbolStore != null)
|
||||
? new SymbolServerService(localSymbolStore, tableModel.getSymbolServers())
|
||||
: null;
|
||||
}
|
||||
|
||||
boolean hasSymbolServer() {
|
||||
return localSymbolStore != null;
|
||||
}
|
||||
|
||||
void setSymbolServers(List<SymbolServer> symbolServers) {
|
||||
tableModel.setSymbolServers(symbolServers);
|
||||
}
|
||||
|
||||
private void setSymbolStorageLocation(File symbolStorageDir, boolean allowGUIPrompt) {
|
||||
if (symbolStorageDir == null) {
|
||||
return;
|
||||
}
|
||||
if (!symbolStorageDir.exists()) {
|
||||
if (!allowGUIPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
int opt =
|
||||
OptionDialog.showOptionDialog(rootPanel, "Create Local Symbol Storage Directory?",
|
||||
"<html>Symbol storage directory<br>" +
|
||||
HTMLUtilities.escapeHTML(symbolStorageDir.getPath()) +
|
||||
"<br>does not exist. Create?",
|
||||
"Yes", OptionDialog.QUESTION_MESSAGE);
|
||||
if (opt == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FileUtilities.checkedMkdirs(symbolStorageDir);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, rootPanel, "Failure",
|
||||
"Failed to create symbol storage directory %s: %s".formatted(symbolStorageDir,
|
||||
e.getMessage()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowGUIPrompt && isEmptyDirectory(symbolStorageDir)) {
|
||||
if (OptionDialog.showYesNoDialog(rootPanel, "Initialize Symbol Storage Directory?",
|
||||
"<html>Initialize new directory as Microsoft symbol storage directory?<br>" +
|
||||
"(Answer <b>No</b> to leave as unorganized storage directory)") == OptionDialog.YES_OPTION) {
|
||||
try {
|
||||
LocalSymbolStore.create(symbolStorageDir,
|
||||
1 /* level1 MS symbol storage directory */);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, rootPanel, "Initialize Failure",
|
||||
"Failed to initialize symbol storage directory " + symbolStorageDir, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localSymbolStore =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(symbolStorageDir.getPath(), symbolServerInstanceCreatorContext,
|
||||
SymbolStore.class);
|
||||
setSymbolStorageLocationPath(symbolStorageDir.getPath());
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
void executeMonitoredRunnable(String taskTitle, boolean canCancel, boolean hasProgress,
|
||||
int delay, MonitoredRunnable runnable) {
|
||||
Task task = new Task(taskTitle, canCancel, hasProgress, false) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
runnable.monitoredRun(monitor);
|
||||
}
|
||||
};
|
||||
executeProgressTask(task, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* The union of the changed status of the local storage path and the additional
|
||||
* search paths table model changed status.
|
||||
*
|
||||
* @return boolean true if the config has changed
|
||||
*/
|
||||
boolean isConfigChanged() {
|
||||
return configChanged || tableModel.isDataChanged();
|
||||
}
|
||||
|
||||
void setConfigChanged(boolean configChanged) {
|
||||
this.configChanged = configChanged;
|
||||
tableModel.setDataChanged(configChanged);
|
||||
}
|
||||
|
||||
/* package */ void saveConfig() {
|
||||
SymbolServerService temporarySymbolServerService = getSymbolServerService();
|
||||
if (temporarySymbolServerService != null) {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(temporarySymbolServerService);
|
||||
Preferences.store();
|
||||
setConfigChanged(false);
|
||||
updateButtonEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
class SymbolServerPanel extends JPanel {
|
||||
|
||||
private GTable table;
|
||||
private JPanel additionalSearchLocationsPanel;
|
||||
private JPanel defaultConfigNotice;
|
||||
|
||||
private JButton refreshSearchLocationsStatusButton;
|
||||
private JButton moveLocationUpButton;
|
||||
private JButton moveLocationDownButton;
|
||||
private JButton deleteLocationButton;
|
||||
private JButton addLocationButton;
|
||||
private JPanel symbolStorageLocationPanel;
|
||||
private HintTextField symbolStorageLocationTextField;
|
||||
private JButton chooseSymbolStorageLocationButton;
|
||||
private JButton saveSearchLocationsButton;
|
||||
|
||||
SymbolServerPanel() {
|
||||
build();
|
||||
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(this,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config"));
|
||||
}
|
||||
|
||||
private void build() {
|
||||
setLayout(new BorderLayout());
|
||||
setBorder(BorderFactory.createTitledBorder("Symbol Server Search Config"));
|
||||
|
||||
buildSymbolStorageLocationPanel();
|
||||
JPanel tableButtonPanel = buildButtonPanel();
|
||||
JScrollPane tableScrollPane = buildTable();
|
||||
defaultConfigNotice = new JPanel();
|
||||
GHtmlLabel label = new GHtmlLabel("<html><center><font color=\"" +
|
||||
Messages.ERROR.toHexString() + "\"><br>Missing / invalid configuration.<br><br>" +
|
||||
"Using default search location:<br>Program's Import Location<br>");
|
||||
label.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
defaultConfigNotice.add(label);
|
||||
defaultConfigNotice.setPreferredSize(tableScrollPane.getPreferredSize());
|
||||
|
||||
additionalSearchLocationsPanel = new JPanel();
|
||||
additionalSearchLocationsPanel
|
||||
.setLayout(new BoxLayout(additionalSearchLocationsPanel, BoxLayout.Y_AXIS));
|
||||
additionalSearchLocationsPanel.add(tableButtonPanel);
|
||||
additionalSearchLocationsPanel.add(tableScrollPane);
|
||||
|
||||
add(symbolStorageLocationPanel, BorderLayout.NORTH);
|
||||
add(additionalSearchLocationsPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private void updateLayout(boolean showTable) {
|
||||
if (showTable == (additionalSearchLocationsPanel.getParent() != null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
remove(additionalSearchLocationsPanel);
|
||||
remove(defaultConfigNotice);
|
||||
add(showTable ? additionalSearchLocationsPanel : defaultConfigNotice,
|
||||
BorderLayout.CENTER);
|
||||
invalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void refreshSymbolServerLocationStatus(boolean trustedOnly) {
|
||||
executeMonitoredRunnable("Refresh Symbol Server Location Status", true, true, 0,
|
||||
monitor -> {
|
||||
List<SymbolServerRow> rowsCopy = new ArrayList<>(tableModel.getModelData());
|
||||
monitor.initialize(rowsCopy.size(), "Refreshing symbol server status");
|
||||
try {
|
||||
for (SymbolServerRow row : rowsCopy) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
monitor.setMessage("Checking " + row.getSymbolServer().getName());
|
||||
monitor.incrementProgress();
|
||||
|
||||
SymbolServer symbolServer = row.getSymbolServer();
|
||||
if (symbolServer instanceof SymbolServer.StatusRequiresContext || // we don't have program context here in the config dialog
|
||||
(trustedOnly && !symbolServer.isTrusted())) {
|
||||
continue;
|
||||
}
|
||||
row.setStatus(symbolServer.isValid(monitor) ? VALID : INVALID);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Swing.runLater(() -> tableModel.fireTableDataChanged());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private JScrollPane buildTable() {
|
||||
table = new GTable(tableModel);
|
||||
table.setVisibleRowCount(4);
|
||||
table.setUserSortingEnabled(false);
|
||||
table.getSelectionManager()
|
||||
.addListSelectionListener(e -> updatePanelButtonEnablement());
|
||||
|
||||
table.setPreferredScrollableViewportSize(new Dimension(500, 100));
|
||||
|
||||
return new JScrollPane(table);
|
||||
}
|
||||
|
||||
private JPanel buildButtonPanel() {
|
||||
|
||||
refreshSearchLocationsStatusButton = createImageButton(Icons.REFRESH_ICON,
|
||||
"Refresh Status", "SymbolServerConfig Refresh Status");
|
||||
refreshSearchLocationsStatusButton.addActionListener(
|
||||
e -> refreshSymbolServerLocationStatus(false /* query all */));
|
||||
|
||||
moveLocationUpButton =
|
||||
createImageButton(Icons.UP_ICON, "Up", "SymbolServerConfig MoveUpDown");
|
||||
moveLocationUpButton.addActionListener(e -> moveLocation(-1));
|
||||
moveLocationUpButton.setToolTipText("Move location up");
|
||||
|
||||
moveLocationDownButton =
|
||||
createImageButton(Icons.DOWN_ICON, "Down", "SymbolServerConfig MoveUpDown");
|
||||
moveLocationDownButton.addActionListener(e -> moveLocation(1));
|
||||
moveLocationDownButton.setToolTipText("Move location down");
|
||||
|
||||
deleteLocationButton =
|
||||
createImageButton(Icons.DELETE_ICON, "Delete", "SymbolServerConfig Delete");
|
||||
deleteLocationButton.addActionListener(e -> deleteLocation());
|
||||
|
||||
addLocationButton = createImageButton(Icons.ADD_ICON, "Add", "SymbolServerConfig Add");
|
||||
addLocationButton.addActionListener(e -> addLocation());
|
||||
|
||||
saveSearchLocationsButton =
|
||||
createImageButton(Icons.SAVE_ICON, "Save Configuration", "SymbolServerConfig Save");
|
||||
saveSearchLocationsButton.addActionListener(e -> saveConfig());
|
||||
|
||||
JPanel tableButtonPanel = new JPanel();
|
||||
tableButtonPanel.setLayout(new BoxLayout(tableButtonPanel, BoxLayout.X_AXIS));
|
||||
tableButtonPanel.add(new GLabel("Additional Search Paths:"));
|
||||
tableButtonPanel.add(Box.createHorizontalGlue());
|
||||
tableButtonPanel.add(addLocationButton);
|
||||
tableButtonPanel.add(deleteLocationButton);
|
||||
tableButtonPanel.add(moveLocationUpButton);
|
||||
tableButtonPanel.add(moveLocationDownButton);
|
||||
tableButtonPanel.add(refreshSearchLocationsStatusButton);
|
||||
tableButtonPanel.add(saveSearchLocationsButton);
|
||||
|
||||
return tableButtonPanel;
|
||||
}
|
||||
|
||||
private JPanel buildSymbolStorageLocationPanel() {
|
||||
symbolStorageLocationTextField = new HintTextField(" Required ");
|
||||
symbolStorageLocationTextField.setEditable(false);
|
||||
symbolStorageLocationTextField.setToolTipText(
|
||||
"User-specified directory where PDB files are stored. Required.");
|
||||
|
||||
chooseSymbolStorageLocationButton = new BrowseButton();
|
||||
chooseSymbolStorageLocationButton.addActionListener(e -> chooseSymbolStorageLocation());
|
||||
|
||||
symbolStorageLocationPanel = new JPanel(new PairLayout(5, 5));
|
||||
GLabel symbolStorageLocLabel =
|
||||
new GLabel("Local Symbol Storage:", SwingConstants.RIGHT);
|
||||
symbolStorageLocLabel.setToolTipText(symbolStorageLocationTextField.getToolTipText());
|
||||
|
||||
symbolStorageLocationPanel.add(symbolStorageLocLabel);
|
||||
symbolStorageLocationPanel.add(LoadPdbDialog.join(null, symbolStorageLocationTextField,
|
||||
chooseSymbolStorageLocationButton));
|
||||
return symbolStorageLocationPanel;
|
||||
}
|
||||
|
||||
private void updatePanelButtonEnablement() {
|
||||
boolean hasLocalSymbolStore = localSymbolStore != null;
|
||||
boolean singleRow = table.getSelectedRowCount() == 1;
|
||||
boolean moreThanOneRow = table.getRowCount() > 1;
|
||||
|
||||
refreshSearchLocationsStatusButton
|
||||
.setEnabled(hasLocalSymbolStore && !tableModel.isEmpty());
|
||||
moveLocationUpButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow);
|
||||
moveLocationDownButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow);
|
||||
addLocationButton.setEnabled(hasLocalSymbolStore);
|
||||
deleteLocationButton.setEnabled(hasLocalSymbolStore && table.getSelectedRowCount() > 0);
|
||||
saveSearchLocationsButton.setEnabled(hasLocalSymbolStore && isConfigChanged());
|
||||
updateLayout(hasLocalSymbolStore);
|
||||
}
|
||||
|
||||
private void chooseSymbolStorageLocation() {
|
||||
GhidraFileChooser chooser = getChooser();
|
||||
File f = chooser.getSelectedFile();
|
||||
chooser.dispose();
|
||||
|
||||
if (f != null) {
|
||||
configChanged = true;
|
||||
setSymbolStorageLocation(f, true);
|
||||
updateButtonEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
private void importLocations() {
|
||||
String envVar = (String) JOptionPane.showInputDialog(this,
|
||||
"<html>Enter value:<br><br>Example: SVR*c:\\symbols*https://msdl.microsoft.com/download/symbols/<br><br>",
|
||||
"Enter Symbol Server Search Path Value", JOptionPane.QUESTION_MESSAGE, null, null,
|
||||
Objects.requireNonNullElse(System.getenv(MS_SYMBOLSERVER_ENVVAR), ""));
|
||||
if (envVar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> symbolServerPaths = getSymbolPathsFromEnvStr(envVar);
|
||||
if (!symbolServerPaths.isEmpty()) {
|
||||
// if the first item in the path list looks like a local symbol storage path,
|
||||
// allow the user to set it as the storage dir (and remove it from the elements
|
||||
// that will be added to the search list)
|
||||
String firstSearchPath = symbolServerPaths.get(0);
|
||||
SymbolServer symbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext);
|
||||
if (symbolServer instanceof LocalSymbolStore localSymbolStore &&
|
||||
localSymbolStore.isValid()) {
|
||||
int choice =
|
||||
OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location",
|
||||
"Set symbol storage location to " + firstSearchPath + "?");
|
||||
if (choice == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
if (choice == OptionDialog.YES_OPTION) {
|
||||
symbolServerPaths.remove(0);
|
||||
configChanged = true;
|
||||
setSymbolStorageLocation(localSymbolStore.getRootDir(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableModel.addSymbolServers(
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.createSymbolServersFromPathList(symbolServerPaths,
|
||||
symbolServerInstanceCreatorContext));
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
private void addLocation() {
|
||||
JPopupMenu menu = createAddLocationPopupMenu();
|
||||
menu.show(addLocationButton, 0, 0);
|
||||
}
|
||||
|
||||
private JPopupMenu createAddLocationPopupMenu() {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
JMenuItem addDirMenuItem = new JMenuItem("Directory");
|
||||
addDirMenuItem.addActionListener(e -> addDirectoryLocation());
|
||||
menu.add(addDirMenuItem);
|
||||
|
||||
JMenuItem addURLMenuItem = new JMenuItem("URL");
|
||||
addURLMenuItem.addActionListener(e -> addUrlLocation());
|
||||
menu.add(addURLMenuItem);
|
||||
|
||||
JMenuItem addProgLocMenuItem =
|
||||
new JMenuItem(SameDirSymbolStore.PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR);
|
||||
addProgLocMenuItem.addActionListener(e -> addSameDirLocation());
|
||||
menu.add(addProgLocMenuItem);
|
||||
|
||||
JMenuItem importEnvMenuItem = new JMenuItem("Import _NT_SYMBOL_PATH");
|
||||
importEnvMenuItem.addActionListener(e -> importLocations());
|
||||
menu.add(importEnvMenuItem);
|
||||
|
||||
if (!knownSymbolServers.isEmpty()) {
|
||||
menu.add(new JSeparator());
|
||||
for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) {
|
||||
JMenuItem mi = new JMenuItem(ssloc.location());
|
||||
mi.addActionListener(e -> addKnownLocation(ssloc));
|
||||
mi.setToolTipText(" [from " + ssloc.fileOrigin() + "]");
|
||||
menu.add(mi);
|
||||
}
|
||||
}
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(menu, new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig_Add"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void addSameDirLocation() {
|
||||
SameDirSymbolStore sameDirSymbolStore =
|
||||
new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir());
|
||||
tableModel.addSymbolServer(sameDirSymbolStore);
|
||||
}
|
||||
|
||||
private void addKnownLocation(WellKnownSymbolServerLocation ssloc) {
|
||||
SymbolServer symbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(ssloc.location(), symbolServerInstanceCreatorContext);
|
||||
if (symbolServer != null) {
|
||||
tableModel.addSymbolServer(symbolServer);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUrlLocation() {
|
||||
String urlLocationString = OptionDialog.showInputSingleLineDialog(this, "Enter URL",
|
||||
"Enter the URL of a Symbol Server: ", "https://");
|
||||
if (urlLocationString == null || urlLocationString.isBlank()) {
|
||||
return;
|
||||
}
|
||||
urlLocationString = urlLocationString.toLowerCase();
|
||||
if (!(urlLocationString.startsWith("http://") ||
|
||||
urlLocationString.startsWith("https://"))) {
|
||||
Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
HttpSymbolServer httpSymbolServer =
|
||||
HttpSymbolServer.createUntrusted(urlLocationString);
|
||||
tableModel.addSymbolServer(httpSymbolServer);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDirectoryLocation() {
|
||||
File dir =
|
||||
FilePromptDialog.chooseDirectory("Enter Path", "Symbol Storage Location: ", null);
|
||||
if (dir == null) {
|
||||
return;
|
||||
}
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
Msg.showError(this, this, "Bad path", "Invalid path: " + dir);
|
||||
return;
|
||||
}
|
||||
LocalSymbolStore symbolStore = new LocalSymbolStore(dir);
|
||||
tableModel.addSymbolServer(symbolStore);
|
||||
}
|
||||
|
||||
private void deleteLocation() {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
tableModel.deleteRows(table.getSelectedRows());
|
||||
if (selectedRow >= 0 && selectedRow < table.getRowCount()) {
|
||||
table.selectRow(selectedRow);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveLocation(int delta) {
|
||||
if (table.getSelectedRowCount() == 1) {
|
||||
tableModel.moveRow(table.getSelectedRow(), delta);
|
||||
}
|
||||
}
|
||||
|
||||
private GhidraFileChooser getChooser() {
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(this);
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
chooser.setApproveButtonText("Choose");
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooser.setTitle("Select Symbol Storage Dir");
|
||||
|
||||
return chooser;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
private static JButton createImageButton(Icon buttonIcon, String alternateText,
|
||||
String helpLoc) {
|
||||
|
||||
JButton button = new GButton(buttonIcon);
|
||||
button.setToolTipText(alternateText);
|
||||
button.setPreferredSize(BUTTON_SIZE);
|
||||
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(button, new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, helpLoc));
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given file path is a directory that contains no files.
|
||||
* <p>
|
||||
*
|
||||
* @param directory path to a location on the file system
|
||||
* @return true if is a directory and it contains no files
|
||||
*/
|
||||
private static boolean isEmptyDirectory(File directory) {
|
||||
if (directory.isDirectory()) {
|
||||
File[] dirContents = directory.listFiles();
|
||||
return dirContents != null && dirContents.length == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<String> getSymbolPathsFromEnvStr(String envString) {
|
||||
// Expect the environment string to be in the MS symbol server search path form:
|
||||
// srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols
|
||||
// srv*c:\symbols*https://msdl.microsoft.com/download/symbols;srv*c:\additional*https://symbol.server.tld/
|
||||
String[] envParts = envString.split("[*;]");
|
||||
List<String> results = new ArrayList<>();
|
||||
Set<String> locationStringDeduplicationSet = new HashSet<>();
|
||||
for (String envPart : envParts) {
|
||||
String locationString = envPart.trim();
|
||||
if (!locationString.isBlank() && !locationString.equalsIgnoreCase("srv") &&
|
||||
!locationStringDeduplicationSet.contains(locationString)) {
|
||||
results.add(locationString);
|
||||
locationStringDeduplicationSet.add(locationString);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -72,6 +72,8 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
|||
ExtensionFileFilter.forExtensions("Microsoft Program Databases", "pdb", "pd_", "pdb.xml");
|
||||
|
||||
private static final SymbolFileInfo UNKNOWN_SYMFILE = makeUnknownSymbolFileInstance("");
|
||||
private static final List<WellKnownSymbolServerLocation> knownSymbolServers =
|
||||
WellKnownSymbolServerLocation.loadAll();
|
||||
|
||||
public static class LoadPdbResults {
|
||||
public File pdbFile;
|
||||
|
@ -270,7 +272,7 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
|||
return SymbolFileInfo.fromValues(pdbPath, uid, age);
|
||||
}
|
||||
|
||||
private void searchForPdbs(boolean allowRemote) {
|
||||
private void searchForPdbs(boolean allowUntrusted) {
|
||||
if (pdbAgeTextField.getText().isBlank() ||
|
||||
pdbAgeTextField.getValue() > NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) {
|
||||
Msg.showWarn(this, null, "Bad PDB Age", "Invalid PDB Age value");
|
||||
|
@ -282,8 +284,8 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
|||
return;
|
||||
}
|
||||
Set<FindOption> findOptions = symbolFilePanel.getFindOptions();
|
||||
if (allowRemote) {
|
||||
findOptions.add(FindOption.ALLOW_REMOTE);
|
||||
if (allowUntrusted) {
|
||||
findOptions.add(FindOption.ALLOW_UNTRUSTED);
|
||||
}
|
||||
executeMonitoredRunnable("Search for PDBs", true, true, 0, monitor -> {
|
||||
try {
|
||||
|
@ -316,10 +318,11 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
|||
setHelpLocation(new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Load PDB File"));
|
||||
|
||||
addStatusTextSupplier(() -> lastSearchOptions != null && advancedToggleButton.isSelected()
|
||||
? SymbolServerPanel.getSymbolServerWarnings(symbolServerService.getSymbolServers())
|
||||
? WellKnownSymbolServerLocation.getWarningsFor(knownSymbolServers,
|
||||
symbolServerService.getSymbolServers())
|
||||
: null);
|
||||
addStatusTextSupplier(this::getSelectedPdbNoticeText);
|
||||
addStatusTextSupplier(this::getAllowRemoteWarning);
|
||||
addStatusTextSupplier(this::getAllowUntrustedWarning);
|
||||
addStatusTextSupplier(this::getFoundCountInfo);
|
||||
|
||||
addButtons();
|
||||
|
@ -600,12 +603,13 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private StatusText getAllowRemoteWarning() {
|
||||
int remoteSymbolServerCount = symbolServerService.getRemoteSymbolServerCount();
|
||||
private StatusText getAllowUntrustedWarning() {
|
||||
int untrustedSymbolServerCount =
|
||||
SymbolServer.getUntrustedCount(symbolServerService.getSymbolServers());
|
||||
return lastSearchOptions != null && advancedToggleButton.isSelected() &&
|
||||
remoteSymbolServerCount != 0 && !lastSearchOptions.contains(FindOption.ALLOW_REMOTE)
|
||||
untrustedSymbolServerCount != 0 && !lastSearchOptions.contains(FindOption.ALLOW_UNTRUSTED)
|
||||
? new StatusText(
|
||||
"Remote servers were excluded. Use \"Search All\" button to also search remote servers.",
|
||||
"Untrusted servers were excluded. Use \"Search All\" button to also include untrusted servers.",
|
||||
MessageType.INFO, false)
|
||||
: null;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import pdb.symbolserver.FindOption;
|
|||
*/
|
||||
class SymbolFilePanel extends JPanel {
|
||||
interface SearchCallback {
|
||||
void searchForPdbs(boolean allowRemote);
|
||||
void searchForPdbs(boolean allowUntrusted);
|
||||
}
|
||||
|
||||
static final String SEARCH_OPTIONS_HELP_ANCHOR = "PDB_Search_Search_Options";
|
||||
|
@ -149,10 +149,10 @@ class SymbolFilePanel extends JPanel {
|
|||
}
|
||||
|
||||
private JPanel buildButtonPanel() {
|
||||
searchLocalButton = new JButton("Search Local");
|
||||
searchLocalButton.setToolTipText("Search local symbol servers only.");
|
||||
searchLocalButton = new JButton("Search");
|
||||
searchLocalButton.setToolTipText("Search trusted symbol servers only.");
|
||||
searchAllButton = new JButton("Search All");
|
||||
searchAllButton.setToolTipText("Search local and remote symbol servers.");
|
||||
searchAllButton.setToolTipText("Search trusted and untrusted symbol servers.");
|
||||
|
||||
ignorePdbUid = new GCheckBox("Ignore GUID/ID");
|
||||
ignorePdbUid.setToolTipText(
|
||||
|
|
|
@ -1,590 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pdb.symbolserver.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.button.BrowseButton;
|
||||
import docking.widgets.button.GButton;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.label.GHtmlLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.table.GTable;
|
||||
import docking.widgets.textfield.HintTextField;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog.StatusText;
|
||||
import resources.Icons;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Panel that allows the user to configure a SymbolServerService: a local
|
||||
* symbol storage directory and a list of search locations.
|
||||
*/
|
||||
class SymbolServerPanel extends JPanel {
|
||||
private static final String MS_SYMBOLSERVER_ENVVAR = "_NT_SYMBOL_PATH";
|
||||
|
||||
private static final Dimension BUTTON_SIZE = new Dimension(32, 32);
|
||||
|
||||
private static List<WellKnownSymbolServerLocation> knownSymbolServers =
|
||||
WellKnownSymbolServerLocation.loadAll();
|
||||
|
||||
private SymbolStore localSymbolStore;
|
||||
private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext;
|
||||
|
||||
private SymbolServerTableModel tableModel;
|
||||
private GTable table;
|
||||
private JPanel additionalSearchLocationsPanel;
|
||||
private JPanel defaultConfigNotice;
|
||||
private Consumer<SymbolServerService> changeCallback;
|
||||
|
||||
private JButton refreshSearchLocationsStatusButton;
|
||||
private JButton moveLocationUpButton;
|
||||
private JButton moveLocationDownButton;
|
||||
private JButton deleteLocationButton;
|
||||
private JButton addLocationButton;
|
||||
private JPanel symbolStorageLocationPanel;
|
||||
private HintTextField symbolStorageLocationTextField;
|
||||
private JButton chooseSymbolStorageLocationButton;
|
||||
private JButton saveSearchLocationsButton;
|
||||
private boolean configChanged;
|
||||
|
||||
SymbolServerPanel(Consumer<SymbolServerService> changeCallback,
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) {
|
||||
this.symbolServerInstanceCreatorContext = symbolServerInstanceCreatorContext;
|
||||
|
||||
build();
|
||||
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(this,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config"));
|
||||
|
||||
SymbolServerService temporarySymbolServerService =
|
||||
PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext);
|
||||
if (temporarySymbolServerService
|
||||
.getSymbolStore() instanceof LocalSymbolStore tempLocalSymbolStore) {
|
||||
setSymbolStorageLocation(tempLocalSymbolStore.getRootDir(), false);
|
||||
}
|
||||
tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers());
|
||||
setConfigChanged(false);
|
||||
|
||||
this.changeCallback = changeCallback;
|
||||
}
|
||||
|
||||
private void build() {
|
||||
setLayout(new BorderLayout());
|
||||
setBorder(BorderFactory.createTitledBorder("Symbol Server Search Config"));
|
||||
|
||||
buildSymbolStorageLocationPanel();
|
||||
JPanel buttonPanel = buildButtonPanel();
|
||||
JScrollPane tableScrollPane = buildTable();
|
||||
defaultConfigNotice = new JPanel();
|
||||
GHtmlLabel label = new GHtmlLabel("<html><center><font color=\"" +
|
||||
Messages.ERROR.toHexString() + "\"><br>Missing / invalid configuration.<br><br>" +
|
||||
"Using default search location:<br>Program's Import Location<br>");
|
||||
label.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
defaultConfigNotice.add(label);
|
||||
defaultConfigNotice.setPreferredSize(tableScrollPane.getPreferredSize());
|
||||
|
||||
additionalSearchLocationsPanel = new JPanel();
|
||||
additionalSearchLocationsPanel
|
||||
.setLayout(new BoxLayout(additionalSearchLocationsPanel, BoxLayout.Y_AXIS));
|
||||
additionalSearchLocationsPanel.add(buttonPanel);
|
||||
additionalSearchLocationsPanel.add(tableScrollPane);
|
||||
|
||||
add(symbolStorageLocationPanel, BorderLayout.NORTH);
|
||||
add(additionalSearchLocationsPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private void updateLayout(boolean showTable) {
|
||||
if (showTable == (additionalSearchLocationsPanel.getParent() != null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
remove(additionalSearchLocationsPanel);
|
||||
remove(defaultConfigNotice);
|
||||
add(showTable ? additionalSearchLocationsPanel : defaultConfigNotice, BorderLayout.CENTER);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link SymbolServerService} instance representing the currently
|
||||
* displayed configuration, or null if the displayed configuration is not valid.
|
||||
*
|
||||
* @return new {@link SymbolServerService} or null
|
||||
*/
|
||||
SymbolServerService getSymbolServerService() {
|
||||
return (localSymbolStore != null)
|
||||
? new SymbolServerService(localSymbolStore, tableModel.getSymbolServers())
|
||||
: null;
|
||||
}
|
||||
|
||||
void setSymbolServers(List<SymbolServer> symbolServers) {
|
||||
tableModel.setSymbolServers(symbolServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* The union of the changed status of the local storage path and the additional
|
||||
* search paths table model changed status.
|
||||
*
|
||||
* @return boolean true if the config has changed
|
||||
*/
|
||||
boolean isConfigChanged() {
|
||||
return configChanged || tableModel.isDataChanged();
|
||||
}
|
||||
|
||||
void setConfigChanged(boolean configChanged) {
|
||||
this.configChanged = configChanged;
|
||||
tableModel.setDataChanged(configChanged);
|
||||
}
|
||||
|
||||
private JScrollPane buildTable() {
|
||||
tableModel = new SymbolServerTableModel();
|
||||
table = new GTable(tableModel);
|
||||
table.setVisibleRowCount(4);
|
||||
table.setUserSortingEnabled(false);
|
||||
table.getSelectionManager().addListSelectionListener(e -> {
|
||||
updateButtonEnablement();
|
||||
});
|
||||
tableModel.addTableModelListener(e -> {
|
||||
updateButtonEnablement();
|
||||
fireChanged();
|
||||
});
|
||||
|
||||
TableColumn enabledColumn = table.getColumnModel().getColumn(0);
|
||||
enabledColumn.setResizable(false);
|
||||
enabledColumn.setPreferredWidth(32);
|
||||
enabledColumn.setMaxWidth(32);
|
||||
enabledColumn.setMinWidth(32);
|
||||
|
||||
TableColumn statusColumn = table.getColumnModel().getColumn(1);
|
||||
statusColumn.setResizable(false);
|
||||
statusColumn.setPreferredWidth(32);
|
||||
statusColumn.setMaxWidth(32);
|
||||
statusColumn.setMinWidth(32);
|
||||
|
||||
table.setPreferredScrollableViewportSize(new Dimension(100, 100));
|
||||
|
||||
return new JScrollPane(table);
|
||||
}
|
||||
|
||||
private JPanel buildButtonPanel() {
|
||||
|
||||
refreshSearchLocationsStatusButton =
|
||||
createImageButton(Icons.REFRESH_ICON, "Refresh Status");
|
||||
refreshSearchLocationsStatusButton.addActionListener(e -> refreshSearchLocationStatus());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(refreshSearchLocationsStatusButton, new HelpLocation(
|
||||
PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Refresh Status"));
|
||||
|
||||
moveLocationUpButton = createImageButton(Icons.UP_ICON, "Up");
|
||||
moveLocationUpButton.addActionListener(e -> moveLocation(-1));
|
||||
moveLocationUpButton.setToolTipText("Move location up");
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(moveLocationUpButton, new HelpLocation(
|
||||
PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig MoveUpDown"));
|
||||
|
||||
moveLocationDownButton = createImageButton(Icons.DOWN_ICON, "Down");
|
||||
moveLocationDownButton.addActionListener(e -> moveLocation(1));
|
||||
moveLocationDownButton.setToolTipText("Move location down");
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(moveLocationDownButton, new HelpLocation(
|
||||
PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig MoveUpDown"));
|
||||
|
||||
deleteLocationButton = createImageButton(Icons.DELETE_ICON, "Delete");
|
||||
deleteLocationButton.addActionListener(e -> deleteLocation());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(deleteLocationButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Delete"));
|
||||
|
||||
addLocationButton = createImageButton(Icons.ADD_ICON, "Add");
|
||||
addLocationButton.addActionListener(e -> addLocation());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(addLocationButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Add"));
|
||||
|
||||
saveSearchLocationsButton = createImageButton(Icons.SAVE_ICON, "Save Configuration");
|
||||
saveSearchLocationsButton.addActionListener(e -> saveConfig());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(saveSearchLocationsButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Save"));
|
||||
|
||||
JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
|
||||
buttonPanel.add(new GLabel("Additional Search Paths:"));
|
||||
buttonPanel.add(Box.createHorizontalGlue());
|
||||
buttonPanel.add(addLocationButton);
|
||||
buttonPanel.add(deleteLocationButton);
|
||||
buttonPanel.add(moveLocationUpButton);
|
||||
buttonPanel.add(moveLocationDownButton);
|
||||
buttonPanel.add(refreshSearchLocationsStatusButton);
|
||||
buttonPanel.add(saveSearchLocationsButton);
|
||||
|
||||
return buttonPanel;
|
||||
}
|
||||
|
||||
private JPanel buildSymbolStorageLocationPanel() {
|
||||
symbolStorageLocationTextField = new HintTextField(" Required ");
|
||||
symbolStorageLocationTextField.setEditable(false);
|
||||
symbolStorageLocationTextField
|
||||
.setToolTipText("User-specified directory where PDB files are stored. Required.");
|
||||
|
||||
chooseSymbolStorageLocationButton = new BrowseButton();
|
||||
chooseSymbolStorageLocationButton.addActionListener(e -> chooseSymbolStorageLocation());
|
||||
|
||||
symbolStorageLocationPanel = new JPanel(new PairLayout(5, 5));
|
||||
GLabel symbolStorageLocLabel = new GLabel("Local Symbol Storage:", SwingConstants.RIGHT);
|
||||
symbolStorageLocLabel.setToolTipText(symbolStorageLocationTextField.getToolTipText());
|
||||
|
||||
symbolStorageLocationPanel.add(symbolStorageLocLabel);
|
||||
symbolStorageLocationPanel.add(LoadPdbDialog.join(null, symbolStorageLocationTextField,
|
||||
chooseSymbolStorageLocationButton));
|
||||
return symbolStorageLocationPanel;
|
||||
}
|
||||
|
||||
private void updateButtonEnablement() {
|
||||
boolean hasLocalSymbolStore = localSymbolStore != null;
|
||||
boolean singleRow = table.getSelectedRowCount() == 1;
|
||||
boolean moreThanOneRow = table.getRowCount() > 1;
|
||||
|
||||
refreshSearchLocationsStatusButton.setEnabled(hasLocalSymbolStore && !tableModel.isEmpty());
|
||||
moveLocationUpButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow);
|
||||
moveLocationDownButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow);
|
||||
addLocationButton.setEnabled(hasLocalSymbolStore);
|
||||
deleteLocationButton.setEnabled(hasLocalSymbolStore && table.getSelectedRowCount() > 0);
|
||||
saveSearchLocationsButton.setEnabled(hasLocalSymbolStore && isConfigChanged());
|
||||
updateLayout(hasLocalSymbolStore);
|
||||
}
|
||||
|
||||
private void setSymbolStorageLocation(File symbolStorageDir, boolean allowGUIPrompt) {
|
||||
if (symbolStorageDir == null) {
|
||||
return;
|
||||
}
|
||||
if (!symbolStorageDir.exists()) {
|
||||
if (!allowGUIPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
int opt = OptionDialog.showOptionDialog(this, "Create Local Symbol Storage Directory?",
|
||||
"<html>Symbol storage directory<br>" +
|
||||
HTMLUtilities.escapeHTML(symbolStorageDir.getPath()) +
|
||||
"<br>does not exist. Create?",
|
||||
"Yes", OptionDialog.QUESTION_MESSAGE);
|
||||
if (opt == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FileUtilities.checkedMkdirs(symbolStorageDir);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, this, "Failure", "Failed to create symbol storage directory " +
|
||||
symbolStorageDir + ": " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowGUIPrompt && isEmptyDirectory(symbolStorageDir)) {
|
||||
if (OptionDialog.showYesNoDialog(this, "Initialize Symbol Storage Directory?",
|
||||
"<html>Initialize new directory as Microsoft symbol storage directory?") == OptionDialog.YES_OPTION) {
|
||||
try {
|
||||
LocalSymbolStore.create(symbolStorageDir,
|
||||
1 /* level1 MS symbol storage directory */);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, this, "Initialize Failure",
|
||||
"Failed to initialize symbol storage directory " + symbolStorageDir, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localSymbolStore =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(symbolStorageDir.getPath(), symbolServerInstanceCreatorContext,
|
||||
SymbolStore.class);
|
||||
symbolStorageLocationTextField.setText(symbolStorageDir.getPath());
|
||||
fireChanged();
|
||||
}
|
||||
|
||||
private void fireChanged() {
|
||||
if (changeCallback != null) {
|
||||
changeCallback.accept(getSymbolServerService());
|
||||
}
|
||||
}
|
||||
|
||||
private void chooseSymbolStorageLocation() {
|
||||
configChanged = true;
|
||||
GhidraFileChooser chooser = getChooser();
|
||||
setSymbolStorageLocation(chooser.getSelectedFile(), true);
|
||||
updateButtonEnablement();
|
||||
chooser.dispose();
|
||||
}
|
||||
|
||||
private void importLocations() {
|
||||
String envVar = (String) JOptionPane.showInputDialog(this,
|
||||
"<html>Enter value:<br><br>Example: SVR*c:\\symbols*https://msdl.microsoft.com/download/symbols/<br><br>",
|
||||
"Enter Symbol Server Search Path Value", JOptionPane.QUESTION_MESSAGE, null, null,
|
||||
Objects.requireNonNullElse(System.getenv(MS_SYMBOLSERVER_ENVVAR), ""));
|
||||
if (envVar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> symbolServerPaths = getSymbolPathsFromEnvStr(envVar);
|
||||
if (!symbolServerPaths.isEmpty()) {
|
||||
// if the first item in the path list looks like a local symbol storage path,
|
||||
// allow the user to set it as the storage dir (and remove it from the elements
|
||||
// that will be added to the search list)
|
||||
String firstSearchPath = symbolServerPaths.get(0);
|
||||
SymbolServer symbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext);
|
||||
if (symbolServer instanceof LocalSymbolStore localSymbolStore &&
|
||||
localSymbolStore.isValid()) {
|
||||
int choice = OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location",
|
||||
"Set symbol storage location to " + firstSearchPath + "?");
|
||||
if (choice == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
if (choice == OptionDialog.YES_OPTION) {
|
||||
symbolServerPaths.remove(0);
|
||||
configChanged = true;
|
||||
setSymbolStorageLocation(localSymbolStore.getRootDir(), true);
|
||||
symbolStorageLocationTextField.setText(symbolServer.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableModel.addSymbolServers(
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.createSymbolServersFromPathList(symbolServerPaths,
|
||||
symbolServerInstanceCreatorContext));
|
||||
fireChanged();
|
||||
}
|
||||
|
||||
private List<String> getSymbolPathsFromEnvStr(String envString) {
|
||||
// Expect the environment string to be in the MS symbol server search path form:
|
||||
// srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols
|
||||
// srv*c:\symbols*https://msdl.microsoft.com/download/symbols;srv*c:\additional*https://symbol.server.tld/
|
||||
String[] envParts = envString.split("[*;]");
|
||||
List<String> results = new ArrayList<>();
|
||||
Set<String> locationStringDeduplicationSet = new HashSet<>();
|
||||
for (String envPart : envParts) {
|
||||
String locationString = envPart.trim();
|
||||
if (!locationString.isBlank() && !locationString.equalsIgnoreCase("srv") &&
|
||||
!locationStringDeduplicationSet.contains(locationString)) {
|
||||
results.add(locationString);
|
||||
locationStringDeduplicationSet.add(locationString);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void addLocation() {
|
||||
JPopupMenu menu = createAddLocationPopupMenu();
|
||||
menu.show(addLocationButton, 0, 0);
|
||||
}
|
||||
|
||||
private JPopupMenu createAddLocationPopupMenu() {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
JMenuItem addDirMenuItem = new JMenuItem("Directory");
|
||||
addDirMenuItem.addActionListener(e -> addDirectoryLocation());
|
||||
menu.add(addDirMenuItem);
|
||||
|
||||
JMenuItem addURLMenuItem = new JMenuItem("URL");
|
||||
addURLMenuItem.addActionListener(e -> addUrlLocation());
|
||||
menu.add(addURLMenuItem);
|
||||
|
||||
JMenuItem addProgLocMenuItem =
|
||||
new JMenuItem(SameDirSymbolStore.PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR);
|
||||
addProgLocMenuItem.addActionListener(e -> addSameDirLocation());
|
||||
menu.add(addProgLocMenuItem);
|
||||
|
||||
JMenuItem importEnvMenuItem = new JMenuItem("Import _NT_SYMBOL_PATH");
|
||||
importEnvMenuItem.addActionListener(e -> importLocations());
|
||||
menu.add(importEnvMenuItem);
|
||||
|
||||
if (!knownSymbolServers.isEmpty()) {
|
||||
menu.add(new JSeparator());
|
||||
for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) {
|
||||
JMenuItem mi = new JMenuItem(ssloc.getLocation());
|
||||
mi.addActionListener(e -> addKnownLocation(ssloc));
|
||||
mi.setToolTipText(" [from " + ssloc.getFileOrigin() + "]");
|
||||
menu.add(mi);
|
||||
}
|
||||
}
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(menu,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig_Add"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void addSameDirLocation() {
|
||||
SameDirSymbolStore sameDirSymbolStore =
|
||||
new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir());
|
||||
tableModel.addSymbolServer(sameDirSymbolStore);
|
||||
}
|
||||
|
||||
private void addKnownLocation(WellKnownSymbolServerLocation ssloc) {
|
||||
SymbolServer symbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(ssloc.getLocation(), symbolServerInstanceCreatorContext);
|
||||
if (symbolServer != null) {
|
||||
tableModel.addSymbolServer(symbolServer);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUrlLocation() {
|
||||
String urlLocationString = OptionDialog.showInputSingleLineDialog(this, "Enter URL",
|
||||
"Enter the URL of a Symbol Server: ", "https://");
|
||||
if (urlLocationString == null || urlLocationString.isBlank()) {
|
||||
return;
|
||||
}
|
||||
urlLocationString = urlLocationString.toLowerCase();
|
||||
if (!(urlLocationString.startsWith("http://") ||
|
||||
urlLocationString.startsWith("https://"))) {
|
||||
Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
HttpSymbolServer httpSymbolServer = new HttpSymbolServer(URI.create(urlLocationString));
|
||||
tableModel.addSymbolServer(httpSymbolServer);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDirectoryLocation() {
|
||||
File dir =
|
||||
FilePromptDialog.chooseDirectory("Enter Path", "Symbol Storage Location: ", null);
|
||||
if (dir == null) {
|
||||
return;
|
||||
}
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
Msg.showError(this, this, "Bad path", "Invalid path: " + dir);
|
||||
return;
|
||||
}
|
||||
LocalSymbolStore symbolStore = new LocalSymbolStore(dir);
|
||||
tableModel.addSymbolServer(symbolStore);
|
||||
}
|
||||
|
||||
private void deleteLocation() {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
tableModel.deleteRows(table.getSelectedRows());
|
||||
if (selectedRow >= 0 && selectedRow < table.getRowCount()) {
|
||||
table.selectRow(selectedRow);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveLocation(int delta) {
|
||||
if (table.getSelectedRowCount() == 1) {
|
||||
tableModel.moveRow(table.getSelectedRow(), delta);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSearchLocationStatus() {
|
||||
tableModel.refreshSymbolServerLocationStatus();
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
/* package */ void saveConfig() {
|
||||
SymbolServerService temporarySymbolServerService = getSymbolServerService();
|
||||
if (temporarySymbolServerService != null) {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(temporarySymbolServerService);
|
||||
Preferences.store();
|
||||
setConfigChanged(false);
|
||||
fireChanged();
|
||||
updateButtonEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
private GhidraFileChooser getChooser() {
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(this);
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
chooser.setApproveButtonText("Choose");
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooser.setTitle("Select Symbol Storage Dir");
|
||||
|
||||
return chooser;
|
||||
}
|
||||
|
||||
/* screen shot usage */ void pushAddLocationButton() {
|
||||
addLocation();
|
||||
}
|
||||
|
||||
/* screen shot usage */ void setSymbolStorageDirectoryTextOnly(String pathStr) {
|
||||
symbolStorageLocationTextField.setText(pathStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given file path is a directory that contains no files.
|
||||
* <p>
|
||||
*
|
||||
* @param directory path to a location on the file system
|
||||
* @return true if is a directory and it contains no files
|
||||
*/
|
||||
private static boolean isEmptyDirectory(File directory) {
|
||||
if (directory.isDirectory()) {
|
||||
File[] dirContents = directory.listFiles();
|
||||
return dirContents != null && dirContents.length == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static JButton createImageButton(Icon buttonIcon, String alternateText) {
|
||||
|
||||
JButton button = new GButton(buttonIcon);
|
||||
button.setToolTipText(alternateText);
|
||||
button.setPreferredSize(BUTTON_SIZE);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
static StatusText getSymbolServerWarnings(List<SymbolServer> symbolServers) {
|
||||
Map<String, String> warningsByLocation = new HashMap<>();
|
||||
for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) {
|
||||
if (ssloc.getWarning() != null && !ssloc.getWarning().isBlank()) {
|
||||
warningsByLocation.put(ssloc.getLocation(), ssloc.getWarning());
|
||||
}
|
||||
}
|
||||
String warning = symbolServers.stream()
|
||||
.map(symbolServer -> warningsByLocation.get(symbolServer.getName()))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("<br>\n"));
|
||||
|
||||
return !warning.isEmpty() ? new StatusText(warning, MessageType.WARNING, false) : null;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,8 +15,12 @@
|
|||
*/
|
||||
package pdb.symbolserver.ui;
|
||||
|
||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.*;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.symbolserver.DisabledSymbolServer;
|
||||
import pdb.symbolserver.SymbolServer;
|
||||
import pdb.symbolserver.SymbolServer.MutableTrust;
|
||||
|
||||
/**
|
||||
* Represents a row in the {@link SymbolServerTableModel}
|
||||
|
@ -59,6 +63,16 @@ class SymbolServerRow {
|
|||
}
|
||||
}
|
||||
|
||||
boolean isTrusted() {
|
||||
return symbolServer.isTrusted();
|
||||
}
|
||||
|
||||
void setTrusted(boolean isTrusted) {
|
||||
if (symbolServer instanceof MutableTrust sswt) {
|
||||
sswt.setTrusted(isTrusted);
|
||||
}
|
||||
}
|
||||
|
||||
LocationStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
@ -67,6 +81,12 @@ class SymbolServerRow {
|
|||
this.status = status;
|
||||
}
|
||||
|
||||
void updateStatus(TaskMonitor monitor) {
|
||||
if (!(symbolServer instanceof SymbolServer.StatusRequiresContext)) {
|
||||
this.status = symbolServer.isValid(monitor) ? VALID : INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SymbolServerRow: [ status: %s, server: %s]", status.toString(),
|
||||
|
|
|
@ -15,30 +15,28 @@
|
|||
*/
|
||||
package pdb.symbolserver.ui;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.INVALID;
|
||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.VALID;
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.FontMetrics;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import pdb.symbolserver.SymbolServer;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* Table model for the {@link SymbolServerPanel} table
|
||||
* Table model for the {@link ConfigPdbDialog} table
|
||||
*/
|
||||
class SymbolServerTableModel
|
||||
extends GDynamicColumnTableModel<SymbolServerRow, List<SymbolServerRow>> {
|
||||
|
@ -64,9 +62,7 @@ class SymbolServerTableModel
|
|||
}
|
||||
|
||||
List<SymbolServer> getSymbolServers() {
|
||||
return rows.stream()
|
||||
.map(SymbolServerRow::getSymbolServer)
|
||||
.collect(toList());
|
||||
return rows.stream().map(SymbolServerRow::getSymbolServer).collect(toList());
|
||||
}
|
||||
|
||||
void addSymbolServer(SymbolServer ss) {
|
||||
|
@ -92,26 +88,6 @@ class SymbolServerTableModel
|
|||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
void refreshSymbolServerLocationStatus() {
|
||||
List<SymbolServerRow> rowsCopy = new ArrayList<>(this.rows);
|
||||
TaskLauncher.launchNonModal("Refresh Symbol Server Location Status", monitor -> {
|
||||
monitor.initialize(rowsCopy.size());
|
||||
monitor.setMessage("Refreshing symbol server status");
|
||||
try {
|
||||
for (SymbolServerRow row : rowsCopy) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
monitor.setMessage("Checking " + row.getSymbolServer().getName());
|
||||
row.setStatus(row.getSymbolServer().isValid(monitor) ? VALID : INVALID);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Swing.runLater(SymbolServerTableModel.this::fireTableDataChanged);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void moveRow(int rowIndex, int deltaIndex) {
|
||||
int destIndex = rowIndex + deltaIndex;
|
||||
if (rowIndex < 0 || rowIndex >= rows.size() || destIndex < 0 || destIndex >= rows.size()) {
|
||||
|
@ -159,18 +135,24 @@ class SymbolServerTableModel
|
|||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
||||
if (column instanceof EnabledColumn) {
|
||||
if (column instanceof EnabledColumn && aValue instanceof Boolean) {
|
||||
SymbolServerRow row = getRowObject(rowIndex);
|
||||
row.setEnabled((Boolean) aValue);
|
||||
dataChanged = true;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
else if (column instanceof TrustedColumn && aValue instanceof Boolean) {
|
||||
SymbolServerRow row = getRowObject(rowIndex);
|
||||
row.setTrusted((Boolean) aValue);
|
||||
dataChanged = true;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
||||
return column instanceof EnabledColumn;
|
||||
return column instanceof EnabledColumn || column instanceof TrustedColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -178,6 +160,7 @@ class SymbolServerTableModel
|
|||
TableColumnDescriptor<SymbolServerRow> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new EnabledColumn());
|
||||
descriptor.addVisibleColumn(new TrustedColumn());
|
||||
descriptor.addVisibleColumn(new StatusColumn());
|
||||
descriptor.addVisibleColumn(new LocationColumn());
|
||||
|
||||
|
@ -187,9 +170,10 @@ class SymbolServerTableModel
|
|||
//-------------------------------------------------------------------------------------------
|
||||
|
||||
private static class StatusColumn extends
|
||||
AbstractDynamicTableColumnStub<SymbolServerRow, SymbolServerRow.LocationStatus> {
|
||||
AbstractDynamicTableColumnStub<SymbolServerRow, SymbolServerRow.LocationStatus>
|
||||
implements TableColumnInitializer {
|
||||
|
||||
private static final Icon VALID_ICON = Icons.get("images/checkmark_green.gif");
|
||||
private static final Icon VALID_ICON = new GIcon("icon.checkmark.green");
|
||||
private static final Icon INVALID_ICON = Icons.ERROR_ICON;
|
||||
|
||||
private static Icon[] icons = new Icon[] { null, VALID_ICON, INVALID_ICON };
|
||||
|
@ -206,7 +190,7 @@ class SymbolServerTableModel
|
|||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return "";
|
||||
return "Status";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -219,14 +203,23 @@ class SymbolServerTableModel
|
|||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTableColumn(TableColumn col, FontMetrics fm, int padding) {
|
||||
int colWidth = fm.stringWidth("Status") + padding;
|
||||
col.setPreferredWidth(colWidth);
|
||||
col.setMaxWidth(colWidth * 2);
|
||||
col.setMinWidth(colWidth);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class EnabledColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolServerRow, Boolean> {
|
||||
extends AbstractDynamicTableColumnStub<SymbolServerRow, Boolean>
|
||||
implements TableColumnInitializer {
|
||||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return "";
|
||||
return "Enabled";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -240,6 +233,43 @@ class SymbolServerTableModel
|
|||
return "Enabled";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTableColumn(TableColumn col, FontMetrics fm, int padding) {
|
||||
int colWidth = fm.stringWidth("Enabled") + padding;
|
||||
col.setPreferredWidth(colWidth);
|
||||
col.setMaxWidth(colWidth * 2);
|
||||
col.setMinWidth(colWidth);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TrustedColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolServerRow, Boolean>
|
||||
implements TableColumnInitializer {
|
||||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return "Trusted";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getValue(SymbolServerRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.isTrusted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Trusted";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTableColumn(TableColumn col, FontMetrics fm, int padding) {
|
||||
int colWidth = fm.stringWidth("Trusted") + padding;
|
||||
col.setPreferredWidth(colWidth);
|
||||
col.setMaxWidth(colWidth * 2);
|
||||
col.setMinWidth(colWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LocationColumn
|
||||
|
@ -305,5 +335,6 @@ class SymbolServerTableModel
|
|||
public String getFilterString(E t, Settings settings) {
|
||||
return t == null ? "" : t.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pdb.symbolserver.ui;
|
||||
|
||||
import java.awt.FontMetrics;
|
||||
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.table.*;
|
||||
|
||||
/**
|
||||
* For Pdb symbolserver gui stuff only.
|
||||
*
|
||||
* Add on interface for DynamicTableColumn classes that let them control aspects of the
|
||||
* matching TableColumn.
|
||||
*/
|
||||
public interface TableColumnInitializer {
|
||||
/**
|
||||
* Best called during {@link DialogComponentProvider#dialogShown} or
|
||||
* {@link ComponentProvider#componentShown}
|
||||
*
|
||||
* @param table table component
|
||||
* @param model table model
|
||||
*/
|
||||
static void initializeTableColumns(GTable table, GDynamicColumnTableModel<?, ?> model) {
|
||||
TableColumnModel colModel = table.getColumnModel();
|
||||
|
||||
FontMetrics fm = table.getTableHeader().getFontMetrics(table.getTableHeader().getFont());
|
||||
int padding = fm.stringWidth("WW"); // w.a.g. for the left+right padding on the header column component
|
||||
|
||||
for (int colIndex = 0; colIndex < model.getColumnCount(); colIndex++) {
|
||||
DynamicTableColumn<?, ?, ?> dtableCol = model.getColumn(colIndex);
|
||||
if (dtableCol instanceof TableColumnInitializer colInitializer) {
|
||||
TableColumn tableCol = colModel.getColumn(colIndex);
|
||||
colInitializer.initializeTableColumn(tableCol, fm, padding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow the initializer to modify the specified TableColumn
|
||||
*
|
||||
* @param col {@link TableColumn}
|
||||
* @param fm {@link FontMetrics} used by the table header gui component
|
||||
* @param padding padding to use in the column
|
||||
*/
|
||||
void initializeTableColumn(TableColumn col, FontMetrics fm, int padding);
|
||||
}
|
|
@ -17,68 +17,27 @@ package pdb.symbolserver.ui;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.MessageType;
|
||||
import ghidra.util.Msg;
|
||||
import pdb.symbolserver.SymbolServer;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog.StatusText;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Represents a well-known symbol server location.
|
||||
* <p>
|
||||
* See the PDB_SYMBOL_SERVER_URLS.pdburl file.
|
||||
* @param location url string
|
||||
* @param locationCategory grouping criteria
|
||||
* @param warning string
|
||||
* @param fileOrigin file name that contained this info
|
||||
*/
|
||||
class WellKnownSymbolServerLocation {
|
||||
private String locationCategory;
|
||||
private String location;
|
||||
private String warning;
|
||||
private String fileOrigin;
|
||||
|
||||
WellKnownSymbolServerLocation(String location, String locationCategory, String warning,
|
||||
String fileOrigin) {
|
||||
this.location = location;
|
||||
this.locationCategory = locationCategory;
|
||||
this.warning = warning;
|
||||
this.fileOrigin = fileOrigin;
|
||||
}
|
||||
|
||||
String getLocationCategory() {
|
||||
return locationCategory;
|
||||
}
|
||||
|
||||
String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
String getWarning() {
|
||||
return warning;
|
||||
}
|
||||
|
||||
String getFileOrigin() {
|
||||
return fileOrigin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(location, locationCategory, warning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
WellKnownSymbolServerLocation other = (WellKnownSymbolServerLocation) obj;
|
||||
return Objects.equals(location, other.location) &&
|
||||
Objects.equals(locationCategory, other.locationCategory) &&
|
||||
Objects.equals(warning, other.warning);
|
||||
}
|
||||
public record WellKnownSymbolServerLocation(String location, String locationCategory,
|
||||
String warning, String fileOrigin) {
|
||||
|
||||
/**
|
||||
* Loads all symbol server location files (*.pdburl) and returns a list of entries.
|
||||
|
@ -103,10 +62,37 @@ class WellKnownSymbolServerLocation {
|
|||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.warn(WellKnownSymbolServerLocation.class, "Unable to read pdburl file: " + file);
|
||||
Msg.warn(WellKnownSymbolServerLocation.class,
|
||||
"Unable to read pdburl file: " + file);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted StatusText containing all the warnings published by any untrusted
|
||||
* {@link WellKnownSymbolServerLocation} found in the list of symbolservers.
|
||||
*
|
||||
* @param knownSymbolServers list
|
||||
* @param symbolServers list
|
||||
* @return StatusText
|
||||
*/
|
||||
public static StatusText getWarningsFor(List<WellKnownSymbolServerLocation> knownSymbolServers,
|
||||
List<SymbolServer> symbolServers) {
|
||||
Map<String, String> warningsByLocation = new HashMap<>();
|
||||
for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) {
|
||||
if (ssloc.warning() != null && !ssloc.warning().isBlank()) {
|
||||
warningsByLocation.put(ssloc.location(), ssloc.warning());
|
||||
}
|
||||
}
|
||||
String warning = symbolServers.stream()
|
||||
.filter(symbolServer -> !symbolServer.isTrusted())
|
||||
.map(symbolServer -> warningsByLocation.get(symbolServer.getName()))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("<br>\n"));
|
||||
|
||||
return !warning.isEmpty() ? new StatusText(warning, MessageType.WARNING, false) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,14 +21,16 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.symbolserver.SymbolServer.MutableTrust;
|
||||
|
||||
/**
|
||||
* A "remote" symbol server that answers affirmatively for any query.
|
||||
*/
|
||||
public class DummySymbolServer implements SymbolServer {
|
||||
public class DummySymbolServer implements SymbolServer, MutableTrust {
|
||||
|
||||
private final byte[] dummyPayload;
|
||||
private final boolean returnCompressedFilenames;
|
||||
private boolean trusted;
|
||||
|
||||
public DummySymbolServer(String dummyPayload) {
|
||||
this(dummyPayload.getBytes(), false);
|
||||
|
@ -78,8 +80,13 @@ public class DummySymbolServer implements SymbolServer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return false;
|
||||
public boolean isTrusted() {
|
||||
return trusted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrusted(boolean isTrusted) {
|
||||
this.trusted = isTrusted;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,16 +15,14 @@
|
|||
*/
|
||||
package pdb.symbolserver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -128,14 +126,14 @@ public class SymbolServerServiceTest extends AbstractGenericTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_Remote() throws IOException, CancelledException {
|
||||
public void test_Trusted() throws IOException, CancelledException {
|
||||
String payload = "testdummy";
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1,
|
||||
List.of(localSymbolStore2, new DummySymbolServer(payload)));
|
||||
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_REMOTE),
|
||||
symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_UNTRUSTED),
|
||||
TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
|
@ -148,8 +146,9 @@ public class SymbolServerServiceTest extends AbstractGenericTest {
|
|||
@Test
|
||||
public void test_NoRemote() throws CancelledException {
|
||||
String payload = "testdummy";
|
||||
DummySymbolServer dummySymbolServer = new DummySymbolServer(payload);
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, List.of(new DummySymbolServer(payload)));
|
||||
new SymbolServerService(localSymbolStore1, List.of(dummySymbolServer));
|
||||
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(searchPdb, FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
|
|
|
@ -31,8 +31,7 @@ import ghidra.framework.options.Options;
|
|||
import ghidra.program.model.listing.Program;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
import pdb.symbolserver.ui.ConfigPdbDialog;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog;
|
||||
import pdb.symbolserver.ui.*;
|
||||
|
||||
public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
|
@ -64,7 +63,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolServerConfig_Screenshot() throws IOException {
|
||||
public void testSymbolServerConfig_Screenshot() {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(null);
|
||||
ConfigPdbDialog configPdbDialog = new ConfigPdbDialog();
|
||||
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||
|
@ -79,7 +78,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
|||
LocalSymbolStore localSymbolStore1 = new LocalSymbolStore(localSymbolStore1Root);
|
||||
SameDirSymbolStore sameDirSymbolStore = new SameDirSymbolStore(null);
|
||||
List<SymbolServer> symbolServers = List.of(sameDirSymbolStore,
|
||||
new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/")));
|
||||
HttpSymbolServer.createTrusted("https://msdl.microsoft.com/download/symbols/"));
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, symbolServers);
|
||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
||||
|
@ -88,7 +87,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
|||
configPdbDialog.setSymbolServerService("/home/user/symbols", symbolServers);
|
||||
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||
waitForSwing();
|
||||
captureDialog(ConfigPdbDialog.class, 410, 280);
|
||||
captureDialog(ConfigPdbDialog.class, 520, 280);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,12 +111,29 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
|||
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||
waitForSwing();
|
||||
runSwing(() -> {
|
||||
configPdbDialog.setWellknownSymbolServers(createFakeWellKnowns());
|
||||
configPdbDialog.pushAddLocationButton();
|
||||
});
|
||||
waitForSwing();
|
||||
captureMenu();
|
||||
}
|
||||
|
||||
List<WellKnownSymbolServerLocation> createFakeWellKnowns() {
|
||||
// due to module dependencies, this screen shot test can't see the contents of the normal
|
||||
// PDBURL file loaded from the 'z public release' module.
|
||||
return List.of( // should be same as the PDB_SYMBOL_SERVERS.PDBURL file
|
||||
new WellKnownSymbolServerLocation("", "https://msdl.microsoft.com/download/symbols/",
|
||||
"WARNING: Check your organization's security policy before downloading files from the internet.",
|
||||
"screen shot"),
|
||||
new WellKnownSymbolServerLocation("",
|
||||
"https://chromium-browser-symsrv.commondatastorage.googleapis.com",
|
||||
"WARNING: Check your organization's security policy before downloading files from the internet.",
|
||||
"screen shot"),
|
||||
new WellKnownSymbolServerLocation("", "https://symbols.mozilla.org/",
|
||||
"WARNING: Check your organization's security policy before downloading files from the internet.",
|
||||
"screen shot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadPdb_Advanced_NeedsConfig() {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(null);
|
||||
|
@ -158,7 +174,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
|||
localSymbolStore1, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 2)),
|
||||
new SymbolFileLocation("HelloWorld.pdb", sameDirSymbolStoreWithFakePath,
|
||||
SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 1)));
|
||||
Set<FindOption> findOptions = FindOption.of(FindOption.ALLOW_REMOTE, FindOption.ANY_AGE);
|
||||
Set<FindOption> findOptions = FindOption.of(FindOption.ALLOW_UNTRUSTED, FindOption.ANY_AGE);
|
||||
runSwing(() -> {
|
||||
loadPdbDialog.setSearchOptions(findOptions);
|
||||
loadPdbDialog.setSearchResults(symbolFileLocations, findOptions);
|
||||
|
|
|
@ -99,7 +99,7 @@ public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTes
|
|||
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
||||
FindOption.of(FindOption.ALLOW_REMOTE), TaskMonitor.DUMMY);
|
||||
FindOption.of(FindOption.ALLOW_UNTRUSTED), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
System.out.println(results.get(0).getLocationStr());
|
||||
|
@ -118,7 +118,7 @@ public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTes
|
|||
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
||||
FindOption.of(FindOption.ALLOW_REMOTE), TaskMonitor.DUMMY);
|
||||
FindOption.of(FindOption.ALLOW_UNTRUSTED), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
System.out.println(results.get(0).getLocationStr());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue