ghidra/Ghidra/Features/PDB/src/main/java/pdb/PdbPlugin.java

333 lines
12 KiB
Java

/* ###
* 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;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.SwingConstants;
import docking.DockingWindowManager;
import docking.action.builder.ActionBuilder;
import docking.tool.ToolConstants;
import docking.widgets.OptionDialog;
import docking.widgets.dialogs.MultiLineMessageDialog;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.plugin.core.analysis.PdbAnalyzerCommon;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.listing.Program;
import ghidra.program.util.GhidraProgramUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
import pdb.symbolserver.*;
import pdb.symbolserver.ui.ConfigPdbDialog;
import pdb.symbolserver.ui.LoadPdbDialog;
import pdb.symbolserver.ui.LoadPdbDialog.LoadPdbResults;
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.COMMON,
shortDescription = "Import External PDB Files",
description = "This plugin manages the import of PDB files to add debug information to a program."
)
//@formatter:on
public class PdbPlugin extends Plugin {
private static final String PDB_SYMBOL_SERVER_OPTIONS = "PdbSymbolServer";
private static final String SYMBOL_STORAGE_DIR_OPTION =
PDB_SYMBOL_SERVER_OPTIONS + ".Symbol_Storage_Directory";
private static final String SYMBOL_SEARCH_PATH_OPTION =
PDB_SYMBOL_SERVER_OPTIONS + ".Symbol_Search_Path";
// the name of the help directory under src/main/help/help/topics
public static final String PDB_PLUGIN_HELP_TOPIC = "Pdb";
public PdbPlugin(PluginTool tool) {
super(tool);
createActions();
}
private void createActions() {
new ActionBuilder("Load PDB File", this.getName())
.supportsDefaultToolContext(true)
.withContext(ProgramActionContext.class)
.validContextWhen(pac -> pac.getProgram() != null &&
PdbAnalyzerCommon.canAnalyzeProgram(pac.getProgram()))
.menuPath(ToolConstants.MENU_FILE, "Load PDB File...")
.menuGroup("Import PDB", "3")
.helpLocation(new HelpLocation(PDB_PLUGIN_HELP_TOPIC, "Load PDB File"))
.onAction(pac -> loadPDB(pac))
.buildAndInstall(tool);
new ActionBuilder("Symbol Server Config", this.getName())
.menuPath(ToolConstants.MENU_EDIT, "Symbol Server Config")
.menuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP)
.helpLocation(new HelpLocation(PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config"))
.onAction(ac -> configPDB())
.buildAndInstall(tool);
}
private void configPDB() {
ConfigPdbDialog.showSymbolServerConfig();
}
private void loadPDB(ProgramActionContext pac) {
Program program = pac.getProgram();
AutoAnalysisManager currentAutoAnalysisManager =
AutoAnalysisManager.getAnalysisManager(program);
if (currentAutoAnalysisManager.isAnalyzing()) {
Msg.showWarn(getClass(), null, "Load PDB",
"Unable to load PDB file while analysis is running.");
return;
}
boolean analyzed = GhidraProgramUtilities.isAnalyzed(program);
if (analyzed) {
int response =
OptionDialog.showOptionDialogWithCancelAsDefaultButton(null, "Load PDB Warning",
"Loading PDB after running analysis may produce poor results." +
"\nPDBs should generally be loaded prior to analysis or" +
"\nautomatically during auto-analysis.",
"Continue");
if (response != OptionDialog.OPTION_ONE) {
return;
}
}
File pdbFile = null;
try {
LoadPdbResults loadPdbResults = LoadPdbDialog.choosePdbForProgram(program);
if (loadPdbResults == null) {
tool.setStatusInfo("Loading PDB was cancelled.");
return;
}
pdbFile = loadPdbResults.pdbFile;
tool.setStatusInfo("");
DataTypeManagerService dataTypeManagerService =
tool.getService(DataTypeManagerService.class);
if (dataTypeManagerService == null) {
Msg.showWarn(getClass(), null, "Load PDB",
"Unable to locate DataTypeService in the current tool.");
return;
}
// note: We intentionally use a 0-delay here. Our underlying task may show modal
// dialog prompts. We want the task progress dialog to be showing before any
// prompts appear.
LoadPdbTask loadPdbTask = new LoadPdbTask(program, pdbFile,
loadPdbResults.useMsDiaParser, loadPdbResults.control, dataTypeManagerService);
TaskBuilder.withTask(loadPdbTask)
.setStatusTextAlignment(SwingConstants.LEADING)
.setLaunchDelay(0);
new TaskLauncher(loadPdbTask, null, 0);
// Check for error messages & exceptions and handle them here
// (previously handled by the task, but dialog parenting issues in a modal
// task cause timing issues)
if (loadPdbTask.getResultException() != null) {
throw loadPdbTask.getResultException();
}
else if (loadPdbTask.getResultMessages() != null) {
MultiLineMessageDialog dialog = new MultiLineMessageDialog("Load PDB File",
"There were warnings/errors loading PDB file: " + pdbFile,
loadPdbTask.getResultMessages(),
MultiLineMessageDialog.WARNING_MESSAGE, false);
DockingWindowManager.showDialog(null, dialog);
}
}
catch (Exception e) {
String message = null;
if (e instanceof InvocationTargetException && e.getCause() != null) {
message =
Objects.requireNonNullElse(e.getCause().getMessage(), e.getCause().toString());
}
else {
message = Objects.requireNonNullElse(e.getMessage(), e.toString());
}
Msg.showError(this, null, "Error Loading PDB",
"Error processing PDB file: " + pdbFile + "\n" + message, e);
}
}
//-------------------------------------------------------------------------------------------------------
/**
* Searches the currently configured symbol server paths for a Pdb symbol file.
*
* @param program the program associated with the requested pdb file
* @param findOptions options that control how to search for the symbol file
* @param monitor a {@link TaskMonitor} that allows the user to cancel
* @return a File that points to the found Pdb symbol file, or null if no file was found
*/
public static File findPdb(Program program, Set<FindOption> findOptions, TaskMonitor monitor) {
try {
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromProgramInfo(program);
if (symbolFileInfo == null) {
return null;
}
// make a copy and add in the ONLY_FIRST_RESULT option
findOptions = findOptions.isEmpty() ? EnumSet.noneOf(FindOption.class)
: EnumSet.copyOf(findOptions);
findOptions.add(FindOption.ONLY_FIRST_RESULT);
SymbolServerInstanceCreatorContext temporarySymbolServerInstanceCreatorContext =
SymbolServerInstanceCreatorRegistry.getInstance().getContext(program);
SymbolServerService temporarySymbolServerService =
getSymbolServerService(temporarySymbolServerInstanceCreatorContext);
List<SymbolFileLocation> results =
temporarySymbolServerService.find(symbolFileInfo, findOptions, monitor);
if (!results.isEmpty()) {
return temporarySymbolServerService.getSymbolFile(results.get(0), monitor);
}
}
catch (CancelledException e) {
// ignore
}
catch (IOException e) {
Msg.error(PdbPlugin.class, "Error getting symbol file", e);
}
return null;
}
/**
* Searches the currently configured symbol server paths for a Pdb symbol file.
* <p>
* Any "SameDir" search location in the configuration will be ignored because there is
* not an associated Program.
*
* @param symbolFileInfo info about the pdb that is being searched for.
* See {@link SymbolFileInfo#fromValues(String, String, int)}
* @param findOptions options that control how to search for the symbol file
* @param monitor a {@link TaskMonitor} that allows the user to cancel
* @return a File that points to the found Pdb symbol file, or null if no file was found
*/
public static File findPdb(SymbolFileInfo symbolFileInfo, Set<FindOption> findOptions,
TaskMonitor monitor) {
if (symbolFileInfo == null) {
return null;
}
try {
// make a copy and add in the ONLY_FIRST_RESULT option
findOptions = findOptions.isEmpty()
? EnumSet.noneOf(FindOption.class)
: EnumSet.copyOf(findOptions);
findOptions.add(FindOption.ONLY_FIRST_RESULT);
SymbolServerInstanceCreatorContext temporarySymbolServerInstanceCreatorContext =
SymbolServerInstanceCreatorRegistry.getInstance().getContext();
SymbolServerService temporarySymbolServerService =
getSymbolServerService(temporarySymbolServerInstanceCreatorContext);
List<SymbolFileLocation> results =
temporarySymbolServerService.find(symbolFileInfo, findOptions, monitor);
if (!results.isEmpty()) {
return temporarySymbolServerService.getSymbolFile(results.get(0), monitor);
}
}
catch (CancelledException e) {
// ignore
}
catch (IOException e) {
Msg.error(PdbPlugin.class, "Error getting symbol file", e);
}
return null;
}
/**
* Returns a new instance of a {@link SymbolServerService} configured with values from the
* application's preferences, defaulting to a minimal instance if there is no config.
*
* @param symbolServerInstanceCreatorContext an object that provides the necessary context to
* the SymbolServerInstanceCreatorRegistry to create the SymbolServers that are listed in the
* config values
* @return a new {@link SymbolServerService} instance, never null
*/
public static SymbolServerService getSymbolServerService(
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) {
SymbolServer temporarySymbolServer =
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
.newSymbolServer(Preferences.getProperty(SYMBOL_STORAGE_DIR_OPTION, "", true),
symbolServerInstanceCreatorContext);
SymbolStore symbolStore =
(temporarySymbolServer instanceof SymbolStore) ? (SymbolStore) temporarySymbolServer
: new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir());
List<SymbolServer> symbolServers =
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
.createSymbolServersFromPathList(getSymbolSearchPaths(),
symbolServerInstanceCreatorContext);
return new SymbolServerService(symbolStore, symbolServers);
}
/**
* Persists the {@link SymbolStore} and {@link SymbolServer}s contained in the
* {@link SymbolServerService}.
*
* @param symbolServerService {@link SymbolServerService} to save, or null if clear p
* reference values
*/
public static void saveSymbolServerServiceConfig(SymbolServerService symbolServerService) {
if (symbolServerService != null) {
Preferences.setProperty(SYMBOL_STORAGE_DIR_OPTION,
symbolServerService.getSymbolStore().getName());
String path = symbolServerService.getSymbolServers()
.stream()
.map(SymbolServer::getName)
.collect(Collectors.joining(";"));
Preferences.setProperty(SYMBOL_SEARCH_PATH_OPTION, path);
}
else {
Preferences.setProperty(SYMBOL_STORAGE_DIR_OPTION, null);
Preferences.setProperty(SYMBOL_SEARCH_PATH_OPTION, null);
}
}
private static List<String> getSymbolSearchPaths() {
String searchPathStr = Preferences.getProperty(SYMBOL_SEARCH_PATH_OPTION, "", true);
String[] pathParts = searchPathStr.split(";");
List<String> result = new ArrayList<>();
for (String part : pathParts) {
part = part.trim();
if (!part.isEmpty()) {
result.add(part);
}
}
return result;
}
}