mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-42 Initial implementation of Pdb symbol store / symbol servers
This commit is contained in:
parent
df72f24b58
commit
425667e640
93 changed files with 7715 additions and 3874 deletions
|
@ -16,30 +16,33 @@
|
|||
package pdb;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import docking.action.MenuData;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.ProgramActionContext;
|
||||
import ghidra.app.context.ProgramContextAction;
|
||||
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.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.pdb.pdbapplicator.PdbApplicatorControl;
|
||||
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.filechooser.ExtensionFileFilter;
|
||||
import ghidra.util.task.TaskBuilder;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
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(
|
||||
|
@ -51,9 +54,14 @@ import ghidra.util.task.TaskLauncher;
|
|||
)
|
||||
//@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";
|
||||
|
||||
private ProgramContextAction loadPdbAction;
|
||||
private GhidraFileChooser pdbChooser;
|
||||
// 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);
|
||||
|
@ -62,33 +70,34 @@ public class PdbPlugin extends Plugin {
|
|||
}
|
||||
|
||||
private void createActions() {
|
||||
loadPdbAction = new ProgramContextAction("Load PDB File", this.getName()) {
|
||||
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);
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ProgramActionContext context) {
|
||||
return context.getProgram() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(ProgramActionContext programContext) {
|
||||
loadPDB();
|
||||
}
|
||||
};
|
||||
|
||||
MenuData menuData =
|
||||
new MenuData(new String[] { "&File", "Load PDB File..." }, null, "Import PDB");
|
||||
menuData.setMenuSubGroup("3"); // below the major actions in the "Import/Export" group
|
||||
loadPdbAction.setMenuBarData(menuData);
|
||||
|
||||
loadPdbAction.setEnabled(false);
|
||||
loadPdbAction.setHelpLocation(new HelpLocation("ImporterPlugin", loadPdbAction.getName()));
|
||||
tool.addAction(loadPdbAction);
|
||||
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 loadPDB() {
|
||||
Program program = GhidraProgramUtilities.getCurrentProgram(tool);
|
||||
AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program);
|
||||
if (aam.isAnalyzing()) {
|
||||
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;
|
||||
|
@ -110,26 +119,17 @@ public class PdbPlugin extends Plugin {
|
|||
}
|
||||
|
||||
try {
|
||||
File pdb = getPdbFile(program);
|
||||
if (pdb == null) {
|
||||
LoadPdbResults loadPdbResults = LoadPdbDialog.choosePdbForProgram(program);
|
||||
if (loadPdbResults == null) {
|
||||
tool.setStatusInfo("Loading PDB was cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isPdbFile = pdb.getName().toLowerCase().endsWith(".pdb");
|
||||
|
||||
AskPdbOptionsDialog optionsDialog = new AskPdbOptionsDialog(null, isPdbFile);
|
||||
if (optionsDialog.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean useMsDiaParser = optionsDialog.useMsDiaParser();
|
||||
PdbApplicatorControl control = optionsDialog.getApplicatorControl();
|
||||
|
||||
tool.setStatusInfo("");
|
||||
|
||||
DataTypeManagerService service = tool.getService(DataTypeManagerService.class);
|
||||
if (service == null) {
|
||||
DataTypeManagerService dataTypeManagerService =
|
||||
tool.getService(DataTypeManagerService.class);
|
||||
if (dataTypeManagerService == null) {
|
||||
Msg.showWarn(getClass(), null, "Load PDB",
|
||||
"Unable to locate DataTypeService in the current tool.");
|
||||
return;
|
||||
|
@ -138,34 +138,122 @@ public class PdbPlugin extends Plugin {
|
|||
// 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 task = new LoadPdbTask(program, pdb, useMsDiaParser, control, service);
|
||||
TaskBuilder.withTask(task)
|
||||
LoadPdbTask loadPdbTask = new LoadPdbTask(program, loadPdbResults.pdbFile,
|
||||
loadPdbResults.useMsDiaParser, loadPdbResults.control,
|
||||
loadPdbResults.debugLogging, dataTypeManagerService);
|
||||
TaskBuilder.withTask(loadPdbTask)
|
||||
.setStatusTextAlignment(SwingConstants.LEADING)
|
||||
.setLaunchDelay(0);
|
||||
new TaskLauncher(task, null, 0);
|
||||
new TaskLauncher(loadPdbTask, null, 0);
|
||||
}
|
||||
catch (Exception pe) {
|
||||
Msg.showError(getClass(), null, "Error Loading PDB", pe.getMessage(), pe);
|
||||
}
|
||||
}
|
||||
|
||||
private File getPdbFile(Program program) {
|
||||
File pdbFile = PdbParser.findPDB(program);
|
||||
if (pdbChooser == null) {
|
||||
pdbChooser = new GhidraFileChooser(tool.getToolFrame());
|
||||
pdbChooser.setTitle("Select PDB file to load:");
|
||||
pdbChooser.setApproveButtonText("Select PDB");
|
||||
pdbChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
pdbChooser.setFileFilter(new ExtensionFileFilter(new String[] { "pdb", "xml" },
|
||||
"Program Database Files and PDB XML Representations"));
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
|
||||
if (pdbFile != null) {
|
||||
pdbChooser.setSelectedFile(pdbFile);
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
File selectedFile = pdbChooser.getSelectedFile();
|
||||
return selectedFile;
|
||||
try {
|
||||
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromMetadata(program.getMetadata());
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue