mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-31 14:57:09 +08:00
Merge remote-tracking branch 'origin/Ghidra_10.1'
This commit is contained in:
@@ -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<SearchLocation> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> 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<GFile> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -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) {
|
||||
|
||||
-36
@@ -127,46 +127,10 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> 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;
|
||||
|
||||
+83
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+4
-6
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-8
@@ -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
|
||||
|
||||
-8
@@ -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);
|
||||
|
||||
+4
-6
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+97
@@ -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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
+132
@@ -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<SearchLocation> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -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<SearchLocation> searchLocations;
|
||||
|
||||
/**
|
||||
* Creates a new instance using the list of search locations.
|
||||
*
|
||||
* @param searchLocations list of {@link SearchLocation search locations}
|
||||
*/
|
||||
public ExternalDebugFilesService(List<SearchLocation> searchLocations) {
|
||||
this.searchLocations = searchLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured search locations.
|
||||
*
|
||||
* @return list of search locations
|
||||
*/
|
||||
public List<SearchLocation> getSearchLocations() {
|
||||
return searchLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the specified external debug file.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/external/ExternalDebugInfo.java
Vendored
+118
@@ -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.
|
||||
* <p>
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
+143
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+99
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+54
@@ -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();
|
||||
}
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+112
@@ -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<SearchLocationCreationInfo> 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<String> 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<String> testFunc;
|
||||
SearchLocationCreator createFunc;
|
||||
|
||||
SearchLocationCreationInfo(Predicate<String> testFunc,
|
||||
SearchLocationCreator createFunc) {
|
||||
this.testFunc = testFunc;
|
||||
this.createFunc = createFunc;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -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;
|
||||
}
|
||||
|
||||
+10
-4
@@ -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());
|
||||
}
|
||||
|
||||
+4
-5
@@ -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
|
||||
|
||||
+14
-5
@@ -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;
|
||||
* <p>
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-7
@@ -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<Function<Program, DWARFSectionProvider>> 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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+143
@@ -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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+61
-2
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
+44
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Address, Address[]> instructionFlowsCache = new LRUMap<>(LRU_SIZE);
|
||||
|
||||
// Cache PcodeOps so that we won't have to grab them again if we re-visit the node.
|
||||
Map<Address, PcodeOp[]> pcodeCache = new LRUMap<>(LRU_SIZE);
|
||||
|
||||
// Cache Instructions looked up by At
|
||||
Map<Address, Instruction> instructionAtCache = new LRUMap<>(LRU_SIZE);
|
||||
|
||||
// Cache instructions looked up by containing
|
||||
Map<Address, Instruction> 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<Address, PcodeOp[]> 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<Address, Instruction> instructionAtCache = new HashMap<>();
|
||||
|
||||
// Cache instructions looked up by containing
|
||||
HashMap<Address, Instruction> 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<Address, Address[]> 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;
|
||||
}
|
||||
|
||||
+39
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<TypeField> &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 \<type> tag. (Don't use for \<typeref> 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;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);
|
||||
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:
|
||||
{
|
||||
|
||||
@@ -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<Datatype *> &deporder,DatatypeSet &mark,Datatype *ct) const; ///< Write out dependency list
|
||||
Datatype *restoreTypedef(const Element *el); ///< Restore a \<def> XML tag describing a typedef
|
||||
Datatype *restoreStruct(const Element *el,bool forcecore); ///< Restore a \<type> 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. <b>This
|
||||
* operation (as with many others on this tree) is asynchronous.</b> 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);
|
||||
|
||||
@@ -318,23 +318,18 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
||||
|
||||
/**
|
||||
* Returns the rootNode for this tree or null if there is no parent path to a
|
||||
* GTRootNode
|
||||
* @return the rootNode for this tree
|
||||
* root node.
|
||||
* @return the rootNode for a tree of nodes in a {@link GTree}
|
||||
*/
|
||||
public GTreeNode getRoot() {
|
||||
GTreeNode myParent = getParent();
|
||||
if (myParent == null || myParent instanceof GTreeRootParentNode) {
|
||||
if (isRoot()) {
|
||||
return this;
|
||||
}
|
||||
return myParent.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a root node
|
||||
* @return true if this is a root node
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return getRoot() == this;
|
||||
GTreeNode myParent = getParent();
|
||||
if (myParent != null) {
|
||||
return myParent.getRoot();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,7 +362,7 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
||||
}
|
||||
}
|
||||
|
||||
if (isRoot() || !list.isEmpty() || filter.acceptsNode(this) || getParent() == null) {
|
||||
if (isRoot() || !list.isEmpty() || filter.acceptsNode(this)) {
|
||||
GTreeNode clone = clone();
|
||||
clone.doSetChildren(list);
|
||||
return clone;
|
||||
|
||||
+15
-10
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package docking.widgets.tree.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.event.TreeModelEvent;
|
||||
import javax.swing.event.TreeModelListener;
|
||||
@@ -125,19 +124,25 @@ public class GTreeModel implements TreeModel {
|
||||
SystemUtilities.assertThisIsTheSwingThread(
|
||||
"GTreeModel.fireNodeStructuredChanged() must be " + "called from the AWT thread");
|
||||
|
||||
GTreeNode node = convertToViewNode(changedNode);
|
||||
if (node == null) {
|
||||
// If the tree is filtered and this is called on the original node, we have to
|
||||
// translate the node to a view node (one the jtree knows).
|
||||
GTreeNode viewNode = convertToViewNode(changedNode);
|
||||
if (viewNode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node != changedNode) {
|
||||
// Note: calling setChildren() here triggers another call to this method. But,
|
||||
// the 'isFiringNodeStructureChanged' flag prevents that notification from
|
||||
// happening. So, we still have to fire the event below.
|
||||
node.setChildren(null);
|
||||
if (viewNode != changedNode) {
|
||||
// This means we are filtered and since the original node's children are invalid,
|
||||
// then the filtered children are invalid also. So clear out the children by
|
||||
// setting an empty list as we don't want to trigger the node to regenerate its
|
||||
// children which happens if you set the children to null.
|
||||
//
|
||||
// This won't cause a second event to the jtree because we are protected
|
||||
// by the isFiringNodeStructureChanged variable
|
||||
viewNode.setChildren(Collections.emptyList());
|
||||
}
|
||||
|
||||
TreeModelEvent event = new TreeModelEvent(this, node.getTreePath());
|
||||
TreeModelEvent event = new TreeModelEvent(this, viewNode.getTreePath());
|
||||
for (TreeModelListener listener : listeners) {
|
||||
listener.treeStructureChanged(event);
|
||||
}
|
||||
|
||||
+13
-3
@@ -36,6 +36,7 @@ public class GTreeStartEditingTask extends GTreeTask {
|
||||
|
||||
private final GTreeNode parent;
|
||||
private final String childName;
|
||||
private GTreeNode editNode;
|
||||
|
||||
public GTreeStartEditingTask(GTree gTree, JTree jTree, GTreeNode parent, String childName) {
|
||||
super(gTree);
|
||||
@@ -43,6 +44,13 @@ public class GTreeStartEditingTask extends GTreeTask {
|
||||
this.childName = childName;
|
||||
}
|
||||
|
||||
public GTreeStartEditingTask(GTree gTree, JTree jTree, GTreeNode editNode) {
|
||||
super(gTree);
|
||||
this.parent = editNode.getParent();
|
||||
this.childName = editNode.getName();
|
||||
this.editNode = editNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(final TaskMonitor monitor) throws CancelledException {
|
||||
runOnSwingThread(() -> {
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-2
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user