/* ### * 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 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 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. *

* 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 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 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 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 getSymbolSearchPaths() { String searchPathStr = Preferences.getProperty(SYMBOL_SEARCH_PATH_OPTION, "", true); String[] pathParts = searchPathStr.split(";"); List result = new ArrayList<>(); for (String part : pathParts) { part = part.trim(); if (!part.isEmpty()) { result.add(part); } } return result; } }