mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-3883 added source file manager
This commit is contained in:
parent
420dd7ce0c
commit
9aeeaa4397
52 changed files with 8432 additions and 306 deletions
93
Ghidra/Features/Base/ghidra_scripts/AddSourceFileScript.java
Normal file
93
Ghidra/Features/Base/ghidra_scripts/AddSourceFileScript.java
Normal file
|
@ -0,0 +1,93 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Adds a SourceFile with a user-defined path and name to the program.
|
||||
//@category SourceMapping
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.features.base.values.GhidraValuesMap;
|
||||
import ghidra.program.database.sourcemap.SourceFile;
|
||||
import ghidra.program.database.sourcemap.SourceFileIdType;
|
||||
import ghidra.util.MessageType;
|
||||
|
||||
public class AddSourceFileScript extends GhidraScript {
|
||||
|
||||
private static final String PATH = "Source File Path";
|
||||
private static final String ID_TYPE = "Id Type";
|
||||
private static final String IDENTIFIER = "Identifier";
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
if (isRunningHeadless()) {
|
||||
println("This script must be run through the Ghidra GUI");
|
||||
return;
|
||||
}
|
||||
if (currentProgram == null) {
|
||||
popup("This script requires an open program");
|
||||
return;
|
||||
}
|
||||
if (!currentProgram.hasExclusiveAccess()) {
|
||||
popup("This script requires exclusive access to the program");
|
||||
return;
|
||||
}
|
||||
|
||||
GhidraValuesMap values = new GhidraValuesMap();
|
||||
values.defineString(PATH, "/");
|
||||
SourceFileIdType[] idTypes = SourceFileIdType.values();
|
||||
String[] enumNames = new String[idTypes.length];
|
||||
for (int i = 0; i < enumNames.length; ++i) {
|
||||
enumNames[i] = idTypes[i].name();
|
||||
}
|
||||
values.defineChoice(ID_TYPE, SourceFileIdType.NONE.name(), enumNames);
|
||||
values.defineString(IDENTIFIER, StringUtils.EMPTY);
|
||||
|
||||
values.setValidator((valueMap, status) -> {
|
||||
String path = valueMap.getString(PATH);
|
||||
SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
|
||||
byte[] identifier = null;
|
||||
if (idType != SourceFileIdType.NONE) {
|
||||
identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
|
||||
}
|
||||
try {
|
||||
SourceFile srcFile = new SourceFile(path, idType, identifier);
|
||||
if (currentProgram.getSourceFileManager().containsSourceFile(srcFile)) {
|
||||
status.setStatusText("SourceFile " + srcFile + " already exists",
|
||||
MessageType.ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
status.setStatusText(e.getMessage(), MessageType.ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
askValues("Enter (Absolute) Source File URI Path",
|
||||
"e.g.: /usr/bin/echo, /C:/Programs/file.exe", values);
|
||||
String absolutePath = values.getString(PATH);
|
||||
SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
|
||||
byte[] identifier = null;
|
||||
if (idType != SourceFileIdType.NONE) {
|
||||
identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
|
||||
}
|
||||
SourceFile srcFile = new SourceFile(absolutePath, idType, identifier);
|
||||
currentProgram.getSourceFileManager().addSourceFile(srcFile);
|
||||
printf("Successfully added source file %s%n", srcFile.toString());
|
||||
}
|
||||
|
||||
}
|
146
Ghidra/Features/Base/ghidra_scripts/AddSourceMapEntryScript.java
Normal file
146
Ghidra/Features/Base/ghidra_scripts/AddSourceMapEntryScript.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Add a source map entry for the current selection.
|
||||
// The current selection must consist of a single address range.
|
||||
// If there is no selection, a length 0 entry will be added at the current address.
|
||||
//@category SourceMapping
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.features.base.values.GhidraValuesMap;
|
||||
import ghidra.program.database.sourcemap.SourceFile;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.sourcemap.*;
|
||||
import ghidra.util.MessageType;
|
||||
|
||||
public class AddSourceMapEntryScript extends GhidraScript {
|
||||
|
||||
private static final String LINE_NUM = "Line Number";
|
||||
private static final String SOURCE_FILE = "Source File";
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
if (isRunningHeadless()) {
|
||||
println("This script must be run through the Ghidra GUI");
|
||||
return;
|
||||
}
|
||||
if (currentProgram == null) {
|
||||
popup("This script requires an open program");
|
||||
return;
|
||||
}
|
||||
if (!currentProgram.hasExclusiveAccess()) {
|
||||
popup("This script requires exclusive access to the program");
|
||||
return;
|
||||
}
|
||||
|
||||
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
|
||||
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
|
||||
if (sourceFiles.isEmpty()) {
|
||||
popup("You must first add at least one source file to the program");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean valid = isCurrentSelectionValid();
|
||||
if (!valid) {
|
||||
return; // checkCurrentSelection will tell the user what the problem is
|
||||
}
|
||||
|
||||
Address baseAddr =
|
||||
currentSelection == null ? currentAddress : currentSelection.getMinAddress();
|
||||
long length = currentSelection == null ? 0 : currentSelection.getNumAddresses();
|
||||
|
||||
AddressRange currentRange =
|
||||
currentSelection == null ? null : currentSelection.getAddressRanges().next();
|
||||
|
||||
Map<String, SourceFile> stringsToSourceFiles = new HashMap<>();
|
||||
sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
|
||||
|
||||
GhidraValuesMap values = new GhidraValuesMap();
|
||||
values.defineInt(LINE_NUM, 1);
|
||||
String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
|
||||
Arrays.sort(sourceFileArray);
|
||||
values.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
|
||||
|
||||
values.setValidator((valueMap, status) -> {
|
||||
int lineNum = values.getInt(LINE_NUM);
|
||||
if (lineNum < 0) {
|
||||
status.setStatusText("Line number cannot be negative", MessageType.ERROR);
|
||||
return false;
|
||||
}
|
||||
if (currentRange == null) {
|
||||
return true; // length 0 entry, nothing else to check
|
||||
}
|
||||
SourceFile source = new SourceFile(values.getChoice(SOURCE_FILE));
|
||||
|
||||
for (SourceMapEntry entry : sourceManager.getSourceMapEntries(source, lineNum)) {
|
||||
// because checkCurrentSelection returned true, if the entries intersect
|
||||
// they must be equal
|
||||
if (entry.getLength() != 0 && entry.getRange().intersects(currentRange)) {
|
||||
status.setStatusText(currentSelection + " is already mapped to " +
|
||||
source.getPath() + ":" + lineNum);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
askValues("Source Map Info", "Enter Source Map Info (length = " + length + ")", values);
|
||||
int lineNum = values.getInt(LINE_NUM);
|
||||
String fileString = values.getChoice(SOURCE_FILE);
|
||||
SourceFile source = stringsToSourceFiles.get(fileString);
|
||||
sourceManager.addSourceMapEntry(source, lineNum, baseAddr, length);
|
||||
}
|
||||
|
||||
// check that the selected range doesn't already intersect a SourceMapEntry with a
|
||||
// conflicting range.
|
||||
private boolean isCurrentSelectionValid() {
|
||||
if (currentSelection == null) {
|
||||
return true;
|
||||
}
|
||||
if (currentSelection.getNumAddressRanges() != 1) {
|
||||
popup("This script requires the current selection to be a single address range");
|
||||
return false;
|
||||
}
|
||||
AddressRange range = currentSelection.getFirstRange();
|
||||
Address end = range.getMaxAddress();
|
||||
SourceMapEntryIterator iter =
|
||||
currentProgram.getSourceFileManager().getSourceMapEntryIterator(end, false);
|
||||
while (iter.hasNext()) {
|
||||
SourceMapEntry entry = iter.next();
|
||||
if (!entry.getBaseAddress().getAddressSpace().equals(range.getAddressSpace())) {
|
||||
return true; // iterator has exhausted entries in the address space, no conflicts
|
||||
// are possible
|
||||
}
|
||||
if (entry.getLength() == 0) {
|
||||
continue; // length 0 entries can't conflict
|
||||
}
|
||||
AddressRange entryRange = entry.getRange();
|
||||
if (entryRange.equals(range)) {
|
||||
return true; // range is the same as a range already in the db, so no problems
|
||||
}
|
||||
if (entryRange.intersects(range)) {
|
||||
popup("Selection conflicts with existing entry " + entry.toString());
|
||||
return false;
|
||||
}
|
||||
if (entryRange.getMaxAddress().compareTo(range.getMinAddress()) < 0) {
|
||||
return true; // no conflicting entries exists
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,857 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import db.*;
|
||||
import db.util.ErrorHandler;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.ManagerDB;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.map.AddressMapDB;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.sourcemap.*;
|
||||
import ghidra.program.util.ProgramEvent;
|
||||
import ghidra.util.Lock;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Database Manager for managing source files and source map information.
|
||||
*/
|
||||
public class SourceFileManagerDB implements SourceFileManager, ManagerDB, ErrorHandler {
|
||||
|
||||
private ProgramDB program;
|
||||
|
||||
private SourceFileAdapter sourceFileTableAdapter;
|
||||
private SourceMapAdapter sourceMapTableAdapter;
|
||||
private AddressMapDB addrMap;
|
||||
|
||||
protected final Lock lock;
|
||||
|
||||
private Long lastKey;
|
||||
private SourceFile lastSourceFile;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param dbh database handle
|
||||
* @param addrMap map longs to addresses
|
||||
* @param openMode mode
|
||||
* @param lock program synchronization lock
|
||||
* @param monitor task monitor
|
||||
* @throws VersionException if the database is incompatible with the current schema
|
||||
*/
|
||||
public SourceFileManagerDB(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode, Lock lock,
|
||||
TaskMonitor monitor) throws VersionException {
|
||||
this.addrMap = addrMap;
|
||||
sourceFileTableAdapter = SourceFileAdapter.getAdapter(dbh, openMode, monitor);
|
||||
sourceMapTableAdapter = SourceMapAdapter.getAdapter(dbh, addrMap, openMode, monitor);
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgram(ProgramDB program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programReady(OpenMode openMode, int currentRevision, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCache(boolean all) throws IOException {
|
||||
lastKey = null;
|
||||
lastSourceFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}<br>
|
||||
* Note: this method will split any source map entries that <b>intersect</b> the address
|
||||
* range but are not entirely contained within it. Parts within the range to delete will
|
||||
* be deleted.
|
||||
* @param start first address in range
|
||||
* @param end last address in range
|
||||
* @param monitor task monitor
|
||||
* @throws CancelledException if {@code monitor} is cancelled
|
||||
*/
|
||||
@Override
|
||||
public void deleteAddressRange(Address start, Address end, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
AddressRange.checkValidRange(start, end);
|
||||
AddressRange rangeToDelete = new AddressRangeImpl(start, end);
|
||||
lock.acquire();
|
||||
try {
|
||||
|
||||
RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(end, false);
|
||||
boolean sourceMapChanged = false;
|
||||
|
||||
// Note: we iterate backwards since records are stored based on the start address.
|
||||
// We need to delete records stored before the beginning of the delete range that
|
||||
// overlap the delete range, but we will never need to delete records that start after
|
||||
// the delete range.
|
||||
List<SourceMapEntryData> entriesToCreate = new ArrayList<>();
|
||||
while (recIter.hasPrevious()) {
|
||||
monitor.checkCancelled();
|
||||
DBRecord rec = recIter.previous();
|
||||
Address recStart = getStartAddress(rec);
|
||||
long recLength = getLength(rec);
|
||||
|
||||
if (recLength == 0) {
|
||||
// if length 0 entry is in range to delete, delete entry
|
||||
// otherwise ignore
|
||||
if (rangeToDelete.contains(recStart)) {
|
||||
recIter.delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Address recEnd = getEndAddress(recStart,recLength);
|
||||
|
||||
if (!rangeToDelete.intersects(recStart, recEnd)) {
|
||||
// we've found an entry that does not touch the range to delete
|
||||
// this means we've handled all relevant entries and can stop
|
||||
break;
|
||||
}
|
||||
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
long fileId = fileAndLine >> 32;
|
||||
int lineNum = (int) (fileAndLine & 0xffffffff);
|
||||
if (isLessInSameSpace(recStart, start)) {
|
||||
// rangeToDelete intersects (recStart,recEnd), so
|
||||
// the entry must overlap the left endpoint of rangeToDelete
|
||||
long length = start.subtract(recStart);
|
||||
SourceMapEntryData data =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, length);
|
||||
entriesToCreate.add(data);
|
||||
}
|
||||
|
||||
if (isLessInSameSpace(end, recEnd)) {
|
||||
// rangeToDelete intersects (recStart,recEnd)
|
||||
// entry must overlap right endpoint of rangeToDelete
|
||||
long length = recEnd.subtract(end);
|
||||
SourceMapEntryData data =
|
||||
new SourceMapEntryData(fileId, lineNum, end.add(1), length);
|
||||
entriesToCreate.add(data);
|
||||
}
|
||||
recIter.delete();
|
||||
}
|
||||
// add the new entries
|
||||
for (SourceMapEntryData data : entriesToCreate) {
|
||||
sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
|
||||
data.baseAddress, data.length);
|
||||
}
|
||||
if (sourceMapChanged) {
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}<br>
|
||||
* Note: this method will move any source map entry which is entirely contained within the
|
||||
* source address range. Entries which also contain addresses outside of this range will
|
||||
* be split and parts within the range to move will be moved.
|
||||
* @param fromAddr first address of range to be moved
|
||||
* @param toAddr target address
|
||||
* @param length number of addresses to move
|
||||
* @param monitor task monitor
|
||||
* @throws AddressOverflowException if overflow occurs when computing new addresses
|
||||
* @throws CancelledException if {@code monitor} is cancelled
|
||||
*/
|
||||
@Override
|
||||
public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
|
||||
throws AddressOverflowException, CancelledException {
|
||||
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid negative length for moveAddressRange: " + length);
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
lock.acquire();
|
||||
|
||||
try {
|
||||
Address rangeToMoveEnd = fromAddr.addNoWrap(length - 1);
|
||||
AddressRange rangeToMove = new AddressRangeImpl(fromAddr, rangeToMoveEnd);
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(rangeToMoveEnd, false);
|
||||
boolean mapChanged = false;
|
||||
|
||||
// Note: we iterate backwards since records are stored based on the start address.
|
||||
// We need to move records stored before the beginning of rangeToMove that
|
||||
// overlap rangeToMove but we will never need to move records that start after
|
||||
// rangeToMove.
|
||||
List<SourceMapEntryData> entriesToCreate = new ArrayList<>();
|
||||
while (recIter.hasPrevious()) {
|
||||
monitor.checkCancelled();
|
||||
DBRecord rec = recIter.previous();
|
||||
long entryLength = getLength(rec);
|
||||
if (entryLength == 0) {
|
||||
continue; // nothing to check
|
||||
}
|
||||
Address recStart = getStartAddress(rec);
|
||||
Address recEnd = getEndAddress(recStart, entryLength);
|
||||
|
||||
if (!rangeToMove.intersects(recStart, recEnd)) {
|
||||
// we've found an entry entirely before rangeToMove
|
||||
// this means we can stop looking
|
||||
break;
|
||||
}
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
long fileId = fileAndLine >> 32;
|
||||
int lineNum = (int) (fileAndLine & 0xffffffff);
|
||||
|
||||
if (isLessInSameSpace(recStart, fromAddr) &&
|
||||
isLessInSameSpace(rangeToMoveEnd, recEnd)) {
|
||||
// entry extends over left and right endpoint of rangeToMove
|
||||
long newLength = fromAddr.subtract(recStart);
|
||||
SourceMapEntryData left =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
|
||||
entriesToCreate.add(left);
|
||||
SourceMapEntryData middle =
|
||||
new SourceMapEntryData(fileId, lineNum, fromAddr, length);
|
||||
entriesToCreate.add(middle);
|
||||
newLength = recEnd.subtract(rangeToMoveEnd);
|
||||
SourceMapEntryData right =
|
||||
new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
|
||||
entriesToCreate.add(right);
|
||||
recIter.delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isLessInSameSpace(recStart, fromAddr)) {
|
||||
// entry extends before left endpoint (but not past right)
|
||||
long newLength = fromAddr.subtract(recStart);
|
||||
SourceMapEntryData left =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
|
||||
entriesToCreate.add(left);
|
||||
newLength = recEnd.subtract(fromAddr) + 1;
|
||||
SourceMapEntryData middle =
|
||||
new SourceMapEntryData(fileId, lineNum, fromAddr, newLength);
|
||||
entriesToCreate.add(middle);
|
||||
recIter.delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isLessInSameSpace(rangeToMoveEnd, recEnd)) {
|
||||
// entry extends past right endpoint (but not before left)
|
||||
long newLength = rangeToMoveEnd.subtract(recStart) + 1;
|
||||
SourceMapEntryData middle =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
|
||||
entriesToCreate.add(middle);
|
||||
newLength = recEnd.subtract(rangeToMoveEnd);
|
||||
SourceMapEntryData right =
|
||||
new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
|
||||
entriesToCreate.add(right);
|
||||
recIter.delete();
|
||||
continue;
|
||||
}
|
||||
// entry is entirely within range to move, no adjustment needed
|
||||
mapChanged = true;
|
||||
|
||||
}
|
||||
|
||||
// add the new entries
|
||||
for (SourceMapEntryData data : entriesToCreate) {
|
||||
sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
|
||||
data.baseAddress, data.length);
|
||||
}
|
||||
|
||||
mapChanged = mapChanged || !entriesToCreate.isEmpty();
|
||||
sourceMapTableAdapter.moveAddressRange(fromAddr, toAddr, length, monitor);
|
||||
|
||||
if (mapChanged) {
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addSourceFile(SourceFile sourceFile) throws LockException {
|
||||
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
|
||||
program.checkExclusiveAccess();
|
||||
lock.acquire();
|
||||
try {
|
||||
Long key = getKeyForSourceFile(sourceFile);
|
||||
if (key != null) {
|
||||
return false;
|
||||
}
|
||||
DBRecord dbRecord = sourceFileTableAdapter.createSourceFileRecord(sourceFile);
|
||||
updateLastSourceFileAndLastKey(dbRecord);
|
||||
program.setObjChanged(ProgramEvent.SOURCE_FILE_ADDED, null, null, sourceFile);
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
|
||||
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
|
||||
boolean mapChanged = false;
|
||||
program.checkExclusiveAccess();
|
||||
lock.acquire();
|
||||
try {
|
||||
Long key = getKeyForSourceFile(sourceFile);
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
|
||||
while (recIter.hasNext()) {
|
||||
recIter.next();
|
||||
recIter.delete();
|
||||
mapChanged = true;
|
||||
}
|
||||
sourceFileTableAdapter.removeSourceFileRecord(key);
|
||||
lastKey = null;
|
||||
lastSourceFile = null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
program.setObjChanged(ProgramEvent.SOURCE_FILE_REMOVED, null, sourceFile, null);
|
||||
if (mapChanged) {
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, sourceFile, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceMapEntry> getSourceMapEntries(Address addr) {
|
||||
|
||||
List<SourceMapEntry> sourceMapEntries = new ArrayList<>();
|
||||
|
||||
lock.acquire();
|
||||
try {
|
||||
RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(addr, false);
|
||||
boolean foundNonZeroLength = false;
|
||||
while (recIter.hasPrevious()) {
|
||||
DBRecord rec = recIter.previous();
|
||||
long entryLength = getLength(rec);
|
||||
Address entryBase = getStartAddress(rec);
|
||||
if (addr.equals(entryBase)) {
|
||||
sourceMapEntries.add(getSourceMapEntry(rec));
|
||||
if (entryLength != 0) {
|
||||
foundNonZeroLength = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (entryLength == 0) {
|
||||
continue; // only want length zero entries if they are based at addr
|
||||
}
|
||||
if (!foundNonZeroLength &&
|
||||
isLessOrEqualInSameSpace(addr, getEndAddress(entryBase, entryLength))) {
|
||||
sourceMapEntries.add(getSourceMapEntry(rec));
|
||||
continue; // continue in case there are additional entries at entryBase
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
Collections.sort(sourceMapEntries);
|
||||
return sourceMapEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
|
||||
long length) throws LockException, AddressOverflowException {
|
||||
if (lineNumber < 0) {
|
||||
throw new IllegalArgumentException("lineNumber cannot be negative");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length cannot be negative");
|
||||
}
|
||||
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
|
||||
Objects.requireNonNull(baseAddr, "baseAddr cannot be null");
|
||||
MemoryBlock startBlock = program.getMemory().getBlock(baseAddr);
|
||||
if (startBlock == null) {
|
||||
throw new AddressOutOfBoundsException(baseAddr + " is not in a defined memory block");
|
||||
}
|
||||
program.checkExclusiveAccess();
|
||||
|
||||
lock.acquire();
|
||||
try {
|
||||
Long sourceFileId = getKeyForSourceFile(sourceFile);
|
||||
if (sourceFileId == null) {
|
||||
throw new IllegalArgumentException(
|
||||
sourceFile.toString() + " not associated with program");
|
||||
}
|
||||
if (length == 0) {
|
||||
return addZeroLengthEntry(sourceFileId, lineNumber, baseAddr);
|
||||
}
|
||||
Address endAddr = baseAddr.addNoWrap(length - 1);
|
||||
|
||||
// check that the entry's range is entirely contained within defined memory blocks
|
||||
if (!startBlock.contains(endAddr)) {
|
||||
if (!program.getMemory().contains(baseAddr, endAddr)) {
|
||||
throw new AddressOutOfBoundsException(
|
||||
baseAddr + "," + endAddr + " spans undefined memory");
|
||||
}
|
||||
}
|
||||
SourceMapEntryDB entry = null;
|
||||
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(endAddr, false);
|
||||
while (recIter.hasPrevious()) {
|
||||
DBRecord rec = recIter.previous();
|
||||
long entryLength = getLength(rec);
|
||||
if (entryLength == 0) {
|
||||
continue; // length 0 entries can't conflict
|
||||
}
|
||||
Address entryBase = getStartAddress(rec);
|
||||
if (entryBase.equals(baseAddr)) {
|
||||
if (entryLength != length) {
|
||||
throw new IllegalArgumentException(
|
||||
"new entry must have the same length as existing entry");
|
||||
}
|
||||
if ((sourceFileId << 32 | lineNumber) == getFileAndLine(rec)) {
|
||||
return getSourceMapEntry(rec); // entry is already in the DB
|
||||
}
|
||||
continue; // non-conflicting entry found, continue checking
|
||||
}
|
||||
if (isLessOrEqualInSameSpace(baseAddr, entryBase)) {
|
||||
throw new IllegalArgumentException(
|
||||
"new entry would overlap entry " + getSourceMapEntry(rec).toString());
|
||||
}
|
||||
if (isLessOrEqualInSameSpace(entryBase, baseAddr)) {
|
||||
if (getEndAddress(entryBase, entryLength).compareTo(baseAddr) >= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"new entry would overlap entry " + getSourceMapEntry(rec).toString());
|
||||
}
|
||||
}
|
||||
break; // safe to add new entry
|
||||
}
|
||||
DBRecord rec =
|
||||
sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, length);
|
||||
entry = new SourceMapEntryDB(this, rec, addrMap);
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
|
||||
return entry;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
throw new AssertionError("addSourceMapEntry unsuccessful - possible database error");
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSourceMapEntry(AddressSetView addrs) {
|
||||
if (addrs == null || addrs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
lock.acquire();
|
||||
try {
|
||||
for (AddressRangeIterator rangeIter = addrs.getAddressRanges(); rangeIter.hasNext();) {
|
||||
AddressRange r = rangeIter.next();
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(r.getMaxAddress(), false);
|
||||
while (recIter.hasPrevious()) {
|
||||
DBRecord rec = recIter.previous();
|
||||
Address entryStart = getStartAddress(rec);
|
||||
if (r.contains(entryStart)) {
|
||||
return true;
|
||||
}
|
||||
long length = getLength(rec);
|
||||
if (length == 0) {
|
||||
continue;
|
||||
}
|
||||
if (r.intersects(entryStart, getEndAddress(entryStart, length))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbError(IOException e) throws RuntimeException {
|
||||
program.dbError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceFile> getMappedSourceFiles() {
|
||||
List<SourceFile> sourceFiles = new ArrayList<>();
|
||||
lock.acquire();
|
||||
try {
|
||||
for (RecordIterator sourceFileRecordIter =
|
||||
sourceFileTableAdapter.getRecords(); sourceFileRecordIter
|
||||
.hasNext();) {
|
||||
DBRecord sourceFileRecord = sourceFileRecordIter.next();
|
||||
long key = sourceFileRecord.getKey();
|
||||
RecordIterator sourceMapEntryRecordIter =
|
||||
sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
|
||||
if (sourceMapEntryRecordIter.hasNext()) {
|
||||
updateLastSourceFileAndLastKey(sourceFileRecord);
|
||||
sourceFiles.add(lastSourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceFile> getAllSourceFiles() {
|
||||
List<SourceFile> sourceFiles = new ArrayList<>();
|
||||
lock.acquire();
|
||||
try {
|
||||
for (RecordIterator recordIter = sourceFileTableAdapter.getRecords(); recordIter
|
||||
.hasNext();) {
|
||||
updateLastSourceFileAndLastKey(recordIter.next());
|
||||
sourceFiles.add(lastSourceFile);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferSourceMapEntries(SourceFile source, SourceFile target)
|
||||
throws LockException {
|
||||
program.checkExclusiveAccess();
|
||||
|
||||
lock.acquire();
|
||||
try {
|
||||
Long srcKey = getKeyForSourceFile(source);
|
||||
if (srcKey == null) {
|
||||
throw new IllegalArgumentException(
|
||||
source.toString() + " is not associated with program");
|
||||
}
|
||||
Long targetKey = getKeyForSourceFile(target);
|
||||
if (targetKey == null) {
|
||||
throw new IllegalArgumentException(
|
||||
target.toString() + " is not associated with program");
|
||||
}
|
||||
if (source.equals(target)) {
|
||||
return; // transfer redundant
|
||||
}
|
||||
for (SourceMapEntry entry : getSourceMapEntries(source, 0, Integer.MAX_VALUE)) {
|
||||
addSourceMapEntry(target, entry.getLineNumber(), entry.getBaseAddress(),
|
||||
entry.getLength());
|
||||
removeSourceMapEntry(entry); // remove fires a SOURCE_MAP_CHANGED event
|
||||
}
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
// can't happen - entry ranges were validated upon insert
|
||||
throw new AssertionError("bad address range in source map entry table");
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
|
||||
try {
|
||||
return new SourceMapEntryIteratorDB(this,
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(address, forward), forward);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
return SourceMapEntryIterator.EMPTY_ITERATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSourceFile(SourceFile sourceFile) {
|
||||
if (sourceFile == null) {
|
||||
return false;
|
||||
}
|
||||
lock.acquire();
|
||||
try {
|
||||
return getKeyForSourceFile(sourceFile) != null;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
|
||||
int maxLine) {
|
||||
if (minLine < 0) {
|
||||
throw new IllegalArgumentException("minLine cannot be negative; was " + minLine);
|
||||
}
|
||||
if (maxLine < 0) {
|
||||
throw new IllegalArgumentException("maxLine cannot be negative; was " + maxLine);
|
||||
}
|
||||
if (maxLine < minLine) {
|
||||
throw new IllegalArgumentException("maxLine cannot be less than minLine");
|
||||
}
|
||||
List<SourceMapEntry> entries = new ArrayList<>();
|
||||
lock.acquire();
|
||||
try {
|
||||
Long key = getKeyForSourceFile(sourceFile);
|
||||
if (key == null) {
|
||||
return entries;
|
||||
}
|
||||
try {
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getRecordsForSourceFile(key, minLine, maxLine);
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
entries.add(getSourceMapEntry(rec));
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
Collections.sort(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
|
||||
Objects.requireNonNull(entry, "entry cannot be null");
|
||||
program.checkExclusiveAccess();
|
||||
lock.acquire();
|
||||
try {
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(entry.getBaseAddress(), true);
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
long length = getLength(rec);
|
||||
if (length != entry.getLength()) {
|
||||
continue;
|
||||
}
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
if (((int) (fileAndLine & 0xffffffff)) != entry.getLineNumber()) {
|
||||
continue;
|
||||
}
|
||||
if (!(entry.getSourceFile().equals(getSourceFileFromKey(fileAndLine >> 32)))) {
|
||||
continue;
|
||||
}
|
||||
sourceMapTableAdapter.removeRecord(rec.getKey());
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, entry, null);
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceFile getSourceFile(long key) {
|
||||
lock.acquire();
|
||||
try {
|
||||
return getSourceFileFromKey(key);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
SourceMapEntry getSourceMapEntry(DBRecord rec) {
|
||||
return new SourceMapEntryDB(this, rec, addrMap);
|
||||
}
|
||||
|
||||
static boolean isLessOrEqualInSameSpace(Address addr1, Address addr2) {
|
||||
if (!addr1.hasSameAddressSpace(addr2)) {
|
||||
return false;
|
||||
}
|
||||
return addr1.compareTo(addr2) <= 0;
|
||||
}
|
||||
|
||||
static boolean isLessInSameSpace(Address addr1, Address addr2) {
|
||||
if (!addr1.hasSameAddressSpace(addr2)) {
|
||||
return false;
|
||||
}
|
||||
return addr1.compareTo(addr2) < 0;
|
||||
}
|
||||
|
||||
// acquire lock before invoking
|
||||
private SourceMapEntry addZeroLengthEntry(long sourceFileId, int lineNumber, Address baseAddr) {
|
||||
SourceMapEntry entry = null;
|
||||
try {
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(baseAddr, true);
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
Address recAddress = getStartAddress(rec);
|
||||
if (!recAddress.equals(baseAddr)) {
|
||||
break;
|
||||
}
|
||||
if (getLength(rec) != 0) {
|
||||
continue;
|
||||
}
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
if ((sourceFileId << 32 | lineNumber) != fileAndLine) {
|
||||
continue;
|
||||
}
|
||||
return getSourceMapEntry(rec);
|
||||
}
|
||||
DBRecord rec = sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, 0);
|
||||
entry = new SourceMapEntryDB(this, rec, addrMap);
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
|
||||
return entry;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
throw new AssertionError("addZeroLengthEntry unsuccessful - possible database error");
|
||||
}
|
||||
}
|
||||
|
||||
private long getLength(DBRecord rec) {
|
||||
return rec.getLongValue(SourceMapAdapter.LENGTH_COL);
|
||||
}
|
||||
|
||||
private Address getStartAddress(DBRecord rec) {
|
||||
return addrMap.decodeAddress(rec.getLongValue(SourceMapAdapter.BASE_ADDR_COL));
|
||||
}
|
||||
|
||||
private long getFileAndLine(DBRecord rec) {
|
||||
return rec.getLongValue(SourceMapAdapter.FILE_LINE_COL);
|
||||
}
|
||||
|
||||
// assumes that start and length are from a valid SourceMapEntry
|
||||
// that is, start.add(length - 1) doesn't wrap and is in the same
|
||||
// space
|
||||
private Address getEndAddress(Address start, long length) {
|
||||
if (length == 0) {
|
||||
return start;
|
||||
}
|
||||
try {
|
||||
return start.addNoWrap(length - 1);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
// shouldn't happen, but return space max to prevent possibility of wrapping
|
||||
return start.getAddressSpace().getMaxAddress();
|
||||
}
|
||||
}
|
||||
|
||||
// acquire lock before invoking
|
||||
private SourceFile getSourceFileFromKey(long key) {
|
||||
if (lastKey == null || lastKey.longValue() != key) {
|
||||
DBRecord dbRecord = null;
|
||||
try {
|
||||
dbRecord = sourceFileTableAdapter.getRecord(key);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return null;
|
||||
}
|
||||
if (dbRecord == null) {
|
||||
return null;
|
||||
}
|
||||
updateLastSourceFileAndLastKey(dbRecord);
|
||||
}
|
||||
return lastSourceFile;
|
||||
}
|
||||
|
||||
// acquire lock before invoking
|
||||
private Long getKeyForSourceFile(SourceFile sourceFile) {
|
||||
if (lastSourceFile == null || !sourceFile.equals(lastSourceFile)) {
|
||||
DBRecord dbRecord = null;
|
||||
try {
|
||||
dbRecord = sourceFileTableAdapter.getRecord(sourceFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return null;
|
||||
}
|
||||
if (dbRecord == null) {
|
||||
return null;
|
||||
}
|
||||
updateLastSourceFileAndLastKey(dbRecord);
|
||||
}
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
private void updateLastSourceFileAndLastKey(DBRecord dbRecord) {
|
||||
lastKey = dbRecord.getKey();
|
||||
String path = dbRecord.getString(SourceFileAdapter.PATH_COL);
|
||||
SourceFileIdType idType =
|
||||
SourceFileIdType.getTypeFromIndex(dbRecord.getByteValue(SourceFileAdapter.ID_TYPE_COL));
|
||||
byte[] identifier = dbRecord.getBinaryData(SourceFileAdapter.ID_COL);
|
||||
lastSourceFile = new SourceFile(path, idType, identifier, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A record for storing information about new source map entries which must be created
|
||||
* during {@link #moveAddressRange} or {@link #deleteAddressRange}
|
||||
*/
|
||||
private record SourceMapEntryData(long sourceFileId, int lineNumber, Address baseAddress,
|
||||
long length) {}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,508 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Test;
|
||||
import org.python.google.common.primitives.Longs;
|
||||
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.util.SourceFileUtils;
|
||||
|
||||
public class SourceFileTest extends AbstractSourceFileTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNonFilePathFailure() {
|
||||
new SourceFile("/test/dir/");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRelativePathCreateFailure() {
|
||||
new SourceFile("test1/test2.c");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNullPathCreateFailure() {
|
||||
new SourceFile((String) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testEmptyPathCreateFailure() {
|
||||
new SourceFile(StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathNormalization() {
|
||||
assertEquals("/src/dir1/dir2/file.c",
|
||||
new SourceFile("/src/test/../dir1/test/../dir2/file.c").getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFilename() {
|
||||
assertEquals("file.c", new SourceFile("/src/test/file.c").getFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUtilityMethod() {
|
||||
SourceFile linux = SourceFileUtils.getSourceFileFromPathString("/src/test/../file1.c");
|
||||
assertEquals("/src/file1.c", linux.getPath());
|
||||
assertEquals("file1.c", linux.getFilename());
|
||||
|
||||
SourceFile windows =
|
||||
SourceFileUtils.getSourceFileFromPathString("c:\\src\\test\\..\\file1.c");
|
||||
assertEquals("/c:/src/file1.c", windows.getPath());
|
||||
assertEquals("file1.c", windows.getFilename());
|
||||
|
||||
windows =
|
||||
SourceFileUtils.getSourceFileFromPathString("/C:/Users//guest/./temp/../file.exe");
|
||||
assertEquals("/C:/Users/guest/file.exe", windows.getPath());
|
||||
assertEquals("file.exe", windows.getFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicAddRemoveTest() throws LockException {
|
||||
assertEquals(3, sourceManager.getAllSourceFiles().size());
|
||||
SourceFile testSource = new SourceFile("/home/user/src/file.c");
|
||||
|
||||
int txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
assertTrue(sourceManager.addSourceFile(testSource));
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
assertEquals(4, sourceManager.getAllSourceFiles().size());
|
||||
|
||||
txId = program.startTransaction("adding same source file");
|
||||
try {
|
||||
assertFalse(sourceManager.addSourceFile(testSource));
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
assertEquals(4, sourceManager.getAllSourceFiles().size());
|
||||
|
||||
txId = program.startTransaction("removing source file");
|
||||
try {
|
||||
assertTrue(sourceManager.removeSourceFile(testSource));
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
assertEquals(3, sourceManager.getAllSourceFiles().size());
|
||||
|
||||
txId = program.startTransaction("removing source file again");
|
||||
try {
|
||||
assertFalse(sourceManager.removeSourceFile(testSource));
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
assertEquals(3, sourceManager.getAllSourceFiles().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsNull() {
|
||||
assertFalse(sourceManager.containsSourceFile(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicContainsTest() throws LockException, IOException {
|
||||
assertTrue(sourceManager.containsSourceFile(source1));
|
||||
int txId = program.startTransaction("removing source1");
|
||||
try {
|
||||
assertTrue(sourceManager.removeSourceFile(source1));
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
assertFalse(sourceManager.containsSourceFile(source1));
|
||||
assertTrue(sourceManager.containsSourceFile(source2));
|
||||
assertTrue(sourceManager.containsSourceFile(source3));
|
||||
|
||||
program.undo();
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(source1));
|
||||
|
||||
program.redo();
|
||||
|
||||
assertFalse(sourceManager.containsSourceFile(source1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoAddingSourceFile() throws LockException, IOException {
|
||||
SourceFile test = new SourceFile("/a/b/c.h");
|
||||
int txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test));
|
||||
|
||||
program.undo();
|
||||
|
||||
assertFalse(sourceManager.containsSourceFile(test));
|
||||
|
||||
program.redo();
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUri() throws URISyntaxException {
|
||||
String path = "/src/test.c";
|
||||
URI uri = new URI("file", null, path, null);
|
||||
SourceFile testFile = new SourceFile(path);
|
||||
assertEquals(uri, testFile.getUri());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentifierDisplayString() {
|
||||
String path = "/src/test/file.c";
|
||||
HexFormat hexFormat = HexFormat.of();
|
||||
|
||||
SourceFile sourceFile = new SourceFile(path, SourceFileIdType.MD5,
|
||||
hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
|
||||
assertEquals(SourceFileIdType.MD5, sourceFile.getIdType());
|
||||
assertEquals(sourceFile.getIdAsString(), "0123456789abcdef0123456789abcdef");
|
||||
|
||||
sourceFile = new SourceFile(path);
|
||||
assertEquals(SourceFileIdType.NONE, sourceFile.getIdType());
|
||||
assertEquals(StringUtils.EMPTY, sourceFile.getIdAsString());
|
||||
|
||||
sourceFile = new SourceFile(path, SourceFileIdType.SHA1,
|
||||
hexFormat.parseHex("0123456789abcdef0123456789abcdef01234567"));
|
||||
assertEquals(SourceFileIdType.SHA1, sourceFile.getIdType());
|
||||
assertEquals("0123456789abcdef0123456789abcdef01234567", sourceFile.getIdAsString());
|
||||
|
||||
sourceFile = new SourceFile(path, SourceFileIdType.SHA256,
|
||||
hexFormat.parseHex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
|
||||
assertEquals(SourceFileIdType.SHA256, sourceFile.getIdType());
|
||||
assertEquals("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
sourceFile.getIdAsString());
|
||||
|
||||
sourceFile = new SourceFile(path, SourceFileIdType.SHA512,
|
||||
hexFormat.parseHex(
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" +
|
||||
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
|
||||
assertEquals(
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" +
|
||||
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
sourceFile.getIdAsString());
|
||||
|
||||
sourceFile = new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
|
||||
assertEquals(SourceFileIdType.TIMESTAMP_64, sourceFile.getIdType());
|
||||
assertEquals("1970-01-01T00:00:00Z", sourceFile.getIdAsString());
|
||||
|
||||
sourceFile = new SourceFile(path, SourceFileIdType.UNKNOWN, new byte[] { 0x12, 0x13 });
|
||||
assertEquals(SourceFileIdType.UNKNOWN, sourceFile.getIdType());
|
||||
assertEquals("1213", sourceFile.getIdAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSamePathDifferentIdentifiers() throws LockException {
|
||||
HexFormat hexFormat = HexFormat.of();
|
||||
|
||||
assertEquals(3, sourceManager.getAllSourceFiles().size());
|
||||
String path = "/src/test/file.c";
|
||||
SourceFile test1 = new SourceFile(path);
|
||||
SourceFile test2 = new SourceFile(path, SourceFileIdType.MD5,
|
||||
hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
|
||||
SourceFile test3 =
|
||||
new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
|
||||
|
||||
assertNotEquals(test1, test2);
|
||||
assertNotEquals(test1, test3);
|
||||
assertNotEquals(test2, test3);
|
||||
|
||||
assertFalse(sourceManager.containsSourceFile(test1));
|
||||
assertFalse(sourceManager.containsSourceFile(test2));
|
||||
assertFalse(sourceManager.containsSourceFile(test3));
|
||||
|
||||
int txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test1);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(4, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertFalse(sourceManager.containsSourceFile(test2));
|
||||
assertFalse(sourceManager.containsSourceFile(test3));
|
||||
|
||||
txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test2);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(5, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertTrue(sourceManager.containsSourceFile(test2));
|
||||
assertFalse(sourceManager.containsSourceFile(test3));
|
||||
|
||||
txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test3);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(6, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertTrue(sourceManager.containsSourceFile(test2));
|
||||
assertTrue(sourceManager.containsSourceFile(test3));
|
||||
|
||||
//repeat adding test3
|
||||
txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test3);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(6, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertTrue(sourceManager.containsSourceFile(test2));
|
||||
assertTrue(sourceManager.containsSourceFile(test3));
|
||||
|
||||
txId = program.startTransaction("removing source file");
|
||||
try {
|
||||
sourceManager.removeSourceFile(test2);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(5, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertFalse(sourceManager.containsSourceFile(test2));
|
||||
assertTrue(sourceManager.containsSourceFile(test3));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameIdentifierDifferentPaths() throws LockException {
|
||||
HexFormat hexFormat = HexFormat.of();
|
||||
assertEquals(3, sourceManager.getAllSourceFiles().size());
|
||||
byte[] md5 = hexFormat.parseHex("0123456789abcdef0123456789abcdef");
|
||||
|
||||
SourceFile test1 = new SourceFile("/src/file1.c", SourceFileIdType.MD5, md5);
|
||||
SourceFile test2 = new SourceFile("/src/file2.c", SourceFileIdType.MD5, md5);
|
||||
SourceFile test3 = new SourceFile("/src/file3.c", SourceFileIdType.MD5, md5);
|
||||
|
||||
assertNotEquals(test1, test2);
|
||||
assertNotEquals(test1, test3);
|
||||
assertNotEquals(test2, test3);
|
||||
|
||||
assertFalse(sourceManager.containsSourceFile(test1));
|
||||
assertFalse(sourceManager.containsSourceFile(test2));
|
||||
assertFalse(sourceManager.containsSourceFile(test3));
|
||||
|
||||
int txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test1);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(4, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertFalse(sourceManager.containsSourceFile(test2));
|
||||
assertFalse(sourceManager.containsSourceFile(test3));
|
||||
|
||||
txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test2);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(5, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertTrue(sourceManager.containsSourceFile(test2));
|
||||
assertFalse(sourceManager.containsSourceFile(test3));
|
||||
|
||||
txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test3);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(6, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertTrue(sourceManager.containsSourceFile(test2));
|
||||
assertTrue(sourceManager.containsSourceFile(test3));
|
||||
|
||||
//repeat adding test3
|
||||
txId = program.startTransaction("adding source file");
|
||||
try {
|
||||
sourceManager.addSourceFile(test3);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(6, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertTrue(sourceManager.containsSourceFile(test2));
|
||||
assertTrue(sourceManager.containsSourceFile(test3));
|
||||
|
||||
txId = program.startTransaction("removing source file");
|
||||
try {
|
||||
sourceManager.removeSourceFile(test2);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
sourceFiles = sourceManager.getAllSourceFiles();
|
||||
assertEquals(5, sourceFiles.size());
|
||||
|
||||
assertTrue(sourceManager.containsSourceFile(test1));
|
||||
assertFalse(sourceManager.containsSourceFile(test2));
|
||||
assertTrue(sourceManager.containsSourceFile(test3));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoIdentifierNonNullArray() {
|
||||
SourceFile sourceFile =
|
||||
new SourceFile("/src/file.c", SourceFileIdType.NONE, new byte[] { 0x11, 0x22 });
|
||||
assertEquals("/src/file.c", sourceFile.getPath());
|
||||
assertEquals(SourceFileIdType.NONE, sourceFile.getIdType());
|
||||
|
||||
// array passed to SourceFile constructor should be ignored
|
||||
assertTrue(Arrays.equals(new byte[0], sourceFile.getIdentifier()));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testBadMd5Length() {
|
||||
new SourceFile("/file.c", SourceFileIdType.MD5, new byte[] { 0x11, 0x22 });
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testMd5NullArray() {
|
||||
new SourceFile("/file.c", SourceFileIdType.MD5, null);
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testAddingNullSourceFile() throws LockException {
|
||||
int txId = program.startTransaction("adding null SourceFile");
|
||||
try {
|
||||
sourceManager.addSourceFile(null);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testRemovingNullSourceFile() throws LockException {
|
||||
int txId = program.startTransaction("removing null source file");
|
||||
try {
|
||||
assertFalse(sourceManager.removeSourceFile(null));
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testConvertingNullArrayToLong() {
|
||||
SourceFileUtils.byteArrayToLong(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testConvertingEmptyArrayToLong() {
|
||||
SourceFileUtils.byteArrayToLong(new byte[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongConversion() {
|
||||
long testLong = 0x0102030405060708L;
|
||||
byte[] testArray = new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 };
|
||||
assertEquals(testLong, SourceFileUtils.byteArrayToLong(testArray));
|
||||
assertTrue(Arrays.equals(testArray, SourceFileUtils.longToByteArray(testLong)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHexStringToByteArrayConversion() {
|
||||
assertTrue(Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(null)));
|
||||
assertTrue(
|
||||
Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(StringUtils.EMPTY)));
|
||||
assertTrue(Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(" ")));
|
||||
byte[] testArray = new byte[] { 0x00, 0x01, (byte) 0xaa, (byte) 0xff };
|
||||
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0001aaff")));
|
||||
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0001AAFF")));
|
||||
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0X0001AAFF")));
|
||||
assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0x0001aaff")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testByteArrayToHexStringConversion() {
|
||||
assertEquals(StringUtils.EMPTY, SourceFileUtils.byteArrayToHexString(null));
|
||||
assertEquals(StringUtils.EMPTY, SourceFileUtils.byteArrayToHexString(new byte[0]));
|
||||
assertEquals("00112233445566778899aabbccddeeff",
|
||||
SourceFileUtils.byteArrayToHexString(new byte[] { 0x0, 0x11, 0x22, 0x33, 0x44, 0x55,
|
||||
0x66,
|
||||
0x77, (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
|
||||
(byte) 0xee, (byte) 0xff }));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressOverflowException;
|
||||
import ghidra.program.model.sourcemap.*;
|
||||
|
||||
public class SourceMapEntryIteratorTest extends AbstractSourceFileTest {
|
||||
|
||||
@Test
|
||||
public void testInappropriateAddress() {
|
||||
assertFalse(sourceManager.getSourceMapEntryIterator(null, true).hasNext());
|
||||
assertFalse(sourceManager.getSourceMapEntryIterator(null, false).hasNext());
|
||||
assertFalse(sourceManager.getSourceMapEntryIterator(Address.NO_ADDRESS, true).hasNext());
|
||||
assertFalse(sourceManager.getSourceMapEntryIterator(Address.NO_ADDRESS, false).hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDummySourceManagerIterators() {
|
||||
Address address = ret2_1.getAddress();
|
||||
assertFalse(
|
||||
SourceFileManager.DUMMY.getSourceMapEntryIterator(address, true).hasNext());
|
||||
assertFalse(
|
||||
SourceFileManager.DUMMY.getSourceMapEntryIterator(address, false).hasNext());
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void testNoSuchElementExceptionForward() throws AddressOverflowException, LockException {
|
||||
SourceMapEntry entry1 = null;
|
||||
|
||||
int txId = program.startTransaction("adding source map entry");
|
||||
try {
|
||||
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
SourceMapEntryIterator iter =
|
||||
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
|
||||
assertTrue(iter.hasNext());
|
||||
assertEquals(entry1, iter.next());
|
||||
assertFalse(iter.hasNext());
|
||||
iter.next();
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
public void testNoSuchElementExceptionBackward()
|
||||
throws AddressOverflowException, LockException {
|
||||
SourceMapEntry entry1 = null;
|
||||
|
||||
int txId = program.startTransaction("adding source map entry");
|
||||
try {
|
||||
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
SourceMapEntryIterator iter =
|
||||
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), false);
|
||||
assertTrue(iter.hasNext());
|
||||
assertEquals(entry1, iter.next());
|
||||
assertFalse(iter.hasNext());
|
||||
iter.next();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForwardIterator() throws AddressOverflowException, LockException {
|
||||
int txId = program.startTransaction("adding first source map entry");
|
||||
SourceMapEntry entry1 = null;
|
||||
SourceMapEntry entry2 = null;
|
||||
SourceMapEntry entry3 = null;
|
||||
SourceMapEntry entry4 = null;
|
||||
SourceMapEntry entry5 = null;
|
||||
|
||||
SourceMapEntryIterator iter =
|
||||
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertFalse(iter.hasNext());
|
||||
}
|
||||
|
||||
try {
|
||||
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
|
||||
entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 2);
|
||||
entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_1.getAddress().add(1), 0);
|
||||
entry4 = sourceManager.addSourceMapEntry(source3, 4, nop1_1.getAddress(), 1);
|
||||
entry5 = sourceManager.addSourceMapEntry(source3, 5, nop1_1.getAddress(), 1);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
Set<SourceMapEntry> entries = new HashSet<>();
|
||||
iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertTrue(iter.hasNext());
|
||||
}
|
||||
entries.add(iter.next());
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(entries.contains(entry1));
|
||||
assertTrue(entries.contains(entry2));
|
||||
|
||||
entries.clear();
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
assertEquals(entry3, iter.next());
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(entries.contains(entry4));
|
||||
assertTrue(entries.contains(entry5));
|
||||
assertFalse(iter.hasNext());
|
||||
entries.clear();
|
||||
|
||||
iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress().add(1), true);
|
||||
assertTrue(iter.hasNext());
|
||||
assertEquals(entry3, iter.next());
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(entries.contains(entry4));
|
||||
assertTrue(entries.contains(entry5));
|
||||
assertFalse(iter.hasNext());
|
||||
|
||||
iter = sourceManager.getSourceMapEntryIterator(ret2_2.getAddress(), true);
|
||||
assertFalse(iter.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackwardIterator() throws AddressOverflowException, LockException {
|
||||
int txId = program.startTransaction("adding first source map entry");
|
||||
SourceMapEntry entry1 = null;
|
||||
SourceMapEntry entry2 = null;
|
||||
SourceMapEntry entry3 = null;
|
||||
SourceMapEntry entry4 = null;
|
||||
SourceMapEntry entry5 = null;
|
||||
|
||||
SourceMapEntryIterator iter =
|
||||
sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), false);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertFalse(iter.hasNext());
|
||||
}
|
||||
|
||||
try {
|
||||
entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
|
||||
entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 2);
|
||||
entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_1.getAddress().add(1), 0);
|
||||
entry4 = sourceManager.addSourceMapEntry(source3, 4, nop1_1.getAddress(), 1);
|
||||
entry5 = sourceManager.addSourceMapEntry(source3, 5, nop1_1.getAddress(), 1);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
Set<SourceMapEntry> entries = new HashSet<>();
|
||||
iter = sourceManager.getSourceMapEntryIterator(nop1_1.getAddress(), false);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertTrue(iter.hasNext());
|
||||
}
|
||||
entries.add(iter.next());
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(entries.contains(entry4));
|
||||
assertTrue(entries.contains(entry5));
|
||||
|
||||
entries.clear();
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
assertEquals(entry3, iter.next());
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(entries.contains(entry1));
|
||||
assertTrue(entries.contains(entry1));
|
||||
assertFalse(iter.hasNext());
|
||||
entries.clear();
|
||||
|
||||
iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress().add(1), false);
|
||||
assertTrue(iter.hasNext());
|
||||
assertEquals(entry3, iter.next());
|
||||
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(iter.hasNext());
|
||||
entries.add(iter.next());
|
||||
assertTrue(entries.contains(entry1));
|
||||
assertTrue(entries.contains(entry2));
|
||||
assertFalse(iter.hasNext());
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue