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

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

View file

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

View file

@ -0,0 +1,90 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.io.IOException;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Base class for adapters to access the Source File table. The table has one column, which stores
* the path of the source file (as a String).
*/
abstract class SourceFileAdapter {
static final String TABLE_NAME = "SourceFiles";
static final int PATH_COL = SourceFileAdapterV0.V0_PATH_COL;
static final int ID_TYPE_COL = SourceFileAdapterV0.V0_ID_TYPE_COL;
static final int ID_COL = SourceFileAdapterV0.V0_ID_COL;
/**
* Creates an adapter for the source file table.
* @param handle database handle
* @param openMode open mode
* @param monitor task monitor
* @return adapter for table
* @throws VersionException if version incompatible
*/
static SourceFileAdapter getAdapter(DBHandle handle, OpenMode openMode, TaskMonitor monitor)
throws VersionException {
return new SourceFileAdapterV0(handle, openMode);
}
/**
* Returns a {@link RecordIterator} for this table.
* @return record iterator
* @throws IOException on db error
*/
abstract RecordIterator getRecords() throws IOException;
/**
* Returns the {@link DBRecord} corresponding to {@code sourceFile}, or {@code null} if
* no such record exists.
* @param sourceFile source file
* @return record or null
* @throws IOException on db error
*/
abstract DBRecord getRecord(SourceFile sourceFile) throws IOException;
/**
* Returns the {@link DBRecord} with key {@code id}, or {@code null} if no such record exists.
* @param id id
* @return record or null
* @throws IOException on db error
*/
abstract DBRecord getRecord(long id) throws IOException;
/**
* Creates a {@link DBRecord} for {@link SourceFile} {@code sourceFile}. If a record for
* that source file already exists, the existing record is returned.
* @param sourceFile source file
* @return db record
* @throws IOException on db error
*/
abstract DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException;
/**
* Deletes the record with id {@code id} from the database.
* @param id id to delete
* @return true if deleted successfully
* @throws IOException on database error
*/
abstract boolean removeSourceFileRecord(long id) throws IOException;
}

View file

@ -0,0 +1,149 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.io.IOException;
import java.util.Arrays;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.util.EmptyRecordIterator;
import ghidra.util.exception.VersionException;
/**
* Initial version of {@link SourceFileAdapter}.
*/
class SourceFileAdapterV0 extends SourceFileAdapter implements DBListener {
final static int SCHEMA_VERSION = 0;
static final int V0_PATH_COL = 0;
static final int V0_ID_TYPE_COL = 1;
static final int V0_ID_COL = 2;
private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
new Field[] { StringField.INSTANCE, ByteField.INSTANCE, BinaryField.INSTANCE },
new String[] { "Path", "IdType", "Identifier" }, new int[] { V0_PATH_COL });
private Table table; // lazy creation, null if empty
private final DBHandle dbHandle;
private static final int[] INDEXED_COLUMNS = new int[] { V0_PATH_COL };
SourceFileAdapterV0(DBHandle dbHandle, OpenMode openMode) throws VersionException {
this.dbHandle = dbHandle;
// As in FunctionTagAdapterV0, we need to add this as a database listener.
// Since the table is created lazily, undoing a transaction which (for example) caused
// the table to be created can leave the table in a bad state.
// The implementation of dbRestored(DBHandle) solves this issue.
this.dbHandle.addListener(this);
if (!openMode.equals(OpenMode.CREATE)) {
table = dbHandle.getTable(TABLE_NAME);
if (table == null) {
return; // perform lazy table creation
}
int version = table.getSchema().getVersion();
if (version != SCHEMA_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
}
}
@Override
public void dbRestored(DBHandle dbh) {
table = dbh.getTable(TABLE_NAME);
}
@Override
public void dbClosed(DBHandle dbh) {
// nothing to do
}
@Override
public void tableDeleted(DBHandle dbh, Table deletedTable) {
// nothing to do
}
@Override
public void tableAdded(DBHandle dbh, Table addedTable) {
// nothing to do
}
@Override
RecordIterator getRecords() throws IOException {
if (table == null) {
return new EmptyRecordIterator();
}
return table.iterator();
}
@Override
DBRecord getRecord(long id) throws IOException {
if (table == null) {
return null;
}
return table.getRecord(id);
}
@Override
DBRecord getRecord(SourceFile sourceFile) throws IOException {
if (table == null) {
return null;
}
StringField field = new StringField(sourceFile.getPath());
RecordIterator iter = table.indexIterator(V0_PATH_COL, field, field, true);
while (iter.hasNext()) {
DBRecord rec = iter.next();
if (rec.getByteValue(V0_ID_TYPE_COL) != sourceFile.getIdType().getIndex()) {
continue;
}
if (Arrays.equals(sourceFile.getIdentifier(), rec.getBinaryData(V0_ID_COL))) {
return rec;
}
}
return null;
}
@Override
DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException {
DBRecord rec = getRecord(sourceFile);
if (rec == null) {
rec = V0_SCHEMA.createRecord(getTable().getKey());
rec.setString(V0_PATH_COL, sourceFile.getPath());
rec.setByteValue(V0_ID_TYPE_COL, sourceFile.getIdType().getIndex());
rec.setBinaryData(V0_ID_COL, sourceFile.getIdentifier());
getTable().putRecord(rec);
}
return rec;
}
@Override
boolean removeSourceFileRecord(long id) throws IOException {
if (table != null) {
return table.deleteRecord(id);
}
return false;
}
private Table getTable() throws IOException {
if (table == null) {
table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
}
return table;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.util.HashMap;
import java.util.Map;
/**
* An enum whose values represent source file id types, such as md5 or sha1.
*/
public enum SourceFileIdType {
NONE((byte) 0, 0),
UNKNOWN((byte) 1, 0),
TIMESTAMP_64((byte) 2, 8),
MD5((byte) 3, 16),
SHA1((byte) 4, 20),
SHA256((byte) 5, 32),
SHA512((byte) 6, 64);
public static final int MAX_LENGTH = 64;
private static Map<Byte, SourceFileIdType> valueToEnum;
static {
valueToEnum = new HashMap<>();
for (SourceFileIdType type : SourceFileIdType.values()) {
valueToEnum.put(type.getIndex(), type);
}
}
private final int byteLength;
private final byte index;
private SourceFileIdType(byte index, int length) {
byteLength = length;
this.index = index;
}
/**
* Returns the byte length of the corresponding identifier. A value of 0 indicates
* no restriction.
*
* @return byte length of identifier
*/
public int getByteLength() {
return byteLength;
}
/**
* Returns the index of the identifier type.
* @return index
*/
byte getIndex() {
return index;
}
/**
* Returns the id type given the index.
* @param index index
* @return id type
*/
static SourceFileIdType getTypeFromIndex(byte index) {
return valueToEnum.get(index);
}
}

View file

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

View file

@ -0,0 +1,116 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.io.IOException;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Base class for adapters to access the Source Map table.
* <p>
* Each entry in the table corresponds to a single {@link SourceMapEntry} and so records
* a {@link SourceFile}, a line number, a base address, and a length.
* <p>
* There are a number of restrictions on a {@link SourceMapEntry}, which are listed in that
* interface's top-level documentation. It is the responsibility of the {@link SourceFileManager}
* to enforce these restrictions.
*/
abstract class SourceMapAdapter {
static final String TABLE_NAME = "SourceMap";
static final int FILE_LINE_COL = SourceMapAdapterV0.V0_FILE_LINE_COL;
static final int BASE_ADDR_COL = SourceMapAdapterV0.V0_BASE_ADDR_COL;
static final int LENGTH_COL = SourceMapAdapterV0.V0_LENGTH_COL;
/**
* Creates an adapter for the Source Map table.
* @param dbh database handle
* @param addrMap address map
* @param openMode mode
* @param monitor task monitor
* @return adapter for table
* @throws VersionException if version incompatible
*/
static SourceMapAdapter getAdapter(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode,
TaskMonitor monitor) throws VersionException {
return new SourceMapAdapterV0(dbh, addrMap, openMode);
}
/**
* Removes a record from the table
* @param key key of record to remove.
* @return true if the record was deleted successfully
* @throws IOException if database error occurs
*/
abstract boolean removeRecord(long key) throws IOException;
/**
* Returns a {@link RecordIterator} based at {@code addr}.
* @param addr starting address
* @param before if true, initial position is before addr, otherwise after
* @return iterator
* @throws IOException if database error occurs
*/
abstract RecordIterator getSourceMapRecordIterator(Address addr, boolean before)
throws IOException;
/**
* Returns a {@link RecordIterator} over all records for the source file with
* id {@code id}, subject to the line bounds {@code minLine} and {@code maxLine}
* @param fileId id of source file
* @param minLine minimum line number
* @param maxLine maximum line number
* @return iterator
* @throws IOException if database error occurs
*/
abstract RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
throws IOException;
/**
* Adds an entry to the source map table. This method assumes that no address
* in the associated range has already been associated with this source file and
* line number.
* @param fileId source file id
* @param lineNum line number
* @param baseAddr minimum address of range
* @param length number of addresses in range
* @return record
* @throws IOException if database error occurs
*/
abstract DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
throws IOException;
/**
* Updates all appropriate entries in the table when an address range is moved.
* @param fromAddr from address
* @param toAddr to address
* @param length number of addresses in moved range
* @param monitor task monitor
* @throws CancelledException if task cancelled
* @throws IOException if database error occurs
*/
abstract void moveAddressRange(Address fromAddr, Address toAddr, long length,
TaskMonitor monitor) throws CancelledException, IOException;
}

View file

@ -0,0 +1,160 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.io.IOException;
import db.*;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.map.AddressIndexPrimaryKeyIterator;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.database.util.DatabaseTableUtils;
import ghidra.program.database.util.EmptyRecordIterator;
import ghidra.program.model.address.Address;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Initial version of {@link SourceMapAdapter}
*/
class SourceMapAdapterV0 extends SourceMapAdapter implements DBListener {
final static int SCHEMA_VERSION = 0;
static final int V0_FILE_LINE_COL = 0; // indexed
static final int V0_BASE_ADDR_COL = 1; // indexed
static final int V0_LENGTH_COL = 2;
// key | ((32-bit source file id << 32) | 32-bit line number) | base addr | length
private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
new Field[] { LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE },
new String[] { "fileAndLine", "baseAddress", "length" }, null);
private static final int[] INDEXED_COLUMNS = new int[] { V0_FILE_LINE_COL, V0_BASE_ADDR_COL };
private Table table; // lazy creation, null if empty
private final DBHandle dbHandle;
private AddressMapDB addrMap;
/**
* Creates an adapter for version 0 of the source map adapter
* @param dbh database handle
* @param addrMap address map
* @param openMode open mode
* @throws VersionException if version incompatible
*/
SourceMapAdapterV0(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode)
throws VersionException {
this.dbHandle = dbh;
this.addrMap = addrMap;
// As in FunctionTagAdapterV0, we need to add this as a database listener.
// Since the table is created lazily, undoing a transaction which (for example) caused
// the table to be created can leave the table in a bad state.
// The implementation of dbRestored(DBHandle) solves this issue.
this.dbHandle.addListener(this);
if (!openMode.equals(OpenMode.CREATE)) {
table = dbHandle.getTable(TABLE_NAME);
if (table == null) {
return; // perform lazy table creation
}
int version = table.getSchema().getVersion();
if (version != SCHEMA_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
}
}
@Override
public void dbRestored(DBHandle dbh) {
table = dbh.getTable(TABLE_NAME);
}
@Override
public void dbClosed(DBHandle dbh) {
// nothing to do
}
@Override
public void tableDeleted(DBHandle dbh, Table t) {
// nothing to do
}
@Override
public void tableAdded(DBHandle dbh, Table t) {
// nothing to do
}
@Override
boolean removeRecord(long key) throws IOException {
if (table != null) {
return table.deleteRecord(key);
}
return false;
}
@Override
RecordIterator getSourceMapRecordIterator(Address addr, boolean before) throws IOException {
if (table == null || addr == null) {
return EmptyRecordIterator.INSTANCE;
}
AddressIndexPrimaryKeyIterator keyIter =
new AddressIndexPrimaryKeyIterator(table, V0_BASE_ADDR_COL, addrMap, addr, before);
return new KeyToRecordIterator(table, keyIter);
}
@Override
RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
throws IOException {
if (table == null) {
return EmptyRecordIterator.INSTANCE;
}
fileId = fileId << 32;
LongField minField = new LongField(fileId | minLine);
LongField maxField = new LongField(fileId | maxLine);
return table.indexIterator(V0_FILE_LINE_COL, minField, maxField, true);
}
@Override
DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
throws IOException {
DBRecord rec = V0_SCHEMA.createRecord(getTable().getKey());
rec.setLongValue(V0_FILE_LINE_COL, (fileId << 32) | lineNum);
rec.setLongValue(V0_BASE_ADDR_COL, addrMap.getKey(baseAddr, true));
rec.setLongValue(V0_LENGTH_COL, length);
table.putRecord(rec);
return rec;
}
@Override
void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
throws CancelledException, IOException {
if (table == null) {
return;
}
DatabaseTableUtils.updateIndexedAddressField(table, V0_BASE_ADDR_COL, addrMap, fromAddr,
toAddr, length, null, monitor);
}
private Table getTable() throws IOException {
if (table == null) {
table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
}
return table;
}
}

View file

@ -0,0 +1,158 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.util.Objects;
import db.DBRecord;
import ghidra.program.database.map.AddressMapDB;
import ghidra.program.model.address.*;
import ghidra.program.model.sourcemap.SourceMapEntry;
/**
* Database implementation of {@link SourceMapEntry} interface.
* <p>
* Note: clients should drop and reacquire all SourceMapEntryDB objects upon undo/redo,
* ProgramEvent.SOURCE_MAP_CHANGED, and ProgramEvent.SOURCE_FILE_REMOVED.
*/
public class SourceMapEntryDB implements SourceMapEntry {
private int lineNumber;
private SourceFile sourceFile;
private Address baseAddress;
private long length;
private AddressRange range = null;
/**
* Creates a new SourceMapEntryDB
* @param manager source file manager
* @param record backing record
* @param addrMap address map
*/
SourceMapEntryDB(SourceFileManagerDB manager, DBRecord record, AddressMapDB addrMap) {
manager.lock.acquire();
try {
long fileAndLine = record.getLongValue(SourceMapAdapter.FILE_LINE_COL);
lineNumber = (int) (fileAndLine & 0xffffffff);
sourceFile = manager.getSourceFile(fileAndLine >> 32);
long encodedAddress = record.getLongValue(SourceMapAdapter.BASE_ADDR_COL);
baseAddress = addrMap.decodeAddress(encodedAddress);
length = record.getLongValue(SourceMapAdapter.LENGTH_COL);
if (length != 0) {
Address max;
try {
max = baseAddress.addNoWrap(length - 1);
}
catch (AddressOverflowException e) {
// shouldn't happen, but return space max to prevent possibility of wrapping
max = baseAddress.getAddressSpace().getMaxAddress();
}
range = new AddressRangeImpl(baseAddress, max);
}
}
finally {
manager.lock.release();
}
}
@Override
public int getLineNumber() {
return lineNumber;
}
@Override
public SourceFile getSourceFile() {
return sourceFile;
}
@Override
public Address getBaseAddress() {
return baseAddress;
}
@Override
public AddressRange getRange() {
return range;
}
@Override
public long getLength() {
return length;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer(getSourceFile().toString());
sb.append(":");
sb.append(getLineNumber());
sb.append(" @ ");
sb.append(getBaseAddress().toString());
sb.append(" (");
sb.append(Long.toString(getLength()));
sb.append(")");
return sb.toString();
}
@Override
public int compareTo(SourceMapEntry o) {
int sourceFileCompare = getSourceFile().compareTo(o.getSourceFile());
if (sourceFileCompare != 0) {
return sourceFileCompare;
}
int lineCompare = Integer.compare(getLineNumber(), o.getLineNumber());
if (lineCompare != 0) {
return lineCompare;
}
int addrCompare = getBaseAddress().compareTo(o.getBaseAddress());
if (addrCompare != 0) {
return addrCompare;
}
return Long.compareUnsigned(getLength(), o.getLength());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SourceMapEntry otherEntry)) {
return false;
}
if (lineNumber != otherEntry.getLineNumber()) {
return false;
}
if (!sourceFile.equals(otherEntry.getSourceFile())) {
return false;
}
if (!baseAddress.equals(otherEntry.getBaseAddress())) {
return false;
}
if (length != otherEntry.getLength()) {
return false;
}
if (!Objects.equals(range, otherEntry.getRange())) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hashCode = lineNumber;
hashCode = 31 * hashCode + sourceFile.hashCode();
hashCode = 31 * hashCode + baseAddress.hashCode();
hashCode = 31 * hashCode + Long.hashCode(length);
return hashCode;
}
}

View file

@ -0,0 +1,95 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.database.sourcemap;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import db.DBRecord;
import db.RecordIterator;
import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.program.model.sourcemap.SourceMapEntryIterator;
/**
* Database implementation of {@link SourceMapEntryIterator}
*/
public class SourceMapEntryIteratorDB implements SourceMapEntryIterator {
private boolean forward;
private RecordIterator recIter;
private SourceFileManagerDB sourceManager;
private SourceMapEntry nextEntry;
/**
* Constructor
* @param sourceManager source manager
* @param recIter record iterator
* @param forward direction to iterate
*/
SourceMapEntryIteratorDB(SourceFileManagerDB sourceManager, RecordIterator recIter,
boolean forward) {
this.sourceManager = sourceManager;
this.recIter = recIter;
this.forward = forward;
this.nextEntry = null;
}
@Override
public boolean hasNext() {
if (nextEntry != null) {
return true;
}
sourceManager.lock.acquire();
try {
boolean recIterNext = forward ? recIter.hasNext() : recIter.hasPrevious();
if (!recIterNext) {
return false;
}
DBRecord rec = forward ? recIter.next() : recIter.previous();
nextEntry = sourceManager.getSourceMapEntry(rec);
return true;
}
catch (IOException e) {
sourceManager.dbError(e);
return false;
}
finally {
sourceManager.lock.release();
}
}
@Override
public SourceMapEntry next() {
if (hasNext()) {
SourceMapEntry entryToReturn = nextEntry;
nextEntry = null;
return entryToReturn;
}
throw new NoSuchElementException();
}
@Override
public Iterator<SourceMapEntry> iterator() {
return this;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

View file

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

View file

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

View file

@ -0,0 +1,100 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.sourcemap;
import java.util.Collections;
import java.util.List;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
/**
* A "dummy" implementation of {@link SourceFileManager}.
*/
public class DummySourceFileManager implements SourceFileManager {
public DummySourceFileManager() {
// nothing to do
}
@Override
public List<SourceMapEntry> getSourceMapEntries(Address addr) {
return Collections.emptyList();
}
@Override
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
long length) throws LockException {
throw new UnsupportedOperationException("Cannot add source map entries with this manager");
}
@Override
public boolean intersectsSourceMapEntry(AddressSetView addrs) {
return false;
}
@Override
public List<SourceFile> getAllSourceFiles() {
return Collections.emptyList();
}
@Override
public List<SourceFile> getMappedSourceFiles() {
return Collections.emptyList();
}
@Override
public void transferSourceMapEntries(SourceFile source, SourceFile target) {
throw new UnsupportedOperationException(
"Dummy source file manager cannot transfer map info");
}
@Override
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
return SourceMapEntryIterator.EMPTY_ITERATOR;
}
@Override
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
int maxLine) {
return Collections.emptyList();
}
@Override
public boolean addSourceFile(SourceFile sourceFile) throws LockException {
throw new UnsupportedOperationException("cannot add source files to this manager");
}
@Override
public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
throw new UnsupportedOperationException("cannot remove source files from this manager");
}
@Override
public boolean containsSourceFile(SourceFile sourceFile) {
return false;
}
@Override
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
throw new UnsupportedOperationException(
"cannot remove source map entries from this manager");
}
}

View file

@ -0,0 +1,199 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.sourcemap;
import java.util.List;
import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.*;
/**
* This interface defines methods for managing {@link SourceFile}s and {@link SourceMapEntry}s.
*/
public interface SourceFileManager {
public static final SourceFileManager DUMMY = new DummySourceFileManager();
/**
* Returns a sorted list of {@link SourceMapEntry}s associated with an address {@code addr}.
* @param addr address
* @return line number
*/
public List<SourceMapEntry> getSourceMapEntries(Address addr);
/**
* Adds a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
* line number {@code lineNumber}, and {@link AddressRange} {@code range} to the program
* database.
* <p>
* Entries with non-zero lengths must either cover the same address range or be disjoint.
* @param sourceFile source file
* @param lineNumber line number
* @param range address range
* @return created SourceMapEntry
* @throws LockException if invoked without exclusive access
* @throws IllegalArgumentException if the range of the new entry intersects, but does
* not equal, the range of an existing entry or if sourceFile was not previously added
* to the program.
* @throws AddressOutOfBoundsException if the range of the new entry contains addresses
* that are not in a defined memory block
*/
public default SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber,
AddressRange range) throws LockException {
try {
return addSourceMapEntry(sourceFile, lineNumber, range.getMinAddress(),
range.getLength());
}
// can't happen
catch (AddressOverflowException e) {
throw new AssertionError("Address overflow with valid AddressRange");
}
}
/**
* Creates a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
* line number {@code lineNumber}, and non-negative length {@code length} and
* adds it to the program database.
* <p>
* Entries with non-zero lengths must either cover the same address range or be disjoint.
* @param sourceFile source file
* @param lineNumber line number
* @param baseAddr minimum address of range
* @param length number of addresses in range
* @return created SourceMapEntry
* @throws AddressOverflowException if baseAddr + length-1 overflows
* @throws LockException if invoked without exclusive access
* @throws IllegalArgumentException if the range of the new entry intersects, but does
* not equal, the range of an existing entry or if sourceFile was not previously added to the
* program.
* @throws AddressOutOfBoundsException if the range of the new entry contains addresses
* that are not in a defined memory block
*/
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
long length) throws AddressOverflowException, LockException;
/**
* Returns {@code true} precisely when at least one {@link Address} in {@code addrs} has
* source map information.
* @param addrs addresses to check
* @return true when at least one address has source map info
*/
public boolean intersectsSourceMapEntry(AddressSetView addrs);
/**
* Adds a {@link SourceFile} to this manager. A SourceFile must be added before it can be
* associated with any source map information.
*
* @param sourceFile source file to add (can't be null)
* @return true if this manager did not already contain sourceFile
* @throws LockException if invoked without exclusive access
*/
public boolean addSourceFile(SourceFile sourceFile) throws LockException;
/**
* Removes a {@link SourceFile} from this manager. Any associated {@link SourceMapEntry}s will
* also be removed.
* @param sourceFile source file to remove
* @return true if sourceFile was in the manager
* @throws LockException if invoked without exclusive access
*/
public boolean removeSourceFile(SourceFile sourceFile) throws LockException;
/**
* Returns true precisely when this manager contains {@code sourceFile}.
* @param sourceFile source file
* @return true if source file already added
*/
public boolean containsSourceFile(SourceFile sourceFile);
/**
* Returns a {@link List} containing all {@link SourceFile}s of the program.
* @return source file list
*/
public List<SourceFile> getAllSourceFiles();
/**
* Returns a {@link List} containing {@link SourceFile}s which are
* mapped to at least one address in the program
* @return mapped source file list
*/
public List<SourceFile> getMappedSourceFiles();
/**
* Changes the source map so that any {@link SourceMapEntry} associated with {@code source}
* is associated with {@code target} instead. Any entries associated with
* {@code target} before invocation will still be associated with
* {@code target} after invocation. {@code source} will not be associated
* with any entries after invocation (unless {@code source} and {@code target}
* are the same). Line number information is not changed.
* @param source source file to get info from
* @param target source file to move info to
* @throws LockException if invoked without exclusive access
* @throws IllegalArgumentException if source or target has not been added previously
*/
public void transferSourceMapEntries(SourceFile source, SourceFile target) throws LockException;
/**
* Returns a {@link SourceMapEntryIterator} starting at {@code address}.
*
* @param address starting address
* @param forward direction of iterator (true = forward)
* @return iterator
*/
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward);
/**
* Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
* between {@code minLine} and {@code maxLine}, inclusive.
* @param sourceFile source file
* @param minLine minimum line number
* @param maxLine maximum line number
* @return source map entries
*/
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
int maxLine);
/**
* Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
* equal to {@code lineNumber}.
* @param sourceFile source file
* @param lineNumber line number
* @return source map entries
*/
public default List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int lineNumber) {
return getSourceMapEntries(sourceFile, lineNumber, lineNumber);
}
/**
* Returns a sorted of list all {@link SourceMapEntry}s in the program corresponding to
* {@code sourceFile}.
* @param sourceFile source file
* @return source map entries
*/
public default List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile) {
return getSourceMapEntries(sourceFile, 0, Integer.MAX_VALUE);
}
/**
* Removes a {@link SourceMapEntry} from this manager.
* @param entry entry to remove
* @return true if entry was in the manager
* @throws LockException if invoked without exclusive access
*/
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException;
}

View file

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.sourcemap;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* A SourceMapEntry consists of a {@link SourceFile}, a line number, a base address,
* and a length. If the length is positive, the base address and the length determine
* an {@link AddressRange}. In this case, the length of a {@code SourceMapEntry} is the
* length of the associated {@link AddressRange}, i.e., the number of {@link Address}es in
* the range (see {@link AddressRange#getLength()}). The intent is that the range
* contains all of the bytes corresponding to a given line of source. The length of a
* {@code SourceMapEntry} can be 0, in which case the associated range is null. Negative
* lengths are not allowed.
* <p>
* The baseAddress of a range must occur within a memory block of the program, as must each
* address within the range of a {@code SourceMapEntry}. A range may span multiple
* (contiguous) memory blocks.
* <p>
* If the ranges of two entries (with non-zero lengths) intersect, then the ranges must be
* identical. The associated {@link SourceFile}s and/or line numbers can be different.
* <p>
* Entries with length zero do not conflict with other entries and may occur within the
* range of another entry.
* <p>
* For a fixed source file, line number, base address, and length, there must be only one
* SourceMapEntry.
* <p>
* SourceMapEntry objects are created using the {@link SourceFileManager} for a program,
* which must enforce the restrictions listed above.
*/
public interface SourceMapEntry extends Comparable<SourceMapEntry> {
/**
* Returns the line number.
* @return line number
*/
public int getLineNumber();
/**
* Returns the source file
* @return source file
*/
public SourceFile getSourceFile();
/**
* Returns the base address of the entry
* @return base address
*/
public Address getBaseAddress();
/**
* Returns the length of the range (number of addresses)
* @return length
*/
public long getLength();
/**
* Returns the address range, or null for length 0 entries
* @return address range or null
*/
public AddressRange getRange();
}

View file

@ -0,0 +1,44 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.sourcemap;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Interface for iterating over {@link SourceMapEntry}s.
*/
public interface SourceMapEntryIterator extends Iterator<SourceMapEntry>, Iterable<SourceMapEntry> {
public static final SourceMapEntryIterator EMPTY_ITERATOR = new SourceMapEntryIterator() {
@Override
public Iterator<SourceMapEntry> iterator() {
return this;
}
@Override
public SourceMapEntry next() {
throw new NoSuchElementException("Empty iterator is empty!");
}
@Override
public boolean hasNext() {
return false;
}
};
}

View file

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

View file

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

View file

@ -0,0 +1,47 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.util;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.sourcemap.SourceMapEntry;
/**
* A {@link ProgramLocation} for source map information.
*/
public class SourceMapFieldLocation extends ProgramLocation {
private SourceMapEntry sourceMapEntry;
public SourceMapFieldLocation(Program program, Address addr, int row, int charOffset,
SourceMapEntry sourceMapEntry) {
super(program, addr, row, 0, charOffset);
this.sourceMapEntry = sourceMapEntry;
}
public SourceMapFieldLocation() {
// for deserialization
}
/**
* Returns the {@link SourceMapEntry} associated with this location.
* @return source map entry
*/
public SourceMapEntry getSourceMapEntry() {
return sourceMapEntry;
}
}