mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-06-02 16:59:49 +08:00
GP-3883 added source file manager
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+4
-2
@@ -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 =
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+19
-1
@@ -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
|
||||
|
||||
+46
-4
@@ -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()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+128
-17
@@ -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;
|
||||
|
||||
|
||||
+1
-1
@@ -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 =
|
||||
|
||||
+17
-5
@@ -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());
|
||||
}
|
||||
|
||||
+259
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+249
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+39
-11
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
+44
-33
@@ -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];
|
||||
}
|
||||
|
||||
+32
-13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
-15
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+18
-2
@@ -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;
|
||||
|
||||
+261
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -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;
|
||||
|
||||
}
|
||||
+149
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+857
File diff suppressed because it is too large
Load Diff
+116
@@ -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;
|
||||
}
|
||||
+160
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+158
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+95
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+3
-2
@@ -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
|
||||
*/
|
||||
|
||||
+10
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+100
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
+199
@@ -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;
|
||||
|
||||
}
|
||||
+80
@@ -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();
|
||||
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+1
-2
@@ -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.
|
||||
|
||||
+5
-1
@@ -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();
|
||||
|
||||
|
||||
+47
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -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());
|
||||
}
|
||||
}
|
||||
+1150
File diff suppressed because it is too large
Load Diff
+1207
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user