diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARFSetExternalDebugFilesLocationPrescript.java b/Ghidra/Features/Base/ghidra_scripts/DWARFSetExternalDebugFilesLocationPrescript.java new file mode 100644 index 0000000000..dbd6b6f34c --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/DWARFSetExternalDebugFilesLocationPrescript.java @@ -0,0 +1,55 @@ +/* ### + * 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. + */ +// Example analyzeHeadless prescript to set the DWARF analyzer's external debug files +// search location. +// +// Example: +// +// export DWARF_EXTERNAL_DEBUG_FILES=/home/myuserid/debugfiles +// analyzeHeadless [...] -preScript DWARFSetExternalDebugFilesLocationPrescript.java +//@category DWARF +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.script.GhidraScript; +import ghidra.app.util.bin.format.dwarf4.external.*; +import ghidra.util.Msg; + +public class DWARFSetExternalDebugFilesLocationPrescript extends GhidraScript { + @Override + protected void run() throws Exception { + String dwarfExtDebugFilesLocEnvVar = System.getenv("DWARF_EXTERNAL_DEBUG_FILES"); + if (dwarfExtDebugFilesLocEnvVar == null) { + return; + } + File dir = new File(dwarfExtDebugFilesLocEnvVar); + if (!dir.isDirectory()) { + Msg.warn(this, "Invalid DWARF external debug files location specified: " + dir); + return; + } + List searchLocations = new ArrayList<>(); + + File buildIdDir = new File(dir, ".build-id"); + if (buildIdDir.isDirectory()) { + searchLocations.add(new BuildIdSearchLocation(buildIdDir)); + } + searchLocations.add(new LocalDirectorySearchLocation(dir)); + ExternalDebugFilesService edfs = new ExternalDebugFilesService(searchLocations); + DWARFExternalDebugFilesPlugin.saveExternalDebugFilesService(edfs); + } + +} diff --git a/Ghidra/Features/Base/ghidra_scripts/ExtractELFDebugFilesScript.java b/Ghidra/Features/Base/ghidra_scripts/ExtractELFDebugFilesScript.java new file mode 100644 index 0000000000..312c16c880 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/ExtractELFDebugFilesScript.java @@ -0,0 +1,119 @@ +/* ### + * 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. + */ +// Bulk extracts ELF external debug files from distro container files (rpm, ddeb, etc) so that +// the DWARF analyzer can find them using the "Edit | DWARF External Debug Config" location. +// +// When using this script, do not co-mingle different architectures or versions of debug files in +// the same directory as debug file names may conflict. +// +// Known issues: symlinks between different locations inside the debug package are not extracted, +// so some layout schemes where a single debug binary file is present in both the .build-id directory +// and the /usr/lib/, /usr/bin/, etc directory will not fully work. +// +// @category DWARF +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import org.apache.commons.io.FilenameUtils; + +import ghidra.app.script.GhidraScript; +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.*; +import ghidra.util.exception.CancelledException; +import utilities.util.FileUtilities; + +public class ExtractELFDebugFilesScript extends GhidraScript { + private static final Set containerExts = + Set.of("ddeb", "tar", "xz", "rpm", "tgz", "deb", "srpm", "cpio"); + + private FileSystemService fsService; + private int fileCount; + + @Override + protected void run() throws Exception { + fsService = FileSystemService.getInstance(); + + File baseDir = askDirectory("Debug Packages Directory", "Select"); + if (baseDir == null) { + return; + } + File destDir = askDirectory("Extract Destination Directory", "Select"); + if (destDir == null) { + return; + } + + println("Source directory: " + baseDir); + println("Destination directory: " + destDir); + try (FileSystemRef fsRef = + fsService.probeFileForFilesystem(fsService.getLocalFSRL(baseDir), monitor, null)) { + processDir(fsRef.getFilesystem().lookup(null), destDir); + } + println("Extracted: " + fileCount); + } + + void processDir(GFile dir, File destDir) throws IOException, CancelledException { + List listing = dir.getListing(); + for (GFile file : listing) { + monitor.checkCanceled(); + if (file.isDirectory()) { + continue; + } + String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(); + if (containerExts.contains(extension)) { + try (FileSystemRef fsRef = + fsService.probeFileForFilesystem(file.getFSRL(), monitor, null)) { + if (fsRef == null) { + continue; + } + + processDir(fsRef.getFilesystem().lookup(null), destDir); + } + } + else if ("debug".equalsIgnoreCase(extension)) { + extractDebugFileToDestDir(file, destDir); + } + } + for (GFile file : listing) { + monitor.checkCanceled(); + if (file.isDirectory()) { + processDir(file, destDir); + } + } + } + + private void extractDebugFileToDestDir(GFile file, File destDir) + throws CancelledException, IOException { + File destFile = new File(destDir, file.getPath()).getCanonicalFile(); + if (!FileUtilities.isPathContainedWithin(destDir, destFile)) { + throw new IOException("Bad path / filename: " + file); + } + if (destFile.exists()) { + printerr("Duplicate debug file: " + file.getFSRL() + ", " + destFile); + return; + } + + try (ByteProvider bp = file.getFilesystem().getByteProvider(file, monitor)) { + FileUtilities.checkedMkdirs(destFile.getParentFile()); + FSUtilities.copyByteProviderToFile(bp, destFile, monitor); + fileCount++; + } + catch (IOException e) { + printerr("Error extracting file: " + file + ", " + e.getMessage()); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java index 6a501ea5e1..d0de2f0bcd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java @@ -123,9 +123,10 @@ public class DWARFAnalyzer extends AbstractAnalyzer { return false; } - DWARFSectionProvider dsp = DWARFSectionProviderFactory.createSectionProviderFor(program); + DWARFSectionProvider dsp = + DWARFSectionProviderFactory.createSectionProviderFor(program, monitor); // closed by DWARFProgram if (dsp == null) { - log.appendMsg("Unable to find DWARF information, skipping DWARF analysis"); + Msg.info(this, "Unable to find DWARF information, skipping DWARF analysis"); return false; } @@ -145,6 +146,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer { parseResults.logSummaryResults(); } propList.setBoolean(DWARF_LOADED_OPTION_NAME, true); + dsp.updateProgramInfo(program); return true; } catch (CancelledException ce) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java index 61ff961d46..d72df68d7b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java @@ -127,46 +127,10 @@ public abstract class AbstractCodeBrowserPlugin

ex fieldOptions.addOptionsChangeListener(this); tool.setDefaultComponent(connectedProvider); markerChangeListener = new MarkerChangeListener(connectedProvider); - createActions(); } protected abstract P createProvider(FormatManager formatManager, boolean isConnected); - private void createActions() { - new ActionBuilder("Select All", getName()) - .menuPath(ToolConstants.MENU_SELECTION, "&All in View") - .menuGroup("Select Group", "a") - .keyBinding("ctrl A") - .supportsDefaultToolContext(true) - .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select All")) - .withContext(CodeViewerActionContext.class) - .inWindow(ActionBuilder.When.CONTEXT_MATCHES) - .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectAll()) - .buildAndInstall(tool); - - new ActionBuilder("Clear Selection", getName()) - .menuPath(ToolConstants.MENU_SELECTION, "&Clear Selection") - .menuGroup("Select Group", "b") - .supportsDefaultToolContext(true) - .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Clear Selection")) - .withContext(CodeViewerActionContext.class) - .inWindow(ActionBuilder.When.CONTEXT_MATCHES) - .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()) - .setSelection(new ProgramSelection())) - .buildAndInstall(tool); - - new ActionBuilder("Select Complement", getName()) - .menuPath(ToolConstants.MENU_SELECTION, "&Complement") - .menuGroup("Select Group", "c") - .supportsDefaultToolContext(true) - .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select Complement")) - .withContext(CodeViewerActionContext.class) - .inWindow(ActionBuilder.When.CONTEXT_MATCHES) - .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectComplement()) - .buildAndInstall(tool); - - } - protected void viewChanged(AddressSetView addrSet) { ProgramLocation currLoc = getCurrentLocation(); currentView = addrSet; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java new file mode 100644 index 0000000000..93fffa3da2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java @@ -0,0 +1,83 @@ +/* ### + * 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 ghidra.app.plugin.core.codebrowser; + +import docking.action.builder.ActionBuilder; +import docking.tool.ToolConstants; +import ghidra.app.CorePluginPackage; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.util.HelpTopics; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.util.ProgramSelection; +import ghidra.util.HelpLocation; + +/** + * Plugin for adding some basic selection actions for Code Browser Listings. + */ + +//@formatter:off +@PluginInfo( + status = PluginStatus.RELEASED, + packageName = CorePluginPackage.NAME, + category = PluginCategoryNames.COMMON, + shortDescription = "Basic Selection actions", + description = "This plugin provides actions for Code Browser Listing components" +) +//@formatter:on +public class CodeBrowserSelectionPlugin extends Plugin { + + public CodeBrowserSelectionPlugin(PluginTool tool) { + super(tool); + createActions(); + } + + private void createActions() { + new ActionBuilder("Select All", getName()) + .menuPath(ToolConstants.MENU_SELECTION, "&All in View") + .menuGroup("Select Group", "a") + .keyBinding("ctrl A") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select All")) + .withContext(CodeViewerActionContext.class) + .inWindow(ActionBuilder.When.CONTEXT_MATCHES) + .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectAll()) + .buildAndInstall(tool); + + new ActionBuilder("Clear Selection", getName()) + .menuPath(ToolConstants.MENU_SELECTION, "&Clear Selection") + .menuGroup("Select Group", "b") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Clear Selection")) + .withContext(CodeViewerActionContext.class) + .inWindow(ActionBuilder.When.CONTEXT_MATCHES) + .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()) + .setSelection(new ProgramSelection())) + .buildAndInstall(tool); + + new ActionBuilder("Select Complement", getName()) + .menuPath(ToolConstants.MENU_SELECTION, "&Complement") + .menuGroup("Select Group", "c") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select Complement")) + .withContext(CodeViewerActionContext.class) + .inWindow(ActionBuilder.When.CONTEXT_MATCHES) + .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectComplement()) + .buildAndInstall(tool); + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RenameAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RenameAction.java index ca5ab9a414..fcd05afe75 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RenameAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RenameAction.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +15,6 @@ */ package ghidra.app.plugin.core.datamgr.actions; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.DataTypesActionContext; -import ghidra.app.plugin.core.datamgr.tree.*; - import javax.swing.tree.TreePath; import docking.ActionContext; @@ -27,6 +22,9 @@ import docking.action.DockingAction; import docking.action.MenuData; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.DataTypesActionContext; +import ghidra.app.plugin.core.datamgr.tree.*; public class RenameAction extends DockingAction { @@ -76,7 +74,7 @@ public class RenameAction extends DockingAction { void rename(final DataTypeArchiveGTree tree) { TreePath path = tree.getSelectionPath(); final GTreeNode node = (GTreeNode) path.getLastPathComponent(); - tree.startEditing(node.getParent(), node.getName()); + tree.startEditing(node); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java index f0d90f614c..009d0c5bd2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java @@ -57,14 +57,6 @@ public class ArchiveNode extends CategoryNode { nodeChanged(); // notify that this nodes display data has changed } - // override clone to install the needed listeners - @Override - public GTreeNode clone() throws CloneNotSupportedException { - ArchiveNode clone = (ArchiveNode) super.clone(); - clone.installDataTypeManagerListener(); - return clone; - } - protected void installDataTypeManagerListener() { if (dataTypeManager == null) { return; // some nodes do not have DataTypeManagers, like InvalidFileArchives diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java index 894c9f88ed..8c38512932 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java @@ -48,14 +48,6 @@ public class ArchiveRootNode extends DataTypeTreeNode { return archiveManager; } - // override clone to install the needed listeners - @Override - public GTreeNode clone() throws CloneNotSupportedException { - ArchiveRootNode clone = (ArchiveRootNode) super.clone(); - clone.init(); - return clone; - } - @Override public void dispose() { archiveManager.removeArchiveManagerListener(archiveListener); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java index 6f379d5ac6..fd142c52a3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +15,13 @@ */ package ghidra.app.plugin.core.symboltree.actions; -import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; -import ghidra.app.plugin.core.symboltree.SymbolTreePlugin; -import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; - import javax.swing.tree.TreePath; import docking.action.MenuData; import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; +import ghidra.app.plugin.core.symboltree.SymbolTreePlugin; +import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; public class RenameAction extends SymbolTreeContextAction { @@ -47,7 +45,7 @@ public class RenameAction extends SymbolTreeContextAction { public void actionPerformed(SymbolTreeActionContext context) { TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent(); - context.getSymbolTree().startEditing(node.getParent(), node.getName()); + context.getSymbolTree().startEditing(node); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java index 6421a9bca1..10023800e2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java @@ -26,6 +26,19 @@ import ghidra.program.model.mem.MemoryBlock; */ public class MemoryByteProvider implements ByteProvider { + /** + * Create a {@link ByteProvider} that is limited to the specified {@link MemoryBlock}. + * + * @param memory {@link Memory} of the program + * @param block {@link MemoryBlock} to read from + * @return new {@link ByteProvider} that contains the bytes of the specified MemoryBlock + */ + public static ByteProvider createMemoryBlockByteProvider(Memory memory, MemoryBlock block) { + long blockLen = block.getEnd().subtract(block.getStart()) + 1; + ByteProvider bp = new MemoryByteProvider(memory, block.getStart()); + return new ByteProviderWrapper(bp, 0, blockLen); + } + protected Memory memory; protected Address baseAddress; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/BuildIdSearchLocation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/BuildIdSearchLocation.java new file mode 100644 index 0000000000..6fbc28fd32 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/BuildIdSearchLocation.java @@ -0,0 +1,97 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.io.File; +import java.io.IOException; + +import ghidra.formats.gfilesystem.FSRL; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A {@link SearchLocation} that expects the external debug files to be named using the hexadecimal + * value of the hash of the file, and to be arranged in a bucketed directory hierarchy using the + * first 2 hexdigits of the hash. + *

+ * For example, the debug file with hash {@code 6addc39dc19c1b45f9ba70baf7fd81ea6508ea7f} would + * be stored as "6a/ddc39dc19c1b45f9ba70baf7fd81ea6508ea7f.debug" (under some root directory). + */ +public class BuildIdSearchLocation implements SearchLocation { + + /** + * Returns true if the specified location string specifies a BuildIdSearchLocation. + * + * @param locString string to test + * @return boolean true if locString specifies a BuildId location + */ + public static boolean isBuildIdSearchLocation(String locString) { + return locString.startsWith(BUILD_ID_PREFIX); + } + + /** + * Creates a new {@link BuildIdSearchLocation} instance using the specified location string. + * + * @param locString string, earlier returned from {@link #getName()} + * @param context {@link SearchLocationCreatorContext} to allow accessing information outside + * of the location string that might be needed to create a new instance + * @return new {@link BuildIdSearchLocation} instance + */ + public static BuildIdSearchLocation create(String locString, + SearchLocationCreatorContext context) { + locString = locString.substring(BUILD_ID_PREFIX.length()); + + return new BuildIdSearchLocation(new File(locString)); + } + + private static final String BUILD_ID_PREFIX = "build-id://"; + private final File rootDir; + + /** + * Creates a new {@link BuildIdSearchLocation} at the specified location. + * + * @param rootDir path to the root directory of the build-id directory (typically ends with + * "./build-id") + */ + public BuildIdSearchLocation(File rootDir) { + this.rootDir = rootDir; + } + + @Override + public String getName() { + return BUILD_ID_PREFIX + rootDir.getPath(); + } + + @Override + public String getDescriptiveName() { + return rootDir.getPath() + " (build-id)"; + } + + @Override + public FSRL findDebugFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) + throws IOException, CancelledException { + String hash = NumericUtilities.convertBytesToString(debugInfo.getHash()); + if (hash == null || hash.length() < 4 /* 2 bytes = 4 hex digits */ ) { + return null; + } + File bucketDir = new File(rootDir, hash.substring(0, 2)); + File file = new File(bucketDir, hash.substring(2) + ".debug"); + return file.isFile() ? FileSystemService.getInstance().getLocalFSRL(file) : null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/DWARFExternalDebugFilesPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/DWARFExternalDebugFilesPlugin.java new file mode 100644 index 0000000000..613b19ccf1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/DWARFExternalDebugFilesPlugin.java @@ -0,0 +1,132 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import java.io.File; + +import docking.action.builder.ActionBuilder; +import docking.tool.ToolConstants; +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import ghidra.app.CorePluginPackage; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.framework.preferences.Preferences; + +//@formatter:off +@PluginInfo( + status = PluginStatus.RELEASED, + packageName = CorePluginPackage.NAME, + category = PluginCategoryNames.COMMON, + shortDescription = "DWARF External Debug Files", + description = "Configure how the DWARF analyzer finds external debug files." +) +//@formatter:on +public class DWARFExternalDebugFilesPlugin extends Plugin { + + private static final String EXT_DEBUG_FILES_OPTION = "ExternalDebugFiles"; + private static final String SEARCH_LOCATIONS_LIST_OPTION = + EXT_DEBUG_FILES_OPTION + ".searchLocations"; + + public DWARFExternalDebugFilesPlugin(PluginTool tool) { + super(tool); + + createActions(); + } + + private void createActions() { + new ActionBuilder("DWARF External Debug Config", this.getName()) + .menuPath(ToolConstants.MENU_EDIT, "DWARF External Debug Config") + .menuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP) + .onAction(ac -> showConfigDialog()) + .buildAndInstall(tool); + } + + private void showConfigDialog() { + // Let the user pick a single directory, and configure a ".build-id/" search location + // and a recursive dir search location at that directory, as well as a + // same-dir search location to search the program's import directory. + GhidraFileChooser chooser = new GhidraFileChooser(tool.getActiveWindow()); + chooser.setMultiSelectionEnabled(false); + chooser.setApproveButtonText("Select"); + chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY); + chooser.setTitle("Select External Debug Files Directory"); + File selectedDir = chooser.getSelectedFile(); + if (selectedDir == null) { + return; + } + + BuildIdSearchLocation bisl = new BuildIdSearchLocation(new File(selectedDir, ".build-id")); + LocalDirectorySearchLocation ldsl = new LocalDirectorySearchLocation(selectedDir); + SameDirSearchLocation sdsl = new SameDirSearchLocation(new File("does not matter")); + + ExternalDebugFilesService edfs = new ExternalDebugFilesService(List.of(bisl, ldsl, sdsl)); + saveExternalDebugFilesService(edfs); + } + + /** + * Get a new instance of {@link ExternalDebugFilesService} using the previously saved + * information (via {@link #saveExternalDebugFilesService(ExternalDebugFilesService)}). + * + * @param context created via {@link SearchLocationRegistry#newContext(ghidra.program.model.listing.Program)} + * @return new {@link ExternalDebugFilesService} instance + */ + public static ExternalDebugFilesService getExternalDebugFilesService( + SearchLocationCreatorContext context) { + SearchLocationRegistry searchLocRegistry = SearchLocationRegistry.getInstance(); + String searchPathStr = Preferences.getProperty(SEARCH_LOCATIONS_LIST_OPTION, "", true); + String[] pathParts = searchPathStr.split(";"); + List searchLocs = new ArrayList<>(); + for (String part : pathParts) { + if (!part.isBlank()) { + SearchLocation searchLoc = searchLocRegistry.createSearchLocation(part, context); + if (searchLoc != null) { + searchLocs.add(searchLoc); + } + } + } + if (searchLocs.isEmpty()) { + // default to search the same directory as the program + searchLocs.add(SameDirSearchLocation.create(null, context)); + } + return new ExternalDebugFilesService(searchLocs); + } + + /** + * Serializes an {@link ExternalDebugFilesService} to a string and writes to the Ghidra + * global preferences. + * + * @param service the {@link ExternalDebugFilesService} to commit to preferences + */ + public static void saveExternalDebugFilesService(ExternalDebugFilesService service) { + if (service != null) { + String path = service.getSearchLocations() + .stream() + .map(SearchLocation::getName) + .collect(Collectors.joining(";")); + Preferences.setProperty(SEARCH_LOCATIONS_LIST_OPTION, path); + } + else { + Preferences.setProperty(SEARCH_LOCATIONS_LIST_OPTION, null); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugFilesService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugFilesService.java new file mode 100644 index 0000000000..23c19eabdf --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugFilesService.java @@ -0,0 +1,78 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.io.IOException; +import java.util.List; + +import ghidra.formats.gfilesystem.FSRL; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A collection of {@link SearchLocation search locations} that can be queried to find a + * DWARF external debug file, which is a second ELF binary that contains the debug information + * that was stripped from the original ELF binary. + */ +public class ExternalDebugFilesService { + private List searchLocations; + + /** + * Creates a new instance using the list of search locations. + * + * @param searchLocations list of {@link SearchLocation search locations} + */ + public ExternalDebugFilesService(List searchLocations) { + this.searchLocations = searchLocations; + } + + /** + * Returns the configured search locations. + * + * @return list of search locations + */ + public List getSearchLocations() { + return searchLocations; + } + + /** + * Searches for the specified external debug file. + *

+ * Returns the FSRL of a matching file, or null if not found. + * + * @param debugInfo information about the external debug file + * @param monitor {@link TaskMonitor} + * @return {@link FSRL} of found file, or {@code null} if not found + * @throws IOException if error + */ + public FSRL findDebugFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) + throws IOException { + try { + for (SearchLocation searchLoc : searchLocations) { + monitor.checkCanceled(); + FSRL result = searchLoc.findDebugFile(debugInfo, monitor); + if (result != null) { + return result; + } + } + } + catch (CancelledException ce) { + // fall thru, return null + } + return null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugInfo.java new file mode 100644 index 0000000000..c6e82a7b05 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugInfo.java @@ -0,0 +1,118 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import ghidra.app.util.bin.format.elf.GnuBuildIdSection; +import ghidra.app.util.bin.format.elf.GnuBuildIdSection.GnuBuildIdValues; +import ghidra.app.util.bin.format.elf.GnuDebugLinkSection; +import ghidra.app.util.bin.format.elf.GnuDebugLinkSection.GnuDebugLinkSectionValues; +import ghidra.program.model.listing.Program; +import ghidra.util.NumericUtilities; + +/** + * Metadata needed to find an ELF/DWARF external debug file, retrieved from an ELF binary's + * ".gnu_debuglink" section and/or ".note.gnu.build-id" section. + *

+ * The debuglink can provide a filename and crc of the external debug file, while the build-id + * can provide a hash that is converted to a filename that identifies the external debug file. + */ +public class ExternalDebugInfo { + + /** + * Create a new {@link ExternalDebugInfo} from information found in the specified program. + * + * @param program {@link Program} to query + * @return new {@link ExternalDebugInfo} or null if no external debug metadata found in + * program + */ + public static ExternalDebugInfo fromProgram(Program program) { + GnuDebugLinkSectionValues debugLinkValues = GnuDebugLinkSection.fromProgram(program); + GnuBuildIdValues buildIdValues = GnuBuildIdSection.fromProgram(program); + if (buildIdValues != null && !buildIdValues.isValid()) { + buildIdValues = null; + } + if (debugLinkValues == null && buildIdValues == null) { + return null; + } + + String filename = debugLinkValues != null ? debugLinkValues.getFilename() : null; + int crc = debugLinkValues != null ? debugLinkValues.getCrc() : 0; + byte[] hash = buildIdValues != null ? buildIdValues.getDescription() : null; + return new ExternalDebugInfo(filename, crc, hash); + } + + private String filename; + private int crc; + private byte[] hash; + + /** + * Constructor to create an {@link ExternalDebugInfo} instance. + * + * @param filename filename of external debug file, or null + * @param crc crc32 of external debug file, or 0 if no filename + * @param hash build-id hash digest found in ".note.gnu.build-id" section, or null if + * not present + */ + public ExternalDebugInfo(String filename, int crc, byte[] hash) { + this.filename = filename; + this.crc = crc; + this.hash = hash; + } + + /** + * Return true if there is a filename + * + * @return boolean true if filename is available, false if not + */ + public boolean hasFilename() { + return filename != null && !filename.isBlank(); + } + + /** + * Return the filename of the external debug file, or null if not specified. + * + * @return String filename of external debug file, or null if not specified + */ + public String getFilename() { + return filename; + } + + /** + * Return the crc of the external debug file. Not valid if filename is missing. + * + * @return int crc32 of external debug file. + */ + public int getCrc() { + return crc; + } + + /** + * Return the build-id hash digest. + * + * @return byte array containing the build-id hash (usually 20 bytes) + */ + public byte[] getHash() { + return hash; + } + + @Override + public String toString() { + return String.format("ExternalDebugInfo [filename=%s, crc=%s, hash=%s]", + filename, + Integer.toHexString(crc), + NumericUtilities.convertBytesToString(hash)); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/LocalDirectorySearchLocation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/LocalDirectorySearchLocation.java new file mode 100644 index 0000000000..f87c5f56e0 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/LocalDirectorySearchLocation.java @@ -0,0 +1,143 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.io.*; +import java.util.zip.CRC32; + +import ghidra.formats.gfilesystem.FSRL; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A {@link SearchLocation} that recursively searches for dwarf external debug files + * under a configured directory. + */ +public class LocalDirectorySearchLocation implements SearchLocation { + + private static final String LOCAL_DIR_PREFIX = "dir://"; + + /** + * Returns true if the specified location string specifies a LocalDirectorySearchLocation. + * + * @param locString string to test + * @return boolean true if locString specifies a local dir search location + */ + public static boolean isLocalDirSearchLoc(String locString) { + return locString.startsWith(LOCAL_DIR_PREFIX); + } + + /** + * Creates a new {@link LocalDirectorySearchLocation} instance using the specified location string. + * + * @param locString string, earlier returned from {@link #getName()} + * @param context {@link SearchLocationCreatorContext} to allow accessing information outside + * of the location string that might be needed to create a new instance + * @return new {@link LocalDirectorySearchLocation} instance + */ + public static LocalDirectorySearchLocation create(String locString, + SearchLocationCreatorContext context) { + locString = locString.substring(LOCAL_DIR_PREFIX.length()); + return new LocalDirectorySearchLocation(new File(locString)); + } + + private final File searchDir; + + /** + * Creates a new {@link LocalDirectorySearchLocation} at the specified location. + * + * @param searchDir path to the root directory of where to search + */ + public LocalDirectorySearchLocation(File searchDir) { + this.searchDir = searchDir; + } + + @Override + public String getName() { + return LOCAL_DIR_PREFIX + searchDir.getPath(); + } + + @Override + public String getDescriptiveName() { + return searchDir.getPath(); + } + + @Override + public FSRL findDebugFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) + throws CancelledException, IOException { + if (!debugInfo.hasFilename()) { + return null; + } + ensureSafeFilename(debugInfo.getFilename()); + return findFile(searchDir, debugInfo, monitor); + } + + private void ensureSafeFilename(String filename) throws IOException { + File testFile = new File(searchDir, filename); + if (!searchDir.equals(testFile.getParentFile())) { + throw new IOException("Unsupported path specified in debug file: " + filename); + } + } + + FSRL findFile(File dir, ExternalDebugInfo debugInfo, TaskMonitor monitor) + throws IOException, CancelledException { + if (!debugInfo.hasFilename()) { + return null; + } + File file = new File(dir, debugInfo.getFilename()); + if (file.isFile()) { + int fileCRC = calcCRC(file); + if (fileCRC == debugInfo.getCrc()) { + return FileSystemService.getInstance().getLocalFSRL(file); + } + Msg.info(this, "DWARF external debug file found with mismatching crc, ignored: " + + file + ", (" + Integer.toHexString(fileCRC) + ")"); + } + File[] subDirs; + if ((subDirs = dir.listFiles(f -> f.isDirectory())) != null) { + for (File subDir : subDirs) { + FSRL result = findFile(subDir, debugInfo, monitor); + if (result != null) { + return result; + } + } + } + return null; + } + + /** + * Calculates the crc32 for the specified file. + * + * @param f {@link File} to read + * @return int crc32 + * @throws IOException if error reading file + */ + public static int calcCRC(File f) throws IOException { + byte[] bytes = new byte[64 * 1024]; + + CRC32 crc32 = new CRC32(); + int bytesRead; + try (FileInputStream fis = new FileInputStream(f)) { + while ((bytesRead = fis.read(bytes)) > 0) { + crc32.update(bytes, 0, bytesRead); + } + } + return (int) crc32.getValue(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SameDirSearchLocation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SameDirSearchLocation.java new file mode 100644 index 0000000000..1e7be9de7c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SameDirSearchLocation.java @@ -0,0 +1,99 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FilenameUtils; + +import ghidra.formats.gfilesystem.FSRL; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A {@link SearchLocation} that only looks in the program's original import directory. + */ +public class SameDirSearchLocation implements SearchLocation { + + /** + * Returns true if the specified location string specifies a SameDirSearchLocation. + * + * @param locString string to test + * @return boolean true if locString specifies a BuildId location + */ + public static boolean isSameDirSearchLocation(String locString) { + return locString.equals("."); + } + + /** + * Creates a new {@link SameDirSearchLocation} instance using the current program's + * import location. + * + * @param locString unused + * @param context {@link SearchLocationCreatorContext} + * @return new {@link SameDirSearchLocation} instance + */ + public static SameDirSearchLocation create(String locString, + SearchLocationCreatorContext context) { + File exeLocation = + new File(FilenameUtils.getFullPath(context.getProgram().getExecutablePath())); + return new SameDirSearchLocation(exeLocation); + } + + private final File progDir; + + /** + * Creates a new {@link SameDirSearchLocation} at the specified location. + * + * @param progDir path to the program's import directory + */ + public SameDirSearchLocation(File progDir) { + this.progDir = progDir; + } + + @Override + public String getName() { + return "."; + } + + @Override + public String getDescriptiveName() { + return progDir.getPath() + " (Program's Import Location)"; + } + + @Override + public FSRL findDebugFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) + throws IOException, CancelledException { + if (!debugInfo.hasFilename()) { + return null; + } + File file = new File(progDir, debugInfo.getFilename()); + if (!file.isFile()) { + return null; + } + int fileCRC = LocalDirectorySearchLocation.calcCRC(file); + if (fileCRC != debugInfo.getCrc()) { + Msg.info(this, "DWARF external debug file found with mismatching crc, ignored: " + + file + ", (" + Integer.toHexString(fileCRC) + ")"); + return null; + } + return FileSystemService.getInstance().getLocalFSRL(file); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocation.java new file mode 100644 index 0000000000..9742073f99 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocation.java @@ -0,0 +1,54 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.io.IOException; + +import ghidra.formats.gfilesystem.FSRL; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Represents a collection of dwarf external debug files that can be searched. + */ +public interface SearchLocation { + /** + * Searchs for a debug file that fulfills the criteria specified in the {@link ExternalDebugInfo}. + * + * @param debugInfo search criteria + * @param monitor {@link TaskMonitor} + * @return {@link FSRL} of the matching file, or {@code null} if not found + * @throws IOException if error + * @throws CancelledException if cancelled + */ + FSRL findDebugFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) + throws IOException, CancelledException; + + /** + * Returns the name of this instance, which should be a serialized copy of this instance. + * + * @return String serialized data of this instance, typically in "something://serialized_data" + * form + */ + String getName(); + + /** + * Returns a human formatted string describing this location, used in UI prompts or lists. + * + * @return formatted string + */ + String getDescriptiveName(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocationCreatorContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocationCreatorContext.java new file mode 100644 index 0000000000..7b25cbb456 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocationCreatorContext.java @@ -0,0 +1,52 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import ghidra.program.model.listing.Program; + +/** + * Information outside of a location string that might be needed to create a new {@link SearchLocation} + * instance. + */ +public class SearchLocationCreatorContext { + private final SearchLocationRegistry registry; + private final Program program; + + /** + * Create a new context object with references to the registry and the current program. + * + * @param registry {@link SearchLocationRegistry} + * @param program the current {@link Program} + */ + public SearchLocationCreatorContext(SearchLocationRegistry registry, Program program) { + this.registry = registry; + this.program = program; + } + + /** + * @return the {@link SearchLocationRegistry} that is creating the {@link SearchLocation} + */ + public SearchLocationRegistry getRegistry() { + return registry; + } + + /** + * @return the current {@link Program} + */ + public Program getProgram() { + return program; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocationRegistry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocationRegistry.java new file mode 100644 index 0000000000..352371f23c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/SearchLocationRegistry.java @@ -0,0 +1,112 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.external; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import ghidra.program.model.listing.Program; + +/** + * List of {@link SearchLocation} types that can be saved / restored from a configuration string. + */ +public class SearchLocationRegistry { + public static SearchLocationRegistry getInstance() { + return instance; + } + + private static final SearchLocationRegistry instance = new SearchLocationRegistry(true); + + private List searchLocCreators = new ArrayList<>(); + + /** + * Creates a new registry, optionally registering the default SearchLocations. + * + * @param registerDefault boolean flag, if true register the built-in {@link SearchLocation}s + */ + public SearchLocationRegistry(boolean registerDefault) { + if (registerDefault) { + register(LocalDirectorySearchLocation::isLocalDirSearchLoc, + LocalDirectorySearchLocation::create); + register(BuildIdSearchLocation::isBuildIdSearchLocation, BuildIdSearchLocation::create); + register(SameDirSearchLocation::isSameDirSearchLocation, SameDirSearchLocation::create); + } + } + + /** + * Adds a {@link SearchLocation} to this registry. + * + * @param testFunc a {@link Predicate} that tests a location string, returning true if the + * string specifies the SearchLocation in question + * @param createFunc a {@link SearchLocationCreator} that will create a new {@link SearchLocation} + * instance given a location string and a {@link SearchLocationCreatorContext context} + */ + public void register(Predicate testFunc, SearchLocationCreator createFunc) { + searchLocCreators.add(new SearchLocationCreationInfo(testFunc, createFunc)); + } + + /** + * Creates a new {@link SearchLocationCreatorContext context}. + * + * @param program {@link Program} + * @return new {@link SearchLocationCreatorContext} + */ + public SearchLocationCreatorContext newContext(Program program) { + return new SearchLocationCreatorContext(this, program); + } + + /** + * Creates a {@link SearchLocation} using the provided location string. + * + * @param locString location string (previously returned by {@link SearchLocation#getName()} + * @param context a {@link SearchLocationCreatorContext context} + * @return new {@link SearchLocation} instance, or null if there are no registered matching + * SearchLocations + */ + public SearchLocation createSearchLocation(String locString, + SearchLocationCreatorContext context) { + for (SearchLocationCreationInfo slci : searchLocCreators) { + if (slci.testFunc.test(locString)) { + return slci.createFunc.create(locString, context); + } + } + return null; + } + + public interface SearchLocationCreator { + /** + * Creates a new {@link SearchLocation} instance using the provided location string. + * + * @param locString location string, previously returned by {@link SearchLocation#getName()} + * @param context {@link SearchLocationCreatorContext context} + * @return new {@link SearchLocation} + */ + SearchLocation create(String locString, SearchLocationCreatorContext context); + } + + private static class SearchLocationCreationInfo { + Predicate testFunc; + SearchLocationCreator createFunc; + + SearchLocationCreationInfo(Predicate testFunc, + SearchLocationCreator createFunc) { + this.testFunc = testFunc; + this.createFunc = createFunc; + } + + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java index ab27c53490..ff278ca586 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java @@ -27,6 +27,7 @@ import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeFactory; import ghidra.app.util.bin.format.dwarf4.encoding.*; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; +import ghidra.app.util.bin.format.dwarf4.external.ExternalDebugInfo; import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.*; import ghidra.app.util.opinion.*; import ghidra.program.model.address.Address; @@ -71,10 +72,8 @@ public class DWARFProgram implements Closeable { switch (format) { case ElfLoader.ELF_NAME: case PeLoader.PE_NAME: - try (DWARFSectionProvider dsp = - DWARFSectionProviderFactory.createSectionProviderFor(program)) { - return dsp != null; - } + return BaseSectionProvider.hasDWARFSections(program) || + ExternalDebugInfo.fromProgram(program) != null; case MachoLoader.MACH_O_NAME: return DSymSectionProvider.getDSYMForProgram(program) != null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/BaseSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/BaseSectionProvider.java index e228bcd741..25ea7fed7a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/BaseSectionProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/BaseSectionProvider.java @@ -15,18 +15,24 @@ */ package ghidra.app.util.bin.format.dwarf4.next.sectionprovider; +import java.io.IOException; + import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.MemoryByteProvider; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; -import java.io.IOException; - /** * Fetches DWARF sections from a normal program using simple Ghidra memory blocks. */ public class BaseSectionProvider implements DWARFSectionProvider { - private Program program; + protected Program program; + + public static boolean hasDWARFSections(Program program) { + try (BaseSectionProvider tmp = new BaseSectionProvider(program)) { + return tmp.hasSection(DWARFSectionNames.MINIMAL_DWARF_SECTIONS); + } + } public static BaseSectionProvider createSectionProviderFor(Program program) { return new BaseSectionProvider(program); @@ -43,7 +49,7 @@ public class BaseSectionProvider implements DWARFSectionProvider { if (block == null) { block = program.getMemory().getBlock("." + sectionName); } - if (block != null) { + if (block != null && block.isInitialized()) { // TODO: limit the returned ByteProvider to block.getSize() bytes return new MemoryByteProvider(program.getMemory(), block.getStart()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DSymSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DSymSectionProvider.java index 6503622487..c61e3a1f6c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DSymSectionProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DSymSectionProvider.java @@ -15,17 +15,16 @@ */ package ghidra.app.util.bin.format.dwarf4.next.sectionprovider; -import ghidra.app.util.bin.*; -import ghidra.app.util.bin.format.macho.*; -import ghidra.app.util.opinion.MachoLoader; -import ghidra.program.model.listing.Program; - import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import generic.continues.RethrowContinuesFactory; +import ghidra.app.util.bin.*; +import ghidra.app.util.bin.format.macho.*; +import ghidra.app.util.opinion.MachoLoader; +import ghidra.program.model.listing.Program; /** * Fetches DWARF section data for a MachO program with co-located .dSYM folder. (ie. Mac OSX diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProvider.java index 54857350e6..d84580bc78 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProvider.java @@ -15,11 +15,12 @@ */ package ghidra.app.util.bin.format.dwarf4.next.sectionprovider; -import ghidra.app.util.bin.ByteProvider; - import java.io.Closeable; import java.io.IOException; +import ghidra.app.util.bin.ByteProvider; +import ghidra.program.model.listing.Program; + /** * A DWARFSectionProvider is responsible for allowing access to DWARF section data of * a Ghidra program. @@ -31,7 +32,15 @@ import java.io.IOException; *

*/ public interface DWARFSectionProvider extends Closeable { - public boolean hasSection(String... sectionNames); - public ByteProvider getSectionAsByteProvider(String sectionName) throws IOException; - public void close(); + boolean hasSection(String... sectionNames); + + ByteProvider getSectionAsByteProvider(String sectionName) throws IOException; + + @Override + void close(); + + default void updateProgramInfo(Program program) { + // do nothing + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java index 7cb75f4a24..4113a3e114 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java @@ -16,12 +16,12 @@ package ghidra.app.util.bin.format.dwarf4.next.sectionprovider; import java.io.Closeable; -import java.util.ArrayList; import java.util.List; import java.util.function.Function; import ghidra.program.model.listing.Program; import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; /** * Auto-detects which {@link DWARFSectionProvider} matches a Ghidra program. @@ -39,12 +39,9 @@ public class DWARFSectionProviderFactory { * The method should not throw anything, instead just return a NULL. */ private static final List> sectionProviderFactoryFuncs = - new ArrayList<>(); - - static { - sectionProviderFactoryFuncs.add(BaseSectionProvider::createSectionProviderFor); - sectionProviderFactoryFuncs.add(DSymSectionProvider::createSectionProviderFor); - } + List.of( + BaseSectionProvider::createSectionProviderFor, + DSymSectionProvider::createSectionProviderFor); /** * Iterates through the statically registered {@link #sectionProviderFactoryFuncs factory funcs}, @@ -78,4 +75,28 @@ public class DWARFSectionProviderFactory { return null; } + /** + * Iterates through the statically registered {@link #sectionProviderFactoryFuncs factory funcs}, + * trying each factory method until one returns a {@link DWARFSectionProvider} + * that can successfully retrieve the {@link DWARFSectionNames#MINIMAL_DWARF_SECTIONS minimal} + * sections we need to do a DWARF import. + *

+ * The resulting {@link DWARFSectionProvider} is {@link Closeable} and it is the caller's + * responsibility to ensure that the object is closed when done. + * + * @param program Ghidra {@link Program} + * @param monitor {@link TaskMonitor} + * @return {@link DWARFSectionProvider} that should be closed by the caller or NULL if no + * section provider types match the specified program. + */ + public static DWARFSectionProvider createSectionProviderFor(Program program, + TaskMonitor monitor) { + DWARFSectionProvider result = createSectionProviderFor(program); + if (result != null) { + return result; + } + result = ExternalDebugFileSectionProvider.createSectionProviderFor(program, monitor); + return result; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/ExternalDebugFileSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/ExternalDebugFileSectionProvider.java new file mode 100644 index 0000000000..04a870f94c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/ExternalDebugFileSectionProvider.java @@ -0,0 +1,143 @@ +/* ### + * 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 ghidra.app.util.bin.format.dwarf4.next.sectionprovider; + +import java.util.List; + +import java.io.IOException; +import java.net.MalformedURLException; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.dwarf4.external.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.opinion.ElfLoader; +import ghidra.formats.gfilesystem.*; +import ghidra.framework.options.Options; +import ghidra.program.database.ProgramDB; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A {@link DWARFSectionProvider} that reads .debug_info (and friends) sections from an external + * ELF file that is referenced in the original ELF file's build-id or debuglink sections. + *

+ * Creates a pinning reference from the temporary external ELF debug file to this SectionProvider + * instance using the program's {@link Program#addConsumer(Object)}, and then releases the + * consumer when this instance is closed, allowing the temporary Program to be destroyed. + */ +public class ExternalDebugFileSectionProvider extends BaseSectionProvider { + public static final String PROGRAM_INFO_DWARF_EXTERNAL_DEBUG_FILE = "DWARF External Debug File"; + + public static DWARFSectionProvider createSectionProviderFor(Program program, + TaskMonitor monitor) { + try { + ExternalDebugInfo extDebugInfo = ExternalDebugInfo.fromProgram(program); + if (extDebugInfo == null) { + return null; + } + Msg.info(ExternalDebugFilesService.class, + "DWARF external debug information found: " + extDebugInfo); + ExternalDebugFilesService edfs = + DWARFExternalDebugFilesPlugin.getExternalDebugFilesService( + SearchLocationRegistry.getInstance().newContext(program)); + FSRL extDebugFile = edfs.findDebugFile(extDebugInfo, monitor); + if (extDebugFile == null) { + return null; + } + Msg.info(ExternalDebugFilesService.class, + "DWARF External Debug File: found: " + extDebugFile); + FileSystemService fsService = FileSystemService.getInstance(); + try ( + RefdFile refdDebugFile = fsService.getRefdFile(extDebugFile, monitor); + ByteProvider debugFileByteProvider = + fsService.getByteProvider(refdDebugFile.file.getFSRL(), false, monitor);) { + Object consumer = new Object(); + Language lang = program.getLanguage(); + CompilerSpec compSpec = + lang.getCompilerSpecByID(program.getCompilerSpec().getCompilerSpecID()); + Program debugProgram = + new ProgramDB("temp external debug info for " + program.getName(), lang, + compSpec, consumer); + ElfLoader elfLoader = new ElfLoader(); + elfLoader.load(debugFileByteProvider, null, List.of(), debugProgram, monitor, + new MessageLog()); + ExternalDebugFileSectionProvider result = + new ExternalDebugFileSectionProvider(debugProgram, + debugFileByteProvider.getFSRL()); + debugProgram.release(consumer); + return result; + } + } + catch (IOException | CancelledException e) { + // fall thru + } + return null; + } + + private final FSRL externalDebugFileLocation; + + /** + * Creates a {@link ExternalDebugFileSectionProvider} + * + * @param program the external ELF {@link Program} + * @param externalDebugFileLocation the location where the external ELF debug file is located + */ + ExternalDebugFileSectionProvider(Program program, FSRL externalDebugFileLocation) { + super(program); + this.externalDebugFileLocation = externalDebugFileLocation; + program.addConsumer(this); + } + + @Override + public void close() { + if (program != null) { + program.release(this); + } + super.close(); + + program = null; + } + + @Override + public void updateProgramInfo(Program program) { + Options options = program.getOptions(Program.PROGRAM_INFO); + options.setString(PROGRAM_INFO_DWARF_EXTERNAL_DEBUG_FILE, + externalDebugFileLocation.toString()); + } + + /** + * Returns the previouly saved value of the external debug file location from the program's + * metadata. + * + * @param program DWARF that previously was analyzed + * @return FSRL of external debug file, or null if missing or corrupted value + */ + public static FSRL getExternalDebugFileLocation(Program program) { + Options options = program.getOptions(Program.PROGRAM_INFO); + String fsrlStr = options.getString(PROGRAM_INFO_DWARF_EXTERNAL_DEBUG_FILE, null); + try { + return fsrlStr != null ? FSRL.fromString(fsrlStr) : null; + } + catch (MalformedURLException e) { + return null; + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java index a48e32f91c..710519a89f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuBuildIdSection.java @@ -17,9 +17,12 @@ package ghidra.app.util.bin.format.elf; import static ghidra.app.util.bin.StructConverter.*; +import java.io.IOException; + +import ghidra.app.util.bin.*; import ghidra.program.model.data.*; -import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.*; import ghidra.util.exception.DuplicateNameException; /** @@ -27,7 +30,63 @@ import ghidra.util.exception.DuplicateNameException; * ELF .note.gnu.build-id section */ public class GnuBuildIdSection extends FactoryStructureDataType { + public static final String BUILD_ID_SECTION_NAME = ".note.gnu.build-id"; private static final int MAX_SANE_STR_LENS = 1024; + + public static GnuBuildIdValues fromProgram(Program program) { + MemoryBlock buildIdSection = program.getMemory().getBlock(BUILD_ID_SECTION_NAME); + if (buildIdSection == null) { + return null; + } + try (ByteProvider bp = MemoryByteProvider.createMemoryBlockByteProvider(program.getMemory(), + buildIdSection)) { + BinaryReader br = new BinaryReader(bp, !program.getMemory().isBigEndian()); + long nameLen = br.readNextUnsignedInt(); + long descLen = br.readNextUnsignedInt(); + int vendorType = br.readNextInt(); + if (nameLen > MAX_SANE_STR_LENS || descLen > MAX_SANE_STR_LENS) { + return null; + } + String name = br.readNextAsciiString((int) nameLen); + byte[] desc = br.readNextByteArray((int) descLen); + return new GnuBuildIdValues(name, desc, vendorType); + } + catch (IOException e) { + // fall thru and return null + } + return null; + } + + public static class GnuBuildIdValues { + private static final int SHA1_DESC_LEN = 20; // 160bit SHA1 == 20 bytes + + private String name; // ie. "gnu" + private byte[] description; // the hash + private int vendorType; + + private GnuBuildIdValues(String name, byte[] description, int vendorType) { + this.name = name; + this.description = description; + this.vendorType = vendorType; + } + + public String getName() { + return name; + } + + public byte[] getDescription() { + return description; + } + + public int getVendorType() { + return vendorType; + } + + public boolean isValid() { + return "GNU".equals(name) && description.length == SHA1_DESC_LEN; + } + } + private long sectionSize; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java index a26cca5f49..3bb897fb85 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/GnuDebugLinkSection.java @@ -17,9 +17,14 @@ package ghidra.app.util.bin.format.elf; import static ghidra.app.util.bin.StructConverter.*; +import java.io.IOException; + +import ghidra.app.util.bin.*; import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.data.*; +import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemBuffer; +import ghidra.program.model.mem.MemoryBlock; import ghidra.util.NumericUtilities; import ghidra.util.exception.DuplicateNameException; @@ -27,6 +32,45 @@ import ghidra.util.exception.DuplicateNameException; * Factory data type that marks up a ELF .gnu_debuglink section. */ public class GnuDebugLinkSection extends FactoryStructureDataType { + public static final String DEBUG_LINK_SECTION_NAME = ".gnu_debuglink"; + + public static GnuDebugLinkSectionValues fromProgram(Program program) { + MemoryBlock debugLinkSection = program.getMemory().getBlock(DEBUG_LINK_SECTION_NAME); + if (debugLinkSection == null) { + return null; + } + try (ByteProvider bp = MemoryByteProvider.createMemoryBlockByteProvider(program.getMemory(), + debugLinkSection)) { + BinaryReader br = new BinaryReader(bp, !program.getMemory().isBigEndian()); + String filename = br.readNextAsciiString(); + br.setPointerIndex(NumericUtilities.getUnsignedAlignedValue(br.getPointerIndex(), 4)); + int crc = br.readNextInt(); + return new GnuDebugLinkSectionValues(filename, crc); + } + catch (IOException e) { + // fall thru and return null + } + return null; + } + + public static class GnuDebugLinkSectionValues { + private String filename; + private int crc; + + public GnuDebugLinkSectionValues(String filename, int crc) { + this.filename = filename; + this.crc = crc; + } + + public String getFilename() { + return filename; + } + + public int getCrc() { + return crc; + } + } + private long sectionSize; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java index 5605220bc4..91e6ef85af 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java @@ -382,7 +382,12 @@ public class DemangledFunction extends DemangledObject { // Account for register context. This class may trigger disassembly, so we need to make // sure that the context is correctly set before that happens. Also, be sure to apply // the function to the correct address. - address = PseudoDisassembler.setTargeContextForDisassembly(program, address); + + //TODO revisit this in terms of external block addresses. Also shoul this check + // be moved into the PsuedoDisassembler??? + if (!address.isExternalAddress()) { + address = PseudoDisassembler.setTargeContextForDisassembly(program, address); + } if (!passesPreconditions(program, address)) { return true; // eventually will not return anything diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java index 6e3f5887ff..f38e35bea2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java @@ -18,6 +18,8 @@ package ghidra.program.util; import java.math.BigInteger; import java.util.*; +import org.apache.commons.collections4.map.LRUMap; + import generic.util.UnsignedDataUtils; import ghidra.app.cmd.disassemble.DisassembleCommand; import ghidra.app.cmd.function.CallDepthChangeInfo; @@ -38,7 +40,7 @@ import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; public class SymbolicPropogator { - + private static int LRU_SIZE = 4096; // QUESTIONS // 1. How are "register-relative" varnodes distinguished based upon target space ? Not sure how we handle wrapping/truncation concerns. // 1) The offset is the only thing that could be used as a reference. @@ -61,7 +63,6 @@ public class SymbolicPropogator { protected boolean canceled = false; protected boolean readExecutableAddress; protected VarnodeContext context; - protected boolean conflict; protected boolean hitCodeFlow = false; // no branching so far @@ -78,6 +79,18 @@ public class SymbolicPropogator { protected static final int MAX_EXACT_INSTRUCTIONS = 100; + // Cache flows from instructions + Map instructionFlowsCache = new LRUMap<>(LRU_SIZE); + + // Cache PcodeOps so that we won't have to grab them again if we re-visit the node. + Map pcodeCache = new LRUMap<>(LRU_SIZE); + + // Cache Instructions looked up by At + Map instructionAtCache = new LRUMap<>(LRU_SIZE); + + // Cache instructions looked up by containing + Map instructionContainingCache = new LRUMap<>(LRU_SIZE); + public SymbolicPropogator(Program program) { this.program = program; @@ -476,12 +489,13 @@ public class SymbolicPropogator { break; } + Address minInstrAddress = instr.getMinAddress(); maxAddr = instr.getMaxAddress(); // if this instruction has a delay slot, adjust maxAddr accordingly // if (instr.getPrototype().hasDelaySlots()) { - maxAddr = instr.getMinAddress().add(instr.getDefaultFallThroughOffset() - 1); + maxAddr = minInstrAddress.add(instr.getDefaultFallThroughOffset() - 1); } vContext.setCurrentInstruction(instr); @@ -498,11 +512,10 @@ public class SymbolicPropogator { // // apply the pcode effects // - conflict = false; Address retAddr = applyPcode(vContext, instr, monitor); // add this instruction to processed body set - body.addRange(instr.getMinAddress(), maxAddr); + body.addRange(minInstrAddress, maxAddr); /* Allow evaluateContext routine to change override the flowtype of an instruction. * Jumps Changed to calls will now continue processing. @@ -544,8 +557,8 @@ public class SymbolicPropogator { } if (!instrFlow.isCall()) { for (Address flow : flows) { - contextStack.push(new SavedFlowState(vContext, - instr.getMinAddress(), flow, continueAfterHittingFlow)); + contextStack.push(new SavedFlowState(vContext, minInstrAddress, + flow, continueAfterHittingFlow)); } } else if (flows.length > 1) { @@ -554,9 +567,8 @@ public class SymbolicPropogator { for (Reference flowRef : flowRefs) { RefType referenceType = flowRef.getReferenceType(); if (referenceType.isComputed() && referenceType.isJump()) { - contextStack.push( - new SavedFlowState(vContext, instr.getMinAddress(), - flowRef.getToAddress(), continueAfterHittingFlow)); + contextStack.push(new SavedFlowState(vContext, minInstrAddress, + flowRef.getToAddress(), continueAfterHittingFlow)); } } } @@ -576,13 +588,13 @@ public class SymbolicPropogator { vContext.mergeToFutureFlowState(maxAddr, inlineCall); vContext.flowEnd(maxAddr); flowConstants(maxAddr, inlineCall, func.getBody(), eval, vContext, monitor); - vContext.mergeToFutureFlowState(instr.getMinAddress(), maxAddr); + vContext.mergeToFutureFlowState(minInstrAddress, maxAddr); // // TODO: WARNING, might not start the flow correctly if there is no future flow here. // FLOW end will probably work correctly, but.... // - vContext.flowStart(instr.getMinAddress(), maxAddr); + vContext.flowStart(minInstrAddress, maxAddr); } } @@ -591,24 +603,29 @@ public class SymbolicPropogator { Address fallThru = instr.getFallThrough(); nextAddr = null; if (retAddr != null) { - contextStack.push(new SavedFlowState(vContext, instr.getMinAddress(), retAddr, + contextStack.push(new SavedFlowState(vContext, minInstrAddress, retAddr, continueAfterHittingFlow)); fallThru = null; } if (fallThru != null) { if (doFallThruLast) { + vContext.mergeToFutureFlowState(minInstrAddress, fallThru); + // put it lowest on the stack to do later! - contextStack.push(new SavedFlowState(vContext, instr.getMinAddress(), - fallThru, !callCouldCauseBadStackDepth)); + contextStack.push(new SavedFlowState(vContext, minInstrAddress, fallThru, + !callCouldCauseBadStackDepth)); } else if (fallThru.compareTo(maxAddr) < 0) { // this isn't a normal fallthru, must break it up // don't continue flowing if something else is hit, this is an odd case + vContext.mergeToFutureFlowState(minInstrAddress, fallThru); + contextStack.push( - new SavedFlowState(vContext, instr.getMinAddress(), fallThru, false)); + new SavedFlowState(vContext, minInstrAddress, fallThru, false)); } else { + // no need to store future flow state, will continue on the fall-thru flow nextAddr = fallThru; fallThru = null; } @@ -684,10 +701,6 @@ public class SymbolicPropogator { return false; } - // Cache PcodeOps so that we won't have to grab them again if we re-visit the node. - // - HashMap pcodeCache = new HashMap<>(); - private PcodeOp[] getInstructionPcode(Instruction instruction) { PcodeOp ops[] = pcodeCache.get(instruction.getMinAddress()); if (ops == null) { @@ -697,12 +710,6 @@ public class SymbolicPropogator { return ops; } - // Cache Instructions looked up by At - HashMap instructionAtCache = new HashMap<>(); - - // Cache instructions looked up by containing - HashMap instructionContainingCache = new HashMap<>(); - private Instruction getInstructionAt(Address addr) { Instruction instr = instructionAtCache.get(addr); if (instr != null) { @@ -739,9 +746,6 @@ public class SymbolicPropogator { return instr; } - // Cache flows from instructions - HashMap instructionFlowsCache = new HashMap<>(); - private Address[] getInstructionFlows(Instruction instruction) { Address addr = instruction.getMinAddress(); @@ -856,8 +860,8 @@ public class SymbolicPropogator { for (Reference flowRef : flowRefs) { RefType referenceType = flowRef.getReferenceType(); if (referenceType.isComputed() && referenceType.isJump()) { - conflict |= vContext.mergeToFutureFlowState( - flowRef.getFromAddress(), flowRef.getToAddress()); + vContext.mergeToFutureFlowState(flowRef.getFromAddress(), + flowRef.getToAddress()); } } @@ -922,8 +926,7 @@ public class SymbolicPropogator { if (target != null) { if (target.isMemoryAddress()) { vContext.propogateResults(false); - conflict |= - vContext.mergeToFutureFlowState(minInstrAddress, target); + vContext.mergeToFutureFlowState(minInstrAddress, target); } func = prog.getFunctionManager().getFunctionAt(target); if (func == null && ptype == PcodeOp.CALLIND) { @@ -995,8 +998,7 @@ public class SymbolicPropogator { instruction.getAddress()); } vContext.propogateResults(false); - conflict |= - vContext.mergeToFutureFlowState(minInstrAddress, in[0].getAddress()); + vContext.mergeToFutureFlowState(minInstrAddress, in[0].getAddress()); pcodeIndex = ops.length; // break out of the processing break; @@ -1007,15 +1009,14 @@ public class SymbolicPropogator { int sequenceOffset = (int) in[0].getOffset(); if ((pcodeIndex + sequenceOffset) >= ops.length) { vContext.propogateResults(false); - conflict |= vContext.mergeToFutureFlowState(minInstrAddress, + vContext.mergeToFutureFlowState(minInstrAddress, instruction.getFallThrough()); } } else if (in[0].isAddress()) { vt = in[0]; vContext.propogateResults(false); - conflict |= vContext.mergeToFutureFlowState(minInstrAddress, - in[0].getAddress()); + vContext.mergeToFutureFlowState(minInstrAddress, in[0].getAddress()); } Varnode condition = null; @@ -1055,7 +1056,7 @@ public class SymbolicPropogator { if (fallThru != null) { // we don't know what will happen from here on, but anything before should in theory propagate vContext.propogateResults(true); - conflict |= vContext.mergeToFutureFlowState(minInstrAddress, + vContext.mergeToFutureFlowState(minInstrAddress, instruction.getFallThrough()); } // everything that is in the cache from here on should be cleared @@ -1431,11 +1432,6 @@ public class SymbolicPropogator { // assume the future flow will have flowed the correct info. nextAddr = fallthru; } - else { - if (fallthru != null) { - conflict |= vContext.mergeToFutureFlowState(minInstrAddress, fallthru); - } - } return nextAddr; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java index 6f854fec5f..83a1150837 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java @@ -377,6 +377,45 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe assertNotNull(programNode.getChild(newCategoryName)); } + @Test + public void testRenameDataTypeWithSameNameAsCategory() throws Exception { + // select a category + expandNode(programNode); + String miscNodeName = "MISC"; + final CategoryNode miscNode = (CategoryNode) programNode.getChild(miscNodeName); + assertNotNull(miscNode); + StructureDataType struct = new StructureDataType("MISC", 0); + struct.add(new DWordDataType()); + builder.addDataType(struct); + waitForTree(); + DataType resolved = program.getDataTypeManager().resolve(struct, null); + DataTypeNode node = programNode.getNode(resolved); + selectNode(node); + + final DockingActionIf action = getAction(plugin, "Rename"); + assertTrue(action.isEnabledForContext(treeContext)); + + // select "Rename" action + final String newDatatypeName = "ItWorked"; + DataTypeTestUtils.performAction(action, tree); + waitForTree(); + runSwing(() -> { + int rowForPath = jTree.getRowForPath(miscNode.getTreePath()); + + DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); + Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, miscNode, + true, true, true, rowForPath); + JTextField textField = (JTextField) container.getComponent(0); + + textField.setText(newDatatypeName); + jTree.stopEditing(); + }); + waitForProgram(); + waitForTree(); + + assertEquals("ItWorked", resolved.getName()); + } + @Test public void testRenameCategoryDuplicate() throws Exception { expandNode(programNode); diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java index 711bae1f52..175292e794 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java @@ -3313,6 +3313,23 @@ public class RecoveredClassUtils { // listing has void too, otherwise, leave it as is, probably a void String returnType = getReturnTypeFromDecompiler(constructorFunction); + // Set error bookmark, add error message, and get the listing return type if the + // decompiler return type is null + if (returnType == null) { + + String msg1 = "Decompiler Error: Failed to decompile function"; + String msg2 = ", possibly due to the addition of class structure."; + + Msg.debug(this, msg1 + " at " + constructorFunction.getEntryPoint() + msg2); + + program.getBookmarkManager().setBookmark(constructorFunction.getEntryPoint(), + BookmarkType.ERROR, "Decompiler Error", msg1 + msg2); + + // get the return type from the listing and in some cases it will + // indicate the correct type to help determine the below type to add + returnType = constructorFunction.getReturnType().getDisplayName(); + } + if (returnType.equals("void")) { DataType voidDataType = new VoidDataType(); constructorFunction.setReturnType(voidDataType, SourceType.ANALYSIS); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc index 598528a373..885231d4a5 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc @@ -158,8 +158,8 @@ int4 Datatype::compare(const Datatype &op,int4 level) const int4 Datatype::compareDependency(const Datatype &op) const { - if (size != op.size) return (op.size-size); if (submeta != op.submeta) return (submeta < op.submeta) ? -1 : 1; + if (size != op.size) return (op.size-size); return 0; } @@ -554,13 +554,11 @@ int4 TypePointer::compare(const Datatype &op,int4 level) const int4 TypePointer::compareDependency(const Datatype &op) const { - int4 res = Datatype::compareDependency(op); - if (res != 0) return res; - // Both must be pointers - TypePointer *tp = (TypePointer *) &op; + if (submeta != op.getSubMeta()) return (submeta < op.getSubMeta()) ? -1 : 1; + TypePointer *tp = (TypePointer *) &op; // Both must be pointers + if (ptrto != tp->ptrto) return (ptrto < tp->ptrto) ? -1 : 1; // Compare absolute pointers if (wordsize != tp->wordsize) return (wordsize < tp->wordsize) ? -1 : 1; - if (ptrto == tp->ptrto) return 0; - return (ptrto < tp->ptrto) ? -1 : 1; // Compare the absolute pointers + return (op.getSize()-size); } void TypePointer::saveXml(ostream &s) const @@ -602,6 +600,8 @@ void TypePointer::calcSubmeta(void) if (ptrto->getMetatype() == TYPE_STRUCT) { if (ptrto->numDepend() > 1 || ptrto->isIncompleteStruct()) submeta = SUB_PTR_STRUCT; + else + submeta = SUB_PTR; } } @@ -698,11 +698,10 @@ int4 TypeArray::compare(const Datatype &op,int4 level) const int4 TypeArray::compareDependency(const Datatype &op) const { - int4 res = Datatype::compareDependency(op); - if (res != 0) return res; + if (submeta != op.getSubMeta()) return (submeta < op.getSubMeta()) ? -1 : 1; TypeArray *ta = (TypeArray *) &op; // Both must be arrays - if (arrayof == ta->arrayof) return 0; - return (arrayof < ta->arrayof) ? -1 : 1; + if (arrayof != ta->arrayof) return (arrayof < ta->arrayof) ? -1 : 1; // Compare absolute pointers + return (op.getSize()-size); } Datatype *TypeArray::getSubType(uintb off,uintb *newoff) const @@ -1271,16 +1270,14 @@ void TypePointerRel::printRaw(ostream &s) const int4 TypePointerRel::compareDependency(const Datatype &op) const { - int4 res = Datatype::compareDependency(op); // Note: we go to Datatype, not TypePointer - if (res != 0) return res; - // Both must be pointers - const TypePointerRel *tp = (const TypePointerRel*)&op; + if (submeta != op.getSubMeta()) return (submeta < op.getSubMeta()) ? -1 : 1; + const TypePointerRel *tp = (const TypePointerRel*)&op; // Both must be TypePointerRel + if (ptrto != tp->ptrto) return (ptrto < tp->ptrto) ? -1 : 1; // Compare absolute pointers if (offset != tp->offset) return (offset < tp->offset) ? -1 : 1; if (parent != tp->parent) return (parent < tp->parent) ? -1 : 1; if (wordsize != tp->wordsize) return (wordsize < tp->wordsize) ? -1 : 1; - if (ptrto == tp->ptrto) return 0; - return (ptrto < tp->ptrto) ? -1 : 1; // Compare the absolute pointers + return (op.getSize()-size); } void TypePointerRel::saveXml(ostream &s) const @@ -2090,6 +2087,8 @@ bool TypeFactory::setFields(vector &fd,TypeStruct *ot,int4 fixedsize, throw LowlevelError("Trying to force too small a size on "+ot->getName()); } tree.insert(ot); + recalcPointerSubmeta(ot, SUB_PTR); + recalcPointerSubmeta(ot, SUB_PTR_STRUCT); return true; } @@ -2302,6 +2301,33 @@ TypeCode *TypeFactory::getTypeCode(const string &nm) return (TypeCode *) findAdd(tmp); } +/// Search for pointers that match the given \b ptrto and sub-metatype and change it to +/// the current calculated sub-metatype. +/// A change in the sub-metatype may involve reinserting the pointer data-type in the functional tree. +/// \param base is the given base data-type +/// \param sub is the type of pointer to search for +void TypeFactory::recalcPointerSubmeta(Datatype *base,sub_metatype sub) + +{ + DatatypeSet::const_iterator iter; + TypePointer top(1,base,0); // This will calculate the current proper sub-meta for pointers to base + sub_metatype curSub = top.submeta; + if (curSub == sub) return; // Don't need to search for pointers with correct submeta + top.submeta = sub; // Search on the incorrect submeta + iter = tree.lower_bound(&top); + while(iter != tree.end()) { + TypePointer *ptr = (TypePointer *)*iter; + if (ptr->getMetatype() != TYPE_PTR) break; + if (ptr->ptrto != base) break; + ++iter; + if (ptr->submeta == sub) { + tree.erase(ptr); + ptr->submeta = curSub; // Change to correct submeta + tree.insert(ptr); // Reinsert + } + } +} + /// Find or create a data-type identical to the given data-type except for its name and id. /// If the name and id already describe an incompatible data-type, an exception is thrown. /// \param ct is the given data-type to clone @@ -2675,6 +2701,57 @@ Datatype *TypeFactory::restoreTypedef(const Element *el) return getTypedef(defedType, nm, id); } +Datatype* TypeFactory::restoreStruct(const Element *el,bool forcecore) + +{ + string structname = el->getAttributeValue("name"); + TypeStruct ts(structname); + int4 num = el->getNumAttributes(); + uint8 newid = 0; + int4 structsize = 0; + bool isVarLength = false; + for(int4 i = 0;i < num;++i) { + const string &attribName(el->getAttributeName(i)); + if (attribName == "id") { + istringstream s(el->getAttributeValue(i)); + s.unsetf(ios::dec | ios::hex | ios::oct); + s >> newid; + } + else if (attribName == "size") { + istringstream s(el->getAttributeValue(i)); + s.unsetf(ios::dec | ios::hex | ios::oct); + s >> structsize; + } + else if (attribName == "varlength") { + isVarLength = xml_readbool(el->getAttributeValue(i)); + } + } + if (newid == 0) + newid = Datatype::hashName(structname); + if (isVarLength) + newid = Datatype::hashSize(newid,structsize); + Datatype *ct = findByIdLocal(structname,newid); + if (ct == (Datatype*)0) { + ts.id = newid; + ts.size = structsize; // Include size if we have it, so arrays can be defined without knowing struct fields + ct = findAdd(ts); // Create stub to allow recursive definitions + } + else if (ct->getMetatype() != TYPE_STRUCT) + throw LowlevelError("Trying to redefine type: " + structname); + ts.restoreXml(el,*this); + if (forcecore) + ts.flags |= Datatype::coretype; + if (!ct->isIncompleteStruct()) { // Structure of this name was already present + if (0 != ct->compareDependency(ts)) + throw LowlevelError("Redefinition of structure: " + structname); + } + else { // If structure is a placeholder stub + if (!setFields(ts.field,(TypeStruct*)ct,ts.size,ts.flags)) // Define structure now by copying fields + throw LowlevelError("Bad structure definition"); + } + return ct; +} + /// Restore a Datatype object from an XML \ tag. (Don't use for \ tags) /// The new Datatype is added to \b this container /// \param el is the XML element @@ -2715,52 +2792,7 @@ Datatype *TypeFactory::restoreXmlTypeNoRef(const Element *el,bool forcecore) } break; case TYPE_STRUCT: - { - string structname = el->getAttributeValue("name"); - TypeStruct ts(structname); - int4 num = el->getNumAttributes(); - uint8 newid = 0; - int4 structsize = 0; - bool isVarLength = false; - for(int4 i=0;igetAttributeName(i)); - if (attribName == "id") { - istringstream s(el->getAttributeValue(i)); - s.unsetf(ios::dec | ios::hex | ios::oct); - s >> newid; - } - else if (attribName == "size") { - istringstream s(el->getAttributeValue(i)); - s.unsetf(ios::dec | ios::hex | ios::oct); - s >> structsize; - } - else if (attribName == "varlength") { - isVarLength = xml_readbool(el->getAttributeValue(i)); - } - } - if (newid == 0) - newid = Datatype::hashName(structname); - if (isVarLength) - newid = Datatype::hashSize(newid, structsize); - ct = findByIdLocal(structname,newid); - if (ct == (Datatype *)0) { - ts.id = newid; - ts.size = structsize; // Include size if we have it, so arrays can be defined without knowing struct fields - ct = findAdd(ts); // Create stub to allow recursive definitions - } - else if (ct->getMetatype() != TYPE_STRUCT) - throw LowlevelError("Trying to redefine type: "+structname); - ts.restoreXml(el,*this); - if (forcecore) - ts.flags |= Datatype::coretype; - if (!ct->isIncompleteStruct()) { // Structure of this name was already present - if (0!=ct->compareDependency(ts)) - throw LowlevelError("Redefinition of structure: "+structname); - } - else // If structure is a placeholder stub - if (!setFields(ts.field,(TypeStruct *)ct,ts.size,ts.flags)) // Define structure now by copying fields - throw LowlevelError("Bad structure definition"); - } + ct = restoreStruct(el,forcecore); break; case TYPE_SPACEBASE: { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh index 4b08806028..eb92e5b13a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh @@ -147,6 +147,7 @@ public: bool isIncompleteStruct(void) const { return (flags & struct_incomplete)!=0; } ///< Is \b this an incompletely defined struct uint4 getInheritable(void) const { return (flags & coretype); } ///< Get properties pointers inherit type_metatype getMetatype(void) const { return metatype; } ///< Get the type \b meta-type + sub_metatype getSubMeta(void) const { return submeta; } ///< Get the \b sub-metatype uint8 getId(void) const { return id; } ///< Get the type id int4 getSize(void) const { return size; } ///< Get the type size const string &getName(void) const { return name; } ///< Get the type name @@ -494,11 +495,13 @@ class TypeFactory { Datatype *findAdd(Datatype &ct); ///< Find data-type in this container or add it void orderRecurse(vector &deporder,DatatypeSet &mark,Datatype *ct) const; ///< Write out dependency list Datatype *restoreTypedef(const Element *el); ///< Restore a \ XML tag describing a typedef + Datatype *restoreStruct(const Element *el,bool forcecore); ///< Restore a \ XML tag describing a structure Datatype *restoreXmlTypeNoRef(const Element *el,bool forcecore); ///< Restore from an XML tag void clearCache(void); ///< Clear the common type cache TypeChar *getTypeChar(const string &n); ///< Create a default "char" type TypeUnicode *getTypeUnicode(const string &nm,int4 sz,type_metatype m); ///< Create a default "unicode" type TypeCode *getTypeCode(const string &n); ///< Create a default "code" type + void recalcPointerSubmeta(Datatype *base,sub_metatype sub); ///< Recalculate submeta for pointers to given base data-type protected: Architecture *glb; ///< The Architecture object that owns this TypeFactory Datatype *findByIdLocal(const string &nm,uint8 id) const; ///< Search locally by name and id diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/CoreGTreeNode.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/CoreGTreeNode.java index d0e5ad49c6..72905d7150 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/CoreGTreeNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/CoreGTreeNode.java @@ -81,6 +81,14 @@ abstract class CoreGTreeNode implements Cloneable { return localParent; } + /** + * Returns true if this is a root node of a GTree + * @return true if this is a root node of a GTree + */ + public final boolean isRoot() { + return parent instanceof GTreeRootParentNode; + } + /** * Sets the parent of this node. This method should only be used by a parent * node when a new child is added to that parent node. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java index 303d4fe19a..20fc91cc7c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java @@ -943,13 +943,12 @@ public class GTree extends JPanel implements BusyListener { } /** - * Requests that the node with the given name, in the given parent, be edited. This - * operation (as with many others on this tree) is asynchronous. This request will be - * buffered as needed to wait for the given node to be added to the parent, up to a timeout - * period. + * Requests that the node with the given name, in the given parent, be edited. This + * operation is asynchronous. This request will be buffered as needed to wait for + * the given node to be added to the parent, up to a timeout period. * * @param parent the parent node - * @param childName the child node name + * @param childName the name of the child to edit */ public void startEditing(GTreeNode parent, final String childName) { @@ -972,6 +971,21 @@ public class GTree extends JPanel implements BusyListener { }); } + /** + * Requests that the node be edited. This operation is asynchronous. + * + * @param child the node to edit + */ + public void startEditing(GTreeNode child) { + + // we call this here, even though the JTree will do this for us, so that we will trigger + // a load call before this task is run, in case lazy nodes are involved in this tree, + // which must be loaded before we can edit + expandPath(child.getParent()); + + runTask(new GTreeStartEditingTask(GTree.this, tree, child)); + } + @Override public synchronized void addMouseListener(MouseListener listener) { mouseListenerDelegate.addMouseListener(listener); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java index 88506a252a..c7beb85b7d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java @@ -318,23 +318,18 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable { @@ -66,10 +74,12 @@ public class GTreeStartEditingTask extends GTreeTask { return; } - GTreeNode editNode = parent.getChild(childName); if (editNode == null) { - Msg.debug(this, "Can't find node \"" + childName + "\" to edit."); - return; + editNode = parent.getChild(childName); + if (editNode == null) { + Msg.debug(this, "Can't find node \"" + childName + "\" to edit."); + return; + } } TreePath path = editNode.getTreePath(); diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/tree/GTreeNodeTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/tree/GTreeNodeTest.java index a7c88467ca..d289372de2 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/tree/GTreeNodeTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/tree/GTreeNodeTest.java @@ -55,6 +55,8 @@ public class GTreeNodeTest { @Before public void setUp() { root = new TestNode("root"); + root.setParent(new GTreeRootParentNode(null)); + node0 = new TestNode("Node0"); node1 = new TestNode("Node1"); node2 = new TestNode("Node2"); @@ -354,7 +356,7 @@ public class GTreeNodeTest { assertEquals(root, node0_1.getRoot()); assertEquals(root, root.getRoot()); TestNode testNode = new TestNode("test"); - assertEquals(testNode, testNode.getRoot()); + assertEquals(null, testNode.getRoot()); } @Test diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataRenameAction.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataRenameAction.java index 2544f91588..df7382e344 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataRenameAction.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataRenameAction.java @@ -66,7 +66,7 @@ public class ProjectDataRenameAction extends FrontendProjectTreeAction { DataTree tree = (DataTree) component; GTreeNode node = (GTreeNode) context.getContextObject(); tree.setEditable(true); - tree.startEditing(node.getParent(), node.getName()); + tree.startEditing(node); } else if (component instanceof GTable) { GTable table = (GTable) component; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java index 1095fd5c59..d15cc35f5c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java @@ -133,9 +133,8 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB { @Override public void close() { - if (dbHandle != null) { + if (!dbHandle.isClosed()) { dbHandle.close(); - dbHandle = null; } super.close(); }