GP-3883 added source file manager

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

View File

@@ -0,0 +1,93 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Adds a SourceFile with a user-defined path and name to the program.
//@category SourceMapping
import java.util.HexFormat;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.util.MessageType;
public class AddSourceFileScript extends GhidraScript {
private static final String PATH = "Source File Path";
private static final String ID_TYPE = "Id Type";
private static final String IDENTIFIER = "Identifier";
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
if (!currentProgram.hasExclusiveAccess()) {
popup("This script requires exclusive access to the program");
return;
}
GhidraValuesMap values = new GhidraValuesMap();
values.defineString(PATH, "/");
SourceFileIdType[] idTypes = SourceFileIdType.values();
String[] enumNames = new String[idTypes.length];
for (int i = 0; i < enumNames.length; ++i) {
enumNames[i] = idTypes[i].name();
}
values.defineChoice(ID_TYPE, SourceFileIdType.NONE.name(), enumNames);
values.defineString(IDENTIFIER, StringUtils.EMPTY);
values.setValidator((valueMap, status) -> {
String path = valueMap.getString(PATH);
SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
byte[] identifier = null;
if (idType != SourceFileIdType.NONE) {
identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
}
try {
SourceFile srcFile = new SourceFile(path, idType, identifier);
if (currentProgram.getSourceFileManager().containsSourceFile(srcFile)) {
status.setStatusText("SourceFile " + srcFile + " already exists",
MessageType.ERROR);
return false;
}
}
catch (IllegalArgumentException e) {
status.setStatusText(e.getMessage(), MessageType.ERROR);
return false;
}
return true;
});
askValues("Enter (Absolute) Source File URI Path",
"e.g.: /usr/bin/echo, /C:/Programs/file.exe", values);
String absolutePath = values.getString(PATH);
SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
byte[] identifier = null;
if (idType != SourceFileIdType.NONE) {
identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
}
SourceFile srcFile = new SourceFile(absolutePath, idType, identifier);
currentProgram.getSourceFileManager().addSourceFile(srcFile);
printf("Successfully added source file %s%n", srcFile.toString());
}
}

View File

@@ -0,0 +1,146 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Add a source map entry for the current selection.
// The current selection must consist of a single address range.
// If there is no selection, a length 0 entry will be added at the current address.
//@category SourceMapping
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.sourcemap.*;
import ghidra.util.MessageType;
public class AddSourceMapEntryScript extends GhidraScript {
private static final String LINE_NUM = "Line Number";
private static final String SOURCE_FILE = "Source File";
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
if (!currentProgram.hasExclusiveAccess()) {
popup("This script requires exclusive access to the program");
return;
}
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
List<SourceFile> sourceFiles = sourceManager.getAllSourceFiles();
if (sourceFiles.isEmpty()) {
popup("You must first add at least one source file to the program");
return;
}
boolean valid = isCurrentSelectionValid();
if (!valid) {
return; // checkCurrentSelection will tell the user what the problem is
}
Address baseAddr =
currentSelection == null ? currentAddress : currentSelection.getMinAddress();
long length = currentSelection == null ? 0 : currentSelection.getNumAddresses();
AddressRange currentRange =
currentSelection == null ? null : currentSelection.getAddressRanges().next();
Map<String, SourceFile> stringsToSourceFiles = new HashMap<>();
sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
GhidraValuesMap values = new GhidraValuesMap();
values.defineInt(LINE_NUM, 1);
String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
Arrays.sort(sourceFileArray);
values.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
values.setValidator((valueMap, status) -> {
int lineNum = values.getInt(LINE_NUM);
if (lineNum < 0) {
status.setStatusText("Line number cannot be negative", MessageType.ERROR);
return false;
}
if (currentRange == null) {
return true; // length 0 entry, nothing else to check
}
SourceFile source = new SourceFile(values.getChoice(SOURCE_FILE));
for (SourceMapEntry entry : sourceManager.getSourceMapEntries(source, lineNum)) {
// because checkCurrentSelection returned true, if the entries intersect
// they must be equal
if (entry.getLength() != 0 && entry.getRange().intersects(currentRange)) {
status.setStatusText(currentSelection + " is already mapped to " +
source.getPath() + ":" + lineNum);
return false;
}
}
return true;
});
askValues("Source Map Info", "Enter Source Map Info (length = " + length + ")", values);
int lineNum = values.getInt(LINE_NUM);
String fileString = values.getChoice(SOURCE_FILE);
SourceFile source = stringsToSourceFiles.get(fileString);
sourceManager.addSourceMapEntry(source, lineNum, baseAddr, length);
}
// check that the selected range doesn't already intersect a SourceMapEntry with a
// conflicting range.
private boolean isCurrentSelectionValid() {
if (currentSelection == null) {
return true;
}
if (currentSelection.getNumAddressRanges() != 1) {
popup("This script requires the current selection to be a single address range");
return false;
}
AddressRange range = currentSelection.getFirstRange();
Address end = range.getMaxAddress();
SourceMapEntryIterator iter =
currentProgram.getSourceFileManager().getSourceMapEntryIterator(end, false);
while (iter.hasNext()) {
SourceMapEntry entry = iter.next();
if (!entry.getBaseAddress().getAddressSpace().equals(range.getAddressSpace())) {
return true; // iterator has exhausted entries in the address space, no conflicts
// are possible
}
if (entry.getLength() == 0) {
continue; // length 0 entries can't conflict
}
AddressRange entryRange = entry.getRange();
if (entryRange.equals(range)) {
return true; // range is the same as a range already in the db, so no problems
}
if (entryRange.intersects(range)) {
popup("Selection conflicts with existing entry " + entry.toString());
return false;
}
if (entryRange.getMaxAddress().compareTo(range.getMinAddress()) < 0) {
return true; // no conflicting entries exists
}
}
return true;
}
}

View File

@@ -4,16 +4,18 @@
* 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 binary
// Adds DWARF source file line number info to the current binary as EOL comments.
// Note that you can run this script on a program that has already been analyzed by the
// DWARF analyzer.
//@category DWARF
import java.io.IOException;
import java.util.List;
@@ -29,7 +31,7 @@ import ghidra.program.model.listing.CodeUnit;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
public class DWARFLineInfoScript extends GhidraScript {
public class DWARFLineInfoCommentScript extends GhidraScript {
@Override
protected void run() throws Exception {
DWARFSectionProvider dsp =

View File

@@ -0,0 +1,154 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Adds DWARF source file line number info to the current program as source map entries.
// Note that you can run this script on a program that has already been analyzed by the
// DWARF analyzer.
//@category DWARF
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.*;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr;
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider;
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
public class DWARFLineInfoSourceMapScript extends GhidraScript {
public static final int ENTRY_MAX_LENGTH = 1000;
@Override
protected void run() throws Exception {
if (!currentProgram.hasExclusiveAccess()) {
Msg.showError(this, null, "Exclusive Access Required",
"Must have exclusive access to a program to add source map info");
return;
}
DWARFSectionProvider dsp =
DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor);
if (dsp == null) {
printerr("Unable to find DWARF information");
return;
}
DWARFImportOptions importOptions = new DWARFImportOptions();
try (DWARFProgram dprog = new DWARFProgram(currentProgram, importOptions, monitor, dsp)) {
dprog.init(monitor);
addSourceLineInfo(dprog);
}
}
private void addSourceLineInfo(DWARFProgram dprog)
throws CancelledException, IOException, LockException, AddressOverflowException {
BinaryReader reader = dprog.getDebugLineBR();
if (reader == null) {
popup("Unable to get reader for debug line info");
return;
}
int entryCount = 0;
monitor.initialize(reader.length(), "DWARF Source Map Info");
List<DWARFCompilationUnit> compUnits = dprog.getCompilationUnits();
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
List<SourceFileAddr> sourceInfo = new ArrayList<>();
for (DWARFCompilationUnit cu : compUnits) {
sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader));
}
sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address()));
monitor.initialize(sourceInfo.size());
for (int i = 0; i < sourceInfo.size(); i++) {
monitor.checkCancelled();
monitor.increment(1);
SourceFileAddr sourceFileAddr = sourceInfo.get(i);
if (sourceFileAddr.isEndSequence()) {
continue;
}
Address addr = dprog.getCodeAddress(sourceFileAddr.address());
if (!currentProgram.getMemory().getExecuteSet().contains(addr)) {
printerr(
"entry for non-executable address; skipping: file %s line %d address: %s %x"
.formatted(sourceFileAddr.fileName(), sourceFileAddr.lineNum(),
addr.toString(), sourceFileAddr.address()));
continue;
}
long length = getLength(i, sourceInfo);
if (length < 0) {
println(
"Error computing entry length for file %s line %d address %s %x; replacing" +
" with length 0 entry".formatted(sourceFileAddr.fileName(),
sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address()));
length = 0;
}
if (length > ENTRY_MAX_LENGTH) {
println(
("entry for file %s line %d address: %s %x length %d too large, replacing " +
"with length 0 entry").formatted(sourceFileAddr.fileName(),
sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address(),
length));
length = 0;
}
if (sourceFileAddr.fileName() == null) {
continue;
}
SourceFile source = null;
try {
SourceFileIdType type =
sourceFileAddr.md5() == null ? SourceFileIdType.NONE : SourceFileIdType.MD5;
source = new SourceFile(sourceFileAddr.fileName(), type, sourceFileAddr.md5());
sourceManager.addSourceFile(source);
}
catch (IllegalArgumentException e) {
printerr("Exception creating source file %s".formatted(e.getMessage()));
continue;
}
try {
sourceManager.addSourceMapEntry(source, sourceFileAddr.lineNum(), addr, length);
}
catch (IllegalArgumentException e) {
printerr(e.getMessage());
continue;
}
entryCount++;
}
println("Added " + entryCount + " source map entries");
}
private long getLength(int i, List<SourceFileAddr> allSFA) {
SourceFileAddr iAddr = allSFA.get(i);
long iOffset = iAddr.address();
for (int j = i + 1; j < allSFA.size(); j++) {
SourceFileAddr current = allSFA.get(j);
long currentAddr = current.address();
if (current.isEndSequence()) {
return currentAddr + 1 - iOffset;
}
if (currentAddr != iOffset) {
return currentAddr - iOffset;
}
}
return -1;
}
}

View File

@@ -0,0 +1,63 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Select and remove a source map entry at the current address.
//@category SourceMapping
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.model.sourcemap.SourceMapEntry;
public class RemoveSourceMapEntryScript extends GhidraScript {
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
if (!currentProgram.hasExclusiveAccess()) {
popup("Modifying the source map requires exclusive access to the program");
return;
}
List<SourceMapEntry> entries =
currentProgram.getSourceFileManager().getSourceMapEntries(currentAddress);
if (entries.isEmpty()) {
popup("No source map entries at " + currentAddress);
return;
}
Map<String, SourceMapEntry> stringToEntry = new HashMap<>();
List<String> choices = new ArrayList<>();
for (SourceMapEntry entry : entries) {
String entryString = entry.toString();
stringToEntry.put(entryString, entry);
choices.add(entryString);
}
GhidraValuesMap values = new GhidraValuesMap();
String[] choiceArray = choices.toArray(new String[] {});
values.defineChoice("Entry", choiceArray[0], choiceArray);
askValues("Select SourceMapEntry to remove", null, values);
String selectedString = values.getChoice("Entry");
SourceMapEntry toDelete = stringToEntry.get(selectedString);
currentProgram.getSourceFileManager().removeSourceMapEntry(toDelete);
}
}

View File

@@ -0,0 +1,113 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Sets the current selection based on source file and line number parameters
//@category SourceMapping
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.util.MessageType;
import ghidra.util.SourceFileUtils;
import ghidra.util.SourceFileUtils.SourceLineBounds;
public class SelectAddressesMappedToSourceFileScript extends GhidraScript {
private static final String SOURCE_FILE = "Source File";
private static final String MIN_LINE = "Minimum Source Line";
private static final String MAX_LINE = " Maximum Source Line";
@Override
protected void run() throws Exception {
if (isRunningHeadless()) {
println("This script must be run through the Ghidra GUI");
return;
}
if (currentProgram == null) {
popup("This script requires an open program");
return;
}
SourceFileManager sourceManager = currentProgram.getSourceFileManager();
List<SourceFile> sourceFiles = sourceManager.getMappedSourceFiles();
if (sourceFiles.isEmpty()) {
popup(currentProgram.getName() + " contains no mapped source files");
return;
}
GhidraValuesMap sourceFileValue = new GhidraValuesMap();
Map<String, SourceFile> stringsToSourceFiles = new HashMap<>();
sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
Arrays.sort(sourceFileArray);
sourceFileValue.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
askValues("Select Source File", "Source File", sourceFileValue);
String sfString = sourceFileValue.getChoice(SOURCE_FILE);
SourceFile mappedSourceFile = stringsToSourceFiles.get(sfString);
SourceLineBounds bounds =
SourceFileUtils.getSourceLineBounds(currentProgram, mappedSourceFile);
GhidraValuesMap boundValues = new GhidraValuesMap();
boundValues.defineInt(MIN_LINE, bounds.min());
boundValues.setInt(MIN_LINE, bounds.min());
boundValues.defineInt(MAX_LINE, bounds.max());
boundValues.setInt(MAX_LINE, bounds.max());
boundValues.setValidator((valueMap, status) -> {
int minLine = boundValues.getInt(MIN_LINE);
if (minLine < 0) {
status.setStatusText("Line number cannot be negative", MessageType.ERROR);
return false;
}
int maxLine = boundValues.getInt(MAX_LINE);
if (maxLine < minLine) {
status.setStatusText("Max line cannot be less than min line", MessageType.ERROR);
return false;
}
return true;
});
setReusePreviousChoices(false);
askValues("Select Line Bounds", "Bounds for " + mappedSourceFile.getFilename(),
boundValues);
setReusePreviousChoices(true);
int minLine = boundValues.getInt(MIN_LINE);
int maxLine = boundValues.getInt(MAX_LINE);
AddressSet selection = new AddressSet();
List<SourceMapEntry> entries =
sourceManager.getSourceMapEntries(mappedSourceFile, minLine, maxLine);
for (SourceMapEntry entry : entries) {
if (entry.getLength() == 0) {
selection.add(entry.getBaseAddress());
continue;
}
selection.add(entry.getRange());
}
if (selection.isEmpty()) {
popup("No addresses for the selected file and range");
return;
}
setCurrentSelection(selection);
}
}