mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +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
|
//The ~/symbols directory should already exist and be initialized as a symbol
|
||||||
//storage location.
|
//storage location.
|
||||||
//@category PDB
|
//@category PDB
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
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 ghidra.app.script.GhidraScript;
|
||||||
import pdb.PdbPlugin;
|
import pdb.PdbPlugin;
|
||||||
import pdb.symbolserver.*;
|
import pdb.symbolserver.*;
|
||||||
|
@ -38,16 +34,11 @@ public class PdbSymbolServerExamplePrescript extends GhidraScript {
|
||||||
File symDir = new File(homeDir, "symbols");
|
File symDir = new File(homeDir, "symbols");
|
||||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(symDir);
|
LocalSymbolStore localSymbolStore = new LocalSymbolStore(symDir);
|
||||||
HttpSymbolServer msSymbolServer =
|
HttpSymbolServer msSymbolServer =
|
||||||
new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/"));
|
HttpSymbolServer.createTrusted("https://msdl.microsoft.com/download/symbols/");
|
||||||
SymbolServerService symbolServerService =
|
SymbolServerService symbolServerService =
|
||||||
new SymbolServerService(localSymbolStore, List.of(msSymbolServer));
|
new SymbolServerService(localSymbolStore, List.of(msSymbolServer));
|
||||||
|
|
||||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.file.AccessMode;
|
import java.nio.file.AccessMode;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -44,8 +43,8 @@ public class GetMSDownloadLinkScript extends GhidraScript {
|
||||||
@Override
|
@Override
|
||||||
protected void run() throws Exception {
|
protected void run() throws Exception {
|
||||||
SymbolServerService symbolService =
|
SymbolServerService symbolService =
|
||||||
new SymbolServerService(new SameDirSymbolStore(null), List.of(
|
new SymbolServerService(new SameDirSymbolStore(null),
|
||||||
new HttpSymbolServer(URI.create(MS_PUBLIC_SYMBOL_SERVER_URL))));
|
List.of(HttpSymbolServer.createTrusted(MS_PUBLIC_SYMBOL_SERVER_URL)));
|
||||||
|
|
||||||
File f = askFile("File To Scan", "Select");
|
File f = askFile("File To Scan", "Select");
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
|
@ -63,9 +62,7 @@ public class GetMSDownloadLinkScript extends GhidraScript {
|
||||||
", sizeOfImage: " + Integer.toHexString(sizeOfImage));
|
", sizeOfImage: " + Integer.toHexString(sizeOfImage));
|
||||||
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromValues(f.getName().toLowerCase(),
|
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromValues(f.getName().toLowerCase(),
|
||||||
Integer.toHexString(timeDateStamp), sizeOfImage);
|
Integer.toHexString(timeDateStamp), sizeOfImage);
|
||||||
List<SymbolFileLocation> findResults =
|
List<SymbolFileLocation> findResults = symbolService.find(symbolFileInfo, monitor);
|
||||||
symbolService.find(symbolFileInfo, FindOption.of(FindOption.ALLOW_REMOTE),
|
|
||||||
monitor);
|
|
||||||
if (findResults.isEmpty()) {
|
if (findResults.isEmpty()) {
|
||||||
println("Not found on " + MS_PUBLIC_SYMBOL_SERVER_URL);
|
println("Not found on " + MS_PUBLIC_SYMBOL_SERVER_URL);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||||
|
|
||||||
private static final String ERROR_TITLE = "Error in PDB Analyzer";
|
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
|
// only try once per transaction due to extensive error logging which may get duplicated
|
||||||
private long lastTransactionId = -1;
|
private long lastTransactionId = -1;
|
||||||
|
@ -85,7 +85,7 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
|
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
||||||
if (pdbFile == null) {
|
if (pdbFile == null) {
|
||||||
// warnings have already been logged
|
// warnings have already been logged
|
||||||
return false;
|
return false;
|
||||||
|
@ -138,15 +138,15 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||||
@Override
|
@Override
|
||||||
public void registerOptions(Options options, Program program) {
|
public void registerOptions(Options options, Program program) {
|
||||||
|
|
||||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS,
|
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS,
|
||||||
searchRemoteLocations, null,
|
searchUntrustedLocations, null,
|
||||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS);
|
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void optionsChanged(Options options, Program program) {
|
public void optionsChanged(Options options, Program program) {
|
||||||
searchRemoteLocations = options.getBoolean(
|
searchUntrustedLocations = options.getBoolean(
|
||||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
|
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.
|
* on the specified program.
|
||||||
* <p>
|
* <p>
|
||||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||||
* will default to NOT searching remote symbol servers. A headless script could
|
* will default to NOT searching untrusted symbol servers. A headless script could
|
||||||
* use this method to allow the analyzer to search remote symbol servers.
|
* use this method to allow the analyzer to search untrusted symbol servers.
|
||||||
*
|
*
|
||||||
* @param program {@link Program}
|
* @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
|
* servers
|
||||||
*/
|
*/
|
||||||
public static void setAllowRemoteOption(Program program, boolean allowRemote) {
|
public static void setAllowUntrustedOption(Program program, boolean allowUntrusted) {
|
||||||
PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote);
|
PdbAnalyzerCommon.setAllowUntrustedOption(NAME, program, allowUntrusted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,9 @@ import pdb.symbolserver.SymbolFileInfo;
|
||||||
* Shared configuration values and pdb searching logic
|
* Shared configuration values and pdb searching logic
|
||||||
*/
|
*/
|
||||||
public class PdbAnalyzerCommon {
|
public class PdbAnalyzerCommon {
|
||||||
static final String OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS =
|
static final String OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS =
|
||||||
"If checked, allow searching remote symbol servers for PDB files.";
|
"If checked, allow searching untrusted symbol servers for PDB files.";
|
||||||
static final String OPTION_NAME_SEARCH_REMOTE_LOCATIONS = "Search remote symbol servers";
|
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_DESCRIPTION_PDB_FILE = "Path to a manually chosen PDB file.";
|
||||||
static final String OPTION_NAME_PDB_FILE = "PDB File";
|
static final String OPTION_NAME_PDB_FILE = "PDB File";
|
||||||
|
@ -101,12 +101,13 @@ public class PdbAnalyzerCommon {
|
||||||
*
|
*
|
||||||
* @param analyzerName name of analyzer
|
* @param analyzerName name of analyzer
|
||||||
* @param program {@link Program}
|
* @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
|
* 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 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;
|
: null;
|
||||||
if (pdbFile == null) {
|
if (pdbFile == null) {
|
||||||
Set<FindOption> findOpts = allowRemote
|
Set<FindOption> findOpts = allowRemote
|
||||||
? FindOption.of(FindOption.ALLOW_REMOTE)
|
? FindOption.of(FindOption.ALLOW_UNTRUSTED)
|
||||||
: FindOption.NO_OPTIONS;
|
: FindOption.NO_OPTIONS;
|
||||||
pdbFile = PdbPlugin.findPdb(program, findOpts, monitor);
|
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 DEFAULT_FORCE_LOAD_FILE = new File(DEFAULT_SYMBOLS_DIR, "sample.pdb");
|
||||||
private File forceLoadFile;
|
private File forceLoadFile;
|
||||||
|
|
||||||
private boolean searchRemoteLocations = false;
|
private boolean searchUntrustedLocations = false;
|
||||||
|
|
||||||
//==============================================================================================
|
//==============================================================================================
|
||||||
// Additional instance data
|
// Additional instance data
|
||||||
|
@ -164,7 +164,7 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||||
pdbFile = forceLoadFile;
|
pdbFile = forceLoadFile;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
|
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
||||||
}
|
}
|
||||||
if (pdbFile == null) {
|
if (pdbFile == null) {
|
||||||
// warnings have already been logged
|
// warnings have already been logged
|
||||||
|
@ -262,9 +262,9 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||||
options.registerOption(OPTION_NAME_FORCELOAD_FILE, OptionType.FILE_TYPE,
|
options.registerOption(OPTION_NAME_FORCELOAD_FILE, OptionType.FILE_TYPE,
|
||||||
DEFAULT_FORCE_LOAD_FILE, null, OPTION_DESCRIPTION_FORCELOAD_FILE);
|
DEFAULT_FORCE_LOAD_FILE, null, OPTION_DESCRIPTION_FORCELOAD_FILE);
|
||||||
}
|
}
|
||||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS,
|
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS,
|
||||||
searchRemoteLocations, null,
|
searchUntrustedLocations, null,
|
||||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS);
|
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS);
|
||||||
|
|
||||||
pdbReaderOptions.registerOptions(options);
|
pdbReaderOptions.registerOptions(options);
|
||||||
pdbApplicatorOptions.registerAnalyzerOptions(options);
|
pdbApplicatorOptions.registerAnalyzerOptions(options);
|
||||||
|
@ -281,8 +281,8 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||||
forceLoadFile = options.getFile(OPTION_NAME_FORCELOAD_FILE, forceLoadFile);
|
forceLoadFile = options.getFile(OPTION_NAME_FORCELOAD_FILE, forceLoadFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchRemoteLocations = options.getBoolean(
|
searchUntrustedLocations = options.getBoolean(
|
||||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
|
PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, searchUntrustedLocations);
|
||||||
|
|
||||||
pdbReaderOptions.loadOptions(options);
|
pdbReaderOptions.loadOptions(options);
|
||||||
pdbApplicatorOptions.loadAnalyzerOptions(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.
|
* on the specified program.
|
||||||
* <p>
|
* <p>
|
||||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||||
* will default to NOT searching remote symbol servers. A headless script could
|
* will default to NOT searching untrusted symbol servers. A headless script could
|
||||||
* use this method to allow the analyzer to search remote symbol servers.
|
* use this method to allow the analyzer to search untrusted symbol servers.
|
||||||
*
|
*
|
||||||
* @param program {@link Program}
|
* @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
|
* servers
|
||||||
*/
|
*/
|
||||||
public static void setAllowRemoteOption(Program program, boolean allowRemote) {
|
public static void setAllowUntrustedOption(Program program, boolean allowUntrusted) {
|
||||||
PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote);
|
PdbAnalyzerCommon.setAllowUntrustedOption(NAME, program, allowUntrusted);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================================
|
//==============================================================================================
|
||||||
|
|
|
@ -136,11 +136,6 @@ public class ContainerFileSymbolServer implements SymbolServer {
|
||||||
return fsFSRL.withPath(filename).toPrettyFullpathString();
|
return fsFSRL.withPath(filename).toPrettyFullpathString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLocal() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ContainerFileSymbolServer: [ fsrl: %s ]".formatted(fsFSRL);
|
return "ContainerFileSymbolServer: [ fsrl: %s ]".formatted(fsFSRL);
|
||||||
|
|
|
@ -114,11 +114,6 @@ public class DisabledSymbolServer implements SymbolServer {
|
||||||
return delegate.getFileLocation(filename);
|
return delegate.getFileLocation(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLocal() {
|
|
||||||
return delegate.isLocal();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("DisabledSymbolServer: [ %s ]", delegate.toString());
|
return String.format("DisabledSymbolServer: [ %s ]", delegate.toString());
|
||||||
|
|
|
@ -15,18 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package pdb.symbolserver;
|
package pdb.symbolserver;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options that control how Pdb files are searched for on a SymbolServer.
|
* Options that control how Pdb files are searched for on a SymbolServer.
|
||||||
*/
|
*/
|
||||||
public enum FindOption {
|
public enum FindOption {
|
||||||
/**
|
/**
|
||||||
* Allow connections to remote symbol servers
|
* Allow connections to untrusted symbol servers
|
||||||
*/
|
*/
|
||||||
ALLOW_REMOTE,
|
ALLOW_UNTRUSTED,
|
||||||
/**
|
/**
|
||||||
* Only return the first result
|
* Only return the first result
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,23 +25,31 @@ import java.net.http.HttpResponse;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import ghidra.net.HttpClients;
|
import ghidra.net.HttpClients;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.CancelledListener;
|
import ghidra.util.task.CancelledListener;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import pdb.symbolserver.SymbolServer.MutableTrust;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SymbolServer} that is accessed via HTTP.
|
* A {@link SymbolServer} that is accessed via HTTP.
|
||||||
* <p>
|
* <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 String GHIDRA_USER_AGENT = "Ghidra_HttpSymbolServer_client";
|
||||||
private static final int HTTP_STATUS_OK = HttpURLConnection.HTTP_OK;
|
private static final int HTTP_STATUS_OK = HttpURLConnection.HTTP_OK;
|
||||||
private static final int HTTP_REQUEST_TIMEOUT_MS = 10 * 1000; // 10 seconds
|
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.
|
* 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
|
* @return boolean true if the string should be handled by the HttpSymbolServer class
|
||||||
*/
|
*/
|
||||||
public static boolean isHttpSymbolServerLocation(String locationString) {
|
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 final URI serverURI;
|
||||||
|
private boolean trusted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of a HttpSymbolServer.
|
* Creates a new instance of a HttpSymbolServer.
|
||||||
|
@ -60,13 +106,29 @@ public class HttpSymbolServer extends AbstractSymbolServer {
|
||||||
* @param serverURI URI / URL of the symbol server
|
* @param serverURI URI / URL of the symbol server
|
||||||
*/
|
*/
|
||||||
public HttpSymbolServer(URI serverURI) {
|
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();
|
String path = serverURI.getPath();
|
||||||
this.serverURI =
|
this.serverURI =
|
||||||
path.endsWith("/") ? serverURI : serverURI.resolve(serverURI.getPath() + "/");
|
path.endsWith("/") ? serverURI : serverURI.resolve(serverURI.getPath() + "/");
|
||||||
|
this.trusted = isTrusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
return (trusted ? "!" : "") + serverURI.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescriptiveName() {
|
||||||
return serverURI.toString();
|
return serverURI.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,14 +232,19 @@ public class HttpSymbolServer extends AbstractSymbolServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocal() {
|
public boolean isTrusted() {
|
||||||
return false;
|
return trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrusted(boolean isTrusted) {
|
||||||
|
this.trusted = isTrusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("HttpSymbolServer: [ url: %s, storageLevel: %d]", serverURI.toString(),
|
return String.format("HttpSymbolServer: [ url: %s, trusted: %b, storageLevel: %d]",
|
||||||
storageLevel);
|
serverURI.toString(), trusted, storageLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String logPrefix() {
|
private String logPrefix() {
|
||||||
|
|
|
@ -371,11 +371,6 @@ public class LocalSymbolStore extends AbstractSymbolServer implements SymbolStor
|
||||||
return new SymbolServerInputStream(new FileInputStream(file), file.length());
|
return new SymbolServerInputStream(new FileInputStream(file), file.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLocal() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("LocalSymbolStore: [ rootDir: %s, storageLevel: %d]",
|
return String.format("LocalSymbolStore: [ rootDir: %s, storageLevel: %d]",
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.util.*;
|
||||||
|
|
||||||
import ghidra.formats.gfilesystem.FSRL;
|
import ghidra.formats.gfilesystem.FSRL;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import pdb.symbolserver.SymbolServer.StatusRequiresContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Pdb symbol server / symbol store, similar to the {@link LocalSymbolStore},
|
* A Pdb symbol server / symbol store, similar to the {@link LocalSymbolStore},
|
||||||
|
@ -27,7 +28,7 @@ import ghidra.util.task.TaskMonitor;
|
||||||
* <p>
|
* <p>
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SameDirSymbolStore implements SymbolStore {
|
public class SameDirSymbolStore implements SymbolStore, StatusRequiresContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Descriptive string
|
* Descriptive string
|
||||||
|
@ -166,11 +167,6 @@ public class SameDirSymbolStore implements SymbolStore {
|
||||||
return getFile(filename).getPath();
|
return getFile(filename).getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLocal() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("SameDirSymbolStore: [ dir: %s ]", rootDir);
|
return String.format("SameDirSymbolStore: [ dir: %s ]", rootDir);
|
||||||
|
|
|
@ -17,8 +17,7 @@ package pdb.symbolserver;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -29,6 +28,27 @@ import ghidra.util.task.TaskMonitor;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public interface SymbolServer {
|
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,
|
* 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);
|
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.
|
* Returns true if the raw filename exists in the symbol server.
|
||||||
*
|
*
|
||||||
|
@ -103,10 +133,13 @@ public interface SymbolServer {
|
||||||
String getFileLocation(String filename);
|
String getFileLocation(String filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this {@link SymbolServer} is 'local', meaning
|
* Returns the number of configured symbol servers that are considered 'untrusted'.
|
||||||
* it can be searched without security issues / warning the user.
|
|
||||||
*
|
*
|
||||||
* @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;
|
package pdb.symbolserver;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URI;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@ -167,7 +166,7 @@ public class SymbolServerInstanceCreatorRegistry {
|
||||||
registerSymbolServerInstanceCreator(0, DisabledSymbolServer::isDisabledSymbolServerLocation,
|
registerSymbolServerInstanceCreator(0, DisabledSymbolServer::isDisabledSymbolServerLocation,
|
||||||
DisabledSymbolServer::createInstance);
|
DisabledSymbolServer::createInstance);
|
||||||
registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation,
|
registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation,
|
||||||
(loc, context) -> new HttpSymbolServer(URI.create(loc)));
|
HttpSymbolServer::createInstance);
|
||||||
registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation,
|
registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation,
|
||||||
SameDirSymbolStore::createInstance);
|
SameDirSymbolStore::createInstance);
|
||||||
registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation,
|
registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation,
|
||||||
|
|
|
@ -15,11 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package pdb.symbolserver;
|
package pdb.symbolserver;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -31,8 +30,7 @@ import pdb.PdbUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A (lowercase-'S') service that searches for and fetches symbol files
|
* A (lowercase-'S') service that searches for and fetches symbol files
|
||||||
* from a set of local and remote {@link SymbolServer symbolservers}. (not to be
|
* from a set of {@link SymbolServer symbolservers}. (not to be confused with a Plugin service)
|
||||||
* confused with a Plugin service)
|
|
||||||
* <p>
|
* <p>
|
||||||
* Instances of this class are meant to be easily created when needed
|
* 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
|
* 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.
|
* Creates a new SymbolServerService instance.
|
||||||
* <p>
|
* <p>
|
||||||
* @param symbolStore a {@link SymbolStore} - where all
|
* @param symbolStore a {@link SymbolStore} - where all remote files are placed when
|
||||||
* remote files are placed when downloaded. Also treated as a SymbolServer
|
* downloaded. Also treated as a SymbolServer and searched first
|
||||||
* and searched first
|
|
||||||
* @param symbolServers a list of {@link SymbolServer symbol servers} - searched in order
|
* @param symbolServers a list of {@link SymbolServer symbol servers} - searched in order
|
||||||
*/
|
*/
|
||||||
public SymbolServerService(SymbolStore symbolStore, List<SymbolServer> symbolServers) {
|
public SymbolServerService(SymbolStore symbolStore, List<SymbolServer> symbolServers) {
|
||||||
|
@ -91,19 +88,6 @@ public class SymbolServerService {
|
||||||
return new ArrayList<>(symbolServers.subList(1, symbolServers.size()));
|
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.
|
* 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) {
|
for_each_symbol_server_loop: for (SymbolServer symbolServer : symbolServers) {
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
if (!symbolServer.isLocal() && !findOptions.contains(FindOption.ALLOW_REMOTE)) {
|
if (!symbolServer.isTrusted() && !findOptions.contains(FindOption.ALLOW_UNTRUSTED)) {
|
||||||
Msg.debug(this,
|
Msg.debug(this,
|
||||||
logPrefix() + ": skipping non-local symbol server " +
|
logPrefix() + ": skipping untrusted symbol server " +
|
||||||
symbolServer.getDescriptiveName());
|
symbolServer.getDescriptiveName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,37 @@
|
||||||
*/
|
*/
|
||||||
package pdb.symbolserver.ui;
|
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.DialogComponentProvider;
|
||||||
import docking.DockingWindowManager;
|
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 pdb.symbolserver.*;
|
||||||
|
import resources.Icons;
|
||||||
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog that allows the user to configure the Pdb search locations and symbol directory
|
* 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;
|
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 SymbolServerPanel symbolServerConfigPanel;
|
||||||
private boolean wasSuccess;
|
private boolean wasSuccess;
|
||||||
|
private boolean configChanged;
|
||||||
|
|
||||||
public ConfigPdbDialog() {
|
public ConfigPdbDialog() {
|
||||||
super("Configure Symbol Server Search", true, false, true, false);
|
super("Configure Symbol Server Search", true, false, true, true);
|
||||||
|
|
||||||
build();
|
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
|
@Override
|
||||||
|
@ -48,27 +101,33 @@ public class ConfigPdbDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void okCallback() {
|
protected void okCallback() {
|
||||||
if (symbolServerConfigPanel.isConfigChanged()) {
|
if (isConfigChanged()) {
|
||||||
symbolServerConfigPanel.saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
wasSuccess = true;
|
wasSuccess = true;
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dialogShown() {
|
||||||
|
TableColumnInitializer.initializeTableColumns(symbolServerConfigPanel.table, tableModel);
|
||||||
|
symbolServerConfigPanel.refreshSymbolServerLocationStatus(true /* only query trusted */);
|
||||||
|
}
|
||||||
|
|
||||||
private void build() {
|
private void build() {
|
||||||
symbolServerConfigPanel = new SymbolServerPanel(this::onSymbolServerServiceChange,
|
tableModel = new SymbolServerTableModel();
|
||||||
SymbolServerInstanceCreatorRegistry.getInstance().getContext());
|
|
||||||
|
symbolServerConfigPanel = new SymbolServerPanel();
|
||||||
|
|
||||||
addButtons();
|
addButtons();
|
||||||
addWorkPanel(symbolServerConfigPanel);
|
addWorkPanel(symbolServerConfigPanel);
|
||||||
setRememberSize(false);
|
setRememberSize(false);
|
||||||
okButton.setEnabled(symbolServerConfigPanel.getSymbolServerService() != null);
|
okButton.setEnabled(hasSymbolServer());
|
||||||
setMinimumSize(400, 250);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSymbolServerServiceChange(SymbolServerService newService) {
|
private void updateButtonEnablement() {
|
||||||
okButton.setEnabled(newService != null);
|
okButton.setEnabled(hasSymbolServer());
|
||||||
rootPanel.revalidate();
|
symbolServerConfigPanel.updatePanelButtonEnablement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addButtons() {
|
private void addButtons() {
|
||||||
|
@ -81,7 +140,16 @@ public class ConfigPdbDialog extends DialogComponentProvider {
|
||||||
* Screen shot usage only
|
* Screen shot usage only
|
||||||
*/
|
*/
|
||||||
public void pushAddLocationButton() {
|
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 fakeDirectoryText fake text to display in the storage directory text field
|
||||||
* @param symbolServers list of symbol servers to force set
|
* @param symbolServers list of symbol servers to force set
|
||||||
*/
|
*/
|
||||||
public void setSymbolServerService(String fakeDirectoryText,
|
public void setSymbolServerService(String fakeDirectoryText, List<SymbolServer> symbolServers) {
|
||||||
List<SymbolServer> symbolServers) {
|
setSymbolServers(symbolServers);
|
||||||
symbolServerConfigPanel.setSymbolServers(symbolServers);
|
setSymbolStorageLocationPath(fakeDirectoryText);
|
||||||
symbolServerConfigPanel.setSymbolStorageDirectoryTextOnly(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");
|
ExtensionFileFilter.forExtensions("Microsoft Program Databases", "pdb", "pd_", "pdb.xml");
|
||||||
|
|
||||||
private static final SymbolFileInfo UNKNOWN_SYMFILE = makeUnknownSymbolFileInstance("");
|
private static final SymbolFileInfo UNKNOWN_SYMFILE = makeUnknownSymbolFileInstance("");
|
||||||
|
private static final List<WellKnownSymbolServerLocation> knownSymbolServers =
|
||||||
|
WellKnownSymbolServerLocation.loadAll();
|
||||||
|
|
||||||
public static class LoadPdbResults {
|
public static class LoadPdbResults {
|
||||||
public File pdbFile;
|
public File pdbFile;
|
||||||
|
@ -270,7 +272,7 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
||||||
return SymbolFileInfo.fromValues(pdbPath, uid, age);
|
return SymbolFileInfo.fromValues(pdbPath, uid, age);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void searchForPdbs(boolean allowRemote) {
|
private void searchForPdbs(boolean allowUntrusted) {
|
||||||
if (pdbAgeTextField.getText().isBlank() ||
|
if (pdbAgeTextField.getText().isBlank() ||
|
||||||
pdbAgeTextField.getValue() > NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) {
|
pdbAgeTextField.getValue() > NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) {
|
||||||
Msg.showWarn(this, null, "Bad PDB Age", "Invalid PDB Age value");
|
Msg.showWarn(this, null, "Bad PDB Age", "Invalid PDB Age value");
|
||||||
|
@ -282,8 +284,8 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Set<FindOption> findOptions = symbolFilePanel.getFindOptions();
|
Set<FindOption> findOptions = symbolFilePanel.getFindOptions();
|
||||||
if (allowRemote) {
|
if (allowUntrusted) {
|
||||||
findOptions.add(FindOption.ALLOW_REMOTE);
|
findOptions.add(FindOption.ALLOW_UNTRUSTED);
|
||||||
}
|
}
|
||||||
executeMonitoredRunnable("Search for PDBs", true, true, 0, monitor -> {
|
executeMonitoredRunnable("Search for PDBs", true, true, 0, monitor -> {
|
||||||
try {
|
try {
|
||||||
|
@ -316,10 +318,11 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
||||||
setHelpLocation(new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Load PDB File"));
|
setHelpLocation(new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Load PDB File"));
|
||||||
|
|
||||||
addStatusTextSupplier(() -> lastSearchOptions != null && advancedToggleButton.isSelected()
|
addStatusTextSupplier(() -> lastSearchOptions != null && advancedToggleButton.isSelected()
|
||||||
? SymbolServerPanel.getSymbolServerWarnings(symbolServerService.getSymbolServers())
|
? WellKnownSymbolServerLocation.getWarningsFor(knownSymbolServers,
|
||||||
|
symbolServerService.getSymbolServers())
|
||||||
: null);
|
: null);
|
||||||
addStatusTextSupplier(this::getSelectedPdbNoticeText);
|
addStatusTextSupplier(this::getSelectedPdbNoticeText);
|
||||||
addStatusTextSupplier(this::getAllowRemoteWarning);
|
addStatusTextSupplier(this::getAllowUntrustedWarning);
|
||||||
addStatusTextSupplier(this::getFoundCountInfo);
|
addStatusTextSupplier(this::getFoundCountInfo);
|
||||||
|
|
||||||
addButtons();
|
addButtons();
|
||||||
|
@ -600,12 +603,13 @@ public class LoadPdbDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StatusText getAllowRemoteWarning() {
|
private StatusText getAllowUntrustedWarning() {
|
||||||
int remoteSymbolServerCount = symbolServerService.getRemoteSymbolServerCount();
|
int untrustedSymbolServerCount =
|
||||||
|
SymbolServer.getUntrustedCount(symbolServerService.getSymbolServers());
|
||||||
return lastSearchOptions != null && advancedToggleButton.isSelected() &&
|
return lastSearchOptions != null && advancedToggleButton.isSelected() &&
|
||||||
remoteSymbolServerCount != 0 && !lastSearchOptions.contains(FindOption.ALLOW_REMOTE)
|
untrustedSymbolServerCount != 0 && !lastSearchOptions.contains(FindOption.ALLOW_UNTRUSTED)
|
||||||
? new StatusText(
|
? 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)
|
MessageType.INFO, false)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ import pdb.symbolserver.FindOption;
|
||||||
*/
|
*/
|
||||||
class SymbolFilePanel extends JPanel {
|
class SymbolFilePanel extends JPanel {
|
||||||
interface SearchCallback {
|
interface SearchCallback {
|
||||||
void searchForPdbs(boolean allowRemote);
|
void searchForPdbs(boolean allowUntrusted);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final String SEARCH_OPTIONS_HELP_ANCHOR = "PDB_Search_Search_Options";
|
static final String SEARCH_OPTIONS_HELP_ANCHOR = "PDB_Search_Search_Options";
|
||||||
|
@ -149,10 +149,10 @@ class SymbolFilePanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel buildButtonPanel() {
|
private JPanel buildButtonPanel() {
|
||||||
searchLocalButton = new JButton("Search Local");
|
searchLocalButton = new JButton("Search");
|
||||||
searchLocalButton.setToolTipText("Search local symbol servers only.");
|
searchLocalButton.setToolTipText("Search trusted symbol servers only.");
|
||||||
searchAllButton = new JButton("Search All");
|
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 = new GCheckBox("Ignore GUID/ID");
|
||||||
ignorePdbUid.setToolTipText(
|
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;
|
package pdb.symbolserver.ui;
|
||||||
|
|
||||||
|
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.*;
|
||||||
|
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
import pdb.symbolserver.DisabledSymbolServer;
|
import pdb.symbolserver.DisabledSymbolServer;
|
||||||
import pdb.symbolserver.SymbolServer;
|
import pdb.symbolserver.SymbolServer;
|
||||||
|
import pdb.symbolserver.SymbolServer.MutableTrust;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a row in the {@link SymbolServerTableModel}
|
* 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() {
|
LocationStatus getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -67,6 +81,12 @@ class SymbolServerRow {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateStatus(TaskMonitor monitor) {
|
||||||
|
if (!(symbolServer instanceof SymbolServer.StatusRequiresContext)) {
|
||||||
|
this.status = symbolServer.isValid(monitor) ? VALID : INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("SymbolServerRow: [ status: %s, server: %s]", status.toString(),
|
return String.format("SymbolServerRow: [ status: %s, server: %s]", status.toString(),
|
||||||
|
|
|
@ -15,30 +15,28 @@
|
||||||
*/
|
*/
|
||||||
package pdb.symbolserver.ui;
|
package pdb.symbolserver.ui;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.*;
|
||||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.INVALID;
|
|
||||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.VALID;
|
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
|
import generic.theme.GIcon;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||||
import ghidra.util.Swing;
|
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
import ghidra.util.task.TaskLauncher;
|
|
||||||
import pdb.symbolserver.SymbolServer;
|
import pdb.symbolserver.SymbolServer;
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table model for the {@link SymbolServerPanel} table
|
* Table model for the {@link ConfigPdbDialog} table
|
||||||
*/
|
*/
|
||||||
class SymbolServerTableModel
|
class SymbolServerTableModel
|
||||||
extends GDynamicColumnTableModel<SymbolServerRow, List<SymbolServerRow>> {
|
extends GDynamicColumnTableModel<SymbolServerRow, List<SymbolServerRow>> {
|
||||||
|
@ -64,9 +62,7 @@ class SymbolServerTableModel
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SymbolServer> getSymbolServers() {
|
List<SymbolServer> getSymbolServers() {
|
||||||
return rows.stream()
|
return rows.stream().map(SymbolServerRow::getSymbolServer).collect(toList());
|
||||||
.map(SymbolServerRow::getSymbolServer)
|
|
||||||
.collect(toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSymbolServer(SymbolServer ss) {
|
void addSymbolServer(SymbolServer ss) {
|
||||||
|
@ -92,26 +88,6 @@ class SymbolServerTableModel
|
||||||
fireTableDataChanged();
|
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) {
|
void moveRow(int rowIndex, int deltaIndex) {
|
||||||
int destIndex = rowIndex + deltaIndex;
|
int destIndex = rowIndex + deltaIndex;
|
||||||
if (rowIndex < 0 || rowIndex >= rows.size() || destIndex < 0 || destIndex >= rows.size()) {
|
if (rowIndex < 0 || rowIndex >= rows.size() || destIndex < 0 || destIndex >= rows.size()) {
|
||||||
|
@ -159,18 +135,24 @@ class SymbolServerTableModel
|
||||||
@Override
|
@Override
|
||||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||||
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
||||||
if (column instanceof EnabledColumn) {
|
if (column instanceof EnabledColumn && aValue instanceof Boolean) {
|
||||||
SymbolServerRow row = getRowObject(rowIndex);
|
SymbolServerRow row = getRowObject(rowIndex);
|
||||||
row.setEnabled((Boolean) aValue);
|
row.setEnabled((Boolean) aValue);
|
||||||
dataChanged = true;
|
dataChanged = true;
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
else if (column instanceof TrustedColumn && aValue instanceof Boolean) {
|
||||||
|
SymbolServerRow row = getRowObject(rowIndex);
|
||||||
|
row.setTrusted((Boolean) aValue);
|
||||||
|
dataChanged = true;
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
||||||
return column instanceof EnabledColumn;
|
return column instanceof EnabledColumn || column instanceof TrustedColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -178,6 +160,7 @@ class SymbolServerTableModel
|
||||||
TableColumnDescriptor<SymbolServerRow> descriptor = new TableColumnDescriptor<>();
|
TableColumnDescriptor<SymbolServerRow> descriptor = new TableColumnDescriptor<>();
|
||||||
|
|
||||||
descriptor.addVisibleColumn(new EnabledColumn());
|
descriptor.addVisibleColumn(new EnabledColumn());
|
||||||
|
descriptor.addVisibleColumn(new TrustedColumn());
|
||||||
descriptor.addVisibleColumn(new StatusColumn());
|
descriptor.addVisibleColumn(new StatusColumn());
|
||||||
descriptor.addVisibleColumn(new LocationColumn());
|
descriptor.addVisibleColumn(new LocationColumn());
|
||||||
|
|
||||||
|
@ -187,9 +170,10 @@ class SymbolServerTableModel
|
||||||
//-------------------------------------------------------------------------------------------
|
//-------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
private static class StatusColumn extends
|
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 final Icon INVALID_ICON = Icons.ERROR_ICON;
|
||||||
|
|
||||||
private static Icon[] icons = new Icon[] { null, VALID_ICON, INVALID_ICON };
|
private static Icon[] icons = new Icon[] { null, VALID_ICON, INVALID_ICON };
|
||||||
|
@ -206,7 +190,7 @@ class SymbolServerTableModel
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnDisplayName(Settings settings) {
|
public String getColumnDisplayName(Settings settings) {
|
||||||
return "";
|
return "Status";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -219,14 +203,23 @@ class SymbolServerTableModel
|
||||||
return renderer;
|
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
|
private static class EnabledColumn
|
||||||
extends AbstractDynamicTableColumnStub<SymbolServerRow, Boolean> {
|
extends AbstractDynamicTableColumnStub<SymbolServerRow, Boolean>
|
||||||
|
implements TableColumnInitializer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnDisplayName(Settings settings) {
|
public String getColumnDisplayName(Settings settings) {
|
||||||
return "";
|
return "Enabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -240,6 +233,43 @@ class SymbolServerTableModel
|
||||||
return "Enabled";
|
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
|
private static class LocationColumn
|
||||||
|
@ -305,5 +335,6 @@ class SymbolServerTableModel
|
||||||
public String getFilterString(E t, Settings settings) {
|
public String getFilterString(E t, Settings settings) {
|
||||||
return t == null ? "" : t.toString();
|
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.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.framework.Application;
|
import ghidra.framework.Application;
|
||||||
|
import ghidra.util.MessageType;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import pdb.symbolserver.SymbolServer;
|
||||||
|
import pdb.symbolserver.ui.LoadPdbDialog.StatusText;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a well-known symbol server location.
|
* Represents a well-known symbol server location.
|
||||||
* <p>
|
* <p>
|
||||||
* See the PDB_SYMBOL_SERVER_URLS.pdburl file.
|
* 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 {
|
public record WellKnownSymbolServerLocation(String location, String locationCategory,
|
||||||
private String locationCategory;
|
String warning, String fileOrigin) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all symbol server location files (*.pdburl) and returns a list of entries.
|
* Loads all symbol server location files (*.pdburl) and returns a list of entries.
|
||||||
|
@ -103,10 +62,37 @@ class WellKnownSymbolServerLocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
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;
|
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 java.util.Set;
|
||||||
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import pdb.symbolserver.SymbolServer.MutableTrust;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "remote" symbol server that answers affirmatively for any query.
|
* 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 byte[] dummyPayload;
|
||||||
private final boolean returnCompressedFilenames;
|
private final boolean returnCompressedFilenames;
|
||||||
|
private boolean trusted;
|
||||||
|
|
||||||
public DummySymbolServer(String dummyPayload) {
|
public DummySymbolServer(String dummyPayload) {
|
||||||
this(dummyPayload.getBytes(), false);
|
this(dummyPayload.getBytes(), false);
|
||||||
|
@ -78,8 +80,13 @@ public class DummySymbolServer implements SymbolServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocal() {
|
public boolean isTrusted() {
|
||||||
return false;
|
return trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrusted(boolean isTrusted) {
|
||||||
|
this.trusted = isTrusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package pdb.symbolserver;
|
package pdb.symbolserver;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -128,14 +126,14 @@ public class SymbolServerServiceTest extends AbstractGenericTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_Remote() throws IOException, CancelledException {
|
public void test_Trusted() throws IOException, CancelledException {
|
||||||
String payload = "testdummy";
|
String payload = "testdummy";
|
||||||
SymbolServerService symbolServerService =
|
SymbolServerService symbolServerService =
|
||||||
new SymbolServerService(localSymbolStore1,
|
new SymbolServerService(localSymbolStore1,
|
||||||
List.of(localSymbolStore2, new DummySymbolServer(payload)));
|
List.of(localSymbolStore2, new DummySymbolServer(payload)));
|
||||||
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
||||||
List<SymbolFileLocation> results =
|
List<SymbolFileLocation> results =
|
||||||
symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_REMOTE),
|
symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_UNTRUSTED),
|
||||||
TaskMonitor.DUMMY);
|
TaskMonitor.DUMMY);
|
||||||
|
|
||||||
assertEquals(1, results.size());
|
assertEquals(1, results.size());
|
||||||
|
@ -148,8 +146,9 @@ public class SymbolServerServiceTest extends AbstractGenericTest {
|
||||||
@Test
|
@Test
|
||||||
public void test_NoRemote() throws CancelledException {
|
public void test_NoRemote() throws CancelledException {
|
||||||
String payload = "testdummy";
|
String payload = "testdummy";
|
||||||
|
DummySymbolServer dummySymbolServer = new DummySymbolServer(payload);
|
||||||
SymbolServerService symbolServerService =
|
SymbolServerService symbolServerService =
|
||||||
new SymbolServerService(localSymbolStore1, List.of(new DummySymbolServer(payload)));
|
new SymbolServerService(localSymbolStore1, List.of(dummySymbolServer));
|
||||||
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
||||||
List<SymbolFileLocation> results =
|
List<SymbolFileLocation> results =
|
||||||
symbolServerService.find(searchPdb, FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
symbolServerService.find(searchPdb, FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||||
|
|
|
@ -31,8 +31,7 @@ import ghidra.framework.options.Options;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import pdb.PdbPlugin;
|
import pdb.PdbPlugin;
|
||||||
import pdb.symbolserver.*;
|
import pdb.symbolserver.*;
|
||||||
import pdb.symbolserver.ui.ConfigPdbDialog;
|
import pdb.symbolserver.ui.*;
|
||||||
import pdb.symbolserver.ui.LoadPdbDialog;
|
|
||||||
|
|
||||||
public class PdbScreenShots extends GhidraScreenShotGenerator {
|
public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||||
|
|
||||||
|
@ -64,7 +63,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSymbolServerConfig_Screenshot() throws IOException {
|
public void testSymbolServerConfig_Screenshot() {
|
||||||
PdbPlugin.saveSymbolServerServiceConfig(null);
|
PdbPlugin.saveSymbolServerServiceConfig(null);
|
||||||
ConfigPdbDialog configPdbDialog = new ConfigPdbDialog();
|
ConfigPdbDialog configPdbDialog = new ConfigPdbDialog();
|
||||||
showDialogWithoutBlocking(tool, configPdbDialog);
|
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||||
|
@ -79,7 +78,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||||
LocalSymbolStore localSymbolStore1 = new LocalSymbolStore(localSymbolStore1Root);
|
LocalSymbolStore localSymbolStore1 = new LocalSymbolStore(localSymbolStore1Root);
|
||||||
SameDirSymbolStore sameDirSymbolStore = new SameDirSymbolStore(null);
|
SameDirSymbolStore sameDirSymbolStore = new SameDirSymbolStore(null);
|
||||||
List<SymbolServer> symbolServers = List.of(sameDirSymbolStore,
|
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 =
|
SymbolServerService symbolServerService =
|
||||||
new SymbolServerService(localSymbolStore1, symbolServers);
|
new SymbolServerService(localSymbolStore1, symbolServers);
|
||||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
||||||
|
@ -88,7 +87,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||||
configPdbDialog.setSymbolServerService("/home/user/symbols", symbolServers);
|
configPdbDialog.setSymbolServerService("/home/user/symbols", symbolServers);
|
||||||
showDialogWithoutBlocking(tool, configPdbDialog);
|
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
captureDialog(ConfigPdbDialog.class, 410, 280);
|
captureDialog(ConfigPdbDialog.class, 520, 280);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -112,12 +111,29 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||||
showDialogWithoutBlocking(tool, configPdbDialog);
|
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
|
configPdbDialog.setWellknownSymbolServers(createFakeWellKnowns());
|
||||||
configPdbDialog.pushAddLocationButton();
|
configPdbDialog.pushAddLocationButton();
|
||||||
});
|
});
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
captureMenu();
|
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
|
@Test
|
||||||
public void testLoadPdb_Advanced_NeedsConfig() {
|
public void testLoadPdb_Advanced_NeedsConfig() {
|
||||||
PdbPlugin.saveSymbolServerServiceConfig(null);
|
PdbPlugin.saveSymbolServerServiceConfig(null);
|
||||||
|
@ -158,7 +174,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||||
localSymbolStore1, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 2)),
|
localSymbolStore1, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 2)),
|
||||||
new SymbolFileLocation("HelloWorld.pdb", sameDirSymbolStoreWithFakePath,
|
new SymbolFileLocation("HelloWorld.pdb", sameDirSymbolStoreWithFakePath,
|
||||||
SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 1)));
|
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(() -> {
|
runSwing(() -> {
|
||||||
loadPdbDialog.setSearchOptions(findOptions);
|
loadPdbDialog.setSearchOptions(findOptions);
|
||||||
loadPdbDialog.setSearchResults(symbolFileLocations, findOptions);
|
loadPdbDialog.setSearchResults(symbolFileLocations, findOptions);
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTes
|
||||||
|
|
||||||
List<SymbolFileLocation> results =
|
List<SymbolFileLocation> results =
|
||||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
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());
|
assertEquals(1, results.size());
|
||||||
System.out.println(results.get(0).getLocationStr());
|
System.out.println(results.get(0).getLocationStr());
|
||||||
|
@ -118,7 +118,7 @@ public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTes
|
||||||
|
|
||||||
List<SymbolFileLocation> results =
|
List<SymbolFileLocation> results =
|
||||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
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());
|
assertEquals(1, results.size());
|
||||||
System.out.println(results.get(0).getLocationStr());
|
System.out.println(results.get(0).getLocationStr());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue