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