GP-3883 added source file manager

This commit is contained in:
James 2023-10-13 14:57:59 +00:00
parent 420dd7ce0c
commit 9aeeaa4397
52 changed files with 8432 additions and 306 deletions

View file

@ -0,0 +1,93 @@
/* ###
* 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.
*/
// Adds a SourceFile with a user-defined path and name to the program.
//@category SourceMapping
import java.util.HexFormat;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.util.MessageType;
public class AddSourceFileScript extends GhidraScript {
private static final String PATH = "Source File Path";
private static final String ID_TYPE = "Id Type";
private static final String IDENTIFIER = "Identifier";
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
if (!currentProgram.hasExclusiveAccess()) {
popup("This script requires exclusive access to the program");
return;
}
GhidraValuesMap values = new GhidraValuesMap();
values.defineString(PATH, "/");
SourceFileIdType[] idTypes = SourceFileIdType.values();
String[] enumNames = new String[idTypes.length];
for (int i = 0; i < enumNames.length; ++i) {
enumNames[i] = idTypes[i].name();
}
values.defineChoice(ID_TYPE, SourceFileIdType.NONE.name(), enumNames);
values.defineString(IDENTIFIER, StringUtils.EMPTY);
values.setValidator((valueMap, status) -> {
String path = valueMap.getString(PATH);
SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
byte[] identifier = null;
if (idType != SourceFileIdType.NONE) {
identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
}
try {
SourceFile srcFile = new SourceFile(path, idType, identifier);
if (currentProgram.getSourceFileManager().containsSourceFile(srcFile)) {
status.setStatusText("SourceFile " + srcFile + " already exists",
MessageType.ERROR);
return false;
}
}
catch (IllegalArgumentException e) {
status.setStatusText(e.getMessage(), MessageType.ERROR);
return false;
}
return true;
});
askValues("Enter (Absolute) Source File URI Path",
"e.g.: /usr/bin/echo, /C:/Programs/file.exe", values);
String absolutePath = values.getString(PATH);
SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
byte[] identifier = null;
if (idType != SourceFileIdType.NONE) {
identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
}
SourceFile srcFile = new SourceFile(absolutePath, idType, identifier);
currentProgram.getSourceFileManager().addSourceFile(srcFile);
printf("Successfully added source file %s%n", srcFile.toString());
}
}

View file

@ -0,0 +1,146 @@
/* ###
* 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.
*/
// Add a source map entry for the current selection.
// The current selection must consist of a single address range.
// If there is no selection, a length 0 entry will be added at the current address.
//@category SourceMapping
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.sourcemap.*;
import ghidra.util.MessageType;
public class AddSourceMapEntryScript extends GhidraScript {
private static final String LINE_NUM = "Line Number";
private static final String SOURCE_FILE = "Source File";
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
if (!currentProgram.hasExclusiveAccess()) {
popup("This script requires exclusive access to the program");
return;
}
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
if (sourceFiles.isEmpty()) {
popup("You must first add at least one source file to the program");
return;
}
boolean valid = isCurrentSelectionValid();
if (!valid) {
return; // checkCurrentSelection will tell the user what the problem is
}
Address baseAddr =
currentSelection == null ? currentAddress : currentSelection.getMinAddress();
long length = currentSelection == null ? 0 : currentSelection.getNumAddresses();
AddressRange currentRange =
currentSelection == null ? null : currentSelection.getAddressRanges().next();
Map<String, SourceFile> stringsToSourceFiles = new HashMap<>();
sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
GhidraValuesMap values = new GhidraValuesMap();
values.defineInt(LINE_NUM, 1);
String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
Arrays.sort(sourceFileArray);
values.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
values.setValidator((valueMap, status) -> {
int lineNum = values.getInt(LINE_NUM);
if (lineNum < 0) {
status.setStatusText("Line number cannot be negative", MessageType.ERROR);
return false;
}
if (currentRange == null) {
return true; // length 0 entry, nothing else to check
}
SourceFile source = new SourceFile(values.getChoice(SOURCE_FILE));
for (SourceMapEntry entry : sourceManager.getSourceMapEntries(source, lineNum)) {
// because checkCurrentSelection returned true, if the entries intersect
// they must be equal
if (entry.getLength() != 0 && entry.getRange().intersects(currentRange)) {
status.setStatusText(currentSelection + " is already mapped to " +
source.getPath() + ":" + lineNum);
return false;
}
}
return true;
});
askValues("Source Map Info", "Enter Source Map Info (length = " + length + ")", values);
int lineNum = values.getInt(LINE_NUM);
String fileString = values.getChoice(SOURCE_FILE);
SourceFile source = stringsToSourceFiles.get(fileString);
sourceManager.addSourceMapEntry(source, lineNum, baseAddr, length);
}
// check that the selected range doesn't already intersect a SourceMapEntry with a
// conflicting range.
private boolean isCurrentSelectionValid() {
if (currentSelection == null) {
return true;
}
if (currentSelection.getNumAddressRanges() != 1) {
popup("This script requires the current selection to be a single address range");
return false;
}
AddressRange range = currentSelection.getFirstRange();
Address end = range.getMaxAddress();
SourceMapEntryIterator iter =
currentProgram.getSourceFileManager().getSourceMapEntryIterator(end, false);
while (iter.hasNext()) {
SourceMapEntry entry = iter.next();
if (!entry.getBaseAddress().getAddressSpace().equals(range.getAddressSpace())) {
return true; // iterator has exhausted entries in the address space, no conflicts
// are possible
}
if (entry.getLength() == 0) {
continue; // length 0 entries can't conflict
}
AddressRange entryRange = entry.getRange();
if (entryRange.equals(range)) {
return true; // range is the same as a range already in the db, so no problems
}
if (entryRange.intersects(range)) {
popup("Selection conflicts with existing entry " + entry.toString());
return false;
}
if (entryRange.getMaxAddress().compareTo(range.getMinAddress()) < 0) {
return true; // no conflicting entries exists
}
}
return true;
}
}

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Adds DWARF source file line number info to the current binary
// Adds DWARF source file line number info to the current binary as EOL comments.
// Note that you can run this script on a program that has already been analyzed by the
// DWARF analyzer.
//@category DWARF
import java.io.IOException;
import java.util.List;
@ -29,7 +31,7 @@ import ghidra.program.model.listing.CodeUnit;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
public class DWARFLineInfoScript extends GhidraScript {
public class DWARFLineInfoCommentScript extends GhidraScript {
@Override
protected void run() throws Exception {
DWARFSectionProvider dsp =

View file

@ -0,0 +1,154 @@
/* ###
* 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.
*/
// Adds DWARF source file line number info to the current program as source map entries.
// Note that you can run this script on a program that has already been analyzed by the
// DWARF analyzer.
//@category DWARF
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.*;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr;
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider;
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
public class DWARFLineInfoSourceMapScript extends GhidraScript {
public static final int ENTRY_MAX_LENGTH = 1000;
@Override
protected void run() throws Exception {
if (!currentProgram.hasExclusiveAccess()) {
Msg.showError(this, null, "Exclusive Access Required",
"Must have exclusive access to a program to add source map info");
return;
}
DWARFSectionProvider dsp =
DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor);
if (dsp == null) {
printerr("Unable to find DWARF information");
return;
}
DWARFImportOptions importOptions = new DWARFImportOptions();
try (DWARFProgram dprog = new DWARFProgram(currentProgram, importOptions, monitor, dsp)) {
dprog.init(monitor);
addSourceLineInfo(dprog);
}
}
private void addSourceLineInfo(DWARFProgram dprog)
throws CancelledException, IOException, LockException, AddressOverflowException {
BinaryReader reader = dprog.getDebugLineBR();
if (reader == null) {
popup("Unable to get reader for debug line info");
return;
}
int entryCount = 0;
monitor.initialize(reader.length(), "DWARF Source Map Info");
List<DWARFCompilationUnit> compUnits = dprog.getCompilationUnits();
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
List<SourceFileAddr> sourceInfo = new ArrayList<>();
for (DWARFCompilationUnit cu : compUnits) {
sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader));
}
sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address()));
monitor.initialize(sourceInfo.size());
for (int i = 0; i < sourceInfo.size(); i++) {
monitor.checkCancelled();
monitor.increment(1);
SourceFileAddr sourceFileAddr = sourceInfo.get(i);
if (sourceFileAddr.isEndSequence()) {
continue;
}
Address addr = dprog.getCodeAddress(sourceFileAddr.address());
if (!currentProgram.getMemory().getExecuteSet().contains(addr)) {
printerr(
"entry for non-executable address; skipping: file %s line %d address: %s %x"
.formatted(sourceFileAddr.fileName(), sourceFileAddr.lineNum(),
addr.toString(), sourceFileAddr.address()));
continue;
}
long length = getLength(i, sourceInfo);
if (length < 0) {
println(
"Error computing entry length for file %s line %d address %s %x; replacing" +
" with length 0 entry".formatted(sourceFileAddr.fileName(),
sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address()));
length = 0;
}
if (length > ENTRY_MAX_LENGTH) {
println(
("entry for file %s line %d address: %s %x length %d too large, replacing " +
"with length 0 entry").formatted(sourceFileAddr.fileName(),
sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address(),
length));
length = 0;
}
if (sourceFileAddr.fileName() == null) {
continue;
}
SourceFile source = null;
try {
SourceFileIdType type =
sourceFileAddr.md5() == null ? SourceFileIdType.NONE : SourceFileIdType.MD5;
source = new SourceFile(sourceFileAddr.fileName(), type, sourceFileAddr.md5());
sourceManager.addSourceFile(source);
}
catch (IllegalArgumentException e) {
printerr("Exception creating source file %s".formatted(e.getMessage()));
continue;
}
try {
sourceManager.addSourceMapEntry(source, sourceFileAddr.lineNum(), addr, length);
}
catch (IllegalArgumentException e) {
printerr(e.getMessage());
continue;
}
entryCount++;
}
println("Added " + entryCount + " source map entries");
}
private long getLength(int i, List<SourceFileAddr> allSFA) {
SourceFileAddr iAddr = allSFA.get(i);
long iOffset = iAddr.address();
for (int j = i + 1; j < allSFA.size(); j++) {
SourceFileAddr current = allSFA.get(j);
long currentAddr = current.address();
if (current.isEndSequence()) {
return currentAddr + 1 - iOffset;
}
if (currentAddr != iOffset) {
return currentAddr - iOffset;
}
}
return -1;
}
}

View file

@ -0,0 +1,63 @@
/* ###
* 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.
*/
// Select and remove a source map entry at the current address.
//@category SourceMapping
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.model.sourcemap.SourceMapEntry;
public class RemoveSourceMapEntryScript extends GhidraScript {
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
if (!currentProgram.hasExclusiveAccess()) {
popup("Modifying the source map requires exclusive access to the program");
return;
}
List<SourceMapEntry> entries =
currentProgram.getSourceFileManager().getSourceMapEntries(currentAddress);
if (entries.isEmpty()) {
popup("No source map entries at " + currentAddress);
return;
}
Map<String, SourceMapEntry> stringToEntry = new HashMap<>();
List<String> choices = new ArrayList<>();
for (SourceMapEntry entry : entries) {
String entryString = entry.toString();
stringToEntry.put(entryString, entry);
choices.add(entryString);
}
GhidraValuesMap values = new GhidraValuesMap();
String[] choiceArray = choices.toArray(new String[] {});
values.defineChoice("Entry", choiceArray[0], choiceArray);
askValues("Select SourceMapEntry to remove", null, values);
String selectedString = values.getChoice("Entry");
SourceMapEntry toDelete = stringToEntry.get(selectedString);
currentProgram.getSourceFileManager().removeSourceMapEntry(toDelete);
}
}

View file

@ -0,0 +1,113 @@
/* ###
* 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.
*/
// Sets the current selection based on source file and line number parameters
//@category SourceMapping
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.util.MessageType;
import ghidra.util.SourceFileUtils;
import ghidra.util.SourceFileUtils.SourceLineBounds;
public class SelectAddressesMappedToSourceFileScript extends GhidraScript {
private static final String SOURCE_FILE = "Source File";
private static final String MIN_LINE = "Minimum Source Line";
private static final String MAX_LINE = " Maximum Source Line";
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
List<SourceFile> sourceFiles = sourceManager.getMappedSourceFiles();
if (sourceFiles.isEmpty()) {
popup(currentProgram.getName() + " contains no mapped source files");
return;
}
GhidraValuesMap sourceFileValue = new GhidraValuesMap();
Map<String, SourceFile> stringsToSourceFiles = new HashMap<>();
sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
Arrays.sort(sourceFileArray);
sourceFileValue.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
askValues("Select Source File", "Source File", sourceFileValue);
String sfString = sourceFileValue.getChoice(SOURCE_FILE);
SourceFile mappedSourceFile = stringsToSourceFiles.get(sfString);
SourceLineBounds bounds =
SourceFileUtils.getSourceLineBounds(currentProgram, mappedSourceFile);
GhidraValuesMap boundValues = new GhidraValuesMap();
boundValues.defineInt(MIN_LINE, bounds.min());
boundValues.setInt(MIN_LINE, bounds.min());
boundValues.defineInt(MAX_LINE, bounds.max());
boundValues.setInt(MAX_LINE, bounds.max());
boundValues.setValidator((valueMap, status) -> {
int minLine = boundValues.getInt(MIN_LINE);
if (minLine < 0) {
status.setStatusText("Line number cannot be negative", MessageType.ERROR);
return false;
}
int maxLine = boundValues.getInt(MAX_LINE);
if (maxLine < minLine) {
status.setStatusText("Max line cannot be less than min line", MessageType.ERROR);
return false;
}
return true;
});
setReusePreviousChoices(false);
askValues("Select Line Bounds", "Bounds for " + mappedSourceFile.getFilename(),
boundValues);
setReusePreviousChoices(true);
int minLine = boundValues.getInt(MIN_LINE);
int maxLine = boundValues.getInt(MAX_LINE);
AddressSet selection = new AddressSet();
List<SourceMapEntry> entries =
sourceManager.getSourceMapEntries(mappedSourceFile, minLine, maxLine);
for (SourceMapEntry entry : entries) {
if (entry.getLength() == 0) {
selection.add(entry.getBaseAddress());
continue;
}
selection.add(entry.getRange());
}
if (selection.isEmpty()) {
popup("No addresses for the selected file and range");
return;
}
setCurrentSelection(selection);
}
}

View file

@ -993,7 +993,7 @@
Option to display the raw PCode directly in the Code Browser (i.e., detailed varnode
specifications are provided).</P>
<P><B>Maximum Lines to Display -</B> The maximum number of lines used to display PCode. Any
<P><B>Maximum Lines to Display</B> - The maximum number of lines used to display PCode. Any
additional lines of PCode will not be shown.</P>
</BLOCKQUOTE>
@ -1010,6 +1010,24 @@
"CodeBrowser.htm#Selection">Selection</A> color.</P>
</BLOCKQUOTE>
<H3><A name="Source_Map_Field"></A>Source Map Field</H3>
<BLOCKQUOTE>
<P><B>Show Filename Only</B> -
If selected, only the file name will be shown in the Listing field (rather than the full
path).</P>
<P><B>Show Source Info at Every Address</B> - If selected, source map information will be
displayed for each address in the Listing. Otherwise the source information for a given
source map entry will only be displayed at the minimum address of the corresponding
range.</P>
<P><B>Maximum Number of Source Map Entries to Display</B> - Maximum number of source
map entries to display per address.</P>
<P><B>Show Identifier</B> - If selected, the source file identifier (md5, sha1,...) will
be shown in the Listing. Note that a source file might not have an identifier.</P>
</BLOCKQUOTE>
<H3><A name="Template_Display_Options"></A>Template Display Options</H3>
<BLOCKQUOTE>
<P><B>Max Template Depth</B> - Sets the depth to display nested templates. A

View file

@ -38,10 +38,10 @@ public class DWARFImportOptions {
"Include source code location info (filename:linenumber) in comments attached to the " +
"Ghidra datatype or function or variable created.";
private static final String OPTION_SOURCE_LINEINFO = "Output Source Line Info";
private static final String OPTION_SOURCE_LINEINFO = "Import Source Line Info";
private static final String OPTION_SOURCE_LINEINFO_DESC =
"Place end-of-line comments containg the source code filename and line number at " +
"each location provided in the DWARF data";
"Create source map entries containing the source code filename, line number, address, and" +
" length at each location provided in the DWARF data";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC =
@ -78,6 +78,10 @@ public class DWARFImportOptions {
private static final String OPTION_DEFAULT_CC_DESC =
"Name of default calling convention to assign to functions (e.g. __cdecl, __stdcall, etc), or leave blank.";
private static final String OPTION_MAX_SOURCE_ENTRY_LENGTH = "Maximum Source Map Entry Length";
private static final String OPTION_MAX_SOURCE_ENTRY_LENGTH_DESC =
"Maximum length for a source map entry. Longer lengths will be replaced with 0";
//==================================================================================================
// Old Option Names - Should stick around for multiple major versions after 10.2
//==================================================================================================
@ -111,9 +115,10 @@ public class DWARFImportOptions {
private boolean specialCaseSizedBaseTypes = true;
private boolean importLocalVariables = true;
private boolean useBookmarks = true;
private boolean outputSourceLineInfo = false;
private boolean outputSourceLineInfo = true;
private boolean ignoreParamStorage = false;
private String defaultCC = "";
private long maxSourceMapEntryLength = 2000;
/**
* Create new instance
@ -376,10 +381,20 @@ public class DWARFImportOptions {
return useBookmarks;
}
/**
* Option to control whether source map info from DWARF is stored in the Program.
*
* @return {@code true} if option turned on
*/
public boolean isOutputSourceLineInfo() {
return outputSourceLineInfo;
}
/**
* Option to control whether source map info from DWARF is stored in the Program.
*
* @param outputSourceLineInfo true to turn option on, false to turn off
*/
public void setOutputSourceLineInfo(boolean outputSourceLineInfo) {
this.outputSourceLineInfo = outputSourceLineInfo;
}
@ -400,6 +415,29 @@ public class DWARFImportOptions {
this.defaultCC = defaultCC;
}
/**
* Option to control the maximum length of a source map entry. If a longer length is calculated
* it will be replaced with 0.
*
* @return max source map entry length
*/
public long getMaxSourceMapEntryLength() {
return maxSourceMapEntryLength;
}
/**
* Option to control the maximum length of a source map entry. If a longer length is calculated
* it will be replaced with 0.
*
* @param maxLength new max source entry length
*/
public void setMaxSourceMapEntryLength(long maxLength) {
if (maxLength < 0) {
maxLength = 0;
}
maxSourceMapEntryLength = maxLength;
}
/**
* See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)}
*
@ -440,6 +478,8 @@ public class DWARFImportOptions {
OPTION_IGNORE_PARAM_STORAGE_DESC);
options.registerOption(OPTION_DEFAULT_CC, getDefaultCC(), null, OPTION_DEFAULT_CC_DESC);
options.registerOption(OPTION_MAX_SOURCE_ENTRY_LENGTH, maxSourceMapEntryLength, null,
OPTION_MAX_SOURCE_ENTRY_LENGTH_DESC);
}
/**
@ -467,6 +507,8 @@ public class DWARFImportOptions {
setIgnoreParamStorage(
options.getBoolean(OPTION_IGNORE_PARAM_STORAGE, isIgnoreParamStorage()));
setDefaultCC(options.getString(OPTION_DEFAULT_CC, getDefaultCC()));
setMaxSourceMapEntryLength(
options.getLong(OPTION_MAX_SOURCE_ENTRY_LENGTH, getMaxSourceMapEntryLength()));
}
}

View file

@ -16,19 +16,22 @@
package ghidra.app.util.bin.format.dwarf;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr;
import ghidra.app.util.bin.format.dwarf.line.DWARFLineProgramExecutor;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.*;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import utility.function.Dummy;
@ -174,28 +177,124 @@ public class DWARFImporter {
return new CategoryPath(newRoot, cpParts.subList(origRootParts.size(), cpParts.size()));
}
private void addSourceLineInfo(BinaryReader reader) throws CancelledException, IOException {
private void addSourceLineInfo(BinaryReader reader)
throws CancelledException, IOException, LockException {
if (reader == null) {
Msg.warn(this, "Can't add source line info - reader is null");
return;
}
monitor.initialize(reader.length(), "DWARF Source Line Info");
int entryCount = 0;
Program ghidraProgram = prog.getGhidraProgram();
BookmarkManager bookmarkManager = ghidraProgram.getBookmarkManager();
long maxLength = prog.getImportOptions().getMaxSourceMapEntryLength();
boolean errorBookmarks = prog.getImportOptions().isUseBookmarks();
List<DWARFCompilationUnit> compUnits = prog.getCompilationUnits();
monitor.initialize(compUnits.size(), "Reading DWARF Source Map Info");
SourceFileManager sourceManager = ghidraProgram.getSourceFileManager();
List<SourceFileAddr> sourceInfo = new ArrayList<>();
for (DWARFCompilationUnit cu : compUnits) {
try {
monitor.increment();
sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader));
}
sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address()));
monitor.initialize(sourceInfo.size(), "Applying DWARF Source Map Info");
for (int i = 0; i < sourceInfo.size() - 1; i++) {
monitor.checkCancelled();
monitor.setProgress(cu.getLine().getStartOffset());
List<SourceFileAddr> allSFA = cu.getLine().getAllSourceFileAddrInfo(cu, reader);
for (SourceFileAddr sfa : allSFA) {
monitor.increment(1);
SourceFileAddr sfa = sourceInfo.get(i);
if (sfa.isEndSequence()) {
continue;
}
Address addr = prog.getCodeAddress(sfa.address());
DWARFUtil.appendComment(prog.getGhidraProgram(), addr, CodeUnit.EOL_COMMENT, "",
"%s:%d".formatted(sfa.fileName(), sfa.lineNum()), ";");
if (!ghidraProgram.getMemory().getExecuteSet().contains(addr)) {
String errorString =
"entry for non-executable address; skipping: file %s line %d address: %s %x"
.formatted(sfa.fileName(), sfa.lineNum(), addr.toString(),
sfa.address());
reportError(bookmarkManager, errorString, addr, errorBookmarks);
continue;
}
long length = getLength(i, sourceInfo);
if (length < 0) {
length = 0;
String errorString =
"Error calculating entry length for file %s line %d address %s %x; replacing " +
"with length 0 entry".formatted(sfa.fileName(), sfa.lineNum(),
addr.toString(), sfa.address());
reportError(bookmarkManager, errorString, addr, errorBookmarks);
}
if (length > maxLength) {
String errorString = ("entry for file %s line %d address: %s %x length %d too" +
" large, replacing with length 0 entry").formatted(sfa.fileName(),
sfa.lineNum(), addr.toString(), sfa.address(), length);
length = 0;
reportError(bookmarkManager, errorString, addr, errorBookmarks);
}
SourceFile source = null;
try {
source = new SourceFile(sfa.fileName());
sourceManager.addSourceFile(source);
}
catch (IllegalArgumentException e) {
Msg.error(this, "Exception creating source file: %s".formatted(e.getMessage()));
continue;
}
try {
sourceManager.addSourceMapEntry(source, sfa.lineNum(), addr, length);
}
catch (AddressOverflowException e) {
String errorString = "AddressOverflowException for source map entry %s %d %s %x %d"
.formatted(source.getFilename(), sfa.lineNum(), addr.toString(),
sfa.address(), length);
reportError(bookmarkManager, errorString, addr, errorBookmarks);
continue;
}
entryCount++;
}
Msg.info(this, "Added %d source map entries".formatted(entryCount));
}
private void reportError(BookmarkManager bManager, String errorString, Address addr,
boolean errorBookmarks) {
if (errorBookmarks) {
bManager.setBookmark(addr, BookmarkType.ERROR, DWARFProgram.DWARF_BOOKMARK_CAT,
errorString);
}
else {
Msg.error(this, errorString);
}
}
catch (IOException e) {
Msg.error(this,
"Failed to read DWARF line info for cu %d".formatted(cu.getUnitNumber()), e);
/**
* In the DWARF format, source line info is only recorded for an address x
* if the info for x differs from the info for address x-1.
* To calculate the length of a source map entry, we need to look for the next
* SourceFileAddr with a different address (there can be multiple records per address
* so there's no guarantee that the SourceFileAddr at position i+1 has a different
* address). Special end-of-sequence markers are used to mark the end of a function,
* so if we find one of these we stop searching. These markers have their addresses tweaked
* by one, which we undo (see {@link DWARFLineProgramExecutor#executeExtended}).
* @param i starting index
* @param allSFA sorted list of SourceFileAddr
* @return computed length or -1 on error
*/
private long getLength(int i, List<SourceFileAddr> allSFA) {
SourceFileAddr iAddr = allSFA.get(i);
long iOffset = iAddr.address();
for (int j = i + 1; j < allSFA.size(); j++) {
SourceFileAddr current = allSFA.get(j);
long currentAddr = current.address();
if (current.isEndSequence()) {
return currentAddr + 1 - iOffset;
}
if (currentAddr != iOffset) {
return currentAddr - iOffset;
}
}
return -1;
}
/**
@ -205,7 +304,8 @@ public class DWARFImporter {
* @throws DWARFException
* @throws CancelledException
*/
public DWARFImportSummary performImport() throws IOException, DWARFException, CancelledException {
public DWARFImportSummary performImport()
throws IOException, DWARFException, CancelledException {
monitor.setIndeterminate(false);
monitor.setShowProgressValue(true);
@ -231,8 +331,19 @@ public class DWARFImporter {
}
if (importOptions.isOutputSourceLineInfo()) {
if (!prog.getGhidraProgram().hasExclusiveAccess()) {
Msg.showError(this, null, "Unable to add source map info",
"Exclusive access to the program is required to add source map info");
}
else {
try {
addSourceLineInfo(prog.getDebugLineBR());
}
catch (LockException e) {
throw new AssertException("LockException after exclusive access verified");
}
}
}
importSummary.totalElapsedMS = System.currentTimeMillis() - start_ts;

View file

@ -54,7 +54,7 @@ public class DWARFProgram implements Closeable {
public static final CategoryPath DWARF_ROOT_CATPATH = CategoryPath.ROOT.extend(DWARF_ROOT_NAME);
public static final CategoryPath UNCAT_CATPATH = DWARF_ROOT_CATPATH.extend("_UNCATEGORIZED_");
private static final String DWARF_BOOKMARK_CAT = "DWARF";
public static final String DWARF_BOOKMARK_CAT = "DWARF";
private static final int NAME_HASH_REPLACEMENT_SIZE = 8 + 2 + 2;
private static final String ELLIPSES_STR = "...";
protected static final EnumSet<DWARFAttribute> REF_ATTRS =

View file

@ -270,14 +270,20 @@ public class DWARFLine {
return lpe;
}
public record SourceFileAddr(long address, String fileName, int lineNum) {}
public record SourceFileAddr(long address, String fileName, byte[] md5, int lineNum,
boolean isEndSequence) {}
public List<SourceFileAddr> getAllSourceFileAddrInfo(DWARFCompilationUnit cu,
BinaryReader reader) throws IOException {
try (DWARFLineProgramExecutor lpe = getLineProgramexecutor(cu, reader)) {
List<SourceFileAddr> results = new ArrayList<>();
for (DWARFLineProgramState row : lpe.allRows()) {
results.add(new SourceFileAddr(row.address, getFilePath(row.file, true), row.line));
byte[] md5 = null;
if (cu.getDWARFVersion() >= 5 && (row.file < cu.getLine().getNumFiles())) {
md5 = cu.getLine().getFile(row.file).getMD5();
}
results.add(new SourceFileAddr(row.address, getFilePath(row.file, true), md5,
row.line, row.isEndSequence));
}
return results;
@ -315,6 +321,14 @@ public class DWARFLine {
"Invalid file index %d for line table at 0x%x: ".formatted(index, startOffset));
}
/**
* Returns the number of indexed files
* @return num files
*/
public int getNumFiles() {
return files.size();
}
public String getFilePath(int index, boolean includePath) {
try {
DWARFFile f = getFile(index);
@ -322,9 +336,7 @@ public class DWARFLine {
return f.getName();
}
String dir = f.getDirectoryIndex() >= 0
? getDir(f.getDirectoryIndex()).getName()
: "";
String dir = f.getDirectoryIndex() >= 0 ? getDir(f.getDirectoryIndex()).getName() : "";
return FSUtilities.appendPath(dir, f.getName());
}

View file

@ -0,0 +1,259 @@
/* ###
* 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.viewer.field;
import java.awt.Color;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.GhidraOptions;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.sourcemap.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.SourceMapFieldLocation;
import ghidra.util.HelpLocation;
/**
* {@link FieldFactory} for showing source and line information in the Listing.
*/
public class SourceMapFieldFactory extends FieldFactory {
static final String FIELD_NAME = "Source Map";
private static final String GROUP_TITLE = "Source Map";
static final String SHOW_FILENAME_ONLY_OPTION_NAME =
GROUP_TITLE + Options.DELIMITER + "Show Filename Only";
static final String SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME =
GROUP_TITLE + Options.DELIMITER + "Show Source Info at Every Address";
static final String MAX_ENTRIES_PER_ADDRESS_OPTION_NAME =
GROUP_TITLE + Options.DELIMITER + "Maximum Number of Source Map Entries to Display";
static final String SHOW_IDENTIFIER_OPTION_NAME =
GROUP_TITLE + Options.DELIMITER + "Show Identifier";
static final String NO_SOURCE_INFO = "unknown:??";
private boolean showOnlyFileNames = true;
private boolean showInfoAtAllAddresses = false;
private boolean showIdentifier = false;
private static final int DEFAULT_MAX_ENTRIES = 4;
private int maxEntries = DEFAULT_MAX_ENTRIES;
static Color OFFCUT_COLOR = Palette.GRAY;
/**
* Default constructor
*/
public SourceMapFieldFactory() {
super(FIELD_NAME);
}
protected SourceMapFieldFactory(FieldFormatModel model,
ListingHighlightProvider highlightProvider, Options displayOptions,
Options fieldOptions) {
super(FIELD_NAME, model, highlightProvider, displayOptions, fieldOptions);
registerOptions(fieldOptions);
}
@Override
public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider highlightProvider, ToolOptions options,
ToolOptions fieldOptions) {
return new SourceMapFieldFactory(formatModel, highlightProvider, options, fieldOptions);
}
@Override
public ListingField getField(ProxyObj<?> obj, int varWidth) {
if (!enabled) {
return null;
}
if (!(obj.getObject() instanceof CodeUnit cu)) {
return null;
}
List<SourceMapEntry> entriesToShow = getSourceMapEntries(cu);
if (entriesToShow.isEmpty()) {
if (!showInfoAtAllAddresses) {
return null;
}
AttributedString attrString =
new AttributedString(NO_SOURCE_INFO, Palette.BLACK, getMetrics());
return ListingTextField.createSingleLineTextField(this, obj,
new TextFieldElement(attrString, 0, 0), startX + varWidth, width, hlProvider);
}
List<FieldElement> fieldElements = new ArrayList<>();
Address cuAddr = cu.getAddress();
if (!showInfoAtAllAddresses) {
List<SourceMapEntry> entriesStartingWithinCu = new ArrayList<>();
for (SourceMapEntry entry : entriesToShow) {
if (entry.getBaseAddress().compareTo(cuAddr) >= 0) {
entriesStartingWithinCu.add(entry);
}
}
if (entriesStartingWithinCu.isEmpty()) {
return null;
}
entriesToShow = entriesStartingWithinCu;
}
for (SourceMapEntry entry : entriesToShow) {
StringBuilder sb = new StringBuilder();
if (showOnlyFileNames) {
sb.append(entry.getSourceFile().getFilename());
}
else {
sb.append(entry.getSourceFile().getPath());
}
sb.append(":");
sb.append(entry.getLineNumber());
sb.append(" (");
sb.append(entry.getLength());
sb.append(")");
if (showIdentifier) {
SourceFile sourceFile = entry.getSourceFile();
if (sourceFile.getIdType().equals(SourceFileIdType.NONE)) {
sb.append(" [no id]");
}
else {
sb.append(" [");
sb.append(sourceFile.getIdType().name());
sb.append("=");
sb.append(sourceFile.getIdAsString());
sb.append("]");
}
}
// use gray for entries which start "within" the code unit
Color color =
entry.getBaseAddress().compareTo(cuAddr) <= 0 ? Palette.BLACK : OFFCUT_COLOR;
AttributedString attrString = new AttributedString(sb.toString(), color, getMetrics());
fieldElements.add(new TextFieldElement(attrString, 0, 0));
}
return ListingTextField.createMultilineTextField(this, obj, fieldElements,
startX + varWidth, width, maxEntries, hlProvider);
}
@Override
public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum,
ProgramLocation loc) {
if (loc instanceof SourceMapFieldLocation sourceField) {
return new FieldLocation(index, fieldNum, sourceField.getRow(),
sourceField.getCharOffset());
}
return null;
}
@Override
public ProgramLocation getProgramLocation(int row, int col, ListingField bf) {
Object obj = bf.getProxy().getObject();
if (!(obj instanceof CodeUnit cu)) {
return null;
}
List<SourceMapEntry> entriesToShow = getSourceMapEntries(cu);
if (entriesToShow == null || entriesToShow.size() <= row) {
return null;
}
return new SourceMapFieldLocation(cu.getProgram(), cu.getAddress(), row, col,
entriesToShow.get(row));
}
@Override
public boolean acceptsType(int category, Class<?> proxyObjectClass) {
return (category == FieldFormatModel.INSTRUCTION_OR_DATA);
}
@Override
public void fieldOptionsChanged(Options options, String optionName, Object oldValue,
Object newValue) {
super.fieldOptionsChanged(options, optionName, oldValue, newValue);
if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) {
if (optionName.equals(SHOW_FILENAME_ONLY_OPTION_NAME)) {
showOnlyFileNames = options.getBoolean(SHOW_FILENAME_ONLY_OPTION_NAME, true);
}
if (optionName.equals(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME)) {
showInfoAtAllAddresses =
options.getBoolean(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
}
if (optionName.equals(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME)) {
maxEntries =
options.getInt(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, DEFAULT_MAX_ENTRIES);
}
if (optionName.equals(SHOW_IDENTIFIER_OPTION_NAME)) {
showIdentifier = options.getBoolean(SHOW_IDENTIFIER_OPTION_NAME, false);
}
model.update();
}
}
private void registerOptions(Options fieldOptions) {
HelpLocation helpLoc = new HelpLocation("CodeBrowserPlugin", "Source_Map_Field");
fieldOptions.registerOption(SHOW_FILENAME_ONLY_OPTION_NAME, true, helpLoc,
"Show only source file name (rather than absolute path)");
showOnlyFileNames = fieldOptions.getBoolean(SHOW_FILENAME_ONLY_OPTION_NAME, true);
fieldOptions.registerOption(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false, helpLoc,
"Show source info at every address " +
"(rather than only at beginning of source map entries)");
showInfoAtAllAddresses =
fieldOptions.getBoolean(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
fieldOptions.registerOption(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, 4, helpLoc,
"Maximum number of source map entries to display");
maxEntries = fieldOptions.getInt(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, DEFAULT_MAX_ENTRIES);
fieldOptions.registerOption(SHOW_IDENTIFIER_OPTION_NAME, false, helpLoc,
"Show source file identifier info");
showIdentifier = fieldOptions.getBoolean(SHOW_IDENTIFIER_OPTION_NAME, false);
}
private List<SourceMapEntry> getSourceMapEntries(CodeUnit cu) {
List<SourceMapEntry> entries = new ArrayList<>();
SourceFileManager sourceManager = cu.getProgram().getSourceFileManager();
// check all addresses in the code unit to handle the (presumably rare) case where
// there is an entry associated with an address in the code unit which is not its
// minimum address
Address cuMinAddr = cu.getMinAddress();
Address cuMaxAddr = cu.getMaxAddress();
SourceMapEntryIterator entryIter =
sourceManager.getSourceMapEntryIterator(cuMaxAddr, false);
while (entryIter.hasNext()) {
SourceMapEntry entry = entryIter.next();
long entryLength = entry.getLength();
long adjusted = entryLength == 0 ? 0 : entryLength - 1;
if (entry.getBaseAddress().add(adjusted).compareTo(cuMinAddr) >= 0) {
entries.add(entry);
continue;
}
if (entryLength != 0) {
break;
}
}
return entries.reversed();
}
}

View file

@ -19,12 +19,16 @@ import java.util.*;
import javax.help.UnsupportedOperationException;
import org.apache.commons.collections4.CollectionUtils;
import ghidra.program.database.properties.UnsupportedMapDB;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.PropertyMap;
import ghidra.program.model.util.PropertyMapManager;
@ -983,6 +987,11 @@ public class ProgramDiff {
monitor.setMessage(monitorMsg);
as = getFunctionTagDifferences(checkAddressSet, monitor);
break;
case ProgramDiffFilter.SOURCE_MAP_DIFFS:
monitorMsg = "Checking Source Map Differences";
monitor.setMessage(monitorMsg);
as = getSourceMapDifferences(checkAddressSet, monitor);
break;
}
if (as != null) {
diffAddrSets.put(diffType, as);
@ -1566,6 +1575,199 @@ public class ProgramDiff {
return c.getObjectDiffs(iter1, iter2, monitor);
}
/**
* Determines the source map differences for the addresses in {@code addressSet}. Source map
* entries which intersect but are not contained within {@code addressSet} are ignored.
* The returned {@link AddressSet} consists of the minimum addresses of all of the differing
* source map entries. This method does not check for differences between non-mapped
* {@link SourceFile}s.
*
* @param addressSet addresses to check for differences (from program1)
* @param monitor task monitor
* @return minimum addresses of differing ranges
* @throws CancelledException if user cancels
*/
private AddressSet getSourceMapDifferences(AddressSetView addressSet, TaskMonitor monitor)
throws CancelledException {
SourceFileManager p1Manager = program1.getSourceFileManager();
SourceFileManager p2Manager = program2.getSourceFileManager();
List<SourceFile> p1Sources = program1.getSourceFileManager().getMappedSourceFiles();
List<SourceFile> p2Sources = program2.getSourceFileManager().getMappedSourceFiles();
Collection<SourceFile> differingPaths = CollectionUtils.disjunction(p1Sources, p2Sources);
Collection<SourceFile> p1Only = CollectionUtils.intersection(p1Sources, differingPaths);
Collection<SourceFile> p2Only = CollectionUtils.intersection(p2Sources, differingPaths);
AddressSet differences =
processDifferingSourceFiles(addressSet, p1Only, p2Only, p1Manager, p2Manager, monitor);
// now consider the mapping info for the source files the programs have in common
Collection<SourceFile> commonSources = CollectionUtils.intersection(p1Sources, p2Sources);
// first check the entries in program1
// only need to check an address once, even if there are multiple entries based at it
AddressSet p1CheckedAddresses = new AddressSet();
AddressSet p2CheckedAddresses = new AddressSet();
for (SourceFile common : commonSources) {
for (SourceMapEntry p1Entry : p1Manager.getSourceMapEntries(common)) {
monitor.checkCancelled();
Address p1Base = p1Entry.getBaseAddress();
if (p1CheckedAddresses.contains(p1Base)) {
continue;
}
Address p2Base = SimpleDiffUtility.getCompatibleAddress(program1, p1Base, program2);
if (p2Base == null) {
p1CheckedAddresses.add(p1Base);
continue;
}
long p1Length = p1Entry.getLength();
if (p1Length != 0) {
Address p1End = p1Base.add(p1Length - 1);
if (!addressSet.contains(p1Base, p1End)) {
if (addressSet.intersects(p1Base, p1End)) {
logSkippedEntry(p1Entry, program1);
}
continue;
}
}
p1CheckedAddresses.add(p1Base);
p2CheckedAddresses.add(p2Base);
List<SourceMapEntry> p1Entries = p1Manager.getSourceMapEntries(p1Base);
List<SourceMapEntry> p2Entries = p2Manager.getSourceMapEntries(p2Base);
if (p1Entries.size() != p2Entries.size()) {
differences.add(p1Base);
continue;
}
for (SourceMapEntry e1 : p1Entries) {
if (Collections.binarySearch(p2Entries, e1) < 0) {
differences.add(p1Base);
break;
}
}
}
}
AddressSet unmatchedP2Addrs =
findUnmatchedP2Addrs(addressSet, p2CheckedAddresses, commonSources, monitor);
differences.add(unmatchedP2Addrs);
return differences;
}
// look for addresses in program2 that have entries where the corresponding
// address in program1 does not have any entries
private AddressSet findUnmatchedP2Addrs(AddressSetView addressSet,
AddressSet p2CheckedAddresses, Collection<SourceFile> commonSources,
TaskMonitor monitor) throws CancelledException {
AddressSet results = new AddressSet();
SourceFileManager p2Manager = program2.getSourceFileManager();
for (SourceFile p2Source : commonSources) {
for (SourceMapEntry p2Entry : p2Manager.getSourceMapEntries(p2Source)) {
monitor.checkCancelled();
Address p2Base = p2Entry.getBaseAddress();
if (p2CheckedAddresses.contains(p2Base)) {
continue;
}
Address p1Base = SimpleDiffUtility.getCompatibleAddress(program2, p2Base, program1);
p2CheckedAddresses.add(p2Base);
if (p1Base == null) {
continue;
}
if (!addressSet.contains(p1Base)) {
continue;
}
List<SourceMapEntry> p1Entries =
program1.getSourceFileManager().getSourceMapEntries(p1Base);
boolean p1BaseAlreadyChecked = false;
for (SourceMapEntry p1Entry : p1Entries) {
if (p1Entry.getBaseAddress().equals(p1Base)) {
p1BaseAlreadyChecked = true;
break;
}
}
if (!p1BaseAlreadyChecked) {
results.add(p1Base);
}
}
}
return results;
}
// process SourceFiles with mapping info in exactly one program
// any associated SourceMapEntry must be a difference
// so just check that the range is part of addressSet
private AddressSet processDifferingSourceFiles(AddressSetView addressSet,
Collection<SourceFile> p1Only, Collection<SourceFile> p2Only,
SourceFileManager p1Manager, SourceFileManager p2Manager, TaskMonitor monitor)
throws CancelledException {
AddressSet result = new AddressSet();
for (SourceFile p1Source : p1Only) {
List<SourceMapEntry> p1Entries = p1Manager.getSourceMapEntries(p1Source);
for (SourceMapEntry p1Entry : p1Entries) {
monitor.checkCancelled();
Address p1Base = p1Entry.getBaseAddress();
if (p1Entry.getLength() == 0) {
if (addressSet.contains(p1Base)) {
result.add(p1Base);
}
continue;
}
Address p1End = p1Base.add(p1Entry.getLength() - 1);
if (addressSet.contains(p1Base, p1End)) {
result.add(p1Base);
continue;
}
if (addressSet.intersects(p1Base, p1End)) {
logSkippedEntry(p1Entry, program1);
}
}
}
for (SourceFile p2Source : p2Only) {
List<SourceMapEntry> p2Entries = p2Manager.getSourceMapEntries(p2Source);
for (SourceMapEntry p2Entry : p2Entries) {
monitor.checkCancelled();
Address p2Base = p2Entry.getBaseAddress();
Address p1Base = SimpleDiffUtility.getCompatibleAddress(program2, p2Base, program1);
if (p1Base == null) {
continue;
}
long p2Length = p2Entry.getLength();
if (p2Length == 0) {
if (addressSet.contains(p1Base)) {
result.add(p1Base);
}
continue;
}
Address p2End = p2Base.add(p2Length - 1);
Address p1End = SimpleDiffUtility.getCompatibleAddress(program2, p2End, program1);
if (p1End == null) {
continue;
}
if (addressSet.contains(p1Base, p1End)) {
result.add(p1Base);
continue;
}
if (addressSet.intersects(p1Base, p1End)) {
logSkippedEntry(p2Entry, program2);
}
}
}
return result;
}
private void logSkippedEntry(SourceMapEntry entry, Program program) {
Msg.warn(this,
"Skipping source map entry " + entry.toString() + " in program " + program.getName());
}
/**
* Determines the code unit addresses where there are differences of the
* indicated type between program1 and program2.

View file

@ -23,13 +23,22 @@ package ghidra.program.util;
* differences of that type between two programs. False indicates no interest
* in this type of program difference.
* <BR>Valid filter types are:
* BYTE_DIFFS, CODE_UNIT_DIFFS,
* PLATE_COMMENT_DIFFS, PRE_COMMENT_DIFFS, EOL_COMMENT_DIFFS,
* REPEATABLE_COMMENT_DIFFS, POST_COMMENT_DIFFS,
* BOOKMARK_DIFFS,
* BYTE_DIFFS,
* CODE_UNIT_DIFFS,
* EQUATE_DIFFS,
* EOL_COMMENT_DIFFS,
* FUNCTION_DIFFS,
* FUNCTION_TAG_DIFFS,
* PLATE_COMMENT_DIFFS,
* POST_COMMENT_DIFFS,
* PRE_COMMENT_DIFFS,
* PROGRAM_CONTEXT_DIFFS,
* REFERENCE_DIFFS,
* USER_DEFINED_DIFFS, BOOKMARK_DIFFS,
* REPEATABLE_COMMENT_DIFFS,
* SOURCE_MAP_DIFFS,
* SYMBOL_DIFFS,
* EQUATE_DIFFS, FUNCTION_DIFFS, PROGRAM_CONTEXT_DIFFS.
* USER_DEFINED_DIFFS.
* <BR>Predefined filter type combinations are:
* COMMENT_DIFFS and ALL_DIFFS.
*/
@ -70,23 +79,21 @@ public class ProgramDiffFilter {
public static final int USER_DEFINED_DIFFS = 1 << 13;
/** Indicates the filter for the function tag differences. */
public static final int FUNCTION_TAG_DIFFS = 1 << 14;
/** Indicates the filter for source map differences */
public static final int SOURCE_MAP_DIFFS = 1 << 15;
// NOTE: If you add a new primary type here, make sure to use the
// next available bit position and update the NUM_PRIMARY_TYPES.
// ** Also don't forget to add it to ALL_DIFFS below. **
/** The total number of primary difference types. */
private static final int NUM_PRIMARY_TYPES = 15;
private static final int NUM_PRIMARY_TYPES = 16;
//********************************************************
//* PREDEFINED DIFFERENCE COMBINATIONS
//********************************************************
/** Indicates all comment filters. */
public static final int COMMENT_DIFFS =
EOL_COMMENT_DIFFS
| PRE_COMMENT_DIFFS
| POST_COMMENT_DIFFS
| REPEATABLE_COMMENT_DIFFS
| PLATE_COMMENT_DIFFS;
public static final int COMMENT_DIFFS = EOL_COMMENT_DIFFS | PRE_COMMENT_DIFFS |
POST_COMMENT_DIFFS | REPEATABLE_COMMENT_DIFFS | PLATE_COMMENT_DIFFS;
/** Indicates all filters for all defined types of differences. */
//@formatter:off
public static final int ALL_DIFFS =
@ -100,7 +107,8 @@ public class ProgramDiffFilter {
| FUNCTION_DIFFS
| BOOKMARK_DIFFS
| FUNCTION_TAG_DIFFS
| PROGRAM_CONTEXT_DIFFS;
| PROGRAM_CONTEXT_DIFFS
| SOURCE_MAP_DIFFS;
//@formatter:on
/** <CODE>filterFlags</CODE> holds the actual indicators for each
@ -108,7 +116,6 @@ public class ProgramDiffFilter {
*/
private int filterFlags = 0;
/** Creates new ProgramDiffFilter with none of the diff types selected.*/
public ProgramDiffFilter() {
}
@ -244,6 +251,8 @@ public class ProgramDiffFilter {
return "ALL_DIFFS";
case ProgramDiffFilter.FUNCTION_TAG_DIFFS:
return "FUNCTION_TAG_DIFFS";
case ProgramDiffFilter.SOURCE_MAP_DIFFS:
return "SOURCE_MAP_DIFFS";
default:
return "";
}

View file

@ -17,9 +17,11 @@ package ghidra.program.util;
import java.util.*;
import ghidra.framework.store.LockException;
import ghidra.program.database.function.FunctionManagerDB;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.database.properties.UnsupportedMapDB;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.disassemble.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
@ -27,6 +29,8 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.mem.*;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.*;
import ghidra.util.*;
@ -3953,4 +3957,48 @@ public class ProgramMerge {
}
}
/**
* Merge the source map information from the origin program to the result program.
*
* @param originAddrs address from origin program to merge
* @param settings merge settings
* @param monitor monitor
* @throws LockException if invoked without exclusive access
*/
public void applySourceMapDifferences(AddressSet originAddrs, int settings, TaskMonitor monitor)
throws LockException {
SourceFileManager originManager = originProgram.getSourceFileManager();
SourceFileManager resultManager = resultProgram.getSourceFileManager();
AddressIterator originAddrIter = originAddrs.getAddresses(true);
while (originAddrIter.hasNext()) {
Address originAddr = originAddrIter.next();
try {
Address resultAddr = originToResultTranslator.getAddress(originAddr);
for (SourceMapEntry resultEntry : resultManager.getSourceMapEntries(resultAddr)) {
if (resultAddr.equals(resultEntry.getBaseAddress())) {
resultManager.removeSourceMapEntry(resultEntry);
}
}
for (SourceMapEntry originEntry : originManager.getSourceMapEntries(originAddr)) {
if (!originEntry.getBaseAddress().equals(originAddr)) {
continue;
}
SourceFile originFile = originEntry.getSourceFile();
resultManager.addSourceFile(originFile);
resultManager.addSourceMapEntry(originFile, originEntry.getLineNumber(),
resultAddr, originEntry.getLength());
}
}
catch (AddressTranslationException e) {
// as long as originAddrs comes from ProgramDiff.getSourceMapDifferences
// this shouldn't happen
throw new AssertException("couldn't translate " + originAddr + " in " +
originProgram.getName() + " to " + resultProgram.getName());
}
catch (AddressOverflowException e) {
throw new AssertException("Address overflow when merging source map entries");
}
}
}
}

View file

@ -140,11 +140,16 @@ public class ProgramMergeFilter {
/** Indicates the <B>merge filter</B> for function tags. */
public static final int FUNCTION_TAGS = 1 << MERGE_FUNCTION_TAGS;
/** Internal array index for "source map" filter value. */
private static final int MERGE_SOURCE_MAP = 17;
/** Indicates the <B>merge filter</B> for source map information. */
public static final int SOURCE_MAP = 1 << MERGE_SOURCE_MAP;
// NOTE: If you add a new primary type here, make sure to use the
// next available integer and update the NUM_PRIMARY_TYPES.
// ** Also don't forget to add it to ALL below. **
/** The total number of primary merge difference types. */
private static final int NUM_PRIMARY_TYPES = 17;
private static final int NUM_PRIMARY_TYPES = 18;
/** Indicates to merge code unit differences. This includes instructions,
* data, and equates.
@ -152,12 +157,13 @@ public class ProgramMergeFilter {
public static final int CODE_UNITS = INSTRUCTIONS | DATA;
/** Indicates to merge all comment differences. */
public static final int COMMENTS = PLATE_COMMENTS | PRE_COMMENTS | EOL_COMMENTS |
REPEATABLE_COMMENTS | POST_COMMENTS;
public static final int COMMENTS =
PLATE_COMMENTS | PRE_COMMENTS | EOL_COMMENTS | REPEATABLE_COMMENTS | POST_COMMENTS;
/** Indicates all <B>merge filters</B> for all types of differences. */
public static final int ALL = PROGRAM_CONTEXT | BYTES | CODE_UNITS | EQUATES | REFERENCES |
COMMENTS | SYMBOLS | PRIMARY_SYMBOL | BOOKMARKS | PROPERTIES | FUNCTIONS | FUNCTION_TAGS;
public static final int ALL =
PROGRAM_CONTEXT | BYTES | CODE_UNITS | EQUATES | REFERENCES | COMMENTS | SYMBOLS |
PRIMARY_SYMBOL | BOOKMARKS | PROPERTIES | FUNCTIONS | FUNCTION_TAGS | SOURCE_MAP;
/** Array holding the filter value for each of the primary merge difference types. */
private int[] filterFlags = new int[NUM_PRIMARY_TYPES];
@ -185,7 +191,7 @@ public class ProgramMergeFilter {
}
/** getFilter determines whether or not the specified type of filter is set.
* Valid types are: BYTES, INSTRUCTIONS, DATA,
* Valid types are: BYTES, INSTRUCTIONS, DATA, SOURCE_MAP,
* SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS.
* INVALID is returned if combinations of merge types (e.g. ALL) are
* passed in.
@ -213,6 +219,7 @@ public class ProgramMergeFilter {
case FUNCTIONS:
case FUNCTION_TAGS:
case EQUATES:
case SOURCE_MAP:
int bitPos = 0;
int tmpType = type;
while (bitPos < NUM_PRIMARY_TYPES) {
@ -234,7 +241,8 @@ public class ProgramMergeFilter {
/** validatePredefinedType determines whether or not the indicated type
* of filter item is a valid predefined type.
* Valid types are: BYTES, INSTRUCTIONS, DATA,
* SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS, ALL.
* SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS,
* SOURCE_MAP, ALL.
*
* @param type the type of difference to look for between the programs.
* @return true if this is a pre-defined merge type.
@ -260,6 +268,7 @@ public class ProgramMergeFilter {
case EQUATES:
case CODE_UNITS:
case COMMENTS:
case SOURCE_MAP:
case ALL:
return true;
default:
@ -316,7 +325,8 @@ public class ProgramMergeFilter {
/** isMergeValidForFilter determines whether or not the <CODE>MERGE</CODE>
* filter if valid for the indicated primary merge type.
* Possible types are: BYTES, INSTRUCTIONS, DATA, REFERENCES,
* SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS, ALL.
* SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS,
* SOURCE_MAP, ALL.
*
* @param type the type of difference to merge between the programs.
* @return true if <CODE>MERGE</CODE> is valid for the merge type.
@ -345,6 +355,7 @@ public class ProgramMergeFilter {
case FUNCTIONS:
case EQUATES:
case PRIMARY_SYMBOL:
case SOURCE_MAP:
return false;
default:
throw new IllegalArgumentException(
@ -469,6 +480,8 @@ public class ProgramMergeFilter {
return "COMMENTS";
case ALL:
return "ALL";
case SOURCE_MAP:
return "SOURCE_MAP";
default:
return "";
}

View file

@ -15,10 +15,13 @@
*/
package ghidra.program.util;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -285,8 +288,8 @@ public class ProgramMergeManager {
* @throws MemoryAccessException if bytes can't be copied.
* @throws CancelledException if user cancels via the monitor.
*/
public boolean merge(Address p2Address, TaskMonitor monitor) throws MemoryAccessException,
CancelledException {
public boolean merge(Address p2Address, TaskMonitor monitor)
throws MemoryAccessException, CancelledException {
return merge(p2Address, mergeFilter, monitor);
}
@ -451,6 +454,7 @@ public class ProgramMergeManager {
mergeBookmarks(p1MergeSet, filter, monitor);
mergeProperties(p1MergeSet, filter, monitor);
mergeFunctionTags(p1MergeSet, filter, monitor);
mergeSourceMap(p1MergeSet, filter, monitor);
merger.reApplyDuplicateEquates();
String dupEquatesMessage = merger.getDuplicateEquatesInfo();
@ -569,8 +573,7 @@ public class ProgramMergeManager {
AddressSet byteDiffs2 = null;
ProgramDiffFilter byteDiffFilter = new ProgramDiffFilter(ProgramDiffFilter.BYTE_DIFFS);
if (filter.getFilter(ProgramMergeFilter.BYTES) == ProgramMergeFilter.IGNORE) {
byteDiffs2 =
DiffUtility.getCompatibleAddressSet(
byteDiffs2 = DiffUtility.getCompatibleAddressSet(
programDiff.getDifferences(byteDiffFilter, monitor), program2);
}
@ -628,6 +631,46 @@ public class ProgramMergeManager {
}
}
/**
* Merges source map information from program 2 into program 1.
* <br>
* Note: This method does not consider unmapped {@link SourceFile}s, i.e., those which are
* not associated with any addresses in program 2. In particular, it will not create new
* unmapped files in program 1.
*
* @param p1AddressSet address set in program 1 to receive changes
* @param filter merge filter
* @param monitor monitor
*/
void mergeSourceMap(AddressSetView p1AddressSet, ProgramMergeFilter filter,
TaskMonitor monitor) {
int setting = filter.getFilter(ProgramMergeFilter.SOURCE_MAP);
if (setting == ProgramMergeFilter.IGNORE) {
return;
}
if (setting == ProgramMergeFilter.MERGE) {
throw new IllegalStateException("Cannot merge source map information");
}
int diffType = ProgramDiffFilter.SOURCE_MAP_DIFFS;
try {
AddressSetView p1DiffSet =
programDiff.getDifferences(new ProgramDiffFilter(diffType), monitor);
AddressSet p1MergeSet = p1DiffSet.intersect(p1AddressSet);
AddressSet p2MergeSet = DiffUtility.getCompatibleAddressSet(p1MergeSet, program2);
merger.applySourceMapDifferences(p2MergeSet, setting, monitor);
}
catch (CancelledException e1) {
// user cancellation
}
catch (LockException e) {
// GUI prevents you from selecting "REPLACE" if you don't have exclusive access
// so we shouldn't get here
throw new AssertException("Attempting to merge source map without exclusive access");
}
}
/**
* <CODE>mergeComments</CODE> merges all comments
* in the specified address set from the second program

View file

@ -0,0 +1,174 @@
/* ###
* 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.util;
import java.net.URI;
import java.util.HexFormat;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import ghidra.formats.gfilesystem.FSUtilities;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.sourcemap.SourceMapEntry;
/**
* A utility class for creating {@link SourceFile}s from native paths, e.g., windows paths.
*/
public class SourceFileUtils {
private static HexFormat hexFormat = HexFormat.of();
private SourceFileUtils() {
// singleton class
}
/**
* Creates a {@link SourceFile} from {@code path} with id type {@link SourceFileIdType#NONE}
* and empty identifier. The path will be transformed using
* {@link FSUtilities#normalizeNativePath(String)} and then {@link URI#normalize}.
*
* @param path path
* @return source file
*/
public static SourceFile getSourceFileFromPathString(String path) {
return getSourceFileFromPathString(path, SourceFileIdType.NONE, null);
}
/**
* Creates a {@link SourceFile} from {@code path} with the provided id type and identifier.
* The path will be transformed using{@link FSUtilities#normalizeNativePath(String)} and
* then {@link URI#normalize}.
*
* @param path path
* @param idType id type
* @param identifier identifier
* @return source file
*/
public static SourceFile getSourceFileFromPathString(String path, SourceFileIdType idType,
byte[] identifier) {
String standardized = FSUtilities.normalizeNativePath(path);
return new SourceFile(standardized, idType, identifier);
}
/**
* Converts a {@code long} value to an byte array of length 8. The most significant byte
* of the long will be at position 0 of the resulting array.
* @param l long
* @return byte array
*/
public static byte[] longToByteArray(long l) {
byte[] bytes = new byte[8];
BigEndianDataConverter.INSTANCE.putLong(bytes, 0, l);
return bytes;
}
/**
* Converts a byte array of length 8 to a {@code long} value. The byte at position 0
* of the array will be the most significant byte of the resulting long.
* @param bytes array to convert
* @return long
* @throws IllegalArgumentException if bytes.length != 8
*/
public static long byteArrayToLong(byte[] bytes) {
if (bytes.length != 8) {
throw new IllegalArgumentException("bytes must have length 8");
}
return BigEndianDataConverter.INSTANCE.getLong(bytes);
}
/**
* Converts a {@code String} of hexadecimal character to an array of bytes. An initial "0x"
* or "0X" is ignored, as is the case of the digits a-f.
* @param hexString String to convert
* @return byte array
*/
public static byte[] hexStringToByteArray(String hexString) {
if (StringUtils.isBlank(hexString)) {
return new byte[0];
}
if (hexString.startsWith("0x") || hexString.startsWith("0X")) {
hexString = hexString.substring(2);
}
return hexFormat.parseHex(hexString);
}
/**
* Converts a byte array to a {@code String} of hexadecimal digits.
* @param bytes array to convert
* @return string
*/
public static String byteArrayToHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return StringUtils.EMPTY;
}
return hexFormat.formatHex(bytes);
}
/**
* Returns a {@link SourceLineBounds} record containing the minimum and maximum mapped line
* for {@code sourceFile} in {@code program}.
* @param program program
* @param sourceFile source file
* @return source line bounds or null
*/
public static SourceLineBounds getSourceLineBounds(Program program, SourceFile sourceFile) {
List<SourceMapEntry> entries =
program.getSourceFileManager().getSourceMapEntries(sourceFile, 0, Integer.MAX_VALUE);
if (entries.isEmpty()) {
return null;
}
int min = Integer.MAX_VALUE;
int max = -1;
for (SourceMapEntry entry : entries) {
int lineNum = entry.getLineNumber();
if (lineNum < min) {
min = lineNum;
}
if (lineNum > max) {
max = lineNum;
}
}
return new SourceLineBounds(min, max);
}
/**
* A record containing the minimum and maximum mapped line numbers
* @param min minimum line number
* @param max maximum line number
*/
public static record SourceLineBounds(int min, int max) {
public SourceLineBounds(int min, int max) {
if (min < 0) {
throw new IllegalArgumentException("min must be greater than or equal to 0");
}
if (max < 0) {
throw new IllegalArgumentException("max must be greater than or equal to 0");
}
if (max < min) {
throw new IllegalArgumentException("max must be greater than or equal to min");
}
this.min = min;
this.max = max;
}
}
}

View file

@ -0,0 +1,249 @@
/* ###
* 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.viewer.field;
import static org.junit.Assert.*;
import javax.swing.SwingUtilities;
import org.junit.*;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.framework.options.Options;
import ghidra.framework.store.LockException;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.*;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.program.util.SourceMapFieldLocation;
import ghidra.test.*;
public class SourceMapFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private ProgramBuilder builder;
private Program program;
private Function entry;
private Address entryPoint;
private CodeBrowserPlugin cb;
private Options fieldOptions;
private SourceFileManager sourceManager;
private static final String SOURCE1_NAME = "test1.c";
private static final String SOURCE1_PATH = "/dir1/" + SOURCE1_NAME;
private static final String SOURCE2_NAME = "test2.c";
private static final String SOURCE2_PATH = "/dir2/dir2/" + SOURCE2_NAME;
private SourceFile source1;
private SourceFile source2;
private static final int ROW = 0;
private static final int COLUMN = 0;
@Before
public void setUp() throws Exception {
init();
entryPoint = program.getAddressFactory().getDefaultAddressSpace().getAddress(0x1006420);
entry = program.getFunctionManager().getFunctionAt(entryPoint);
env = new TestEnv();
env.launchDefaultTool(program);
cb = env.getPlugin(CodeBrowserPlugin.class);
SourceMapFieldFactory factory = new SourceMapFieldFactory();
runSwing(() -> cb.getFormatManager().getCodeUnitFormat().addFactory(factory, ROW, COLUMN));
fieldOptions = cb.getFormatManager().getFieldOptions();
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testNoSourceInfo() throws Exception {
assertNotNull(entry);
setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
assertFalse(cb.goToField(entryPoint, SourceMapFieldFactory.FIELD_NAME, ROW, COLUMN));
setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, true);
ListingTextField textField = getTextField(entryPoint);
assertEquals(1, textField.getNumRows());
assertEquals(SourceMapFieldFactory.NO_SOURCE_INFO, textField.getText());
}
@Test
public void testShowFilename() throws Exception {
int txID = program.startTransaction("adding source map entry");
try {
sourceManager.addSourceMapEntry(source1, 1, entryPoint, 2);
}
finally {
program.endTransaction(txID, true);
}
setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, false);
ListingTextField textField = getTextField(entryPoint);
assertEquals(1, textField.getNumRows());
assertEquals(SOURCE1_PATH + ":1 (2)", textField.getText());
setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, true);
textField = getTextField(entryPoint);
assertEquals(1, textField.getNumRows());
assertEquals(SOURCE1_NAME + ":1 (2)", textField.getText());
}
@Test
public void testShowIdentifier() throws Exception {
int txID = program.startTransaction("adding source map entry");
try {
sourceManager.addSourceMapEntry(source1, 1, entryPoint, 2);
}
finally {
program.endTransaction(txID, true);
}
setBooleanOption(SourceMapFieldFactory.SHOW_IDENTIFIER_OPTION_NAME, false);
ListingTextField textField = getTextField(entryPoint);
assertEquals(1, textField.getNumRows());
assertEquals(SOURCE1_NAME + ":1 (2)", textField.getText());
setBooleanOption(SourceMapFieldFactory.SHOW_IDENTIFIER_OPTION_NAME, true);
textField = getTextField(entryPoint);
assertEquals(1, textField.getNumRows());
assertEquals(SOURCE1_NAME + ":1 (2) [no id]", textField.getText());
}
@Test
public void testShowInfoAtAllAddresses() throws Exception {
setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, false);
Address addr = entryPoint.next();
Instruction inst = program.getListing().getInstructionAt(addr);
assertEquals(2, inst.getLength());
Instruction testInst = inst.getNext();
Address testAddr = testInst.getAddress();
int txID = program.startTransaction("adding source map entry");
try {
sourceManager.addSourceMapEntry(source1, 1, addr, 5);
}
finally {
program.endTransaction(txID, true);
}
setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, true);
ListingTextField textField = getTextField(testAddr);
assertEquals(1, textField.getNumRows());
assertEquals(SOURCE1_PATH + ":1 (5)", textField.getText());
setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
assertFalse(cb.goToField(testAddr, SourceMapFieldFactory.FIELD_NAME, ROW, COLUMN));
}
@Test
public void testOffcutSourceMapEntries() throws Exception {
setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, false);
Address addr = entryPoint.next();
Instruction inst = program.getListing().getInstructionAt(addr);
assertEquals(2, inst.getLength());
Address testAddr = inst.getAddress().next();
int txID = program.startTransaction("adding source map entry");
try {
sourceManager.addSourceMapEntry(source1, 1, testAddr, 1);
}
finally {
program.endTransaction(txID, true);
}
setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, true);
ListingTextField textField = getTextField(inst.getAddress());
assertEquals(1, textField.getNumRows());
assertEquals(SOURCE1_PATH + ":1 (1)", textField.getText());
assertEquals(SourceMapFieldFactory.OFFCUT_COLOR,
textField.getFieldElement(0, 0).getColor(0));
}
@Test
public void testMaxNumEntries() throws Exception {
int txID = program.startTransaction("adding source map entries");
try {
sourceManager.addSourceMapEntry(source1, 1, entryPoint, 1);
sourceManager.addSourceMapEntry(source2, 2, entryPoint, 1);
}
finally {
program.endTransaction(txID, true);
}
ListingTextField textField = getTextField(entryPoint);
assertEquals(2, textField.getNumRows());
SwingUtilities.invokeAndWait(() -> fieldOptions
.setInt(SourceMapFieldFactory.MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, 1));
waitForSwing();
cb.updateNow();
textField = getTextField(entryPoint);
assertEquals(1, textField.getNumRows());
}
@Test
public void testFieldLocationSourceMapEntry() throws AddressOverflowException, LockException {
int txID = program.startTransaction("adding source map entries");
SourceMapEntry entry1 = null;
SourceMapEntry entry2 = null;
try {
entry1 = sourceManager.addSourceMapEntry(source1, 1, entryPoint, 2);
entry2 = sourceManager.addSourceMapEntry(source2, 3, entryPoint, 2);
}
finally {
program.endTransaction(txID, true);
}
ListingTextField textField = getTextField(entryPoint);
FieldFactory fieldFactory = textField.getFieldFactory();
SourceMapFieldLocation one =
(SourceMapFieldLocation) fieldFactory.getProgramLocation(0, 0, textField);
assertEquals(entry1, one.getSourceMapEntry());
SourceMapFieldLocation two =
(SourceMapFieldLocation) fieldFactory.getProgramLocation(1, 0, textField);
assertEquals(entry2, two.getSourceMapEntry());
}
private void init() throws Exception {
builder = new ClassicSampleX86ProgramBuilder();
program = builder.getProgram();
sourceManager = program.getSourceFileManager();
int txId = program.startTransaction("adding source files");
source1 = new SourceFile(SOURCE1_PATH);
source2 = new SourceFile(SOURCE2_PATH);
try {
assertTrue(sourceManager.addSourceFile(source1));
assertTrue(sourceManager.addSourceFile(source2));
}
finally {
program.endTransaction(txId, true);
}
}
private void setBooleanOption(final String name, boolean value) throws Exception {
SwingUtilities.invokeAndWait(() -> fieldOptions.setBoolean(name, value));
waitForSwing();
cb.updateNow();
}
private ListingTextField getTextField(Address address) {
assertTrue(cb.goToField(address, SourceMapFieldFactory.FIELD_NAME, ROW, COLUMN));
ListingTextField tf = (ListingTextField) cb.getCurrentField();
return tf;
}
}

View file

@ -0,0 +1,496 @@
/* ###
* 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.program.util;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.*;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.test.ClassicSampleX86ProgramBuilder;
import ghidra.util.SourceFileUtils;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class SourceMapDiffTest extends AbstractProgramDiffTest {
private static final String COMMON_FILE = "/common";
// differing files will have the same path but different identifiers
private static final String DIFFERENT = "/different";
private static final String TEST_FUNC_ADDR_STRING = "0x01002239";
private SourceFile common;
private SourceFile p1Only;
private SourceFile p2Only;
private SourceFileManager p1Manager;
private SourceFileManager p2Manager;
private List<Instruction> p1Insts;
private List<Instruction> p2Insts;
private AddressSet p1InstBodies;
private AddressSet p1DiffInsts;
@Before
public void setUp() throws Exception {
programBuilder1 = new ClassicSampleX86ProgramBuilder(false);
programBuilder2 = new ClassicSampleX86ProgramBuilder(false);
p1 = programBuilder1.getProgram();
p2 = programBuilder2.getProgram();
p1Insts = new ArrayList<>();
p2Insts = new ArrayList<>();
p1Manager = p1.getSourceFileManager();
p2Manager = p2.getSourceFileManager();
p1InstBodies = new AddressSet();
p1DiffInsts = new AddressSet();
int p1_txID = p1.startTransaction("setup");
int p2_txID = p2.startTransaction("setup");
common = new SourceFile(COMMON_FILE);
byte[] p1Val = SourceFileUtils.longToByteArray(0x11111111);
p1Only =
new SourceFile(DIFFERENT, SourceFileIdType.TIMESTAMP_64, p1Val);
byte[] p2Val = SourceFileUtils.longToByteArray(0x22222222);
p2Only =
new SourceFile(DIFFERENT, SourceFileIdType.TIMESTAMP_64, p2Val);
try {
p1Manager.addSourceFile(common);
p1Manager.addSourceFile(p1Only);
p2Manager.addSourceFile(common);
p2Manager.addSourceFile(p2Only);
Address prog1start = p1.getFunctionManager()
.getFunctionAt(p1.getAddressFactory().getAddress(TEST_FUNC_ADDR_STRING))
.getEntryPoint();
InstructionIterator p1Iter = p1.getListing().getInstructions(prog1start, true);
Address prog2start = p2.getFunctionManager()
.getFunctionAt(p2.getAddressFactory().getAddress(TEST_FUNC_ADDR_STRING))
.getEntryPoint();
InstructionIterator p2Iter = p2.getListing().getInstructions(prog2start, true);
/**
* 0) no entries
* 1) p1 yes, p2 no
* 2) no entries
* 3) p1 no, p2 yes
* 4) no entries
* 5) files and lines different
* 6) no entries
* 7) files different, lines not
* 8) no entries
* 9) files agree, lines different
* 10) no entries
* 11) files and lines agree
* 12) no entries
* 13) p1 two entries two files, p2 one of them
* 14) no entries
* 15) p2 two entries two files, p1 one of them
* 16) no entries
* 17) p1 two entries one file, p2 two entries one file, one line number diff
* 18) no entries
* 19) length difference
* 20) no entries
* 21) length 0 entry in p1, nothing in p2
* 22) no entries
* 23) nothing in p1, length 0 entry in p2
* 24) no entries
* 25) equal length 0 entries
* 26) no entries
*
*/
// 0
Instruction inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 1
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(common, 1, getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 2
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 3
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(common, 3, getBody(inst));
p2Insts.add(inst);
// 4
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 5
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(p1Only, 51, getBody(inst));
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(p2Only, 52, getBody(inst));
p2Insts.add(inst);
// 6
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 7
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(p1Only, 7, getBody(inst));
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(p2Only, 7, getBody(inst));
p2Insts.add(inst);
// 8
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 9
// XOR EAX,EAX length 2
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(common, 91, getBody(inst));
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(common, 92, getBody(inst));
p2Insts.add(inst);
// 10
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 11
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
// files,lines, and lengths agree - not a diff
p1Manager.addSourceMapEntry(common, 11, getBody(inst));
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(common, 11, getBody(inst));
p2Insts.add(inst);
// 12
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 13
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(common, 13, getBody(inst));
p1Manager.addSourceMapEntry(p1Only, 13, getBody(inst));
inst = p2Iter.next();
p2Insts.add(inst);
p2Manager.addSourceMapEntry(common, 13, getBody(inst));
// 14
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 15
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(common, 15, getBody(inst));
inst = p2Iter.next();
p2Insts.add(inst);
p2Manager.addSourceMapEntry(common, 15, getBody(inst));
p2Manager.addSourceMapEntry(p2Only, 15, getBody(inst));
// 16
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 17
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(common, 17, getBody(inst));
p1Manager.addSourceMapEntry(common, 18, getBody(inst));
inst = p2Iter.next();
p2Insts.add(inst);
p2Manager.addSourceMapEntry(common, 17, getBody(inst));
p1Manager.addSourceMapEntry(common, 19, getBody(inst));
// 18
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 19
inst = p1Iter.next();
// length of this instruction is 2
p1InstBodies.add(getBody(inst));
p1Manager.addSourceMapEntry(common, 1000, inst.getAddress(), 1);
p1Insts.add(inst);
p1DiffInsts.add(inst.getAddress());
inst = p2Iter.next();
p2Manager.addSourceMapEntry(common, 1000, inst.getAddress(), 2);
p2Insts.add(inst);
// 20
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 21
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Manager.addSourceMapEntry(p1Only, 1, inst.getAddress(), 0);
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 22
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 23
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1DiffInsts.add(inst.getAddress());
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(p2Only, 3, inst.getAddress(), 0);
p2Insts.add(inst);
// 24
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
// 25
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
// files,lines, and lengths agree - not a diff
p1Manager.addSourceMapEntry(common, 25, inst.getAddress(), 0);
p1Insts.add(inst);
inst = p2Iter.next();
p2Manager.addSourceMapEntry(common, 25, inst.getAddress(), 0);
p2Insts.add(inst);
// 26
inst = p1Iter.next();
p1InstBodies.add(getBody(inst));
p1Insts.add(inst);
p2Insts.add(p2Iter.next());
}
finally {
p1.endTransaction(p1_txID, true);
p2.endTransaction(p2_txID, true);
}
}
@After
public void tearDown() throws Exception {
if (programBuilder1 != null) {
programBuilder1.dispose();
}
if (programBuilder2 != null) {
programBuilder2.dispose();
}
}
@Test
public void simpleDiffTest()
throws ProgramConflictException, IllegalArgumentException, CancelledException {
assertEquals(p1Insts.size(), p2Insts.size());
AddressSet testSet = new AddressSet();
p1Insts.forEach(i -> testSet.add(getBody(i)));
assertEquals(testSet, p1InstBodies);
programDiff = new ProgramDiff(p1, p2, p1InstBodies);
programDiff.setFilter(new ProgramDiffFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS));
// verify that the differences found by progamDiff.getDifferences are exactly
// the differences created in the setUp method
assertEquals(p1DiffInsts,
programDiff.getDifferences(programDiff.getFilter(), TaskMonitor.DUMMY));
}
@Test
public void testReplace()
throws ProgramConflictException, MemoryAccessException, CancelledException {
ProgramMergeManager programMerge =
new ProgramMergeManager(p1, p2, p1InstBodies);
ProgramMergeFilter mergeFilter =
new ProgramMergeFilter(ProgramMergeFilter.SOURCE_MAP, ProgramMergeFilter.REPLACE);
// for good measure, verify one of the differences before merge
SourceMapEntry p1Info = p1Manager.getSourceMapEntries(p1Insts.get(5).getAddress()).get(0);
SourceMapEntry p2Info = p2Manager.getSourceMapEntries(p2Insts.get(5).getAddress()).get(0);
assertNotEquals(p1Info.getSourceFile(), p2Info.getSourceFile());
assertNotEquals(p1Info.getLineNumber(), p2Info.getLineNumber());
int txId = p1.startTransaction("merging");
try {
boolean success = programMerge.merge(p1InstBodies, mergeFilter, TaskMonitor.DUMMY);
assertTrue(success);
}
finally {
p1.endTransaction(txId, true);
}
// now verify that source map info is the same for all addresses (not just beginnings of
// instructions)
AddressSet p2InstBodies = new AddressSet();
p2Insts.forEach(i -> p2InstBodies.add(getBody(i)));
assertEquals(p1InstBodies.getNumAddresses(), p2InstBodies.getNumAddresses());
AddressIterator p1Iter = p1InstBodies.getAddresses(true);
AddressIterator p2Iter = p2InstBodies.getAddresses(true);
while (p1Iter.hasNext()) {
Address p1Addr = p1Iter.next();
Address p2Addr = p2Iter.next();
List<SourceMapEntry> p1Entries = p1Manager.getSourceMapEntries(p1Addr);
List<SourceMapEntry> p2Entries = p2Manager.getSourceMapEntries(p2Addr);
assertEquals(p1Entries.size(), p2Entries.size());
for (SourceMapEntry p1Entry : p1Entries) {
int index = Collections.binarySearch(p2Entries, p1Entry);
assertTrue(index >= 0);
}
}
}
@Test
public void testIgnoreFilter()
throws ProgramConflictException, MemoryAccessException, CancelledException {
ProgramMergeManager programMerge =
new ProgramMergeManager(p1, p2, p1InstBodies);
ProgramMergeFilter mergeFilter =
new ProgramMergeFilter(ProgramMergeFilter.SOURCE_MAP, ProgramMergeFilter.IGNORE);
// verify one of the differences before merge
SourceMapEntry p1Info = p1Manager.getSourceMapEntries(p1Insts.get(5).getAddress()).get(0);
SourceMapEntry p2Info = p2Manager.getSourceMapEntries(p2Insts.get(5).getAddress()).get(0);
assertNotEquals(p1Info.getSourceFile(), p2Info.getSourceFile());
assertNotEquals(p1Info.getLineNumber(), p2Info.getLineNumber());
int txId = p1.startTransaction("merging");
try {
boolean success = programMerge.merge(p1InstBodies, mergeFilter, TaskMonitor.DUMMY);
assertTrue(success);
}
finally {
p1.endTransaction(txId, true);
}
// verify the difference is still there
p1Info = p1Manager.getSourceMapEntries(p1Insts.get(5).getAddress()).get(0);
p2Info = p2Manager.getSourceMapEntries(p2Insts.get(5).getAddress()).get(0);
assertNotEquals(p1Info.getSourceFile(), p2Info.getSourceFile());
assertNotEquals(p1Info.getLineNumber(), p2Info.getLineNumber());
}
@Test
public void testIgnoreOverlappingEntriesInDiff()
throws ProgramConflictException, IllegalArgumentException, CancelledException {
Address p1Inst9Addr = p1.getAddressFactory().getDefaultAddressSpace().getAddress(0x1002257);
Instruction p1Inst9 = p1.getListing().getInstructionAt(p1Inst9Addr);
assertNotNull(p1Inst9);
assertEquals(2, p1Inst9.getLength());
assertEquals("XOR EAX,EAX", p1Inst9.toString());
AddressSet testSet = new AddressSet();
testSet.add(getBody(p1Inst9));
programDiff = new ProgramDiff(p1, p2, testSet);
programDiff.setFilter(new ProgramDiffFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS));
//verify that there is a difference at p1Inst9Addr
assertEquals(new AddressSet(p1Inst9Addr),
programDiff.getDifferences(programDiff.getFilter(), TaskMonitor.DUMMY));
testSet.clear();
testSet.add(p1Inst9Addr.add(1));
programDiff = new ProgramDiff(p1, p2, testSet);
programDiff.setFilter(new ProgramDiffFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS));
//verify that no differences are reported, since the beginning of the source map entry
//is not in testSet
assertTrue(
programDiff.getDifferences(programDiff.getFilter(), TaskMonitor.DUMMY).isEmpty());
}
@Test
public void testMergeFilterChanged() {
ProgramMergeFilter mergeFilter =
new ProgramMergeFilter(ProgramMergeFilter.SOURCE_MAP, ProgramMergeFilter.MERGE);
// MERGE not valid for source files - should be changed to REPLACE by the ProgramMergeFilter
// constructor
assertEquals(ProgramMergeFilter.REPLACE,
mergeFilter.getFilter(ProgramMergeFilter.SOURCE_MAP));
}
private AddressRange getBody(CodeUnit cu) {
return new AddressRangeImpl(cu.getMinAddress(), cu.getMaxAddress());
}
}

View file

@ -15,12 +15,16 @@
*/
package ghidra.app.util.bin.format.pdb;
import java.io.File;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.util.Msg;
import ghidra.util.SourceFileUtils;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.XmlUtilities;
import ghidra.xml.XmlElement;
@ -38,6 +42,12 @@ class ApplyLineNumbers {
}
void applyTo(TaskMonitor monitor, MessageLog log) {
if (!program.hasExclusiveAccess()) {
Msg.showWarn(this, null, "Cannot Apply SourceMap Information",
"Exclusive access to the program is required to apply source map information");
return;
}
SourceFileManager manager = program.getSourceFileManager();
while (xmlParser.hasNext()) {
if (monitor.isCancelled()) {
return;
@ -47,11 +57,16 @@ class ApplyLineNumbers {
break;
}
elem = xmlParser.next();//line number start tag
String sourcefileName = elem.getAttribute("source_file");
String sourceFilePath = elem.getAttribute("source_file");
int start = XmlUtilities.parseInt(elem.getAttribute("start"));
int addr = XmlUtilities.parseInt(elem.getAttribute("addr"));
Address address = PdbUtil.reladdr(program, addr);
long length = 0;
String lengthElem = elem.getAttribute("length");
if (lengthElem != null) {
length = XmlUtilities.parseLong(lengthElem);
}
// The following line was changed from getCodeUnitAt(address) to
// getCodeUnitContaining(address) in order to fix an issue where the PDB associates
// a line number with base part of an instruction instead of the prefix part of an
@ -63,16 +78,29 @@ class ApplyLineNumbers {
CodeUnit cu = program.getListing().getCodeUnitContaining(address);
if (cu == null) {
log.appendMsg("PDB",
"Could not apply source code line number (no code unit found at " + address +
")");
"Skipping source map info (no code unit found at " + address + ")");
continue;
}
else {
cu.setProperty("Source Path", sourcefileName);
cu.setProperty("Source File", new File(sourcefileName).getName());
cu.setProperty("Source Line", start);
try {
SourceFile sourceFile =
SourceFileUtils.getSourceFileFromPathString(sourceFilePath);
manager.addSourceFile(sourceFile);
manager.addSourceMapEntry(sourceFile, start, address, length);
}
//String comment = sourcefile.getName()+":"+start;
//setComment(CodeUnit.PRE_COMMENT, program.getListing(), address, comment);
catch (LockException e) {
throw new AssertionError("LockException after exclusive access verified!");
}
catch (AddressOverflowException e) {
log.appendMsg("PDB", "AddressOverflow for source map info: %s, %d, %s, %d"
.formatted(sourceFilePath, start, address.toString(), length));
}
catch (IllegalArgumentException e) {
// thrown by SourceFileManager.addSourceMapEntry if the new entry conflicts
// with an existing entry
log.appendMsg("PDB", e.getMessage());
}
elem = xmlParser.next();//line number end tag
}
}

View file

@ -322,9 +322,11 @@ void dumpFunctionLines( IDiaSymbol& symbol, IDiaSession& session )
pLine->get_lineNumber( &start );
DWORD end = 0;
pLine->get_lineNumberEnd( &end );
DWORD range_length = 0;
pLine->get_length( &range_length );
printf("%S<line_number source_file=\"%ws\" start=\"%d\" end=\"%d\" addr=\"0x%x\" /> \n",
indent(12).c_str(), escapeXmlEntities(wsSourceFileName).c_str(), start, end, addr);
printf("%S<line_number source_file=\"%ws\" start=\"%d\" end=\"%d\" addr=\"0x%x\" length=\"%d\" /> \n",
indent(12).c_str(), escapeXmlEntities(wsSourceFileName).c_str(), start, end, addr, range_length);
}
}

View file

@ -462,6 +462,9 @@
<li>Function tags differ.</li>
</ul>
</blockquote>
<p><b>Source Map<a name="ExecuteDiffDialog_DoDifferencesOn_SourceMap"></a></b>
- detect any addresses where the source map information is different.
</p>
</blockquote>
<p>When the <i>Determine Program Differences</i> dialog is initially
displayed, all the Differences check boxes are checked. This indicates
@ -1305,6 +1308,15 @@
Can be: <i>Ignore</i>, <i>Replace</i>, or <i>Merge</i>.<br>
</td>
</tr>
<tr>
<td align="left" nowrap="nowrap" valign="top" width="180">
Source Map<br>
</td>
<td align="left" valign="top" width="100">
Controls whether Source Map differences will be applied.
Can be: <i>Ignore</i> (the default) or <i>Replace</i>.<br>
</td>
</tr>
</tbody>
</table>
</center>

View file

@ -43,35 +43,38 @@ class DiffApplySettingsOptionManager {
private static final int PROPERTIES = 1 << 11;
private static final int FUNCTIONS = 1 << 12;
private static final int FUNCTION_TAGS = 1 << 13;
private static final int SOURCE_MAP = 1 << 14;
private static final String OPTION_PROGRAM_CONTEXT = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Program Context";
private static final String OPTION_BYTES = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
"Bytes";
private static final String OPTION_CODE_UNITS = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Code Units";
private static final String OPTION_REFERENCES = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "References";
private static final String OPTION_PLATE_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Plate Comments";
private static final String OPTION_PRE_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Pre Comments";
private static final String OPTION_EOL_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "End Of Line Comments";
private static final String OPTION_REPEATABLE_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Repeatable Comments";
private static final String OPTION_POST_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Post Comments";
private static final String OPTION_SYMBOLS = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
"Labels";
private static final String OPTION_BOOKMARKS = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
"Bookmarks";
private static final String OPTION_PROPERTIES = DIFF_APPLY_SETTINGS_OPTIONS +
Options.DELIMITER + "Properties";
private static final String OPTION_FUNCTIONS = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
"Functions";
private static final String OPTION_PROGRAM_CONTEXT =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Program Context";
private static final String OPTION_BYTES =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Bytes";
private static final String OPTION_CODE_UNITS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Code Units";
private static final String OPTION_REFERENCES =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "References";
private static final String OPTION_PLATE_COMMENTS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Plate Comments";
private static final String OPTION_PRE_COMMENTS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Pre Comments";
private static final String OPTION_EOL_COMMENTS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "End Of Line Comments";
private static final String OPTION_REPEATABLE_COMMENTS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Repeatable Comments";
private static final String OPTION_POST_COMMENTS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Post Comments";
private static final String OPTION_SYMBOLS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Labels";
private static final String OPTION_BOOKMARKS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Bookmarks";
private static final String OPTION_PROPERTIES =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Properties";
private static final String OPTION_FUNCTIONS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Functions";
private static final String OPTION_FUNCTION_TAGS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Function Tags";
private static final String OPTION_SOURCE_MAP =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Source Map";
// public static final String MERGE = "Merge";
// public static final String MERGE_SYMBOLS_1 = "Merge";
@ -79,6 +82,7 @@ class DiffApplySettingsOptionManager {
public static enum REPLACE_CHOICE {
IGNORE("Ignore"), REPLACE("Replace");
private String description;
REPLACE_CHOICE(String description) {
@ -93,6 +97,7 @@ class DiffApplySettingsOptionManager {
public static enum MERGE_CHOICE {
IGNORE("Ignore"), REPLACE("Replace"), MERGE("Merge");
private String description;
MERGE_CHOICE(String description) {
@ -111,6 +116,7 @@ class DiffApplySettingsOptionManager {
REPLACE("Replace"),
MERGE_DONT_SET_PRIMARY("Merge"),
MERGE_AND_SET_PRIMARY("Merge & Set Primary");
private String description;
SYMBOL_MERGE_CHOICE(String description) {
@ -146,10 +152,7 @@ class DiffApplySettingsOptionManager {
options.setOptionsHelpLocation(help);
// Set the help strings
options.registerOption(
OPTION_PROGRAM_CONTEXT,
REPLACE_CHOICE.REPLACE,
help,
options.registerOption(OPTION_PROGRAM_CONTEXT, REPLACE_CHOICE.REPLACE, help,
getReplaceDescription("program context register value",
"program context register values"));
options.registerOption(OPTION_BYTES, REPLACE_CHOICE.REPLACE, help,
@ -180,6 +183,8 @@ class DiffApplySettingsOptionManager {
getReplaceDescription("function", "functions"));
options.registerOption(OPTION_FUNCTION_TAGS, MERGE_CHOICE.MERGE, help,
getReplaceDescription("function tag", "function tags"));
options.registerOption(OPTION_SOURCE_MAP, REPLACE_CHOICE.IGNORE, help,
getReplaceDescription("source map", "source map"));
getDefaultApplyFilter();
}
@ -218,6 +223,7 @@ class DiffApplySettingsOptionManager {
REPLACE_CHOICE properties = options.getEnum(OPTION_PROPERTIES, REPLACE_CHOICE.REPLACE);
REPLACE_CHOICE functions = options.getEnum(OPTION_FUNCTIONS, REPLACE_CHOICE.REPLACE);
MERGE_CHOICE functionTags = options.getEnum(OPTION_FUNCTION_TAGS, MERGE_CHOICE.MERGE);
REPLACE_CHOICE sourceMap = options.getEnum(OPTION_SOURCE_MAP, REPLACE_CHOICE.IGNORE);
// Convert the options to a merge filter.
ProgramMergeFilter filter = new ProgramMergeFilter();
@ -238,6 +244,7 @@ class DiffApplySettingsOptionManager {
filter.setFilter(ProgramMergeFilter.FUNCTION_TAGS, functionTags.ordinal());
filter.setFilter(ProgramMergeFilter.PRIMARY_SYMBOL,
convertSymbolMergeChoiceToReplaceChoiceForPrimay(symbols).ordinal());
filter.setFilter(ProgramMergeFilter.SOURCE_MAP, sourceMap.ordinal());
return filter;
}
@ -293,6 +300,7 @@ class DiffApplySettingsOptionManager {
saveReplaceOption(options, newDefaultApplyFilter, BOOKMARKS);
saveReplaceOption(options, newDefaultApplyFilter, PROPERTIES);
saveReplaceOption(options, newDefaultApplyFilter, FUNCTIONS);
saveReplaceOption(options, newDefaultApplyFilter, SOURCE_MAP);
saveMergeOption(options, newDefaultApplyFilter, PLATE_COMMENTS);
saveMergeOption(options, newDefaultApplyFilter, PRE_COMMENTS);
@ -311,7 +319,8 @@ class DiffApplySettingsOptionManager {
private void saveCodeUnitReplaceOption(Options options, ProgramMergeFilter defaultApplyFilter,
int setting) {
int filter =
(defaultApplyFilter.getFilter(ProgramMergeFilter.INSTRUCTIONS) >= defaultApplyFilter.getFilter(ProgramMergeFilter.DATA)) ? ProgramMergeFilter.INSTRUCTIONS
(defaultApplyFilter.getFilter(ProgramMergeFilter.INSTRUCTIONS) >= defaultApplyFilter
.getFilter(ProgramMergeFilter.DATA)) ? ProgramMergeFilter.INSTRUCTIONS
: ProgramMergeFilter.DATA;
REPLACE_CHOICE defaultSetting = REPLACE_CHOICE.REPLACE;
REPLACE_CHOICE optionSetting = options.getEnum(getOptionName(setting), defaultSetting);
@ -332,7 +341,8 @@ class DiffApplySettingsOptionManager {
}
}
private void saveMergeOption(Options options, ProgramMergeFilter defaultApplyFilter, int setting) {
private void saveMergeOption(Options options, ProgramMergeFilter defaultApplyFilter,
int setting) {
MERGE_CHOICE defaultSetting = MERGE_CHOICE.MERGE;
MERGE_CHOICE optionSetting = options.getEnum(getOptionName(setting), defaultSetting);
MERGE_CHOICE diffSetting =
@ -507,7 +517,8 @@ class DiffApplySettingsOptionManager {
* @param type the ProgramMergeFilter filter type
* @return the StringEnum
*/
private REPLACE_CHOICE convertTypeToReplaceEnum(ProgramMergeFilter defaultApplyFilter, int type) {
private REPLACE_CHOICE convertTypeToReplaceEnum(ProgramMergeFilter defaultApplyFilter,
int type) {
int filter = defaultApplyFilter.getFilter(type);
return REPLACE_CHOICE.values()[filter];
}

View file

@ -34,6 +34,7 @@ import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.util.ProgramMergeFilter;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* The DiffSettingsDialog is used to change the types of differences currently
@ -63,6 +64,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
private Choice propertiesCB;
private Choice functionsCB;
private Choice functionTagsCB;
private Choice sourceMapCB;
private int applyProgramContext;
private int applyBytes;
@ -78,6 +80,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
private int applyProperties;
private int applyFunctions;
private int applyFunctionTags;
private int applySourceMap;
private int replacePrimary;
private ProgramMergeFilter applyFilter;
@ -107,8 +110,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
public void addActions() {
plugin.getTool()
.addLocalAction(this,
new SaveApplySettingsAction(this, plugin.applySettingsMgr));
.addLocalAction(this, new SaveApplySettingsAction(this, plugin.applySettingsMgr));
plugin.getTool().addLocalAction(this, new DiffIgnoreAllAction(this));
plugin.getTool().addLocalAction(this, new DiffReplaceAllAction(this));
plugin.getTool().addLocalAction(this, new DiffMergeAllAction(this));
@ -152,7 +154,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(refsCB);
plateCommentsCB = new Choice("Plate Comments", true);
plateCommentsCB = new Choice("Comments, Plate", true);
plateCommentsCB.addActionListener(e -> {
applyPlateComments = plateCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.PLATE_COMMENTS, applyPlateComments);
@ -160,7 +162,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(plateCommentsCB);
preCommentsCB = new Choice("Pre Comments", true);
preCommentsCB = new Choice("Comments, Pre", true);
preCommentsCB.addActionListener(e -> {
applyPreComments = preCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.PRE_COMMENTS, applyPreComments);
@ -168,7 +170,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(preCommentsCB);
eolCommentsCB = new Choice("Eol Comments", true);
eolCommentsCB = new Choice("Comments, EOL", true);
eolCommentsCB.addActionListener(e -> {
applyEolComments = eolCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.EOL_COMMENTS, applyEolComments);
@ -176,7 +178,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(eolCommentsCB);
repeatableCommentsCB = new Choice("Repeatable Comments", true);
repeatableCommentsCB = new Choice("Comments, Repeatable", true);
repeatableCommentsCB.addActionListener(e -> {
applyRepeatableComments = repeatableCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.REPEATABLE_COMMENTS, applyRepeatableComments);
@ -184,7 +186,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(repeatableCommentsCB);
postCommentsCB = new Choice("Post Comments", true);
postCommentsCB = new Choice("Comments, Post", true);
postCommentsCB.addActionListener(e -> {
applyPostComments = postCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.POST_COMMENTS, applyPostComments);
@ -240,6 +242,25 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(functionTagsCB);
sourceMapCB = new Choice("Source Map", false);
if (!plugin.getFirstProgram().hasExclusiveAccess()) {
sourceMapCB.setSelectedIndex(ProgramMergeFilter.IGNORE);
}
sourceMapCB.addActionListener(e -> {
applySourceMap = sourceMapCB.getSelectedIndex();
if (!plugin.getFirstProgram().hasExclusiveAccess()) {
if (applySourceMap != ProgramMergeFilter.IGNORE) {
Msg.showWarn(this, null, "Exclusive Access Required",
"Exclusive access required to change source map information");
sourceMapCB.setSelectedIndex(ProgramMergeFilter.IGNORE);
applySourceMap = ProgramMergeFilter.IGNORE;
}
}
applyFilter.setFilter(ProgramMergeFilter.SOURCE_MAP, applySourceMap);
applyFilterChanged();
});
choices.add(sourceMapCB);
int maxLabelWidth = 0;
int maxComboWidth = 0;
for (Choice choice : choices) {
@ -294,6 +315,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
propertiesCB.setSelectedIndex(applyProperties);
functionsCB.setSelectedIndex(applyFunctions);
functionTagsCB.setSelectedIndex(applyFunctionTags);
sourceMapCB.setSelectedIndex(applySourceMap);
}
finally {
adjustingApplyFilter = false;
@ -340,6 +362,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
applyFunctions = applyFilter.getFilter(ProgramMergeFilter.FUNCTIONS);
applyFunctionTags = applyFilter.getFilter(ProgramMergeFilter.FUNCTION_TAGS);
replacePrimary = applyFilter.getFilter(ProgramMergeFilter.PRIMARY_SYMBOL);
applySourceMap = applyFilter.getFilter(ProgramMergeFilter.SOURCE_MAP);
adjustApplyFilter();
}
@ -402,11 +425,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
new GComboBox<>(allowMerge ? DiffApplySettingsOptionManager.MERGE_CHOICE.values()
: DiffApplySettingsOptionManager.REPLACE_CHOICE.values());
applyCB.setName(type + " Diff Apply CB");
String typeName = type;
if (typeName.endsWith(" Comments")) {
typeName = "Comments, " + typeName.substring(0, typeName.length() - 9);
}
label = new GDLabel(" " + typeName + " ");
label = new GDLabel(" " + type + " ");
label.setHorizontalAlignment(SwingConstants.RIGHT);
add(applyCB, BorderLayout.EAST);
add(label, BorderLayout.CENTER);
@ -438,7 +457,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
@Override
public int compareTo(Choice o) {
return label.toString().compareTo(o.label.toString());
return type.compareTo(o.type);
}
}

View file

@ -52,6 +52,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
private JCheckBox diffBookmarksCB;
private JCheckBox diffPropertiesCB;
private JCheckBox diffFunctionsCB;
private JCheckBox diffSourceMapCB;
private JButton selectAllButton = new JButton("Select All");
private JButton deselectAllButton = new JButton("Deselect All");
private JCheckBox limitToSelectionCB;
@ -66,6 +67,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
private boolean diffBookmarks;
private boolean diffProperties;
private boolean diffFunctions;
private boolean diffSourceMap;
private ProgramDiffFilter diffFilter;
private JPanel diffPanel;
@ -191,8 +193,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
*/
private JPanel createDiffFilterPanel() {
JPanel checkBoxPanel = new JPanel();
checkBoxPanel.setToolTipText(
"Check the types of differences between the two " +
checkBoxPanel.setToolTipText("Check the types of differences between the two " +
"programs that you want detected and highlighted.");
createBytesCheckBox();
@ -204,17 +205,19 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
createBookmarksCheckBox();
createPropertiesCheckBox();
createFunctionsCheckBox();
createSourceMapCheckBox();
checkBoxPanel.setLayout(new GridLayout(3, 3, 5, 0));
checkBoxPanel.add(diffBytesCB);
checkBoxPanel.add(diffLabelsCB);
checkBoxPanel.add(diffCodeUnitsCB);
checkBoxPanel.add(diffReferencesCB);
checkBoxPanel.add(diffProgramContextCB);
checkBoxPanel.add(diffCommentsCB);
checkBoxPanel.setLayout(new GridLayout(2, 5, 5, 0));
checkBoxPanel.add(diffBookmarksCB);
checkBoxPanel.add(diffPropertiesCB);
checkBoxPanel.add(diffBytesCB);
checkBoxPanel.add(diffCodeUnitsCB);
checkBoxPanel.add(diffCommentsCB);
checkBoxPanel.add(diffFunctionsCB);
checkBoxPanel.add(diffLabelsCB);
checkBoxPanel.add(diffProgramContextCB);
checkBoxPanel.add(diffPropertiesCB);
checkBoxPanel.add(diffReferencesCB);
checkBoxPanel.add(diffSourceMapCB);
JPanel buttonPanel = new JPanel();
createSelectAllButton();
@ -254,8 +257,8 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
private void createCodeUnitsCheckBox() {
diffCodeUnitsCB = new GCheckBox("Code Units", diffCodeUnits);
diffCodeUnitsCB.setName("CodeUnitsDiffCB");
diffCodeUnitsCB.setToolTipText(
"Highlight the instruction, data, " + "and equate differences.");
diffCodeUnitsCB
.setToolTipText("Highlight the instruction, data, " + "and equate differences.");
diffCodeUnitsCB.addItemListener(event -> {
diffCodeUnits = (event.getStateChange() == ItemEvent.SELECTED);
diffFilter.setFilter(ProgramDiffFilter.CODE_UNIT_DIFFS, diffCodeUnits);
@ -334,6 +337,17 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
});
}
private void createSourceMapCheckBox() {
diffSourceMapCB = new GCheckBox("Source Map", diffSourceMap);
diffSourceMapCB.setName("SourceMapDiffCB");
diffSourceMapCB.setToolTipText("Highlight Source Map Differences");
diffSourceMapCB.addItemListener(event -> {
diffSourceMap = (event.getStateChange() == ItemEvent.SELECTED);
diffFilter.setFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS, diffSourceMap);
clearStatusText();
});
}
private void createSelectAllButton() {
selectAllButton.addActionListener(e -> setSelectAll(true));
selectAllButton.setMnemonic('S');
@ -354,6 +368,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
diffBookmarksCB.setSelected(selected);
diffPropertiesCB.setSelected(selected);
diffFunctionsCB.setSelected(selected);
diffSourceMapCB.setSelected(selected);
}
private void adjustDiffFilter() {
@ -366,7 +381,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
diffBookmarksCB.setSelected(diffBookmarks);
diffPropertiesCB.setSelected(diffProperties);
diffFunctionsCB.setSelected(diffFunctions);
diffSourceMapCB.setSelected(diffSourceMap);
}
void setPgmContextEnabled(boolean enable) {
@ -402,6 +417,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
diffBookmarks = diffFilter.getFilter(ProgramDiffFilter.BOOKMARK_DIFFS);
diffProperties = diffFilter.getFilter(ProgramDiffFilter.USER_DEFINED_DIFFS);
diffFunctions = diffFilter.getFilter(ProgramDiffFilter.FUNCTION_DIFFS);
diffSourceMap = diffFilter.getFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS);
adjustDiffFilter();
}
@ -469,7 +485,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
*/
boolean hasDiffSelection() {
return (diffBytes || diffLabels || diffCodeUnits || diffProgramContext || diffReferences ||
diffComments || diffBookmarks || diffProperties || diffFunctions);
diffComments || diffBookmarks || diffProperties || diffFunctions || diffSourceMap);
}
/**
@ -478,7 +494,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
boolean isMarkingAllDiffs() {
return (diffBytes && diffLabels && diffCodeUnits &&
((!pgmContextEnabled) || diffProgramContext) && diffReferences && diffComments &&
diffBookmarks && diffProperties && diffFunctions);
diffBookmarks && diffProperties && diffFunctions && diffSourceMap);
}
}

View file

@ -36,7 +36,7 @@ public interface RecordIterator {
public boolean hasPrevious() throws IOException;
/**
* Return the nexy Record or null if one is not available.
* Return the next Record or null if one is not available.
* @throws IOException thrown if an IO error occurs
*/
public DBRecord next() throws IOException;

View file

@ -44,6 +44,7 @@ import ghidra.program.database.properties.DBPropertyMapManager;
import ghidra.program.database.references.ReferenceDBManager;
import ghidra.program.database.register.ProgramRegisterContextDB;
import ghidra.program.database.reloc.RelocationManager;
import ghidra.program.database.sourcemap.SourceFileManagerDB;
import ghidra.program.database.symbol.*;
import ghidra.program.database.util.AddressSetPropertyMapDB;
import ghidra.program.model.address.*;
@ -112,8 +113,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
* unused flag bits.
* 19-Oct-2023 - version 28 Revised overlay address space table and eliminated min/max.
* Multiple blocks are permitted within a single overlay space.
* 13-Dec-2024 - version 29 Added source file manager.
*/
static final int DB_VERSION = 28;
static final int DB_VERSION = 29;
/**
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION anytime the
@ -184,8 +186,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
private static final int PROPERTY_MGR = 11;
private static final int TREE_MGR = 12;
private static final int RELOC_MGR = 13;
private static final int SOURCE_FILE_MGR = 14;
private static final int NUM_MANAGERS = 14;
private static final int NUM_MANAGERS = 15;
private ManagerDB[] managers = new ManagerDB[NUM_MANAGERS];
private OldFunctionManager oldFunctionMgr;
@ -613,6 +616,11 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
return (RelocationManager) managers[RELOC_MGR];
}
@Override
public SourceFileManagerDB getSourceFileManager() {
return (SourceFileManagerDB) managers[SOURCE_FILE_MGR];
}
@Override
public String getCompiler() {
String compiler = null;
@ -1793,6 +1801,14 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
versionExc = e.combine(versionExc);
}
try {
managers[SOURCE_FILE_MGR] =
new SourceFileManagerDB(dbh, addrMap, openMode, lock, monitor);
}
catch (VersionException e) {
versionExc = e.combine(versionExc);
}
monitor.checkCancelled();
return versionExc;

View file

@ -0,0 +1,261 @@
/* ###
* 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.program.database.sourcemap;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.util.Arrays;
import java.util.HexFormat;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.BigEndianDataConverter;
/**
* A SourceFile is an immutable object representing a source file. It contains an
* absolute path along with an optional {@link SourceFileIdType} and identifier.
* For example, if the id type is {@link SourceFileIdType#MD5}, the identifier would
* be the md5 sum of the source file (stored as a byte array).
* <p>
* Note: path parameters are assumed to be absolute file paths with forward slashes as the
* separator. For other cases, e.g. windows paths, consider the static convenience methods in
* the {@code SourceFileUtils} class.
* <p>
* Note: you can use {@code SourceFileUtils.hexStringToByteArray} to convert hex Strings to byte
* arrays. You can use {@code SourceFileUtils.longToByteArray} to convert long values to the
* appropriate byte arrays.
*/
public final class SourceFile implements Comparable<SourceFile> {
private static final String FILE_SCHEME = "file";
private static HexFormat hexFormat = HexFormat.of();
private final String path;
private final String filename;
private final SourceFileIdType idType;
private final byte[] identifier;
private final int hash;
private final String idDisplayString;
/**
* Constructor requiring only a path. The path will be normalized (see {@link URI#normalize})
* The id type will be set to {@code SourceFileIdType.NONE} and the identifier will
* be set to an array of length 0.
*
* @param path path
*/
public SourceFile(String path) {
this(path, SourceFileIdType.NONE, null, true);
}
/**
* Constructor. The path will be normalized (see {@link URI#normalize}).
* <p>
* Note: if {@code type} is {@code SourceFileIdType.NONE}, the {@code identifier}
* parameter is ignored.
* <p>
* Note: use {@code SourceFileUtils.longToByteArray} to convert a {@code long} value
* to the appropriate {@code byte} array.
* @param path path
* @param type id type
* @param identifier id
*/
public SourceFile(String path, SourceFileIdType type, byte[] identifier) {
this(path, type, identifier, true);
}
/**
* Constructor. The path will be normalized (see {@link URI#normalize}).
* <p>
* Note: if {@code type} is {@code SourceFileIdType.NONE}, the {@code identifier}
* parameter is ignored.
* <p>
* IMPORTANT: only pass {@code false} as {@code validate} parameter if you are certain that
* validation can be skipped, e.g., you are creating a SourceFile from information
* read out of the database which was validated at insertion.
* @param pathToValidate path
* @param type sourcefile id type
* @param identifier identifier
* @param validate true if params should be validated
*/
SourceFile(String pathToValidate, SourceFileIdType type, byte[] identifier, boolean validate) {
if (validate) {
if (StringUtils.isBlank(pathToValidate)) {
throw new IllegalArgumentException("pathToValidate cannot be null or blank");
}
try {
URI uri = new URI(FILE_SCHEME, null, pathToValidate, null).normalize();
path = uri.getPath();
if (path.endsWith("/")) {
throw new IllegalArgumentException(
"SourceFile URI must represent a file (not a directory)");
}
}
catch (URISyntaxException e) {
throw new IllegalArgumentException("path not valid: " + e.getMessage());
}
}
else {
path = pathToValidate;
}
this.idType = type;
filename = path.substring(path.lastIndexOf("/") + 1);
this.identifier = validateAndCopyIdentifier(identifier);
hash = computeHashcode();
idDisplayString = computeIdDisplayString();
}
/**
* Returns a file URI for this SourceFile.
* @return uri
*/
public URI getUri() {
try {
return new URI(FILE_SCHEME, null, path, null);
}
catch (URISyntaxException e) {
throw new AssertionError("URISyntaxException on validated path");
}
}
/**
* Returns the path
* @return path
*/
public String getPath() {
return path;
}
/**
* Returns the filename
* @return filename
*/
public String getFilename() {
return filename;
}
/**
* Returns the source file identifier type
* @return id type
*/
public SourceFileIdType getIdType() {
return idType;
}
/**
* Returns (a copy of) the identifier
* @return identifier
*/
public byte[] getIdentifier() {
byte[] copy = new byte[identifier.length];
System.arraycopy(identifier, 0, copy, 0, identifier.length);
return copy;
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SourceFile otherFile)) {
return false;
}
if (!path.equals(otherFile.path)) {
return false;
}
if (!idType.equals(otherFile.idType)) {
return false;
}
return Arrays.equals(identifier, otherFile.identifier);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(path);
if (idType.equals(SourceFileIdType.NONE)) {
return sb.toString();
}
sb.append(" [");
sb.append(idType.name());
sb.append("=");
sb.append(getIdAsString());
sb.append("]");
return sb.toString();
}
@Override
public int compareTo(SourceFile sourceFile) {
int comp = path.compareTo(sourceFile.path);
if (comp != 0) {
return comp;
}
comp = idType.compareTo(sourceFile.idType);
if (comp != 0) {
return comp;
}
return Arrays.compare(identifier, sourceFile.identifier);
}
/**
* Returns a String representation of the identifier
* @return id display string
*/
public String getIdAsString() {
return idDisplayString;
}
// immutable object - compute hash and cache
private int computeHashcode() {
int result = path.hashCode();
result = 31 * result + idType.hashCode();
result = 31 * result + Arrays.hashCode(identifier);
return result;
}
private byte[] validateAndCopyIdentifier(byte[] array) {
if (array == null || idType == SourceFileIdType.NONE) {
array = new byte[0];
}
if (array.length > SourceFileIdType.MAX_LENGTH) {
throw new IllegalArgumentException(
"identifier array too long; max is " + SourceFileIdType.MAX_LENGTH);
}
if (idType.getByteLength() != 0 && idType.getByteLength() != array.length) {
throw new IllegalArgumentException(
"identifier array has wrong length for " + idType.name());
}
byte[] copy = new byte[array.length];
System.arraycopy(array, 0, copy, 0, array.length);
return copy;
}
private String computeIdDisplayString() {
switch (idType) {
case NONE:
return StringUtils.EMPTY;
case TIMESTAMP_64:
return Instant.ofEpochMilli(BigEndianDataConverter.INSTANCE.getLong(identifier))
.toString();
default:
return hexFormat.formatHex(identifier);
}
}
}

View file

@ -0,0 +1,90 @@
/* ###
* 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.program.database.sourcemap;
import java.io.IOException;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Base class for adapters to access the Source File table. The table has one column, which stores
* the path of the source file (as a String).
*/
abstract class SourceFileAdapter {
static final String TABLE_NAME = "SourceFiles";
static final int PATH_COL = SourceFileAdapterV0.V0_PATH_COL;
static final int ID_TYPE_COL = SourceFileAdapterV0.V0_ID_TYPE_COL;
static final int ID_COL = SourceFileAdapterV0.V0_ID_COL;
/**
* Creates an adapter for the source file table.
* @param handle database handle
* @param openMode open mode
* @param monitor task monitor
* @return adapter for table
* @throws VersionException if version incompatible
*/
static SourceFileAdapter getAdapter(DBHandle handle, OpenMode openMode, TaskMonitor monitor)
throws VersionException {
return new SourceFileAdapterV0(handle, openMode);
}
/**
* Returns a {@link RecordIterator} for this table.
* @return record iterator
* @throws IOException on db error
*/
abstract RecordIterator getRecords() throws IOException;
/**
* Returns the {@link DBRecord} corresponding to {@code sourceFile}, or {@code null} if
* no such record exists.
* @param sourceFile source file
* @return record or null
* @throws IOException on db error
*/
abstract DBRecord getRecord(SourceFile sourceFile) throws IOException;
/**
* Returns the {@link DBRecord} with key {@code id}, or {@code null} if no such record exists.
* @param id id
* @return record or null
* @throws IOException on db error
*/
abstract DBRecord getRecord(long id) throws IOException;
/**
* Creates a {@link DBRecord} for {@link SourceFile} {@code sourceFile}. If a record for
* that source file already exists, the existing record is returned.
* @param sourceFile source file
* @return db record
* @throws IOException on db error
*/
abstract DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException;
/**
* Deletes the record with id {@code id} from the database.
* @param id id to delete
* @return true if deleted successfully
* @throws IOException on database error
*/
abstract boolean removeSourceFileRecord(long id) throws IOException;
}

View file

@ -0,0 +1,149 @@
/* ###
* 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.program.database.sourcemap;
import java.io.IOException;
import java.util.Arrays;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.util.EmptyRecordIterator;
import ghidra.util.exception.VersionException;
/**
* Initial version of {@link SourceFileAdapter}.
*/
class SourceFileAdapterV0 extends SourceFileAdapter implements DBListener {
final static int SCHEMA_VERSION = 0;
static final int V0_PATH_COL = 0;
static final int V0_ID_TYPE_COL = 1;
static final int V0_ID_COL = 2;
private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
new Field[] { StringField.INSTANCE, ByteField.INSTANCE, BinaryField.INSTANCE },
new String[] { "Path", "IdType", "Identifier" }, new int[] { V0_PATH_COL });
private Table table; // lazy creation, null if empty
private final DBHandle dbHandle;
private static final int[] INDEXED_COLUMNS = new int[] { V0_PATH_COL };
SourceFileAdapterV0(DBHandle dbHandle, OpenMode openMode) throws VersionException {
this.dbHandle = dbHandle;
// As in FunctionTagAdapterV0, we need to add this as a database listener.
// Since the table is created lazily, undoing a transaction which (for example) caused
// the table to be created can leave the table in a bad state.
// The implementation of dbRestored(DBHandle) solves this issue.
this.dbHandle.addListener(this);
if (!openMode.equals(OpenMode.CREATE)) {
table = dbHandle.getTable(TABLE_NAME);
if (table == null) {
return; // perform lazy table creation
}
int version = table.getSchema().getVersion();
if (version != SCHEMA_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
}
}
@Override
public void dbRestored(DBHandle dbh) {
table = dbh.getTable(TABLE_NAME);
}
@Override
public void dbClosed(DBHandle dbh) {
// nothing to do
}
@Override
public void tableDeleted(DBHandle dbh, Table deletedTable) {
// nothing to do
}
@Override
public void tableAdded(DBHandle dbh, Table addedTable) {
// nothing to do
}
@Override
RecordIterator getRecords() throws IOException {
if (table == null) {
return new EmptyRecordIterator();
}
return table.iterator();
}
@Override
DBRecord getRecord(long id) throws IOException {
if (table == null) {
return null;
}
return table.getRecord(id);
}
@Override
DBRecord getRecord(SourceFile sourceFile) throws IOException {
if (table == null) {
return null;
}
StringField field = new StringField(sourceFile.getPath());
RecordIterator iter = table.indexIterator(V0_PATH_COL, field, field, true);
while (iter.hasNext()) {
DBRecord rec = iter.next();
if (rec.getByteValue(V0_ID_TYPE_COL) != sourceFile.getIdType().getIndex()) {
continue;
}
if (Arrays.equals(sourceFile.getIdentifier(), rec.getBinaryData(V0_ID_COL))) {
return rec;
}
}
return null;
}
@Override
DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException {
DBRecord rec = getRecord(sourceFile);
if (rec == null) {
rec = V0_SCHEMA.createRecord(getTable().getKey());
rec.setString(V0_PATH_COL, sourceFile.getPath());
rec.setByteValue(V0_ID_TYPE_COL, sourceFile.getIdType().getIndex());
rec.setBinaryData(V0_ID_COL, sourceFile.getIdentifier());
getTable().putRecord(rec);
}
return rec;
}
@Override
boolean removeSourceFileRecord(long id) throws IOException {
if (table != null) {
return table.deleteRecord(id);
}
return false;
}
private Table getTable() throws IOException {
if (table == null) {
table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
}
return table;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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.program.database.sourcemap;
import java.util.HashMap;
import java.util.Map;
/**
* An enum whose values represent source file id types, such as md5 or sha1.
*/
public enum SourceFileIdType {
NONE((byte) 0, 0),
UNKNOWN((byte) 1, 0),
TIMESTAMP_64((byte) 2, 8),
MD5((byte) 3, 16),
SHA1((byte) 4, 20),
SHA256((byte) 5, 32),
SHA512((byte) 6, 64);
public static final int MAX_LENGTH = 64;
private static Map<Byte, SourceFileIdType> valueToEnum;
static {
valueToEnum = new HashMap<>();
for (SourceFileIdType type : SourceFileIdType.values()) {
valueToEnum.put(type.getIndex(), type);
}
}
private final int byteLength;
private final byte index;
private SourceFileIdType(byte index, int length) {
byteLength = length;
this.index = index;
}
/**
* Returns the byte length of the corresponding identifier. A value of 0 indicates
* no restriction.
*
* @return byte length of identifier
*/
public int getByteLength() {
return byteLength;
}
/**
* Returns the index of the identifier type.
* @return index
*/
byte getIndex() {
return index;
}
/**
* Returns the id type given the index.
* @param index index
* @return id type
*/
static SourceFileIdType getTypeFromIndex(byte index) {
return valueToEnum.get(index);
}
}

View file

@ -0,0 +1,857 @@
/* ###
* 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.program.database.sourcemap;
import java.io.IOException;
import java.util.*;
import db.*;
import db.util.ErrorHandler;
import ghidra.framework.data.OpenMode;
import ghidra.framework.store.LockException;
import ghidra.program.database.ManagerDB;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.sourcemap.*;
import ghidra.program.util.ProgramEvent;
import ghidra.util.Lock;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Database Manager for managing source files and source map information.
*/
public class SourceFileManagerDB implements SourceFileManager, ManagerDB, ErrorHandler {
private ProgramDB program;
private SourceFileAdapter sourceFileTableAdapter;
private SourceMapAdapter sourceMapTableAdapter;
private AddressMapDB addrMap;
protected final Lock lock;
private Long lastKey;
private SourceFile lastSourceFile;
/**
* Constructor
* @param dbh database handle
* @param addrMap map longs to addresses
* @param openMode mode
* @param lock program synchronization lock
* @param monitor task monitor
* @throws VersionException if the database is incompatible with the current schema
*/
public SourceFileManagerDB(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode, Lock lock,
TaskMonitor monitor) throws VersionException {
this.addrMap = addrMap;
sourceFileTableAdapter = SourceFileAdapter.getAdapter(dbh, openMode, monitor);
sourceMapTableAdapter = SourceMapAdapter.getAdapter(dbh, addrMap, openMode, monitor);
this.lock = lock;
}
@Override
public void setProgram(ProgramDB program) {
this.program = program;
}
@Override
public void programReady(OpenMode openMode, int currentRevision, TaskMonitor monitor)
throws IOException, CancelledException {
// nothing to do
}
@Override
public void invalidateCache(boolean all) throws IOException {
lastKey = null;
lastSourceFile = null;
}
/**
* {@inheritDoc}<br>
* Note: this method will split any source map entries that <b>intersect</b> the address
* range but are not entirely contained within it. Parts within the range to delete will
* be deleted.
* @param start first address in range
* @param end last address in range
* @param monitor task monitor
* @throws CancelledException if {@code monitor} is cancelled
*/
@Override
public void deleteAddressRange(Address start, Address end, TaskMonitor monitor)
throws CancelledException {
AddressRange.checkValidRange(start, end);
AddressRange rangeToDelete = new AddressRangeImpl(start, end);
lock.acquire();
try {
RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(end, false);
boolean sourceMapChanged = false;
// Note: we iterate backwards since records are stored based on the start address.
// We need to delete records stored before the beginning of the delete range that
// overlap the delete range, but we will never need to delete records that start after
// the delete range.
List<SourceMapEntryData> entriesToCreate = new ArrayList<>();
while (recIter.hasPrevious()) {
monitor.checkCancelled();
DBRecord rec = recIter.previous();
Address recStart = getStartAddress(rec);
long recLength = getLength(rec);
if (recLength == 0) {
// if length 0 entry is in range to delete, delete entry
// otherwise ignore
if (rangeToDelete.contains(recStart)) {
recIter.delete();
}
continue;
}
Address recEnd = getEndAddress(recStart,recLength);
if (!rangeToDelete.intersects(recStart, recEnd)) {
// we've found an entry that does not touch the range to delete
// this means we've handled all relevant entries and can stop
break;
}
long fileAndLine = getFileAndLine(rec);
long fileId = fileAndLine >> 32;
int lineNum = (int) (fileAndLine & 0xffffffff);
if (isLessInSameSpace(recStart, start)) {
// rangeToDelete intersects (recStart,recEnd), so
// the entry must overlap the left endpoint of rangeToDelete
long length = start.subtract(recStart);
SourceMapEntryData data =
new SourceMapEntryData(fileId, lineNum, recStart, length);
entriesToCreate.add(data);
}
if (isLessInSameSpace(end, recEnd)) {
// rangeToDelete intersects (recStart,recEnd)
// entry must overlap right endpoint of rangeToDelete
long length = recEnd.subtract(end);
SourceMapEntryData data =
new SourceMapEntryData(fileId, lineNum, end.add(1), length);
entriesToCreate.add(data);
}
recIter.delete();
}
// add the new entries
for (SourceMapEntryData data : entriesToCreate) {
sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
data.baseAddress, data.length);
}
if (sourceMapChanged) {
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
}
}
catch (IOException e) {
dbError(e);
}
finally {
lock.release();
}
}
/**
* {@inheritDoc}<br>
* Note: this method will move any source map entry which is entirely contained within the
* source address range. Entries which also contain addresses outside of this range will
* be split and parts within the range to move will be moved.
* @param fromAddr first address of range to be moved
* @param toAddr target address
* @param length number of addresses to move
* @param monitor task monitor
* @throws AddressOverflowException if overflow occurs when computing new addresses
* @throws CancelledException if {@code monitor} is cancelled
*/
@Override
public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
throws AddressOverflowException, CancelledException {
if (length < 0) {
throw new IllegalArgumentException(
"Invalid negative length for moveAddressRange: " + length);
}
if (length == 0) {
return; // nothing to do
}
lock.acquire();
try {
Address rangeToMoveEnd = fromAddr.addNoWrap(length - 1);
AddressRange rangeToMove = new AddressRangeImpl(fromAddr, rangeToMoveEnd);
RecordIterator recIter =
sourceMapTableAdapter.getSourceMapRecordIterator(rangeToMoveEnd, false);
boolean mapChanged = false;
// Note: we iterate backwards since records are stored based on the start address.
// We need to move records stored before the beginning of rangeToMove that
// overlap rangeToMove but we will never need to move records that start after
// rangeToMove.
List<SourceMapEntryData> entriesToCreate = new ArrayList<>();
while (recIter.hasPrevious()) {
monitor.checkCancelled();
DBRecord rec = recIter.previous();
long entryLength = getLength(rec);
if (entryLength == 0) {
continue; // nothing to check
}
Address recStart = getStartAddress(rec);
Address recEnd = getEndAddress(recStart, entryLength);
if (!rangeToMove.intersects(recStart, recEnd)) {
// we've found an entry entirely before rangeToMove
// this means we can stop looking
break;
}
long fileAndLine = getFileAndLine(rec);
long fileId = fileAndLine >> 32;
int lineNum = (int) (fileAndLine & 0xffffffff);
if (isLessInSameSpace(recStart, fromAddr) &&
isLessInSameSpace(rangeToMoveEnd, recEnd)) {
// entry extends over left and right endpoint of rangeToMove
long newLength = fromAddr.subtract(recStart);
SourceMapEntryData left =
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
entriesToCreate.add(left);
SourceMapEntryData middle =
new SourceMapEntryData(fileId, lineNum, fromAddr, length);
entriesToCreate.add(middle);
newLength = recEnd.subtract(rangeToMoveEnd);
SourceMapEntryData right =
new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
entriesToCreate.add(right);
recIter.delete();
continue;
}
if (isLessInSameSpace(recStart, fromAddr)) {
// entry extends before left endpoint (but not past right)
long newLength = fromAddr.subtract(recStart);
SourceMapEntryData left =
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
entriesToCreate.add(left);
newLength = recEnd.subtract(fromAddr) + 1;
SourceMapEntryData middle =
new SourceMapEntryData(fileId, lineNum, fromAddr, newLength);
entriesToCreate.add(middle);
recIter.delete();
continue;
}
if (isLessInSameSpace(rangeToMoveEnd, recEnd)) {
// entry extends past right endpoint (but not before left)
long newLength = rangeToMoveEnd.subtract(recStart) + 1;
SourceMapEntryData middle =
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
entriesToCreate.add(middle);
newLength = recEnd.subtract(rangeToMoveEnd);
SourceMapEntryData right =
new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
entriesToCreate.add(right);
recIter.delete();
continue;
}
// entry is entirely within range to move, no adjustment needed
mapChanged = true;
}
// add the new entries
for (SourceMapEntryData data : entriesToCreate) {
sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
data.baseAddress, data.length);
}
mapChanged = mapChanged || !entriesToCreate.isEmpty();
sourceMapTableAdapter.moveAddressRange(fromAddr, toAddr, length, monitor);
if (mapChanged) {
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
}
}
catch (IOException e) {
dbError(e);
}
finally {
lock.release();
}
}
@Override
public boolean addSourceFile(SourceFile sourceFile) throws LockException {
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
program.checkExclusiveAccess();
lock.acquire();
try {
Long key = getKeyForSourceFile(sourceFile);
if (key != null) {
return false;
}
DBRecord dbRecord = sourceFileTableAdapter.createSourceFileRecord(sourceFile);
updateLastSourceFileAndLastKey(dbRecord);
program.setObjChanged(ProgramEvent.SOURCE_FILE_ADDED, null, null, sourceFile);
return true;
}
catch (IOException e) {
dbError(e);
return false;
}
finally {
lock.release();
}
}
@Override
public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
boolean mapChanged = false;
program.checkExclusiveAccess();
lock.acquire();
try {
Long key = getKeyForSourceFile(sourceFile);
if (key == null) {
return false;
}
RecordIterator recIter =
sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
while (recIter.hasNext()) {
recIter.next();
recIter.delete();
mapChanged = true;
}
sourceFileTableAdapter.removeSourceFileRecord(key);
lastKey = null;
lastSourceFile = null;
}
catch (IOException e) {
dbError(e);
}
finally {
lock.release();
}
program.setObjChanged(ProgramEvent.SOURCE_FILE_REMOVED, null, sourceFile, null);
if (mapChanged) {
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, sourceFile, null);
}
return true;
}
@Override
public List<SourceMapEntry> getSourceMapEntries(Address addr) {
List<SourceMapEntry> sourceMapEntries = new ArrayList<>();
lock.acquire();
try {
RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(addr, false);
boolean foundNonZeroLength = false;
while (recIter.hasPrevious()) {
DBRecord rec = recIter.previous();
long entryLength = getLength(rec);
Address entryBase = getStartAddress(rec);
if (addr.equals(entryBase)) {
sourceMapEntries.add(getSourceMapEntry(rec));
if (entryLength != 0) {
foundNonZeroLength = true;
}
continue;
}
if (entryLength == 0) {
continue; // only want length zero entries if they are based at addr
}
if (!foundNonZeroLength &&
isLessOrEqualInSameSpace(addr, getEndAddress(entryBase, entryLength))) {
sourceMapEntries.add(getSourceMapEntry(rec));
continue; // continue in case there are additional entries at entryBase
}
break;
}
}
catch (IOException e) {
dbError(e);
}
finally {
lock.release();
}
Collections.sort(sourceMapEntries);
return sourceMapEntries;
}
@Override
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
long length) throws LockException, AddressOverflowException {
if (lineNumber < 0) {
throw new IllegalArgumentException("lineNumber cannot be negative");
}
if (length < 0) {
throw new IllegalArgumentException("length cannot be negative");
}
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
Objects.requireNonNull(baseAddr, "baseAddr cannot be null");
MemoryBlock startBlock = program.getMemory().getBlock(baseAddr);
if (startBlock == null) {
throw new AddressOutOfBoundsException(baseAddr + " is not in a defined memory block");
}
program.checkExclusiveAccess();
lock.acquire();
try {
Long sourceFileId = getKeyForSourceFile(sourceFile);
if (sourceFileId == null) {
throw new IllegalArgumentException(
sourceFile.toString() + " not associated with program");
}
if (length == 0) {
return addZeroLengthEntry(sourceFileId, lineNumber, baseAddr);
}
Address endAddr = baseAddr.addNoWrap(length - 1);
// check that the entry's range is entirely contained within defined memory blocks
if (!startBlock.contains(endAddr)) {
if (!program.getMemory().contains(baseAddr, endAddr)) {
throw new AddressOutOfBoundsException(
baseAddr + "," + endAddr + " spans undefined memory");
}
}
SourceMapEntryDB entry = null;
RecordIterator recIter =
sourceMapTableAdapter.getSourceMapRecordIterator(endAddr, false);
while (recIter.hasPrevious()) {
DBRecord rec = recIter.previous();
long entryLength = getLength(rec);
if (entryLength == 0) {
continue; // length 0 entries can't conflict
}
Address entryBase = getStartAddress(rec);
if (entryBase.equals(baseAddr)) {
if (entryLength != length) {
throw new IllegalArgumentException(
"new entry must have the same length as existing entry");
}
if ((sourceFileId << 32 | lineNumber) == getFileAndLine(rec)) {
return getSourceMapEntry(rec); // entry is already in the DB
}
continue; // non-conflicting entry found, continue checking
}
if (isLessOrEqualInSameSpace(baseAddr, entryBase)) {
throw new IllegalArgumentException(
"new entry would overlap entry " + getSourceMapEntry(rec).toString());
}
if (isLessOrEqualInSameSpace(entryBase, baseAddr)) {
if (getEndAddress(entryBase, entryLength).compareTo(baseAddr) >= 0) {
throw new IllegalArgumentException(
"new entry would overlap entry " + getSourceMapEntry(rec).toString());
}
}
break; // safe to add new entry
}
DBRecord rec =
sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, length);
entry = new SourceMapEntryDB(this, rec, addrMap);
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
return entry;
}
catch (IOException e) {
dbError(e);
throw new AssertionError("addSourceMapEntry unsuccessful - possible database error");
}
finally {
lock.release();
}
}
@Override
public boolean intersectsSourceMapEntry(AddressSetView addrs) {
if (addrs == null || addrs.isEmpty()) {
return false;
}
lock.acquire();
try {
for (AddressRangeIterator rangeIter = addrs.getAddressRanges(); rangeIter.hasNext();) {
AddressRange r = rangeIter.next();
RecordIterator recIter =
sourceMapTableAdapter.getSourceMapRecordIterator(r.getMaxAddress(), false);
while (recIter.hasPrevious()) {
DBRecord rec = recIter.previous();
Address entryStart = getStartAddress(rec);
if (r.contains(entryStart)) {
return true;
}
long length = getLength(rec);
if (length == 0) {
continue;
}
if (r.intersects(entryStart, getEndAddress(entryStart, length))) {
return true;
}
break;
}
}
return false;
}
catch (IOException e) {
dbError(e);
return false;
}
finally {
lock.release();
}
}
@Override
public void dbError(IOException e) throws RuntimeException {
program.dbError(e);
}
@Override
public List<SourceFile> getMappedSourceFiles() {
List<SourceFile> sourceFiles = new ArrayList<>();
lock.acquire();
try {
for (RecordIterator sourceFileRecordIter =
sourceFileTableAdapter.getRecords(); sourceFileRecordIter
.hasNext();) {
DBRecord sourceFileRecord = sourceFileRecordIter.next();
long key = sourceFileRecord.getKey();
RecordIterator sourceMapEntryRecordIter =
sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
if (sourceMapEntryRecordIter.hasNext()) {
updateLastSourceFileAndLastKey(sourceFileRecord);
sourceFiles.add(lastSourceFile);
}
}
}
catch (IOException e) {
dbError(e);
}
finally {
lock.release();
}
return sourceFiles;
}
@Override
public List<SourceFile> getAllSourceFiles() {
List<SourceFile> sourceFiles = new ArrayList<>();
lock.acquire();
try {
for (RecordIterator recordIter = sourceFileTableAdapter.getRecords(); recordIter
.hasNext();) {
updateLastSourceFileAndLastKey(recordIter.next());
sourceFiles.add(lastSourceFile);
}
}
catch (IOException e) {
dbError(e);
}
finally {
lock.release();
}
return sourceFiles;
}
@Override
public void transferSourceMapEntries(SourceFile source, SourceFile target)
throws LockException {
program.checkExclusiveAccess();
lock.acquire();
try {
Long srcKey = getKeyForSourceFile(source);
if (srcKey == null) {
throw new IllegalArgumentException(
source.toString() + " is not associated with program");
}
Long targetKey = getKeyForSourceFile(target);
if (targetKey == null) {
throw new IllegalArgumentException(
target.toString() + " is not associated with program");
}
if (source.equals(target)) {
return; // transfer redundant
}
for (SourceMapEntry entry : getSourceMapEntries(source, 0, Integer.MAX_VALUE)) {
addSourceMapEntry(target, entry.getLineNumber(), entry.getBaseAddress(),
entry.getLength());
removeSourceMapEntry(entry); // remove fires a SOURCE_MAP_CHANGED event
}
}
catch (AddressOverflowException e) {
// can't happen - entry ranges were validated upon insert
throw new AssertionError("bad address range in source map entry table");
}
finally {
lock.release();
}
}
@Override
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
try {
return new SourceMapEntryIteratorDB(this,
sourceMapTableAdapter.getSourceMapRecordIterator(address, forward), forward);
}
catch (IOException e) {
dbError(e);
}
return SourceMapEntryIterator.EMPTY_ITERATOR;
}
@Override
public boolean containsSourceFile(SourceFile sourceFile) {
if (sourceFile == null) {
return false;
}
lock.acquire();
try {
return getKeyForSourceFile(sourceFile) != null;
}
finally {
lock.release();
}
}
@Override
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
int maxLine) {
if (minLine < 0) {
throw new IllegalArgumentException("minLine cannot be negative; was " + minLine);
}
if (maxLine < 0) {
throw new IllegalArgumentException("maxLine cannot be negative; was " + maxLine);
}
if (maxLine < minLine) {
throw new IllegalArgumentException("maxLine cannot be less than minLine");
}
List<SourceMapEntry> entries = new ArrayList<>();
lock.acquire();
try {
Long key = getKeyForSourceFile(sourceFile);
if (key == null) {
return entries;
}
try {
RecordIterator recIter =
sourceMapTableAdapter.getRecordsForSourceFile(key, minLine, maxLine);
while (recIter.hasNext()) {
DBRecord rec = recIter.next();
entries.add(getSourceMapEntry(rec));
}
}
catch (IOException e) {
dbError(e);
}
}
finally {
lock.release();
}
Collections.sort(entries);
return entries;
}
@Override
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
Objects.requireNonNull(entry, "entry cannot be null");
program.checkExclusiveAccess();
lock.acquire();
try {
RecordIterator recIter =
sourceMapTableAdapter.getSourceMapRecordIterator(entry.getBaseAddress(), true);
while (recIter.hasNext()) {
DBRecord rec = recIter.next();
long length = getLength(rec);
if (length != entry.getLength()) {
continue;
}
long fileAndLine = getFileAndLine(rec);
if (((int) (fileAndLine & 0xffffffff)) != entry.getLineNumber()) {
continue;
}
if (!(entry.getSourceFile().equals(getSourceFileFromKey(fileAndLine >> 32)))) {
continue;
}
sourceMapTableAdapter.removeRecord(rec.getKey());
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, entry, null);
return true;
}
}
catch (IOException e) {
dbError(e);
return false;
}
finally {
lock.release();
}
return false;
}
SourceFile getSourceFile(long key) {
lock.acquire();
try {
return getSourceFileFromKey(key);
}
finally {
lock.release();
}
}
SourceMapEntry getSourceMapEntry(DBRecord rec) {
return new SourceMapEntryDB(this, rec, addrMap);
}
static boolean isLessOrEqualInSameSpace(Address addr1, Address addr2) {
if (!addr1.hasSameAddressSpace(addr2)) {
return false;
}
return addr1.compareTo(addr2) <= 0;
}
static boolean isLessInSameSpace(Address addr1, Address addr2) {
if (!addr1.hasSameAddressSpace(addr2)) {
return false;
}
return addr1.compareTo(addr2) < 0;
}
// acquire lock before invoking
private SourceMapEntry addZeroLengthEntry(long sourceFileId, int lineNumber, Address baseAddr) {
SourceMapEntry entry = null;
try {
RecordIterator recIter =
sourceMapTableAdapter.getSourceMapRecordIterator(baseAddr, true);
while (recIter.hasNext()) {
DBRecord rec = recIter.next();
Address recAddress = getStartAddress(rec);
if (!recAddress.equals(baseAddr)) {
break;
}
if (getLength(rec) != 0) {
continue;
}
long fileAndLine = getFileAndLine(rec);
if ((sourceFileId << 32 | lineNumber) != fileAndLine) {
continue;
}
return getSourceMapEntry(rec);
}
DBRecord rec = sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, 0);
entry = new SourceMapEntryDB(this, rec, addrMap);
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
return entry;
}
catch (IOException e) {
dbError(e);
throw new AssertionError("addZeroLengthEntry unsuccessful - possible database error");
}
}
private long getLength(DBRecord rec) {
return rec.getLongValue(SourceMapAdapter.LENGTH_COL);
}
private Address getStartAddress(DBRecord rec) {
return addrMap.decodeAddress(rec.getLongValue(SourceMapAdapter.BASE_ADDR_COL));
}
private long getFileAndLine(DBRecord rec) {
return rec.getLongValue(SourceMapAdapter.FILE_LINE_COL);
}
// assumes that start and length are from a valid SourceMapEntry
// that is, start.add(length - 1) doesn't wrap and is in the same
// space
private Address getEndAddress(Address start, long length) {
if (length == 0) {
return start;
}
try {
return start.addNoWrap(length - 1);
}
catch (AddressOverflowException e) {
// shouldn't happen, but return space max to prevent possibility of wrapping
return start.getAddressSpace().getMaxAddress();
}
}
// acquire lock before invoking
private SourceFile getSourceFileFromKey(long key) {
if (lastKey == null || lastKey.longValue() != key) {
DBRecord dbRecord = null;
try {
dbRecord = sourceFileTableAdapter.getRecord(key);
}
catch (IOException e) {
dbError(e);
return null;
}
if (dbRecord == null) {
return null;
}
updateLastSourceFileAndLastKey(dbRecord);
}
return lastSourceFile;
}
// acquire lock before invoking
private Long getKeyForSourceFile(SourceFile sourceFile) {
if (lastSourceFile == null || !sourceFile.equals(lastSourceFile)) {
DBRecord dbRecord = null;
try {
dbRecord = sourceFileTableAdapter.getRecord(sourceFile);
}
catch (IOException e) {
dbError(e);
return null;
}
if (dbRecord == null) {
return null;
}
updateLastSourceFileAndLastKey(dbRecord);
}
return lastKey;
}
private void updateLastSourceFileAndLastKey(DBRecord dbRecord) {
lastKey = dbRecord.getKey();
String path = dbRecord.getString(SourceFileAdapter.PATH_COL);
SourceFileIdType idType =
SourceFileIdType.getTypeFromIndex(dbRecord.getByteValue(SourceFileAdapter.ID_TYPE_COL));
byte[] identifier = dbRecord.getBinaryData(SourceFileAdapter.ID_COL);
lastSourceFile = new SourceFile(path, idType, identifier, false);
}
/**
* A record for storing information about new source map entries which must be created
* during {@link #moveAddressRange} or {@link #deleteAddressRange}
*/
private record SourceMapEntryData(long sourceFileId, int lineNumber, Address baseAddress,
long length) {}
}

View file

@ -0,0 +1,116 @@
/* ###
* 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.program.database.sourcemap;
import java.io.IOException;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Base class for adapters to access the Source Map table.
* <p>
* Each entry in the table corresponds to a single {@link SourceMapEntry} and so records
* a {@link SourceFile}, a line number, a base address, and a length.
* <p>
* There are a number of restrictions on a {@link SourceMapEntry}, which are listed in that
* interface's top-level documentation. It is the responsibility of the {@link SourceFileManager}
* to enforce these restrictions.
*/
abstract class SourceMapAdapter {
static final String TABLE_NAME = "SourceMap";
static final int FILE_LINE_COL = SourceMapAdapterV0.V0_FILE_LINE_COL;
static final int BASE_ADDR_COL = SourceMapAdapterV0.V0_BASE_ADDR_COL;
static final int LENGTH_COL = SourceMapAdapterV0.V0_LENGTH_COL;
/**
* Creates an adapter for the Source Map table.
* @param dbh database handle
* @param addrMap address map
* @param openMode mode
* @param monitor task monitor
* @return adapter for table
* @throws VersionException if version incompatible
*/
static SourceMapAdapter getAdapter(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode,
TaskMonitor monitor) throws VersionException {
return new SourceMapAdapterV0(dbh, addrMap, openMode);
}
/**
* Removes a record from the table
* @param key key of record to remove.
* @return true if the record was deleted successfully
* @throws IOException if database error occurs
*/
abstract boolean removeRecord(long key) throws IOException;
/**
* Returns a {@link RecordIterator} based at {@code addr}.
* @param addr starting address
* @param before if true, initial position is before addr, otherwise after
* @return iterator
* @throws IOException if database error occurs
*/
abstract RecordIterator getSourceMapRecordIterator(Address addr, boolean before)
throws IOException;
/**
* Returns a {@link RecordIterator} over all records for the source file with
* id {@code id}, subject to the line bounds {@code minLine} and {@code maxLine}
* @param fileId id of source file
* @param minLine minimum line number
* @param maxLine maximum line number
* @return iterator
* @throws IOException if database error occurs
*/
abstract RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
throws IOException;
/**
* Adds an entry to the source map table. This method assumes that no address
* in the associated range has already been associated with this source file and
* line number.
* @param fileId source file id
* @param lineNum line number
* @param baseAddr minimum address of range
* @param length number of addresses in range
* @return record
* @throws IOException if database error occurs
*/
abstract DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
throws IOException;
/**
* Updates all appropriate entries in the table when an address range is moved.
* @param fromAddr from address
* @param toAddr to address
* @param length number of addresses in moved range
* @param monitor task monitor
* @throws CancelledException if task cancelled
* @throws IOException if database error occurs
*/
abstract void moveAddressRange(Address fromAddr, Address toAddr, long length,
TaskMonitor monitor) throws CancelledException, IOException;
}

View file

@ -0,0 +1,160 @@
/* ###
* 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.program.database.sourcemap;
import java.io.IOException;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.map.AddressIndexPrimaryKeyIterator;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.database.util.DatabaseTableUtils;
import ghidra.program.database.util.EmptyRecordIterator;
import ghidra.program.model.address.Address;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Initial version of {@link SourceMapAdapter}
*/
class SourceMapAdapterV0 extends SourceMapAdapter implements DBListener {
final static int SCHEMA_VERSION = 0;
static final int V0_FILE_LINE_COL = 0; // indexed
static final int V0_BASE_ADDR_COL = 1; // indexed
static final int V0_LENGTH_COL = 2;
// key | ((32-bit source file id << 32) | 32-bit line number) | base addr | length
private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
new Field[] { LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE },
new String[] { "fileAndLine", "baseAddress", "length" }, null);
private static final int[] INDEXED_COLUMNS = new int[] { V0_FILE_LINE_COL, V0_BASE_ADDR_COL };
private Table table; // lazy creation, null if empty
private final DBHandle dbHandle;
private AddressMapDB addrMap;
/**
* Creates an adapter for version 0 of the source map adapter
* @param dbh database handle
* @param addrMap address map
* @param openMode open mode
* @throws VersionException if version incompatible
*/
SourceMapAdapterV0(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode)
throws VersionException {
this.dbHandle = dbh;
this.addrMap = addrMap;
// As in FunctionTagAdapterV0, we need to add this as a database listener.
// Since the table is created lazily, undoing a transaction which (for example) caused
// the table to be created can leave the table in a bad state.
// The implementation of dbRestored(DBHandle) solves this issue.
this.dbHandle.addListener(this);
if (!openMode.equals(OpenMode.CREATE)) {
table = dbHandle.getTable(TABLE_NAME);
if (table == null) {
return; // perform lazy table creation
}
int version = table.getSchema().getVersion();
if (version != SCHEMA_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
}
}
@Override
public void dbRestored(DBHandle dbh) {
table = dbh.getTable(TABLE_NAME);
}
@Override
public void dbClosed(DBHandle dbh) {
// nothing to do
}
@Override
public void tableDeleted(DBHandle dbh, Table t) {
// nothing to do
}
@Override
public void tableAdded(DBHandle dbh, Table t) {
// nothing to do
}
@Override
boolean removeRecord(long key) throws IOException {
if (table != null) {
return table.deleteRecord(key);
}
return false;
}
@Override
RecordIterator getSourceMapRecordIterator(Address addr, boolean before) throws IOException {
if (table == null || addr == null) {
return EmptyRecordIterator.INSTANCE;
}
AddressIndexPrimaryKeyIterator keyIter =
new AddressIndexPrimaryKeyIterator(table, V0_BASE_ADDR_COL, addrMap, addr, before);
return new KeyToRecordIterator(table, keyIter);
}
@Override
RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
throws IOException {
if (table == null) {
return EmptyRecordIterator.INSTANCE;
}
fileId = fileId << 32;
LongField minField = new LongField(fileId | minLine);
LongField maxField = new LongField(fileId | maxLine);
return table.indexIterator(V0_FILE_LINE_COL, minField, maxField, true);
}
@Override
DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
throws IOException {
DBRecord rec = V0_SCHEMA.createRecord(getTable().getKey());
rec.setLongValue(V0_FILE_LINE_COL, (fileId << 32) | lineNum);
rec.setLongValue(V0_BASE_ADDR_COL, addrMap.getKey(baseAddr, true));
rec.setLongValue(V0_LENGTH_COL, length);
table.putRecord(rec);
return rec;
}
@Override
void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
throws CancelledException, IOException {
if (table == null) {
return;
}
DatabaseTableUtils.updateIndexedAddressField(table, V0_BASE_ADDR_COL, addrMap, fromAddr,
toAddr, length, null, monitor);
}
private Table getTable() throws IOException {
if (table == null) {
table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
}
return table;
}
}

View file

@ -0,0 +1,158 @@
/* ###
* 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.program.database.sourcemap;
import java.util.Objects;
import db.DBRecord;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.model.address.*;
import ghidra.program.model.sourcemap.SourceMapEntry;
/**
* Database implementation of {@link SourceMapEntry} interface.
* <p>
* Note: clients should drop and reacquire all SourceMapEntryDB objects upon undo/redo,
* ProgramEvent.SOURCE_MAP_CHANGED, and ProgramEvent.SOURCE_FILE_REMOVED.
*/
public class SourceMapEntryDB implements SourceMapEntry {
private int lineNumber;
private SourceFile sourceFile;
private Address baseAddress;
private long length;
private AddressRange range = null;
/**
* Creates a new SourceMapEntryDB
* @param manager source file manager
* @param record backing record
* @param addrMap address map
*/
SourceMapEntryDB(SourceFileManagerDB manager, DBRecord record, AddressMapDB addrMap) {
manager.lock.acquire();
try {
long fileAndLine = record.getLongValue(SourceMapAdapter.FILE_LINE_COL);
lineNumber = (int) (fileAndLine & 0xffffffff);
sourceFile = manager.getSourceFile(fileAndLine >> 32);
long encodedAddress = record.getLongValue(SourceMapAdapter.BASE_ADDR_COL);
baseAddress = addrMap.decodeAddress(encodedAddress);
length = record.getLongValue(SourceMapAdapter.LENGTH_COL);
if (length != 0) {
Address max;
try {
max = baseAddress.addNoWrap(length - 1);
}
catch (AddressOverflowException e) {
// shouldn't happen, but return space max to prevent possibility of wrapping
max = baseAddress.getAddressSpace().getMaxAddress();
}
range = new AddressRangeImpl(baseAddress, max);
}
}
finally {
manager.lock.release();
}
}
@Override
public int getLineNumber() {
return lineNumber;
}
@Override
public SourceFile getSourceFile() {
return sourceFile;
}
@Override
public Address getBaseAddress() {
return baseAddress;
}
@Override
public AddressRange getRange() {
return range;
}
@Override
public long getLength() {
return length;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer(getSourceFile().toString());
sb.append(":");
sb.append(getLineNumber());
sb.append(" @ ");
sb.append(getBaseAddress().toString());
sb.append(" (");
sb.append(Long.toString(getLength()));
sb.append(")");
return sb.toString();
}
@Override
public int compareTo(SourceMapEntry o) {
int sourceFileCompare = getSourceFile().compareTo(o.getSourceFile());
if (sourceFileCompare != 0) {
return sourceFileCompare;
}
int lineCompare = Integer.compare(getLineNumber(), o.getLineNumber());
if (lineCompare != 0) {
return lineCompare;
}
int addrCompare = getBaseAddress().compareTo(o.getBaseAddress());
if (addrCompare != 0) {
return addrCompare;
}
return Long.compareUnsigned(getLength(), o.getLength());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SourceMapEntry otherEntry)) {
return false;
}
if (lineNumber != otherEntry.getLineNumber()) {
return false;
}
if (!sourceFile.equals(otherEntry.getSourceFile())) {
return false;
}
if (!baseAddress.equals(otherEntry.getBaseAddress())) {
return false;
}
if (length != otherEntry.getLength()) {
return false;
}
if (!Objects.equals(range, otherEntry.getRange())) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hashCode = lineNumber;
hashCode = 31 * hashCode + sourceFile.hashCode();
hashCode = 31 * hashCode + baseAddress.hashCode();
hashCode = 31 * hashCode + Long.hashCode(length);
return hashCode;
}
}

View file

@ -0,0 +1,95 @@
/* ###
* 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.program.database.sourcemap;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import db.DBRecord;
import db.RecordIterator;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.program.model.sourcemap.SourceMapEntryIterator;
/**
* Database implementation of {@link SourceMapEntryIterator}
*/
public class SourceMapEntryIteratorDB implements SourceMapEntryIterator {
private boolean forward;
private RecordIterator recIter;
private SourceFileManagerDB sourceManager;
private SourceMapEntry nextEntry;
/**
* Constructor
* @param sourceManager source manager
* @param recIter record iterator
* @param forward direction to iterate
*/
SourceMapEntryIteratorDB(SourceFileManagerDB sourceManager, RecordIterator recIter,
boolean forward) {
this.sourceManager = sourceManager;
this.recIter = recIter;
this.forward = forward;
this.nextEntry = null;
}
@Override
public boolean hasNext() {
if (nextEntry != null) {
return true;
}
sourceManager.lock.acquire();
try {
boolean recIterNext = forward ? recIter.hasNext() : recIter.hasPrevious();
if (!recIterNext) {
return false;
}
DBRecord rec = forward ? recIter.next() : recIter.previous();
nextEntry = sourceManager.getSourceMapEntry(rec);
return true;
}
catch (IOException e) {
sourceManager.dbError(e);
return false;
}
finally {
sourceManager.lock.release();
}
}
@Override
public SourceMapEntry next() {
if (hasNext()) {
SourceMapEntry entryToReturn = nextEntry;
nextEntry = null;
return entryToReturn;
}
throw new NoSuchElementException();
}
@Override
public Iterator<SourceMapEntry> iterator() {
return this;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

View file

@ -105,7 +105,7 @@ public class AddressRangeMapDB implements DBListener {
* @param errHandler database error handler
* @param valueField specifies the type for the values stored in this map
* @param indexed if true, values will be indexed allowing use of the
* getValueRangeIterator method
* {@link AddressRangeMapDB#getAddressSet(Field)} method.
*/
public AddressRangeMapDB(DBHandle dbHandle, AddressMap addressMap, Lock lock, String name,
ErrorHandler errHandler, Field valueField, boolean indexed) {
@ -343,7 +343,8 @@ public class AddressRangeMapDB implements DBListener {
}
/**
* Returns set of addresses where the given value has been set
* Returns set of addresses where the given value has been set.
* This method may only be invoked on indexed {@link AddressRangeMapDB}s!
* @param value the value to search for
* @return set of addresses where the given value has been set
*/

View file

@ -28,6 +28,7 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.reloc.RelocationTable;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.AddressSetPropertyMap;
import ghidra.program.model.util.PropertyMapManager;
@ -149,6 +150,14 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
*/
public BookmarkManager getBookmarkManager();
/**
* Returns the program's {@link SourceFileManager}.
* @return the source file manager
*/
default public SourceFileManager getSourceFileManager() {
return SourceFileManager.DUMMY;
}
/**
* Gets the default pointer size in bytes as it may be stored within the program listing.
* @return default pointer size.
@ -535,4 +544,5 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
* @return unique program ID
*/
public long getUniqueProgramID();
}

View file

@ -0,0 +1,100 @@
/* ###
* 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.program.model.sourcemap;
import java.util.Collections;
import java.util.List;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
/**
* A "dummy" implementation of {@link SourceFileManager}.
*/
public class DummySourceFileManager implements SourceFileManager {
public DummySourceFileManager() {
// nothing to do
}
@Override
public List<SourceMapEntry> getSourceMapEntries(Address addr) {
return Collections.emptyList();
}
@Override
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
long length) throws LockException {
throw new UnsupportedOperationException("Cannot add source map entries with this manager");
}
@Override
public boolean intersectsSourceMapEntry(AddressSetView addrs) {
return false;
}
@Override
public List<SourceFile> getAllSourceFiles() {
return Collections.emptyList();
}
@Override
public List<SourceFile> getMappedSourceFiles() {
return Collections.emptyList();
}
@Override
public void transferSourceMapEntries(SourceFile source, SourceFile target) {
throw new UnsupportedOperationException(
"Dummy source file manager cannot transfer map info");
}
@Override
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
return SourceMapEntryIterator.EMPTY_ITERATOR;
}
@Override
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
int maxLine) {
return Collections.emptyList();
}
@Override
public boolean addSourceFile(SourceFile sourceFile) throws LockException {
throw new UnsupportedOperationException("cannot add source files to this manager");
}
@Override
public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
throw new UnsupportedOperationException("cannot remove source files from this manager");
}
@Override
public boolean containsSourceFile(SourceFile sourceFile) {
return false;
}
@Override
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
throw new UnsupportedOperationException(
"cannot remove source map entries from this manager");
}
}

View file

@ -0,0 +1,199 @@
/* ###
* 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.program.model.sourcemap;
import java.util.List;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.*;
/**
* This interface defines methods for managing {@link SourceFile}s and {@link SourceMapEntry}s.
*/
public interface SourceFileManager {
public static final SourceFileManager DUMMY = new DummySourceFileManager();
/**
* Returns a sorted list of {@link SourceMapEntry}s associated with an address {@code addr}.
* @param addr address
* @return line number
*/
public List<SourceMapEntry> getSourceMapEntries(Address addr);
/**
* Adds a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
* line number {@code lineNumber}, and {@link AddressRange} {@code range} to the program
* database.
* <p>
* Entries with non-zero lengths must either cover the same address range or be disjoint.
* @param sourceFile source file
* @param lineNumber line number
* @param range address range
* @return created SourceMapEntry
* @throws LockException if invoked without exclusive access
* @throws IllegalArgumentException if the range of the new entry intersects, but does
* not equal, the range of an existing entry or if sourceFile was not previously added
* to the program.
* @throws AddressOutOfBoundsException if the range of the new entry contains addresses
* that are not in a defined memory block
*/
public default SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber,
AddressRange range) throws LockException {
try {
return addSourceMapEntry(sourceFile, lineNumber, range.getMinAddress(),
range.getLength());
}
// can't happen
catch (AddressOverflowException e) {
throw new AssertionError("Address overflow with valid AddressRange");
}
}
/**
* Creates a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
* line number {@code lineNumber}, and non-negative length {@code length} and
* adds it to the program database.
* <p>
* Entries with non-zero lengths must either cover the same address range or be disjoint.
* @param sourceFile source file
* @param lineNumber line number
* @param baseAddr minimum address of range
* @param length number of addresses in range
* @return created SourceMapEntry
* @throws AddressOverflowException if baseAddr + length-1 overflows
* @throws LockException if invoked without exclusive access
* @throws IllegalArgumentException if the range of the new entry intersects, but does
* not equal, the range of an existing entry or if sourceFile was not previously added to the
* program.
* @throws AddressOutOfBoundsException if the range of the new entry contains addresses
* that are not in a defined memory block
*/
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
long length) throws AddressOverflowException, LockException;
/**
* Returns {@code true} precisely when at least one {@link Address} in {@code addrs} has
* source map information.
* @param addrs addresses to check
* @return true when at least one address has source map info
*/
public boolean intersectsSourceMapEntry(AddressSetView addrs);
/**
* Adds a {@link SourceFile} to this manager. A SourceFile must be added before it can be
* associated with any source map information.
*
* @param sourceFile source file to add (can't be null)
* @return true if this manager did not already contain sourceFile
* @throws LockException if invoked without exclusive access
*/
public boolean addSourceFile(SourceFile sourceFile) throws LockException;
/**
* Removes a {@link SourceFile} from this manager. Any associated {@link SourceMapEntry}s will
* also be removed.
* @param sourceFile source file to remove
* @return true if sourceFile was in the manager
* @throws LockException if invoked without exclusive access
*/
public boolean removeSourceFile(SourceFile sourceFile) throws LockException;
/**
* Returns true precisely when this manager contains {@code sourceFile}.
* @param sourceFile source file
* @return true if source file already added
*/
public boolean containsSourceFile(SourceFile sourceFile);
/**
* Returns a {@link List} containing all {@link SourceFile}s of the program.
* @return source file list
*/
public List<SourceFile> getAllSourceFiles();
/**
* Returns a {@link List} containing {@link SourceFile}s which are
* mapped to at least one address in the program
* @return mapped source file list
*/
public List<SourceFile> getMappedSourceFiles();
/**
* Changes the source map so that any {@link SourceMapEntry} associated with {@code source}
* is associated with {@code target} instead. Any entries associated with
* {@code target} before invocation will still be associated with
* {@code target} after invocation. {@code source} will not be associated
* with any entries after invocation (unless {@code source} and {@code target}
* are the same). Line number information is not changed.
* @param source source file to get info from
* @param target source file to move info to
* @throws LockException if invoked without exclusive access
* @throws IllegalArgumentException if source or target has not been added previously
*/
public void transferSourceMapEntries(SourceFile source, SourceFile target) throws LockException;
/**
* Returns a {@link SourceMapEntryIterator} starting at {@code address}.
*
* @param address starting address
* @param forward direction of iterator (true = forward)
* @return iterator
*/
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward);
/**
* Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
* between {@code minLine} and {@code maxLine}, inclusive.
* @param sourceFile source file
* @param minLine minimum line number
* @param maxLine maximum line number
* @return source map entries
*/
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
int maxLine);
/**
* Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
* equal to {@code lineNumber}.
* @param sourceFile source file
* @param lineNumber line number
* @return source map entries
*/
public default List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int lineNumber) {
return getSourceMapEntries(sourceFile, lineNumber, lineNumber);
}
/**
* Returns a sorted of list all {@link SourceMapEntry}s in the program corresponding to
* {@code sourceFile}.
* @param sourceFile source file
* @return source map entries
*/
public default List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile) {
return getSourceMapEntries(sourceFile, 0, Integer.MAX_VALUE);
}
/**
* Removes a {@link SourceMapEntry} from this manager.
* @param entry entry to remove
* @return true if entry was in the manager
* @throws LockException if invoked without exclusive access
*/
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException;
}

View file

@ -0,0 +1,80 @@
/* ###
* 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.program.model.sourcemap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* A SourceMapEntry consists of a {@link SourceFile}, a line number, a base address,
* and a length. If the length is positive, the base address and the length determine
* an {@link AddressRange}. In this case, the length of a {@code SourceMapEntry} is the
* length of the associated {@link AddressRange}, i.e., the number of {@link Address}es in
* the range (see {@link AddressRange#getLength()}). The intent is that the range
* contains all of the bytes corresponding to a given line of source. The length of a
* {@code SourceMapEntry} can be 0, in which case the associated range is null. Negative
* lengths are not allowed.
* <p>
* The baseAddress of a range must occur within a memory block of the program, as must each
* address within the range of a {@code SourceMapEntry}. A range may span multiple
* (contiguous) memory blocks.
* <p>
* If the ranges of two entries (with non-zero lengths) intersect, then the ranges must be
* identical. The associated {@link SourceFile}s and/or line numbers can be different.
* <p>
* Entries with length zero do not conflict with other entries and may occur within the
* range of another entry.
* <p>
* For a fixed source file, line number, base address, and length, there must be only one
* SourceMapEntry.
* <p>
* SourceMapEntry objects are created using the {@link SourceFileManager} for a program,
* which must enforce the restrictions listed above.
*/
public interface SourceMapEntry extends Comparable<SourceMapEntry> {
/**
* Returns the line number.
* @return line number
*/
public int getLineNumber();
/**
* Returns the source file
* @return source file
*/
public SourceFile getSourceFile();
/**
* Returns the base address of the entry
* @return base address
*/
public Address getBaseAddress();
/**
* Returns the length of the range (number of addresses)
* @return length
*/
public long getLength();
/**
* Returns the address range, or null for length 0 entries
* @return address range or null
*/
public AddressRange getRange();
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.program.model.sourcemap;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Interface for iterating over {@link SourceMapEntry}s.
*/
public interface SourceMapEntryIterator extends Iterator<SourceMapEntry>, Iterable<SourceMapEntry> {
public static final SourceMapEntryIterator EMPTY_ITERATOR = new SourceMapEntryIterator() {
@Override
public Iterator<SourceMapEntry> iterator() {
return this;
}
@Override
public SourceMapEntry next() {
throw new NoSuchElementException("Empty iterator is empty!");
}
@Override
public boolean hasNext() {
return false;
}
};
}

View file

@ -17,7 +17,6 @@ package ghidra.program.model.symbol;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
@ -137,7 +136,7 @@ public class SymbolUtilities {
/**
* Check for invalid characters
* (space, colon, asterisk, plus, bracket)
* (space or unprintable ascii below 0x20)
* in labels.
*
* @param str the string to be checked for invalid characters.

View file

@ -147,7 +147,11 @@ public enum ProgramEvent implements EventType {
CODE_UNIT_USER_DATA_CHANGED, // user data has changed for some code unit
USER_DATA_CHANGED, // general user data has changed at some address
RELOCATION_ADDED; // a relocation entry was added
RELOCATION_ADDED, // a relocation entry was added
SOURCE_FILE_ADDED, // a source file was added
SOURCE_FILE_REMOVED, // a source file was removed
SOURCE_MAP_CHANGED; // source map information was changed
private final int id = DomainObjectEventIdGenerator.next();

View file

@ -0,0 +1,47 @@
/* ###
* 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.program.util;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.sourcemap.SourceMapEntry;
/**
* A {@link ProgramLocation} for source map information.
*/
public class SourceMapFieldLocation extends ProgramLocation {
private SourceMapEntry sourceMapEntry;
public SourceMapFieldLocation(Program program, Address addr, int row, int charOffset,
SourceMapEntry sourceMapEntry) {
super(program, addr, row, 0, charOffset);
this.sourceMapEntry = sourceMapEntry;
}
public SourceMapFieldLocation() {
// for deserialization
}
/**
* Returns the {@link SourceMapEntry} associated with this location.
* @return source map entry
*/
public SourceMapEntry getSourceMapEntry() {
return sourceMapEntry;
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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.program.database.sourcemap;
import org.junit.Before;
import generic.test.AbstractGenericTest;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.test.ToyProgramBuilder;
public class AbstractSourceFileTest extends AbstractGenericTest {
protected Program program;
protected ToyProgramBuilder builder;
protected int baseOffset = 0x1001000;
protected Address baseAddress;
protected SourceFile source1;
protected SourceFile source2;
protected SourceFile source3;
protected Instruction ret2_1;
protected Instruction ret2_2;
protected Instruction ret2_3;
protected Instruction nop1_1;
protected Instruction nop1_2;
protected Instruction nop1_3;
protected Instruction ret2_4;
protected Instruction nop1_4;
protected String path1 = "/test1";
protected String path2 = "/test2/test2";
protected String path3 = "/test3/test3/test3";
protected String path4 = "/test4/test4/test4/test4";
protected SourceFileManager sourceManager;
@Before
public void setUp() throws Exception {
builder = new ToyProgramBuilder("testprogram", true, false, this);
int txID = builder.getProgram().startTransaction("create source map test program");
long currentOffset = baseOffset;
try {
builder.createMemory(".text", Integer.toHexString(baseOffset), 64).setExecute(true);
builder.addBytesReturn(currentOffset);
currentOffset += 2;
builder.addBytesNOP(currentOffset++, 1);
builder.addBytesReturn(currentOffset);
currentOffset += 2;
builder.addBytesNOP(currentOffset++, 1);
builder.addBytesReturn(currentOffset);
currentOffset += 2;
builder.addBytesNOP(currentOffset++, 1);
builder.addBytesReturn(currentOffset);
currentOffset += 2;
builder.addBytesNOP(currentOffset++, 1);
builder.disassemble(Integer.toHexString(baseOffset),
(int) (currentOffset - baseOffset));
}
finally {
builder.getProgram().endTransaction(txID, true);
}
program = builder.getProgram();
baseAddress = program.getAddressFactory().getDefaultAddressSpace().getAddress(baseOffset);
InstructionIterator instIter = program.getListing().getInstructions(baseAddress, true);
ret2_1 = instIter.next();
nop1_1 = instIter.next();
ret2_2 = instIter.next();
nop1_2 = instIter.next();
ret2_3 = instIter.next();
nop1_3 = instIter.next();
ret2_4 = instIter.next();
nop1_4 = instIter.next();
sourceManager = program.getSourceFileManager();
source1 = new SourceFile(path1);
source2 = new SourceFile(path2);
source3 = new SourceFile(path3);
//leave path4 as String without a corresponding source file
txID = program.startTransaction("adding source files");
try {
sourceManager.addSourceFile(source1);
sourceManager.addSourceFile(source2);
sourceManager.addSourceFile(source3);
}
finally {
program.endTransaction(txID, true);
}
builder.dispose();
}
protected AddressRange getBody(CodeUnit cu) {
return new AddressRangeImpl(cu.getMinAddress(), cu.getMaxAddress());
}
}

View file

@ -0,0 +1,131 @@
/* ###
* 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.program.database.sourcemap;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.framework.store.LockException;
import ghidra.program.model.sourcemap.SourceMapEntry;
public class GetSourceFilesTest extends AbstractSourceFileTest {
@Test
public void testGetAllSourceFiles() throws LockException {
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(3, sourceFiles.size());
assertTrue(sourceFiles.contains(source1));
assertTrue(sourceFiles.contains(source2));
assertTrue(sourceFiles.contains(source3));
int txId = program.startTransaction("deleting source file 2");
try {
sourceManager.removeSourceFile(source2);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(2, sourceFiles.size());
assertTrue(sourceFiles.contains(source1));
assertTrue(sourceFiles.contains(source3));
txId = program.startTransaction("deleting source files");
try {
sourceManager.removeSourceFile(source1);
sourceManager.removeSourceFile(source3);
}
finally {
program.endTransaction(txId, true);
}
assertTrue(sourceManager.getAllSourceFiles().isEmpty());
}
@Test
public void testGetMappedSourceFiles() throws LockException {
List<SourceFile> sourceFiles = sourceManager.getMappedSourceFiles();
assertTrue(sourceFiles.isEmpty());
int txId = program.startTransaction("adding source map info");
try {
sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getMappedSourceFiles();
assertEquals(1, sourceFiles.size());
SourceFile file = sourceFiles.get(0);
assertEquals(source1.getPath(), file.getPath());
txId = program.startTransaction("adding source map info");
try {
sourceManager.addSourceMapEntry(source2, 1, getBody(ret2_2));
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getMappedSourceFiles();
assertEquals(2, sourceFiles.size());
assertTrue(sourceFiles.contains(source1));
assertTrue(sourceFiles.contains(source2));
txId = program.startTransaction("transferring source map entries");
try {
sourceManager.transferSourceMapEntries(source2, source3);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getMappedSourceFiles();
assertEquals(2, sourceFiles.size());
assertTrue(sourceFiles.contains(source1));
assertTrue(sourceFiles.contains(source3));
txId = program.startTransaction("deleting source1");
try {
sourceManager.removeSourceFile(source1);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getMappedSourceFiles();
assertEquals(1, sourceFiles.size());
assertTrue(sourceFiles.contains(source3));
txId = program.startTransaction("clearing mapping info for source3");
try {
List<SourceMapEntry> entries = sourceManager.getSourceMapEntries(source3);
for (SourceMapEntry entry : entries) {
assertTrue(sourceManager.removeSourceMapEntry(entry));
}
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getMappedSourceFiles();
assertTrue(sourceFiles.isEmpty());
}
}

View file

@ -0,0 +1,508 @@
/* ###
* 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.program.database.sourcemap;
import static org.junit.Assert.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.python.google.common.primitives.Longs;
import ghidra.framework.store.LockException;
import ghidra.util.SourceFileUtils;
public class SourceFileTest extends AbstractSourceFileTest {
@Test(expected = IllegalArgumentException.class)
public void testNonFilePathFailure() {
new SourceFile("/test/dir/");
}
@Test(expected = IllegalArgumentException.class)
public void testRelativePathCreateFailure() {
new SourceFile("test1/test2.c");
}
@Test(expected = IllegalArgumentException.class)
public void testNullPathCreateFailure() {
new SourceFile((String) null);
}
@Test(expected = IllegalArgumentException.class)
public void testEmptyPathCreateFailure() {
new SourceFile(StringUtils.EMPTY);
}
@Test
public void testPathNormalization() {
assertEquals("/src/dir1/dir2/file.c",
new SourceFile("/src/test/../dir1/test/../dir2/file.c").getPath());
}
@Test
public void testGetFilename() {
assertEquals("file.c", new SourceFile("/src/test/file.c").getFilename());
}
@Test
public void testUtilityMethod() {
SourceFile linux = SourceFileUtils.getSourceFileFromPathString("/src/test/../file1.c");
assertEquals("/src/file1.c", linux.getPath());
assertEquals("file1.c", linux.getFilename());
SourceFile windows =
SourceFileUtils.getSourceFileFromPathString("c:\\src\\test\\..\\file1.c");
assertEquals("/c:/src/file1.c", windows.getPath());
assertEquals("file1.c", windows.getFilename());
windows =
SourceFileUtils.getSourceFileFromPathString("/C:/Users//guest/./temp/../file.exe");
assertEquals("/C:/Users/guest/file.exe", windows.getPath());
assertEquals("file.exe", windows.getFilename());
}
@Test
public void basicAddRemoveTest() throws LockException {
assertEquals(3, sourceManager.getAllSourceFiles().size());
SourceFile testSource = new SourceFile("/home/user/src/file.c");
int txId = program.startTransaction("adding source file");
try {
assertTrue(sourceManager.addSourceFile(testSource));
}
finally {
program.endTransaction(txId, true);
}
assertEquals(4, sourceManager.getAllSourceFiles().size());
txId = program.startTransaction("adding same source file");
try {
assertFalse(sourceManager.addSourceFile(testSource));
}
finally {
program.endTransaction(txId, true);
}
assertEquals(4, sourceManager.getAllSourceFiles().size());
txId = program.startTransaction("removing source file");
try {
assertTrue(sourceManager.removeSourceFile(testSource));
}
finally {
program.endTransaction(txId, true);
}
assertEquals(3, sourceManager.getAllSourceFiles().size());
txId = program.startTransaction("removing source file again");
try {
assertFalse(sourceManager.removeSourceFile(testSource));
}
finally {
program.endTransaction(txId, true);
}
assertEquals(3, sourceManager.getAllSourceFiles().size());
}
@Test
public void testContainsNull() {
assertFalse(sourceManager.containsSourceFile(null));
}
@Test
public void basicContainsTest() throws LockException, IOException {
assertTrue(sourceManager.containsSourceFile(source1));
int txId = program.startTransaction("removing source1");
try {
assertTrue(sourceManager.removeSourceFile(source1));
}
finally {
program.endTransaction(txId, true);
}
assertFalse(sourceManager.containsSourceFile(source1));
assertTrue(sourceManager.containsSourceFile(source2));
assertTrue(sourceManager.containsSourceFile(source3));
program.undo();
assertTrue(sourceManager.containsSourceFile(source1));
program.redo();
assertFalse(sourceManager.containsSourceFile(source1));
}
@Test
public void testUndoAddingSourceFile() throws LockException, IOException {
SourceFile test = new SourceFile("/a/b/c.h");
int txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test);
}
finally {
program.endTransaction(txId, true);
}
assertTrue(sourceManager.containsSourceFile(test));
program.undo();
assertFalse(sourceManager.containsSourceFile(test));
program.redo();
assertTrue(sourceManager.containsSourceFile(test));
}
@Test
public void testGetUri() throws URISyntaxException {
String path = "/src/test.c";
URI uri = new URI("file", null, path, null);
SourceFile testFile = new SourceFile(path);
assertEquals(uri, testFile.getUri());
}
@Test
public void testIdentifierDisplayString() {
String path = "/src/test/file.c";
HexFormat hexFormat = HexFormat.of();
SourceFile sourceFile = new SourceFile(path, SourceFileIdType.MD5,
hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
assertEquals(SourceFileIdType.MD5, sourceFile.getIdType());
assertEquals(sourceFile.getIdAsString(), "0123456789abcdef0123456789abcdef");
sourceFile = new SourceFile(path);
assertEquals(SourceFileIdType.NONE, sourceFile.getIdType());
assertEquals(StringUtils.EMPTY, sourceFile.getIdAsString());
sourceFile = new SourceFile(path, SourceFileIdType.SHA1,
hexFormat.parseHex("0123456789abcdef0123456789abcdef01234567"));
assertEquals(SourceFileIdType.SHA1, sourceFile.getIdType());
assertEquals("0123456789abcdef0123456789abcdef01234567", sourceFile.getIdAsString());
sourceFile = new SourceFile(path, SourceFileIdType.SHA256,
hexFormat.parseHex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
assertEquals(SourceFileIdType.SHA256, sourceFile.getIdType());
assertEquals("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
sourceFile.getIdAsString());
sourceFile = new SourceFile(path, SourceFileIdType.SHA512,
hexFormat.parseHex(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" +
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
assertEquals(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" +
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
sourceFile.getIdAsString());
sourceFile = new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
assertEquals(SourceFileIdType.TIMESTAMP_64, sourceFile.getIdType());
assertEquals("1970-01-01T00:00:00Z", sourceFile.getIdAsString());
sourceFile = new SourceFile(path, SourceFileIdType.UNKNOWN, new byte[] { 0x12, 0x13 });
assertEquals(SourceFileIdType.UNKNOWN, sourceFile.getIdType());
assertEquals("1213", sourceFile.getIdAsString());
}
@Test
public void testSamePathDifferentIdentifiers() throws LockException {
HexFormat hexFormat = HexFormat.of();
assertEquals(3, sourceManager.getAllSourceFiles().size());
String path = "/src/test/file.c";
SourceFile test1 = new SourceFile(path);
SourceFile test2 = new SourceFile(path, SourceFileIdType.MD5,
hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
SourceFile test3 =
new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
assertNotEquals(test1, test2);
assertNotEquals(test1, test3);
assertNotEquals(test2, test3);
assertFalse(sourceManager.containsSourceFile(test1));
assertFalse(sourceManager.containsSourceFile(test2));
assertFalse(sourceManager.containsSourceFile(test3));
int txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test1);
}
finally {
program.endTransaction(txId, true);
}
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(4, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertFalse(sourceManager.containsSourceFile(test2));
assertFalse(sourceManager.containsSourceFile(test3));
txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test2);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(5, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertTrue(sourceManager.containsSourceFile(test2));
assertFalse(sourceManager.containsSourceFile(test3));
txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test3);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(6, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertTrue(sourceManager.containsSourceFile(test2));
assertTrue(sourceManager.containsSourceFile(test3));
//repeat adding test3
txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test3);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(6, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertTrue(sourceManager.containsSourceFile(test2));
assertTrue(sourceManager.containsSourceFile(test3));
txId = program.startTransaction("removing source file");
try {
sourceManager.removeSourceFile(test2);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(5, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertFalse(sourceManager.containsSourceFile(test2));
assertTrue(sourceManager.containsSourceFile(test3));
}
@Test
public void testSameIdentifierDifferentPaths() throws LockException {
HexFormat hexFormat = HexFormat.of();
assertEquals(3, sourceManager.getAllSourceFiles().size());
byte[] md5 = hexFormat.parseHex("0123456789abcdef0123456789abcdef");
SourceFile test1 = new SourceFile("/src/file1.c", SourceFileIdType.MD5, md5);
SourceFile test2 = new SourceFile("/src/file2.c", SourceFileIdType.MD5, md5);
SourceFile test3 = new SourceFile("/src/file3.c", SourceFileIdType.MD5, md5);
assertNotEquals(test1, test2);
assertNotEquals(test1, test3);
assertNotEquals(test2, test3);
assertFalse(sourceManager.containsSourceFile(test1));
assertFalse(sourceManager.containsSourceFile(test2));
assertFalse(sourceManager.containsSourceFile(test3));
int txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test1);
}
finally {
program.endTransaction(txId, true);
}
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(4, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertFalse(sourceManager.containsSourceFile(test2));
assertFalse(sourceManager.containsSourceFile(test3));
txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test2);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(5, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertTrue(sourceManager.containsSourceFile(test2));
assertFalse(sourceManager.containsSourceFile(test3));
txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test3);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(6, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertTrue(sourceManager.containsSourceFile(test2));
assertTrue(sourceManager.containsSourceFile(test3));
//repeat adding test3
txId = program.startTransaction("adding source file");
try {
sourceManager.addSourceFile(test3);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(6, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertTrue(sourceManager.containsSourceFile(test2));
assertTrue(sourceManager.containsSourceFile(test3));
txId = program.startTransaction("removing source file");
try {
sourceManager.removeSourceFile(test2);
}
finally {
program.endTransaction(txId, true);
}
sourceFiles = sourceManager.getAllSourceFiles();
assertEquals(5, sourceFiles.size());
assertTrue(sourceManager.containsSourceFile(test1));
assertFalse(sourceManager.containsSourceFile(test2));
assertTrue(sourceManager.containsSourceFile(test3));
}
@Test
public void testNoIdentifierNonNullArray() {
SourceFile sourceFile =
new SourceFile("/src/file.c", SourceFileIdType.NONE, new byte[] { 0x11, 0x22 });
assertEquals("/src/file.c", sourceFile.getPath());
assertEquals(SourceFileIdType.NONE, sourceFile.getIdType());
// array passed to SourceFile constructor should be ignored
assertTrue(Arrays.equals(new byte[0], sourceFile.getIdentifier()));
}
@Test(expected = IllegalArgumentException.class)
public void testBadMd5Length() {
new SourceFile("/file.c", SourceFileIdType.MD5, new byte[] { 0x11, 0x22 });
}
@Test(expected = IllegalArgumentException.class)
public void testMd5NullArray() {
new SourceFile("/file.c", SourceFileIdType.MD5, null);
}
@Test(expected = NullPointerException.class)
public void testAddingNullSourceFile() throws LockException {
int txId = program.startTransaction("adding null SourceFile");
try {
sourceManager.addSourceFile(null);
}
finally {
program.endTransaction(txId, true);
}
}
@Test(expected = NullPointerException.class)
public void testRemovingNullSourceFile() throws LockException {
int txId = program.startTransaction("removing null source file");
try {
assertFalse(sourceManager.removeSourceFile(null));
}
finally {
program.endTransaction(txId, true);
}
}
@Test(expected = NullPointerException.class)
public void testConvertingNullArrayToLong() {
SourceFileUtils.byteArrayToLong(null);
}
@Test(expected = IllegalArgumentException.class)
public void testConvertingEmptyArrayToLong() {
SourceFileUtils.byteArrayToLong(new byte[0]);
}
@Test
public void testLongConversion() {
long testLong = 0x0102030405060708L;
byte[] testArray = new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 };
assertEquals(testLong, SourceFileUtils.byteArrayToLong(testArray));
assertTrue(Arrays.equals(testArray, SourceFileUtils.longToByteArray(testLong)));
}
@Test
public void testHexStringToByteArrayConversion() {
assertTrue(Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(null)));
assertTrue(
Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(StringUtils.EMPTY)));
assertTrue(Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(" ")));
byte[] testArray = new byte[] { 0x00, 0x01, (byte) 0xaa, (byte) 0xff };
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0001aaff")));
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0001AAFF")));
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0X0001AAFF")));
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0x0001aaff")));
}
@Test
public void testByteArrayToHexStringConversion() {
assertEquals(StringUtils.EMPTY, SourceFileUtils.byteArrayToHexString(null));
assertEquals(StringUtils.EMPTY, SourceFileUtils.byteArrayToHexString(new byte[0]));
assertEquals("00112233445566778899aabbccddeeff",
SourceFileUtils.byteArrayToHexString(new byte[] { 0x0, 0x11, 0x22, 0x33, 0x44, 0x55,
0x66,
0x77, (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
(byte) 0xee, (byte) 0xff }));
}
}

View file

@ -0,0 +1,222 @@
/* ###
* 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.program.database.sourcemap;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Test;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.sourcemap.*;
public class SourceMapEntryIteratorTest extends AbstractSourceFileTest {
@Test
public void testInappropriateAddress() {
assertFalse(sourceManager.getSourceMapEntryIterator(null, true).hasNext());
assertFalse(sourceManager.getSourceMapEntryIterator(null, false).hasNext());
assertFalse(sourceManager.getSourceMapEntryIterator(Address.NO_ADDRESS, true).hasNext());
assertFalse(sourceManager.getSourceMapEntryIterator(Address.NO_ADDRESS, false).hasNext());
}
@Test
public void testDummySourceManagerIterators() {
Address address = ret2_1.getAddress();
assertFalse(
SourceFileManager.DUMMY.getSourceMapEntryIterator(address, true).hasNext());
assertFalse(
SourceFileManager.DUMMY.getSourceMapEntryIterator(address, false).hasNext());
}
@Test(expected = NoSuchElementException.class)
public void testNoSuchElementExceptionForward() throws AddressOverflowException, LockException {
SourceMapEntry entry1 = null;
int txId = program.startTransaction("adding source map entry");
try {
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
}
finally {
program.endTransaction(txId, true);
}
SourceMapEntryIterator iter =
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
assertTrue(iter.hasNext());
assertEquals(entry1, iter.next());
assertFalse(iter.hasNext());
iter.next();
}
@Test(expected = NoSuchElementException.class)
public void testNoSuchElementExceptionBackward()
throws AddressOverflowException, LockException {
SourceMapEntry entry1 = null;
int txId = program.startTransaction("adding source map entry");
try {
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
}
finally {
program.endTransaction(txId, true);
}
SourceMapEntryIterator iter =
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), false);
assertTrue(iter.hasNext());
assertEquals(entry1, iter.next());
assertFalse(iter.hasNext());
iter.next();
}
@Test
public void testForwardIterator() throws AddressOverflowException, LockException {
int txId = program.startTransaction("adding first source map entry");
SourceMapEntry entry1 = null;
SourceMapEntry entry2 = null;
SourceMapEntry entry3 = null;
SourceMapEntry entry4 = null;
SourceMapEntry entry5 = null;
SourceMapEntryIterator iter =
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
for (int i = 0; i < 10; i++) {
assertFalse(iter.hasNext());
}
try {
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 2);
entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_1.getAddress().add(1), 0);
entry4 = sourceManager.addSourceMapEntry(source3, 4, nop1_1.getAddress(), 1);
entry5 = sourceManager.addSourceMapEntry(source3, 5, nop1_1.getAddress(), 1);
}
finally {
program.endTransaction(txId, true);
}
Set<SourceMapEntry> entries = new HashSet<>();
iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
for (int i = 0; i < 10; i++) {
assertTrue(iter.hasNext());
}
entries.add(iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(entries.contains(entry1));
assertTrue(entries.contains(entry2));
entries.clear();
assertTrue(iter.hasNext());
assertEquals(entry3, iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(entries.contains(entry4));
assertTrue(entries.contains(entry5));
assertFalse(iter.hasNext());
entries.clear();
iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress().add(1), true);
assertTrue(iter.hasNext());
assertEquals(entry3, iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(entries.contains(entry4));
assertTrue(entries.contains(entry5));
assertFalse(iter.hasNext());
iter = sourceManager.getSourceMapEntryIterator(ret2_2.getAddress(), true);
assertFalse(iter.hasNext());
}
@Test
public void testBackwardIterator() throws AddressOverflowException, LockException {
int txId = program.startTransaction("adding first source map entry");
SourceMapEntry entry1 = null;
SourceMapEntry entry2 = null;
SourceMapEntry entry3 = null;
SourceMapEntry entry4 = null;
SourceMapEntry entry5 = null;
SourceMapEntryIterator iter =
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), false);
for (int i = 0; i < 10; i++) {
assertFalse(iter.hasNext());
}
try {
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 2);
entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_1.getAddress().add(1), 0);
entry4 = sourceManager.addSourceMapEntry(source3, 4, nop1_1.getAddress(), 1);
entry5 = sourceManager.addSourceMapEntry(source3, 5, nop1_1.getAddress(), 1);
}
finally {
program.endTransaction(txId, true);
}
Set<SourceMapEntry> entries = new HashSet<>();
iter = sourceManager.getSourceMapEntryIterator(nop1_1.getAddress(), false);
for (int i = 0; i < 10; i++) {
assertTrue(iter.hasNext());
}
entries.add(iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(entries.contains(entry4));
assertTrue(entries.contains(entry5));
entries.clear();
assertTrue(iter.hasNext());
assertEquals(entry3, iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(entries.contains(entry1));
assertTrue(entries.contains(entry1));
assertFalse(iter.hasNext());
entries.clear();
iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress().add(1), false);
assertTrue(iter.hasNext());
assertEquals(entry3, iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(iter.hasNext());
entries.add(iter.next());
assertTrue(entries.contains(entry1));
assertTrue(entries.contains(entry2));
assertFalse(iter.hasNext());
}
}